1/** 2 * @license AngularJS v1.4.0 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.0/' + 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 (source) { 868 if (isArray(source)) { 869 destination = copy(source, [], stackSource, stackDest); 870 } else if (isTypedArray(source)) { 871 destination = new source.constructor(source); 872 } else if (isDate(source)) { 873 destination = new Date(source.getTime()); 874 } else if (isRegExp(source)) { 875 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 876 destination.lastIndex = source.lastIndex; 877 } else if (isObject(source)) { 878 var emptyObject = Object.create(getPrototypeOf(source)); 879 destination = copy(source, emptyObject, stackSource, stackDest); 880 } 881 } 882 } else { 883 if (source === destination) throw ngMinErr('cpi', 884 "Can't copy! Source and destination are identical."); 885 886 stackSource = stackSource || []; 887 stackDest = stackDest || []; 888 889 if (isObject(source)) { 890 var index = stackSource.indexOf(source); 891 if (index !== -1) return stackDest[index]; 892 893 stackSource.push(source); 894 stackDest.push(destination); 895 } 896 897 var result, key; 898 if (isArray(source)) { 899 destination.length = 0; 900 for (var i = 0; i < source.length; i++) { 901 result = copy(source[i], null, stackSource, stackDest); 902 if (isObject(source[i])) { 903 stackSource.push(source[i]); 904 stackDest.push(result); 905 } 906 destination.push(result); 907 } 908 } else { 909 var h = destination.$$hashKey; 910 if (isArray(destination)) { 911 destination.length = 0; 912 } else { 913 forEach(destination, function(value, key) { 914 delete destination[key]; 915 }); 916 } 917 if (isBlankObject(source)) { 918 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 919 for (key in source) { 920 putValue(key, source[key], destination, stackSource, stackDest); 921 } 922 } else if (source && typeof source.hasOwnProperty === 'function') { 923 // Slow path, which must rely on hasOwnProperty 924 for (key in source) { 925 if (source.hasOwnProperty(key)) { 926 putValue(key, source[key], destination, stackSource, stackDest); 927 } 928 } 929 } else { 930 // Slowest path --- hasOwnProperty can't be called as a method 931 for (key in source) { 932 if (hasOwnProperty.call(source, key)) { 933 putValue(key, source[key], destination, stackSource, stackDest); 934 } 935 } 936 } 937 setHashKey(destination,h); 938 } 939 } 940 return destination; 941 942 function putValue(key, val, destination, stackSource, stackDest) { 943 // No context allocation, trivial outer scope, easily inlined 944 var result = copy(val, null, stackSource, stackDest); 945 if (isObject(val)) {
946 stackSource.push(val); 947 stackDest.push(result); 948 } 949 destination[key] = result; 950 } 951} 952 953/** 954 * Creates a shallow copy of an object, an array or a primitive. 955 * 956 * Assumes that there are no proto properties for objects. 957 */ 958function shallowCopy(src, dst) { 959 if (isArray(src)) { 960 dst = dst || []; 961 962 for (var i = 0, ii = src.length; i < ii; i++) { 963 dst[i] = src[i]; 964 } 965 } else if (isObject(src)) { 966 dst = dst || {}; 967 968 for (var key in src) { 969 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 970 dst[key] = src[key]; 971 } 972 } 973 } 974 975 return dst || src; 976} 977 978 979/** 980 * @ngdoc function 981 * @name angular.equals 982 * @module ng 983 * @kind function 984 * 985 * @description 986 * Determines if two objects or two values are equivalent. Supports value types, regular 987 * expressions, arrays and objects. 988 * 989 * Two objects or values are considered equivalent if at least one of the following is true: 990 * 991 * * Both objects or values pass `===` comparison. 992 * * Both objects or values are of the same type and all of their properties are equal by 993 * comparing them with `angular.equals`. 994 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 995 * * Both values represent the same regular expression (In JavaScript, 996 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 997 * representation matches). 998 * 999 * During a property comparison, properties of `function` type and properties with names 1000 * that begin with `$` are ignored. 1001 * 1002 * Scope and DOMWindow objects are being compared only by identify (`===`). 1003 * 1004 * @param {*} o1 Object or value to compare. 1005 * @param {*} o2 Object or value to compare. 1006 * @returns {boolean} True if arguments are equal. 1007 */ 1008function equals(o1, o2) { 1009 if (o1 === o2) return true; 1010 if (o1 === null || o2 === null) return false; 1011 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1012 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1013 if (t1 == t2) { 1014 if (t1 == 'object') { 1015 if (isArray(o1)) { 1016 if (!isArray(o2)) return false; 1017 if ((length = o1.length) == o2.length) { 1018 for (key = 0; key < length; key++) { 1019 if (!equals(o1[key], o2[key])) return false; 1020 } 1021 return true; 1022 } 1023 } else if (isDate(o1)) { 1024 if (!isDate(o2)) return false; 1025 return equals(o1.getTime(), o2.getTime()); 1026 } else if (isRegExp(o1)) { 1027 return isRegExp(o2) ? o1.toString() == o2.toString() : false; 1028 } else { 1029 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1030 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1031 keySet = createMap(); 1032 for (key in o1) { 1033 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1034 if (!equals(o1[key], o2[key])) return false; 1035 keySet[key] = true; 1036 } 1037 for (key in o2) { 1038 if (!(key in keySet) && 1039 key.charAt(0) !== '$' && 1040 o2[key] !== undefined && 1041 !isFunction(o2[key])) return false; 1042 } 1043 return true; 1044 } 1045 } 1046 } 1047 return false; 1048} 1049 1050var csp = function() { 1051 if (isDefined(csp.isActive_)) return csp.isActive_; 1052 1053 var active = !!(document.querySelector('[ng-csp]') || 1054 document.querySelector('[data-ng-csp]')); 1055 1056 if (!active) { 1057 try { 1058 /* jshint -W031, -W054 */ 1059 new Function(''); 1060 /* jshint +W031, +W054 */ 1061 } catch (e) { 1062 active = true; 1063 } 1064 } 1065 1066 return (csp.isActive_ = active); 1067}; 1068 1069/** 1070 * @ngdoc directive 1071 * @module ng 1072 * @name ngJq 1073 * 1074 * @element ANY 1075 * @param {string=} ngJq the name of the library available under `window` 1076 * to be used for angular.element 1077 * @description 1078 * Use this directive to force the angular.element library. This should be 1079 * used to force either jqLite by leaving ng-jq blank or setting the name of 1080 * the jquery variable under window (eg. jQuery). 1081 * 1082 * Since angular looks for this directive when it is loaded (doesn't wait for the 1083 * DOMContentLoaded event), it must be placed on an element that comes before the script 1084 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1085 * others ignored. 1086 * 1087 * @example 1088 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1089 ```html 1090 <!doctype html> 1091 <html ng-app ng-jq> 1092 ... 1093 ... 1094 </html> 1095 ``` 1096 * @example 1097 * This example shows how to use a jQuery based library of a different name. 1098 * The library name must be available at the top most 'window'. 1099 ```html 1100 <!doctype html> 1101 <html ng-app ng-jq="jQueryLib"> 1102 ... 1103 ... 1104 </html> 1105 ``` 1106 */ 1107var jq = function() { 1108 if (isDefined(jq.name_)) return jq.name_; 1109 var el; 1110 var i, ii = ngAttrPrefixes.length, prefix, name; 1111 for (i = 0; i < ii; ++i) { 1112 prefix = ngAttrPrefixes[i]; 1113 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 1114 name = el.getAttribute(prefix + 'jq'); 1115 break; 1116 } 1117 } 1118 1119 return (jq.name_ = name); 1120}; 1121 1122function concat(array1, array2, index) { 1123 return array1.concat(slice.call(array2, index)); 1124} 1125 1126function sliceArgs(args, startIndex) { 1127 return slice.call(args, startIndex || 0); 1128} 1129 1130
vendor: 8,838 bytes, lines 1131-1408
1131/* jshint -W101 */ 1132/** 1133 * @ngdoc function 1134 * @name angular.bind 1135 * @module ng 1136 * @kind function 1137 * 1138 * @description 1139 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1140 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1141 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1142 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1143 * 1144 * @param {Object} self Context which `fn` should be evaluated in. 1145 * @param {function()} fn Function to be bound. 1146 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1147 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1148 */ 1149/* jshint +W101 */ 1150function bind(self, fn) { 1151 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1152 if (isFunction(fn) && !(fn instanceof RegExp)) { 1153 return curryArgs.length 1154 ? function() { 1155 return arguments.length 1156 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1157 : fn.apply(self, curryArgs); 1158 } 1159 : function() { 1160 return arguments.length 1161 ? fn.apply(self, arguments) 1162 : fn.call(self); 1163 }; 1164 } else { 1165 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1166 return fn; 1167 } 1168} 1169 1170 1171function toJsonReplacer(key, value) { 1172 var val = value; 1173 1174 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1175 val = undefined; 1176 } else if (isWindow(value)) { 1177 val = '$WINDOW'; 1178 } else if (value && document === value) { 1179 val = '$DOCUMENT'; 1180 } else if (isScope(value)) { 1181 val = '$SCOPE'; 1182 } 1183 1184 return val; 1185} 1186 1187 1188/** 1189 * @ngdoc function 1190 * @name angular.toJson 1191 * @module ng 1192 * @kind function 1193 * 1194 * @description 1195 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1196 * stripped since angular uses this notation internally. 1197 * 1198 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1199 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1200 * If set to an integer, the JSON output will contain that many spaces per indentation. 1201 * @returns {string|undefined} JSON-ified string representing `obj`. 1202 */ 1203function toJson(obj, pretty) { 1204 if (typeof obj === 'undefined') return undefined; 1205 if (!isNumber(pretty)) { 1206 pretty = pretty ? 2 : null; 1207 } 1208 return JSON.stringify(obj, toJsonReplacer, pretty); 1209} 1210 1211 1212/** 1213 * @ngdoc function 1214 * @name angular.fromJson 1215 * @module ng 1216 * @kind function 1217 * 1218 * @description 1219 * Deserializes a JSON string. 1220 * 1221 * @param {string} json JSON string to deserialize. 1222 * @returns {Object|Array|string|number} Deserialized JSON string. 1223 */ 1224function fromJson(json) { 1225 return isString(json) 1226 ? JSON.parse(json) 1227 : json; 1228} 1229 1230 1231function timezoneToOffset(timezone, fallback) { 1232 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1233 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1234} 1235 1236 1237function addDateMinutes(date, minutes) { 1238 date = new Date(date.getTime()); 1239 date.setMinutes(date.getMinutes() + minutes); 1240 return date; 1241} 1242 1243 1244function convertTimezoneToLocal(date, timezone, reverse) { 1245 reverse = reverse ? -1 : 1; 1246 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 1247 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); 1248} 1249 1250 1251/** 1252 * @returns {string} Returns the string representation of the element. 1253 */ 1254function startingTag(element) { 1255 element = jqLite(element).clone(); 1256 try { 1257 // turns out IE does not let you set .html() on elements which 1258 // are not allowed to have children. So we just ignore it. 1259 element.empty(); 1260 } catch (e) {} 1261 var elemHtml = jqLite('<div>').append(element).html(); 1262 try { 1263 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1264 elemHtml. 1265 match(/^(<[^>]+>)/)[1]. 1266 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1267 } catch (e) { 1268 return lowercase(elemHtml); 1269 } 1270 1271} 1272 1273 1274///////////////////////////////////////////////// 1275 1276/** 1277 * Tries to decode the URI component without throwing an exception. 1278 * 1279 * @private 1280 * @param str value potential URI component to check. 1281 * @returns {boolean} True if `value` can be decoded 1282 * with the decodeURIComponent function. 1283 */ 1284function tryDecodeURIComponent(value) { 1285 try { 1286 return decodeURIComponent(value); 1287 } catch (e) { 1288 // Ignore any invalid uri component 1289 } 1290} 1291 1292 1293/** 1294 * Parses an escaped url query string into key-value pairs. 1295 * @returns {Object.<string,boolean|Array>} 1296 */ 1297function parseKeyValue(/**string*/keyValue) { 1298 var obj = {}, key_value, key; 1299 forEach((keyValue || "").split('&'), function(keyValue) { 1300 if (keyValue) { 1301 key_value = keyValue.replace(/\+/g,'%20').split('='); 1302 key = tryDecodeURIComponent(key_value[0]); 1303 if (isDefined(key)) { 1304 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1305 if (!hasOwnProperty.call(obj, key)) { 1306 obj[key] = val; 1307 } else if (isArray(obj[key])) { 1308 obj[key].push(val); 1309 } else { 1310 obj[key] = [obj[key],val]; 1311 } 1312 } 1313 } 1314 }); 1315 return obj; 1316} 1317 1318function toKeyValue(obj) { 1319 var parts = []; 1320 forEach(obj, function(value, key) { 1321 if (isArray(value)) { 1322 forEach(value, function(arrayValue) { 1323 parts.push(encodeUriQuery(key, true) + 1324 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1325 }); 1326 } else { 1327 parts.push(encodeUriQuery(key, true) + 1328 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1329 } 1330 }); 1331 return parts.length ? parts.join('&') : ''; 1332} 1333 1334 1335/** 1336 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1337 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1338 * segments: 1339 * segment = *pchar 1340 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1341 * pct-encoded = "%" HEXDIG HEXDIG 1342 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1343 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1344 * / "*" / "+" / "," / ";" / "=" 1345 */ 1346function encodeUriSegment(val) { 1347 return encodeUriQuery(val, true). 1348 replace(/%26/gi, '&'). 1349 replace(/%3D/gi, '='). 1350 replace(/%2B/gi, '+'); 1351} 1352 1353 1354/** 1355 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1356 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1357 * encoded per http://tools.ietf.org/html/rfc3986: 1358 * query = *( pchar / "/" / "?" ) 1359 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1360 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1361 * pct-encoded = "%" HEXDIG HEXDIG 1362 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1363 * / "*" / "+" / "," / ";" / "=" 1364 */ 1365function encodeUriQuery(val, pctEncodeSpaces) { 1366 return encodeURIComponent(val). 1367 replace(/%40/gi, '@'). 1368 replace(/%3A/gi, ':'). 1369 replace(/%24/g, '$'). 1370 replace(/%2C/gi, ','). 1371 replace(/%3B/gi, ';'). 1372 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1373} 1374 1375var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1376 1377function getNgAttribute(element, ngAttr) { 1378 var attr, i, ii = ngAttrPrefixes.length; 1379 for (i = 0; i < ii; ++i) { 1380 attr = ngAttrPrefixes[i] + ngAttr; 1381 if (isString(attr = element.getAttribute(attr))) { 1382 return attr; 1383 } 1384 } 1385 return null; 1386} 1387 1388/** 1389 * @ngdoc directive 1390 * @name ngApp 1391 * @module ng 1392 * 1393 * @element ANY 1394 * @param {angular.Module} ngApp an optional application 1395 * {@link angular.module module} name to load. 1396 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1397 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1398 * do not use explicit function annotation (and are thus unsuitable for minification), as described 1399 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1400 * tracking down the root of these bugs. 1401 * 1402 * @description 1403 * 1404 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1405 * designates the **root element** of the application and is typically placed near the root element 1406 * of the page - e.g. on the `<body>` or `<html>` tags. 1407 * 1408 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
1408p` 1409 * found in the document will be used to define the root element to auto-bootstrap as an 1410 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1411 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1412 * 1413 * You can specify an **AngularJS module** to be used as the root module for the application. This 1414 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1415 * should contain the application code needed or have dependencies on other modules that will 1416 * contain the code. See {@link angular.module} for more information. 1417 * 1418 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1419 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1420 * would not be resolved to `3`. 1421 * 1422 * `ngApp` is the easiest, and most common way to bootstrap an application. 1423 * 1424 <example module="ngAppDemo"> 1425 <file name="index.html"> 1426 <div ng-controller="ngAppDemoController"> 1427 I can add: {{a}} + {{b}} = {{ a+b }} 1428 </div> 1429 </file> 1430 <file name="script.js"> 1431 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1432 $scope.a = 1; 1433 $scope.b = 2; 1434 }); 1435 </file> 1436 </example> 1437 * 1438 * Using `ngStrictDi`, you would see something like this: 1439 * 1440 <example ng-app-included="true"> 1441 <file name="index.html"> 1442 <div ng-app="ngAppStrictDemo" ng-strict-di> 1443 <div ng-controller="GoodController1"> 1444 I can add: {{a}} + {{b}} = {{ a+b }} 1445 1446 <p>This renders because the controller does not fail to 1447 instantiate, by using explicit annotation style (see 1448 script.js for details) 1449 </p> 1450 </div> 1451 1452 <div ng-controller="GoodController2"> 1453 Name: <input ng-model="name"><br /> 1454 Hello, {{name}}! 1455 1456 <p>This renders because the controller does not fail to 1457 instantiate, by using explicit annotation style 1458 (see script.js for details) 1459 </p> 1460 </div> 1461 1462 <div ng-controller="BadController"> 1463 I can add: {{a}} + {{b}} = {{ a+b }} 1464 1465 <p>The controller could not be instantiated, due to relying 1466 on automatic function annotations (which are disabled in 1467 strict mode). As such, the content of this section is not 1468 interpolated, and there should be an error in your web console. 1469 </p> 1470 </div> 1471 </div> 1472 </file> 1473 <file name="script.js"> 1474 angular.module('ngAppStrictDemo', []) 1475 // BadController will fail to instantiate, due to relying on automatic function annotation, 1476 // rather than an explicit annotation 1477 .controller('BadController', function($scope) { 1478 $scope.a = 1; 1479 $scope.b = 2; 1480 }) 1481 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1482 // due to using explicit annotations using the array style and $inject property, respectively. 1483 .controller('GoodController1', ['$scope', function($scope) { 1484 $scope.a = 1; 1485 $scope.b = 2; 1486 }]) 1487 .controller('GoodController2', GoodController2); 1488 function GoodController2($scope) { 1489 $scope.name = "World"; 1490 } 1491 GoodController2.$inject = ['$scope']; 1492 </file> 1493 <file name="style.css"> 1494 div[ng-controller] { 1495 margin-bottom: 1em; 1496 -webkit-border-radius: 4px; 1497 border-radius: 4px; 1498 border: 1px solid; 1499 padding: .5em; 1500 } 1501 div[ng-controller^=Good] { 1502 border-color: #d6e9c6; 1503 background-color: #dff0d8; 1504 color: #3c763d; 1505 } 1506 div[ng-controller^=Bad] { 1507 border-color: #ebccd1; 1508 background-color: #f2dede; 1509 color: #a94442; 1510 margin-bottom: 0; 1511 } 1512 </file> 1513 </example> 1514 */ 1515function angularInit(element, bootstrap) { 1516 var appElement, 1517 module, 1518 config = {}; 1519 1520 // The element `element` has priority over any other element 1521 forEach(ngAttrPrefixes, function(prefix) { 1522 var name = prefix + 'app'; 1523 1524 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1525 appElement = element; 1526 module = element.getAttribute(name); 1527 } 1528 }); 1529 forEach(ngAttrPrefixes, function(prefix) { 1530 var name = prefix + 'app'; 1531 var candidate; 1532 1533 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1534 appElement = candidate; 1535 module = candidate.getAttribute(name); 1536 } 1537 }); 1538 if (appElement) { 1539 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 1540 bootstrap(appElement, module ? [module] : [], config); 1541 } 1542} 1543 1544/** 1545 * @ngdoc function 1546 * @name angular.bootstrap 1547 * @module ng 1548 * @description 1549 * Use this function to manually start up angular application. 1550 * 1551 * See: {@link guide/bootstrap Bootstrap} 1552 *
1553 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. 1554 * They must use {@link ng.directive:ngApp ngApp}. 1555 * 1556 * Angular will detect if it has been loaded into the browser more than once and only allow the 1557 * first loaded script to be bootstrapped and will report a warning to the browser console for 1558 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1559 * multiple instances of Angular try to work on the DOM. 1560 * 1561 * ```html 1562 * <!doctype html> 1563 * <html> 1564 * <body> 1565 * <div ng-controller="WelcomeController"> 1566 * {{greeting}} 1567 * </div> 1568 * 1569 * <script src="angular.js"></script> 1570 * <script> 1571 * var app = angular.module('demo', []) 1572 * .controller('WelcomeController', function($scope) { 1573 * $scope.greeting = 'Welcome!'; 1574 * }); 1575 * angular.bootstrap(document, ['demo']); 1576 * </script> 1577 * </body> 1578 * </html> 1579 * ``` 1580 * 1581 * @param {DOMElement} element DOM element which is the root of angular application. 1582 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1583 * Each item in the array should be the name of a predefined module or a (DI annotated) 1584 * function that will be invoked by the injector as a `config` block. 1585 * See: {@link angular.module modules} 1586 * @param {Object=} config an object for defining configuration options for the application. The 1587 * following keys are supported: 1588 * 1589 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1590 * assist in finding bugs which break minified code. Defaults to `false`. 1591 * 1592 * @returns {auto.$injector} Returns the newly created injector for this app. 1593 */ 1594function bootstrap(element, modules, config) { 1595 if (!isObject(config)) config = {}; 1596 var defaultConfig = { 1597 strictDi: false 1598 }; 1599 config = extend(defaultConfig, config); 1600 var doBootstrap = function() { 1601 element = jqLite(element); 1602 1603 if (element.injector()) { 1604 var tag = (element[0] === document) ? 'document' : startingTag(element); 1605 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1606 throw ngMinErr( 1607 'btstrpd', 1608 "App Already Bootstrapped with this Element '{0}'", 1609 tag.replace(/</,'<').replace(/>/,'>')); 1610 } 1611 1612 modules = modules || []; 1613 modules.unshift(['$provide', function($provide) { 1614 $provide.value('$rootElement', element); 1615 }]); 1616 1617 if (config.debugInfoEnabled) { 1618 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1619 modules.push(['$compileProvider', function($compileProvider) { 1620 $compileProvider.debugInfoEnabled(true); 1621 }]); 1622 } 1623 1624 modules.unshift('ng'); 1625 var injector = createInjector(modules, config.strictDi); 1626 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1627 function bootstrapApply(scope, element, compile, injector) { 1628 scope.$apply(function() { 1629 element.data('$injector', injector); 1630 compile(element)(scope); 1631 }); 1632 }] 1633 ); 1634 return injector; 1635 }; 1636 1637 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1638 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1639 1640 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1641 config.debugInfoEnabled = true; 1642 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1643 } 1644 1645 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1646 return doBootstrap(); 1647 } 1648 1649 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1650 angular.resumeBootstrap = function(extraModules) { 1651 forEach(extraModules, function(module) { 1652 modules.push(module); 1653 }); 1654 return doBootstrap(); 1655 }; 1656 1657 if (isFunction(angular.resumeDeferredBootstrap)) { 1658 angular.resumeDeferredBootstrap(); 1659 } 1660} 1661 1662/** 1663 * @ngdoc function 1664 * @name angular.reloadWithDebugInfo 1665 * @module ng 1666 * @description 1667 * Use this function to reload the current application with debug information turned on. 1668 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 1669 * 1670 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 1671 */ 1672function reloadWithDebugInfo() { 1673 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 1674 window.location.reload(); 1675} 1676 1677/** 1678 * @name angular.getTestability 1679 * @module ng 1680 * @description 1681 * Get the testability service for the instance of Angular on the given 1682 * element. 1683 * @param {DOMElement} element DOM element which is the root of angular application. 1684 */ 1685function getTestability(rootElement) { 1686 var injector = angular.element(rootElement).injector(); 1687 if (!injector) { 1688 throw ngMinErr('test', 1689 'no injector found for element argument to getTestability'); 1690 } 1691 return injector.get('$$testability'); 1692} 1693 1694var SNAKE_CASE_REGEXP = /[A-Z]/g; 1695function snake_case(name, separator) { 1696 separator = separator || '_'; 1697 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1698 return (pos ? separator : '') + letter.toLowerCase(); 1699 }); 1700} 1701 1702var bindJQueryFired = false;
1703var skipDestroyOnNextJQueryCleanData; 1704function bindJQuery() { 1705 var originalCleanData; 1706 1707 if (bindJQueryFired) { 1708 return; 1709 } 1710 1711 // bind to jQuery if present; 1712 var jqName = jq(); 1713 jQuery = window.jQuery; // use default jQuery. 1714 if (isDefined(jqName)) { // `ngJq` present 1715 jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`. 1716 } 1717 1718 // Use jQuery if it exists with proper functionality, otherwise default to us. 1719 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 1720 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 1721 // versions. It will not work for sure with jQuery <1.7, though. 1722 if (jQuery && jQuery.fn.on) { 1723 jqLite = jQuery; 1724 extend(jQuery.fn, { 1725 scope: JQLitePrototype.scope, 1726 isolateScope: JQLitePrototype.isolateScope, 1727 controller: JQLitePrototype.controller, 1728 injector: JQLitePrototype.injector, 1729 inheritedData: JQLitePrototype.inheritedData 1730 }); 1731 1732 // All nodes removed from the DOM via various jQuery APIs like .remove() 1733 // are passed through jQuery.cleanData. Monkey-patch this method to fire 1734 // the $destroy event on all removed nodes. 1735 originalCleanData = jQuery.cleanData; 1736 jQuery.cleanData = function(elems) { 1737 var events; 1738 if (!skipDestroyOnNextJQueryCleanData) { 1739 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 1740 events = jQuery._data(elem, "events"); 1741 if (events && events.$destroy) { 1742 jQuery(elem).triggerHandler('$destroy'); 1743 } 1744 } 1745 } else { 1746 skipDestroyOnNextJQueryCleanData = false; 1747 } 1748 originalCleanData(elems); 1749 }; 1750 } else { 1751 jqLite = JQLite; 1752 } 1753 1754 angular.element = jqLite; 1755 1756 // Prevent double-proxying. 1757 bindJQueryFired = true; 1758} 1759 1760/** 1761 * throw error if the argument is falsy. 1762 */ 1763function assertArg(arg, name, reason) { 1764 if (!arg) { 1765 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1766 } 1767 return arg; 1768} 1769 1770function assertArgFn(arg, name, acceptArrayAnnotation) { 1771 if (acceptArrayAnnotation && isArray(arg)) { 1772 arg = arg[arg.length - 1]; 1773 } 1774 1775 assertArg(isFunction(arg), name, 'not a function, got ' + 1776 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1777 return arg; 1778} 1779 1780/** 1781 * throw error if the name given is hasOwnProperty 1782 * @param {String} name the name to test 1783 * @param {String} context the context in which the name is used, such as module or directive 1784 */ 1785function assertNotHasOwnProperty(name, context) { 1786 if (name === 'hasOwnProperty') { 1787 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1788 } 1789} 1790 1791/** 1792 * Return the value accessible from the object by path. Any undefined traversals are ignored 1793 * @param {Object} obj starting object 1794 * @param {String} path path to traverse 1795 * @param {boolean} [bindFnToScope=true] 1796 * @returns {Object} value as accessible by path 1797 */ 1798//TODO(misko): this function needs to be removed 1799function getter(obj, path, bindFnToScope) { 1800 if (!path) return obj; 1801 var keys = path.split('.'); 1802 var key; 1803 var lastInstance = obj; 1804 var len = keys.length; 1805 1806 for (var i = 0; i < len; i++) { 1807 key = keys[i]; 1808 if (obj) { 1809 obj = (lastInstance = obj)[key]; 1810 } 1811 } 1812 if (!bindFnToScope && isFunction(obj)) { 1813 return bind(lastInstance, obj); 1814 } 1815 return obj; 1816} 1817 1818/** 1819 * Return the DOM siblings between the first and last node in the given array. 1820 * @param {Array} array like object 1821 * @returns {jqLite} jqLite collection containing the nodes 1822 */ 1823function getBlockNodes(nodes) { 1824 // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original 1825 // collection, otherwise update the original collection. 1826 var node = nodes[0]; 1827 var endNode = nodes[nodes.length - 1]; 1828 var blockNodes = [node]; 1829 1830 do { 1831 node = node.nextSibling; 1832 if (!node) break; 1833 blockNodes.push(node); 1834 } while (node !== endNode); 1835 1836 return jqLite(blockNodes); 1837} 1838 1839 1840/** 1841 * Creates a new object without a prototype. This object is useful for lookup without having to 1842 * guard against prototypically inherited properties via hasOwnProperty. 1843 * 1844 * Related micro-benchmarks: 1845 * - http://jsperf.com/object-create2 1846 * - http://jsperf.com/proto-map-lookup/2 1847 * - http://jsperf.com/for-in-vs-object-keys2 1848 * 1849 * @returns {Object} 1850 */ 1851function createMap() { 1852 return Object.create(null); 1853} 1854 1855var NODE_TYPE_ELEMENT = 1; 1856var NODE_TYPE_ATTRIBUTE = 2; 1857var NODE_TYPE_TEXT = 3; 1858var NODE_TYPE_COMMENT = 8; 1859var NODE_TYPE_DOCUMENT = 9; 1860var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 1861 1862/** 1863 * @ngdoc type 1864 * @name angular.Module 1865 * @module ng 1866 * @description 1867 * 1868 * Interface for configuring angular {@link angular.module modules}. 1869 */ 1870 1871function setupModuleLoader(window) { 1872 1873 var $injectorMinErr = minErr('$injector'); 1874 var ngMinErr = minErr('ng'); 1875 1876 function ensure(obj, name, factory) {
vendor: 4,138 bytes, lines 1877-1991
1877 return obj[name] || (obj[name] = factory()); 1878 } 1879 1880 var angular = ensure(window, 'angular', Object); 1881 1882 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1883 angular.$$minErr = angular.$$minErr || minErr; 1884 1885 return ensure(angular, 'module', function() { 1886 /** @type {Object.<string, angular.Module>} */ 1887 var modules = {}; 1888 1889 /** 1890 * @ngdoc function 1891 * @name angular.module 1892 * @module ng 1893 * @description 1894 * 1895 * The `angular.module` is a global place for creating, registering and retrieving Angular 1896 * modules. 1897 * All modules (angular core or 3rd party) that should be available to an application must be 1898 * registered using this mechanism. 1899 * 1900 * When passed two or more arguments, a new module is created. If passed only one argument, an 1901 * existing module (the name passed as the first argument to `module`) is retrieved. 1902 * 1903 * 1904 * # Module 1905 * 1906 * A module is a collection of services, directives, controllers, filters, and configuration information. 1907 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1908 * 1909 * ```js 1910 * // Create a new module 1911 * var myModule = angular.module('myModule', []); 1912 * 1913 * // register a new service 1914 * myModule.value('appName', 'MyCoolApp'); 1915 * 1916 * // configure existing services inside initialization blocks. 1917 * myModule.config(['$locationProvider', function($locationProvider) { 1918 * // Configure existing providers 1919 * $locationProvider.hashPrefix('!'); 1920 * }]); 1921 * ``` 1922 * 1923 * Then you can create an injector and load your modules like this: 1924 * 1925 * ```js 1926 * var injector = angular.injector(['ng', 'myModule']) 1927 * ``` 1928 * 1929 * However it's more likely that you'll just use 1930 * {@link ng.directive:ngApp ngApp} or 1931 * {@link angular.bootstrap} to simplify this process for you. 1932 * 1933 * @param {!string} name The name of the module to create or retrieve. 1934 * @param {!Array.<string>=} requires If specified then new module is being created. If 1935 * unspecified then the module is being retrieved for further configuration. 1936 * @param {Function=} configFn Optional configuration function for the module. Same as 1937 * {@link angular.Module#config Module#config()}. 1938 * @returns {module} new module with the {@link angular.Module} api. 1939 */ 1940 return function module(name, requires, configFn) { 1941 var assertNotHasOwnProperty = function(name, context) { 1942 if (name === 'hasOwnProperty') { 1943 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1944 } 1945 }; 1946 1947 assertNotHasOwnProperty(name, 'module'); 1948 if (requires && modules.hasOwnProperty(name)) { 1949 modules[name] = null; 1950 } 1951 return ensure(modules, name, function() { 1952 if (!requires) { 1953 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1954 "the module name or forgot to load it. If registering a module ensure that you " + 1955 "specify the dependencies as the second argument.", name); 1956 } 1957 1958 /** @type {!Array.<Array.<*>>} */ 1959 var invokeQueue = []; 1960 1961 /** @type {!Array.<Function>} */ 1962 var configBlocks = []; 1963 1964 /** @type {!Array.<Function>} */ 1965 var runBlocks = []; 1966 1967 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 1968 1969 /** @type {angular.Module} */ 1970 var moduleInstance = { 1971 // Private state 1972 _invokeQueue: invokeQueue, 1973 _configBlocks: configBlocks, 1974 _runBlocks: runBlocks, 1975 1976 /** 1977 * @ngdoc property 1978 * @name angular.Module#requires 1979 * @module ng 1980 * 1981 * @description 1982 * Holds the list of modules which the injector will load before the current module is 1983 * loaded. 1984 */ 1985 requires: requires, 1986 1987 /** 1988 * @ngdoc property 1989 * @name angular.Module#name 1990 * @module ng 1991 *
1992 * @description 1993 * Name of the module. 1994 */ 1995 name: name, 1996 1997 1998 /** 1999 * @ngdoc method 2000 * @name angular.Module#provider 2001 * @module ng 2002 * @param {string} name service name 2003 * @param {Function} providerType Construction function for creating new instance of the 2004 * service. 2005 * @description 2006 * See {@link auto.$provide#provider $provide.provider()}. 2007 */ 2008 provider: invokeLater('$provide', 'provider'), 2009 2010 /** 2011 * @ngdoc method 2012 * @name angular.Module#factory 2013 * @module ng 2014 * @param {string} name service name 2015 * @param {Function} providerFunction Function for creating new instance of the service. 2016 * @description 2017 * See {@link auto.$provide#factory $provide.factory()}. 2018 */ 2019 factory: invokeLater('$provide', 'factory'), 2020 2021 /** 2022 * @ngdoc method 2023 * @name angular.Module#service 2024 * @module ng 2025 * @param {string} name service name 2026 * @param {Function} constructor A constructor function that will be instantiated. 2027 * @description 2028 * See {@link auto.$provide#service $provide.service()}. 2029 */ 2030 service: invokeLater('$provide', 'service'), 2031 2032 /** 2033 * @ngdoc method 2034 * @name angular.Module#value 2035 * @module ng 2036 * @param {string} name service name 2037 * @param {*} object Service instance object. 2038 * @description 2039 * See {@link auto.$provide#value $provide.value()}. 2040 */ 2041 value: invokeLater('$provide', 'value'), 2042 2043 /** 2044 * @ngdoc method 2045 * @name angular.Module#constant 2046 * @module ng 2047 * @param {string} name constant name 2048 * @param {*} object Constant value. 2049 * @description 2050 * Because the constant are fixed, they get applied before other provide methods. 2051 * See {@link auto.$provide#constant $provide.constant()}. 2052 */ 2053 constant: invokeLater('$provide', 'constant', 'unshift'), 2054 2055 /** 2056 * @ngdoc method 2057 * @name angular.Module#decorator 2058 * @module ng 2059 * @param {string} The name of the service to decorate. 2060 * @param {Function} This function will be invoked when the service needs to be 2061 * instantiated and should return the decorated service instance. 2062 * @description 2063 * See {@link auto.$provide#decorator $provide.decorator()}. 2064 */ 2065 decorator: invokeLater('$provide', 'decorator'), 2066 2067 /** 2068 * @ngdoc method 2069 * @name angular.Module#animation 2070 * @module ng 2071 * @param {string} name animation name 2072 * @param {Function} animationFactory Factory function for creating new instance of an 2073 * animation. 2074 * @description 2075 * 2076 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2077 * 2078 * 2079 * Defines an animation hook that can be later used with 2080 * {@link $animate $animate} service and directives that use this service. 2081 * 2082 * ```js 2083 * module.animation('.animation-name', function($inject1, $inject2) { 2084 * return { 2085 * eventName : function(element, done) { 2086 * //code to run the animation 2087 * //once complete, then run done() 2088 * return function cancellationFunction(element) { 2089 * //code to cancel the animation 2090 * } 2091 * } 2092 * } 2093 * }) 2094 * ``` 2095 * 2096 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2097 * {@link ngAnimate ngAnimate module} for more information. 2098 */ 2099 animation: invokeLater('$animateProvider', 'register'), 2100 2101 /** 2102 * @ngdoc method 2103 * @name angular.Module#filter 2104 * @module ng 2105 * @param {string} name Filter name - this must be a valid angular expression identifier 2106 * @param {Function} filterFactory Factory function for creating new instance of filter.
2107 * @description 2108 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2109 * 2110 * <div class="alert alert-warning"> 2111 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 2112 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2113 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2114 * (`myapp_subsection_filterx`). 2115 * </div> 2116 */ 2117 filter: invokeLater('$filterProvider', 'register'), 2118 2119 /** 2120 * @ngdoc method 2121 * @name angular.Module#controller 2122 * @module ng 2123 * @param {string|Object} name Controller name, or an object map of controllers where the 2124 * keys are the names and the values are the constructors. 2125 * @param {Function} constructor Controller constructor function. 2126 * @description 2127 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2128 */ 2129 controller: invokeLater('$controllerProvider', 'register'), 2130 2131 /** 2132 * @ngdoc method 2133 * @name angular.Module#directive 2134 * @module ng 2135 * @param {string|Object} name Directive name, or an object map of directives where the 2136 * keys are the names and the values are the factories. 2137 * @param {Function} directiveFactory Factory function for creating new instance of 2138 * directives. 2139 * @description 2140 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2141 */ 2142 directive: invokeLater('$compileProvider', 'directive'), 2143 2144 /** 2145 * @ngdoc method 2146 * @name angular.Module#config 2147 * @module ng 2148 * @param {Function} configFn Execute this function on module load. Useful for service 2149 * configuration. 2150 * @description 2151 * Use this method to register work which needs to be performed on module loading. 2152 * For more about how to configure services, see 2153 * {@link providers#provider-recipe Provider Recipe}. 2154 */ 2155 config: config, 2156 2157 /** 2158 * @ngdoc method 2159 * @name angular.Module#run 2160 * @module ng 2161 * @param {Function} initializationFn Execute this function after injector creation. 2162 * Useful for application initialization. 2163 * @description 2164 * Use this method to register work which should be performed when the injector is done 2165 * loading all modules. 2166 */ 2167 run: function(block) { 2168 runBlocks.push(block); 2169 return this; 2170 } 2171 }; 2172 2173 if (configFn) { 2174 config(configFn); 2175 } 2176 2177 return moduleInstance; 2178 2179 /** 2180 * @param {string} provider 2181 * @param {string} method 2182 * @param {String=} insertMethod 2183 * @returns {angular.Module} 2184 */ 2185 function invokeLater(provider, method, insertMethod, queue) { 2186 if (!queue) queue = invokeQueue; 2187 return function() { 2188 queue[insertMethod || 'push']([provider, method, arguments]); 2189 return moduleInstance; 2190 }; 2191 } 2192 }); 2193 }; 2194 }); 2195 2196} 2197 2198/* global: toDebugString: true */ 2199 2200function serializeObject(obj) { 2201 var seen = []; 2202 2203 return JSON.stringify(obj, function(key, val) { 2204 val = toJsonReplacer(key, val); 2205 if (isObject(val)) { 2206 2207 if (seen.indexOf(val) >= 0) return '<<already seen>>'; 2208 2209 seen.push(val); 2210 } 2211 return val; 2212 }); 2213} 2214 2215function toDebugString(obj) { 2216 if (typeof obj === 'function') { 2217 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2218 } else if (typeof obj === 'undefined') { 2219 return 'undefined'; 2220 } else if (typeof obj !== 'string') { 2221 return serializeObject(obj); 2222 } 2223 return obj; 2224} 2225 2226/* global angularModule: true, 2227 version: true, 2228 2229 $LocaleProvider, 2230 $CompileProvider, 2231 2232 htmlAnchorDirective, 2233 inputDirective, 2234 inputDirective, 2235 formDirective, 2236 scriptDirective, 2237 selectDirective, 2238 styleDirective, 2239 optionDirective, 2240 ngBindDirective, 2241 ngBindHtmlDirective, 2242 ngBindTemplateDirective, 2243 ngClassDirective, 2244 ngClassEvenDirective, 2245 ngClassOddDirective, 2246 ngCspDirective, 2247 ngCloakDirective, 2248 ngControllerDirective, 2249 ngFormDirective, 2250 ngHideDirective, 2251 ngIfDirective, 2252 ngIncludeDirective, 2253 ngIncludeFillContentDirective, 2254 ngInitDirective, 2255 ngNonBindableDirective, 2256 ngPluralizeDirective, 2257 ngRepeatDirective, 2258 ngShowDirective, 2259 ngStyleDirective, 2260 ngSwitchDirective, 2261 ngSwitchWhenDirective, 2262 ngSwitchDefaultDirective, 2263 ngOptionsDirective, 2264 ngTranscludeDirective, 2265 ngModelDirective, 2266 ngListDirective, 2267 ngChangeDirective, 2268 patternDirective, 2269 patternDirective, 2270 requiredDirective, 2271 requiredDirective, 2272 minlengthDirective, 2273 minlengthDirective, 2274 maxlengthDirective, 2275 maxlengthDirective, 2276 ngValueDirective, 2277 ngModelOptionsDirective, 2278 ngAttributeAliasDirectives, 2279 ngEventDirectives, 2280 2281 $AnchorScrollProvider, 2282 $AnimateProvider, 2283 $$CoreAnimateQueueProvider, 2284 $$CoreAnimateRunnerProvider, 2285 $BrowserProvider,
2286 $CacheFactoryProvider, 2287 $ControllerProvider, 2288 $DocumentProvider, 2289 $ExceptionHandlerProvider, 2290 $FilterProvider, 2291 $InterpolateProvider, 2292 $IntervalProvider, 2293 $$HashMapProvider, 2294 $HttpProvider, 2295 $HttpParamSerializerProvider, 2296 $HttpParamSerializerJQLikeProvider, 2297 $HttpBackendProvider, 2298 $LocationProvider, 2299 $LogProvider, 2300 $ParseProvider, 2301 $RootScopeProvider, 2302 $QProvider, 2303 $$QProvider, 2304 $$SanitizeUriProvider, 2305 $SceProvider, 2306 $SceDelegateProvider, 2307 $SnifferProvider, 2308 $TemplateCacheProvider, 2309 $TemplateRequestProvider, 2310 $$TestabilityProvider, 2311 $TimeoutProvider, 2312 $$RAFProvider, 2313 $$AsyncCallbackProvider, 2314 $WindowProvider, 2315 $$jqLiteProvider, 2316 $$CookieReaderProvider 2317*/ 2318 2319 2320/** 2321 * @ngdoc object 2322 * @name angular.version 2323 * @module ng 2324 * @description 2325 * An object that contains information about the current AngularJS version. This object has the 2326 * following properties: 2327 * 2328 * - `full` â `{string}` â Full version string, such as "0.9.18". 2329 * - `major` â `{number}` â Major version number, such as "0". 2330 * - `minor` â `{number}` â Minor version number, such as "9". 2331 * - `dot` â `{number}` â Dot version number, such as "18". 2332 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 2333 */ 2334var version = { 2335 full: '1.4.0', // all of these placeholder strings will be replaced by grunt's 2336 major: 1, // package task 2337 minor: 4, 2338 dot: 0, 2339 codeName: 'jaracimrman-existence' 2340}; 2341 2342 2343function publishExternalAPI(angular) { 2344 extend(angular, { 2345 'bootstrap': bootstrap, 2346 'copy': copy, 2347 'extend': extend, 2348 'merge': merge, 2349 'equals': equals, 2350 'element': jqLite, 2351 'forEach': forEach, 2352 'injector': createInjector, 2353 'noop': noop, 2354 'bind': bind, 2355 'toJson': toJson, 2356 'fromJson': fromJson, 2357 'identity': identity, 2358 'isUndefined': isUndefined, 2359 'isDefined': isDefined, 2360 'isString': isString, 2361 'isFunction': isFunction, 2362 'isObject': isObject, 2363 'isNumber': isNumber, 2364 'isElement': isElement, 2365 'isArray': isArray, 2366 'version': version, 2367 'isDate': isDate, 2368 'lowercase': lowercase, 2369 'uppercase': uppercase, 2370 'callbacks': {counter: 0}, 2371 'getTestability': getTestability, 2372 '$$minErr': minErr, 2373 '$$csp': csp, 2374 'reloadWithDebugInfo': reloadWithDebugInfo 2375 }); 2376 2377 angularModule = setupModuleLoader(window); 2378 try { 2379 angularModule('ngLocale'); 2380 } catch (e) { 2381 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 2382 } 2383 2384 angularModule('ng', ['ngLocale'], ['$provide', 2385 function ngModule($provide) { 2386 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2387 $provide.provider({ 2388 $$sanitizeUri: $$SanitizeUriProvider 2389 }); 2390 $provide.provider('$compile', $CompileProvider). 2391 directive({ 2392 a: htmlAnchorDirective, 2393 input: inputDirective, 2394 textarea: inputDirective, 2395 form: formDirective, 2396 script: scriptDirective, 2397 select: selectDirective, 2398 style: styleDirective, 2399 option: optionDirective, 2400 ngBind: ngBindDirective, 2401 ngBindHtml: ngBindHtmlDirective, 2402 ngBindTemplate: ngBindTemplateDirective, 2403 ngClass: ngClassDirective, 2404 ngClassEven: ngClassEvenDirective, 2405 ngClassOdd: ngClassOddDirective, 2406 ngCloak: ngCloakDirective, 2407 ngController: ngControllerDirective, 2408 ngForm: ngFormDirective, 2409 ngHide: ngHideDirective, 2410 ngIf: ngIfDirective, 2411 ngInclude: ngIncludeDirective, 2412 ngInit: ngInitDirective, 2413 ngNonBindable: ngNonBindableDirective, 2414 ngPluralize: ngPluralizeDirective, 2415 ngRepeat: ngRepeatDirective, 2416 ngShow: ngShowDirective, 2417 ngStyle: ngStyleDirective, 2418 ngSwitch: ngSwitchDirective, 2419 ngSwitchWhen: ngSwitchWhenDirective, 2420 ngSwitchDefault: ngSwitchDefaultDirective, 2421 ngOptions: ngOptionsDirective, 2422 ngTransclude: ngTranscludeDirective, 2423 ngModel: ngModelDirective, 2424 ngList: ngListDirective, 2425 ngChange: ngChangeDirective, 2426 pattern: patternDirective, 2427 ngPattern: patternDirective, 2428 required: requiredDirective, 2429 ngRequired: requiredDirective, 2430 minlength: minlengthDirective, 2431 ngMinlength: minlengthDirective, 2432 maxlength: maxlengthDirective, 2433 ngMaxlength: maxlengthDirective, 2434 ngValue: ngValueDirective, 2435 ngModelOptions: ngModelOptionsDirective 2436 }). 2437 directive({ 2438 ngInclude: ngIncludeFillContentDirective 2439 }). 2440 directive(ngAttributeAliasDirectives). 2441 directive(ngEventDirectives); 2442 $provide.provider({ 2443 $anchorScroll: $AnchorScrollProvider, 2444 $animate: $AnimateProvider, 2445 $$animateQueue: $$CoreAnimateQueueProvider, 2446 $$AnimateRunner: $$CoreAnimateRunnerProvider, 2447 $browser: $BrowserProvider,
2448 $cacheFactory: $CacheFactoryProvider, 2449 $controller: $ControllerProvider, 2450 $document: $DocumentProvider, 2451 $exceptionHandler: $ExceptionHandlerProvider, 2452 $filter: $FilterProvider, 2453 $interpolate: $InterpolateProvider, 2454 $interval: $IntervalProvider, 2455 $http: $HttpProvider, 2456 $httpParamSerializer: $HttpParamSerializerProvider, 2457 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2458 $httpBackend: $HttpBackendProvider, 2459 $location: $LocationProvider, 2460 $log: $LogProvider, 2461 $parse: $ParseProvider, 2462 $rootScope: $RootScopeProvider, 2463 $q: $QProvider, 2464 $$q: $$QProvider, 2465 $sce: $SceProvider, 2466 $sceDelegate: $SceDelegateProvider, 2467 $sniffer: $SnifferProvider, 2468 $templateCache: $TemplateCacheProvider, 2469 $templateRequest: $TemplateRequestProvider, 2470 $$testability: $$TestabilityProvider, 2471 $timeout: $TimeoutProvider, 2472 $window: $WindowProvider, 2473 $$rAF: $$RAFProvider, 2474 $$asyncCallback: $$AsyncCallbackProvider, 2475 $$jqLite: $$jqLiteProvider, 2476 $$HashMap: $$HashMapProvider, 2477 $$cookieReader: $$CookieReaderProvider 2478 }); 2479 } 2480 ]); 2481} 2482 2483/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2484 * Any commits to this file should be reviewed with security in mind. * 2485 * Changes to this file can potentially create security vulnerabilities. * 2486 * An approval from 2 Core members with history of modifying * 2487 * this file is required. * 2488 * * 2489 * Does the change somehow allow for arbitrary javascript to be executed? * 2490 * Or allows for someone to change the prototype of built-in objects? * 2491 * Or gives undesired access to variables likes document or window? * 2492 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2493 2494/* global JQLitePrototype: true, 2495 addEventListenerFn: true, 2496 removeEventListenerFn: true, 2497 BOOLEAN_ATTR: true, 2498 ALIASED_ATTR: true, 2499*/ 2500 2501////////////////////////////////// 2502//JQLite 2503////////////////////////////////// 2504 2505/** 2506 * @ngdoc function 2507 * @name angular.element 2508 * @module ng 2509 * @kind function 2510 * 2511 * @description 2512 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2513 * 2514 * If jQuery is available, `angular.element` is an alias for the 2515 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2516 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2517 * 2518 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2519 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2520 * commonly needed functionality with the goal of having a very small footprint.</div> 2521 * 2522 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. 2523 * 2524 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2525 * jqLite; they are never raw DOM references.</div> 2526 * 2527 * ## Angular's jqLite 2528 * jqLite provides only the following jQuery methods: 2529 * 2530 * - [`addClass()`](http://api.jquery.com/addClass/) 2531 * - [`after()`](http://api.jquery.com/after/) 2532 * - [`append()`](http://api.jquery.com/append/) 2533 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 2534 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2535 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2536 * - [`clone()`](http://api.jquery.com/clone/) 2537 * - [`contents()`](http://api.jquery.com/contents/) 2538 * - [`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'. 2539 * - [`data()`](http://api.jquery.com/data/) 2540 * - [`detach()`](http://api.jquery.com/detach/) 2541 * - [`empty()`](http://api.jquery.com/empty/) 2542 * - [`eq()`](http://api.jquery.com/eq/) 2543 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2544 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2545 * - [`html()`](http://api.jquery.com/html/) 2546 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2547 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2548 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2549 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2550 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2551 * - [`prepend()`](http://api.jquery.com/prepend/) 2552 * - [`prop()`](http://api.jquery.com/prop/) 2553 * - [`ready()`](http://api.jquery.com/ready/) 2554 * - [`remove()`](http://api.jquery.com/remove/) 2555 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2556 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2557 * - [`removeData()`](http://api.jquery.com/removeData/) 2558 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2559 * - [`text()`](http://api.jquery.com/text/) 2560 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2561 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2562 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2563 * - [`val()`](http://api.jquery.com/val/) 2564 * - [`wrap()`](http://api.jquery.com/wrap/) 2565 * 2566 * ## jQuery/jqLite Extras 2567 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2568 * 2569 * ### Events 2570 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2570pis and fires this event 2571 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2572 * element before it is removed. 2573 * 2574 * ### Methods 2575 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2576 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2577 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2578 * `'ngModel'`). 2579 * - `injector()` - retrieves the injector of the current element or its parent. 2580 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2581 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 2582 * be enabled. 2583 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2584 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2585 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2586 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 2587 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2588 * parent element is reached. 2589 * 2590 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2591 * @returns {Object} jQuery object. 2592 */ 2593 2594JQLite.expando = 'ng339'; 2595 2596var jqCache = JQLite.cache = {}, 2597 jqId = 1, 2598 addEventListenerFn = function(element, type, fn) { 2599 element.addEventListener(type, fn, false); 2600 }, 2601 removeEventListenerFn = function(element, type, fn) { 2602 element.removeEventListener(type, fn, false); 2603 }; 2604 2605/* 2606 * !!! This is an undocumented "private" function !!! 2607 */ 2608JQLite._data = function(node) { 2609 //jQuery always returns an object on cache miss 2610 return this.cache[node[this.expando]] || {}; 2611}; 2612 2613function jqNextId() { return ++jqId; } 2614 2615 2616var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2617var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2618var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 2619var jqLiteMinErr = minErr('jqLite'); 2620 2621/** 2622 * Converts snake_case to camelCase. 2623 * Also there is special case for Moz prefix starting with upper case letter. 2624 * @param name Name to normalize 2625 */ 2626function camelCase(name) { 2627 return name. 2628 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2629 return offset ? letter.toUpperCase() : letter; 2630 }). 2631 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2632} 2633 2634var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; 2635var HTML_REGEXP = /<|&#?\w+;/; 2636var TAG_NAME_REGEXP = /<([\w:]+)/; 2637var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 2638 2639var wrapMap = { 2640 'option': [1, '<select multiple="multiple">', '</select>'], 2641 2642 'thead': [1, '<table>', '</table>'], 2643 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2644 'tr': [2, '<table><tbody>', '</tbody></table>'], 2645 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2646 '_default': [0, "", ""] 2647}; 2648 2649wrapMap.optgroup = wrapMap.option; 2650wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2651wrapMap.th = wrapMap.td; 2652 2653 2654function jqLiteIsTextNode(html) { 2655 return !HTML_REGEXP.test(html); 2656} 2657 2658function jqLiteAcceptsData(node) { 2659 // The window object can accept data but has no nodeType 2660 // Otherwise we are only interested in elements (1) and documents (9) 2661 var nodeType = node.nodeType; 2662 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 2663} 2664 2665function jqLiteBuildFragment(html, context) { 2666 var tmp, tag, wrap, 2667 fragment = context.createDocumentFragment(), 2668 nodes = [], i; 2669 2670 if (jqLiteIsTextNode(html)) { 2671 // Convert non-html into a text node 2672 nodes.push(context.createTextNode(html)); 2673 } else { 2674 // Convert html into DOM nodes 2675 tmp = tmp || fragment.appendChild(context.createElement("div")); 2676 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2677 wrap = wrapMap[tag] || wrapMap._default; 2678 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2679 2680 // Descend through wrappers to the right content 2681 i = wrap[0]; 2682 while (i--) { 2683 tmp = tmp.lastChild; 2684 } 2685 2686 nodes = concat(nodes, tmp.childNodes); 2687 2688 tmp = fragment.firstChild; 2689 tmp.textContent = ""; 2690 } 2691 2692 // Remove wrapper from fragment 2693 fragment.textContent = ""; 2694 fragment.innerHTML = ""; // Clear inner HTML 2695 forEach(nodes, function(node) { 2696 fragment.appendChild(node); 2697 }); 2698 2699 return fragment; 2700} 2701 2702function jqLiteParseHTML(html, context) { 2703 context = context || document; 2704 var parsed; 2705 2706 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2707 return [context.createElement(parsed[1])]; 2708 } 2709 2710 if ((parsed = jqLiteBuildFragment(html, context))) { 2711 return parsed.childNodes; 2712 } 2713 2714 return []; 2715} 2716
vendor: 1,065 bytes, lines 2717-2758
2717///////////////////////////////////////////// 2718function JQLite(element) { 2719 if (element instanceof JQLite) { 2720 return element; 2721 } 2722 2723 var argIsString; 2724 2725 if (isString(element)) { 2726 element = trim(element); 2727 argIsString = true; 2728 } 2729 if (!(this instanceof JQLite)) { 2730 if (argIsString && element.charAt(0) != '<') { 2731 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2732 } 2733 return new JQLite(element); 2734 } 2735 2736 if (argIsString) { 2737 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2738 } else { 2739 jqLiteAddNodes(this, element); 2740 } 2741} 2742 2743function jqLiteClone(element) { 2744 return element.cloneNode(true); 2745} 2746 2747function jqLiteDealoc(element, onlyDescendants) { 2748 if (!onlyDescendants) jqLiteRemoveData(element); 2749 2750 if (element.querySelectorAll) { 2751 var descendants = element.querySelectorAll('*'); 2752 for (var i = 0, l = descendants.length; i < l; i++) { 2753 jqLiteRemoveData(descendants[i]); 2754 } 2755 } 2756} 2757 2758function jqLiteOff(element, type, fn, unsupported) {
2759 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2760 2761 var expandoStore = jqLiteExpandoStore(element); 2762 var events = expandoStore && expandoStore.events; 2763 var handle = expandoStore && expandoStore.handle; 2764 2765 if (!handle) return; //no listeners registered 2766 2767 if (!type) { 2768 for (type in events) { 2769 if (type !== '$destroy') { 2770 removeEventListenerFn(element, type, handle); 2771 } 2772 delete events[type]; 2773 } 2774 } else { 2775 forEach(type.split(' '), function(type) { 2776 if (isDefined(fn)) { 2777 var listenerFns = events[type]; 2778 arrayRemove(listenerFns || [], fn); 2779 if (listenerFns && listenerFns.length > 0) { 2780 return; 2781 } 2782 } 2783 2784 removeEventListenerFn(element, type, handle); 2785 delete events[type]; 2786 }); 2787 } 2788} 2789 2790function jqLiteRemoveData(element, name) { 2791 var expandoId = element.ng339; 2792 var expandoStore = expandoId && jqCache[expandoId]; 2793 2794 if (expandoStore) { 2795 if (name) { 2796 delete expandoStore.data[name]; 2797 return; 2798 } 2799 2800 if (expandoStore.handle) { 2801 if (expandoStore.events.$destroy) { 2802 expandoStore.handle({}, '$destroy'); 2803 } 2804 jqLiteOff(element); 2805 } 2806 delete jqCache[expandoId]; 2807 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2808 } 2809} 2810 2811 2812function jqLiteExpandoStore(element, createIfNecessary) { 2813 var expandoId = element.ng339, 2814 expandoStore = expandoId && jqCache[expandoId]; 2815 2816 if (createIfNecessary && !expandoStore) { 2817 element.ng339 = expandoId = jqNextId(); 2818 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 2819 } 2820 2821 return expandoStore; 2822} 2823 2824 2825function jqLiteData(element, key, value) { 2826 if (jqLiteAcceptsData(element)) { 2827 2828 var isSimpleSetter = isDefined(value); 2829 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 2830 var massGetter = !key; 2831 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 2832 var data = expandoStore && expandoStore.data; 2833 2834 if (isSimpleSetter) { // data('key', value) 2835 data[key] = value; 2836 } else { 2837 if (massGetter) { // data() 2838 return data; 2839 } else { 2840 if (isSimpleGetter) { // data('key') 2841 // don't force creation of expandoStore if it doesn't exist yet 2842 return data && data[key]; 2843 } else { // mass-setter: data({key1: val1, key2: val2}) 2844 extend(data, key); 2845 } 2846 } 2847 } 2848 } 2849} 2850 2851function jqLiteHasClass(element, selector) { 2852 if (!element.getAttribute) return false; 2853 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2854 indexOf(" " + selector + " ") > -1); 2855} 2856 2857function jqLiteRemoveClass(element, cssClasses) { 2858 if (cssClasses && element.setAttribute) { 2859 forEach(cssClasses.split(' '), function(cssClass) { 2860 element.setAttribute('class', trim( 2861 (" " + (element.getAttribute('class') || '') + " ") 2862 .replace(/[\n\t]/g, " ") 2863 .replace(" " + trim(cssClass) + " ", " ")) 2864 ); 2865 }); 2866 } 2867} 2868 2869function jqLiteAddClass(element, cssClasses) { 2870 if (cssClasses && element.setAttribute) { 2871 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2872 .replace(/[\n\t]/g, " "); 2873 2874 forEach(cssClasses.split(' '), function(cssClass) { 2875 cssClass = trim(cssClass); 2876 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2877 existingClasses += cssClass + ' '; 2878 } 2879 }); 2880 2881 element.setAttribute('class', trim(existingClasses)); 2882 } 2883} 2884 2885 2886function jqLiteAddNodes(root, elements) { 2887 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 2888 2889 if (elements) { 2890 2891 // if a Node (the most common case) 2892 if (elements.nodeType) { 2893 root[root.length++] = elements; 2894 } else { 2895 var length = elements.length; 2896 2897 // if an Array or NodeList and not a Window 2898 if (typeof length === 'number' && elements.window !== elements) { 2899 if (length) { 2900 for (var i = 0; i < length; i++) { 2901 root[root.length++] = elements[i]; 2902 } 2903 } 2904 } else { 2905 root[root.length++] = elements; 2906 } 2907 } 2908 } 2909} 2910 2911 2912function jqLiteController(element, name) { 2913 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 2914} 2915 2916function jqLiteInheritedData(element, name, value) { 2917 // if element is the document object work with the html element instead 2918 // this makes $(document).scope() possible 2919 if (element.nodeType == NODE_TYPE_DOCUMENT) { 2920 element = element.documentElement; 2921 } 2922 var names = isArray(name) ? name : [name]; 2923 2924 while (element) { 2925 for (var i = 0, ii = names.length; i < ii; i++) { 2926 if ((value = jqLite.data(element, names[i])) !== undefined) return value; 2927 } 2928 2929 // If dealing with a document fragment node with a host element, and no parent, use the host 2930 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2931 // to lookup parent controllers. 2932 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 2933 } 2934} 2935 2936function jqLiteEmpty(element) { 2937 jqLiteDealoc(element, true); 2938 while (element.firstChild) { 2939 element.removeChild(element.firstChild); 2940 } 2941} 2942 2943function jqLiteRemove(element, keepData) { 2944 if (!keepData) jqLiteDealoc(element); 2945 var parent = element.parentNode; 2946 if (parent) parent.removeChild(element); 2947} 2948 2949 2950function jqLiteDocumentLoaded(action, win) { 2951 win = win || window; 2952 if (win.document.readyState === 'complete') { 2953 // Force the action to be run async for consistent behaviour 2954 // from the action's point of view 2955 // i.e. it will definitely not be in a $apply 2956 win.setTimeout(action); 2957 } else { 2958 // No need to unbind this handler as load is only ever called once 2959 jqLite(win).on('load', action); 2960 } 2961} 2962
2963////////////////////////////////////////// 2964// Functions which are declared directly. 2965////////////////////////////////////////// 2966var JQLitePrototype = JQLite.prototype = { 2967 ready: function(fn) { 2968 var fired = false; 2969 2970 function trigger() { 2971 if (fired) return; 2972 fired = true; 2973 fn(); 2974 } 2975 2976 // check if document is already loaded 2977 if (document.readyState === 'complete') { 2978 setTimeout(trigger); 2979 } else { 2980 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2981 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2982 // jshint -W064 2983 JQLite(window).on('load', trigger); // fallback to window.onload for others 2984 // jshint +W064 2985 } 2986 }, 2987 toString: function() { 2988 var value = []; 2989 forEach(this, function(e) { value.push('' + e);}); 2990 return '[' + value.join(', ') + ']'; 2991 }, 2992 2993 eq: function(index) { 2994 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2995 }, 2996 2997 length: 0, 2998 push: push, 2999 sort: [].sort, 3000 splice: [].splice 3001}; 3002 3003////////////////////////////////////////// 3004// Functions iterating getter/setters. 3005// these functions return self on setter and 3006// value on get. 3007////////////////////////////////////////// 3008var BOOLEAN_ATTR = {}; 3009forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3010 BOOLEAN_ATTR[lowercase(value)] = value; 3011}); 3012var BOOLEAN_ELEMENTS = {}; 3013forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3014 BOOLEAN_ELEMENTS[value] = true; 3015}); 3016var ALIASED_ATTR = { 3017 'ngMinlength': 'minlength', 3018 'ngMaxlength': 'maxlength', 3019 'ngMin': 'min', 3020 'ngMax': 'max', 3021 'ngPattern': 'pattern' 3022}; 3023 3024function getBooleanAttrName(element, name) { 3025 // check dom last since we will most likely fail on name 3026 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3027 3028 // booleanAttr is here twice to minimize DOM access 3029 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3030} 3031 3032function getAliasedAttrName(element, name) { 3033 var nodeName = element.nodeName; 3034 return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name]; 3035} 3036 3037forEach({ 3038 data: jqLiteData, 3039 removeData: jqLiteRemoveData 3040}, function(fn, name) { 3041 JQLite[name] = fn; 3042}); 3043 3044forEach({ 3045 data: jqLiteData, 3046 inheritedData: jqLiteInheritedData, 3047 3048 scope: function(element) { 3049 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3050 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3051 }, 3052 3053 isolateScope: function(element) { 3054 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3055 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3056 }, 3057 3058 controller: jqLiteController, 3059 3060 injector: function(element) { 3061 return jqLiteInheritedData(element, '$injector'); 3062 }, 3063 3064 removeAttr: function(element, name) { 3065 element.removeAttribute(name); 3066 }, 3067 3068 hasClass: jqLiteHasClass, 3069 3070 css: function(element, name, value) { 3071 name = camelCase(name); 3072 3073 if (isDefined(value)) { 3074 element.style[name] = value; 3075 } else { 3076 return element.style[name]; 3077 } 3078 }, 3079 3080 attr: function(element, name, value) { 3081 var nodeType = element.nodeType; 3082 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 3083 return; 3084 } 3085 var lowercasedName = lowercase(name); 3086 if (BOOLEAN_ATTR[lowercasedName]) { 3087 if (isDefined(value)) { 3088 if (!!value) { 3089 element[name] = true; 3090 element.setAttribute(name, lowercasedName); 3091 } else { 3092 element[name] = false; 3093 element.removeAttribute(lowercasedName); 3094 } 3095 } else { 3096 return (element[name] || 3097 (element.attributes.getNamedItem(name) || noop).specified) 3098 ? lowercasedName 3099 : undefined; 3100 } 3101 } else if (isDefined(value)) { 3102 element.setAttribute(name, value); 3103 } else if (element.getAttribute) { 3104 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 3105 // some elements (e.g. Document) don't have get attribute, so return undefined 3106 var ret = element.getAttribute(name, 2); 3107 // normalize non-existing attributes to undefined (as jQuery) 3108 return ret === null ? undefined : ret; 3109 } 3110 }, 3111 3112 prop: function(element, name, value) { 3113 if (isDefined(value)) { 3114 element[name] = value; 3115 } else { 3116 return element[name]; 3117 } 3118 }, 3119 3120 text: (function() { 3121 getText.$dv = ''; 3122 return getText; 3123 3124 function getText(element, value) { 3125 if (isUndefined(value)) { 3126 var nodeType = element.nodeType; 3127 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3128 } 3129 element.textContent = value; 3130 } 3131 })(), 3132 3133 val: function(element, value) { 3134 if (isUndefined(value)) { 3135 if (element.multiple && nodeName_(element) === 'select') { 3136 var result = [];
vendor: 3,900 bytes, lines 3137-3266
3137 forEach(element.options, function(option) { 3138 if (option.selected) { 3139 result.push(option.value || option.text); 3140 } 3141 }); 3142 return result.length === 0 ? null : result; 3143 } 3144 return element.value; 3145 } 3146 element.value = value; 3147 }, 3148 3149 html: function(element, value) { 3150 if (isUndefined(value)) { 3151 return element.innerHTML; 3152 } 3153 jqLiteDealoc(element, true); 3154 element.innerHTML = value; 3155 }, 3156 3157 empty: jqLiteEmpty 3158}, function(fn, name) { 3159 /** 3160 * Properties: writes return selection, reads return first value 3161 */ 3162 JQLite.prototype[name] = function(arg1, arg2) { 3163 var i, key; 3164 var nodeCount = this.length; 3165 3166 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3167 // in a way that survives minification. 3168 // jqLiteEmpty takes no arguments but is a setter. 3169 if (fn !== jqLiteEmpty && 3170 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 3171 if (isObject(arg1)) { 3172 3173 // we are a write, but the object properties are the key/values 3174 for (i = 0; i < nodeCount; i++) { 3175 if (fn === jqLiteData) { 3176 // data() takes the whole object in jQuery 3177 fn(this[i], arg1); 3178 } else { 3179 for (key in arg1) { 3180 fn(this[i], key, arg1[key]); 3181 } 3182 } 3183 } 3184 // return self for chaining 3185 return this; 3186 } else { 3187 // we are a read, so read the first child. 3188 // TODO: do we still need this? 3189 var value = fn.$dv; 3190 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3191 var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount; 3192 for (var j = 0; j < jj; j++) { 3193 var nodeValue = fn(this[j], arg1, arg2); 3194 value = value ? value + nodeValue : nodeValue; 3195 } 3196 return value; 3197 } 3198 } else { 3199 // we are a write, so apply to all children 3200 for (i = 0; i < nodeCount; i++) { 3201 fn(this[i], arg1, arg2); 3202 } 3203 // return self for chaining 3204 return this; 3205 } 3206 }; 3207}); 3208 3209function createEventHandler(element, events) { 3210 var eventHandler = function(event, type) { 3211 // jQuery specific api 3212 event.isDefaultPrevented = function() { 3213 return event.defaultPrevented; 3214 }; 3215 3216 var eventFns = events[type || event.type]; 3217 var eventFnsLength = eventFns ? eventFns.length : 0; 3218 3219 if (!eventFnsLength) return; 3220 3221 if (isUndefined(event.immediatePropagationStopped)) { 3222 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3223 event.stopImmediatePropagation = function() { 3224 event.immediatePropagationStopped = true; 3225 3226 if (event.stopPropagation) { 3227 event.stopPropagation(); 3228 } 3229 3230 if (originalStopImmediatePropagation) { 3231 originalStopImmediatePropagation.call(event); 3232 } 3233 }; 3234 } 3235 3236 event.isImmediatePropagationStopped = function() { 3237 return event.immediatePropagationStopped === true; 3238 }; 3239 3240 // Copy event handlers in case event handlers array is modified during execution. 3241 if ((eventFnsLength > 1)) { 3242 eventFns = shallowCopy(eventFns); 3243 } 3244 3245 for (var i = 0; i < eventFnsLength; i++) { 3246 if (!event.isImmediatePropagationStopped()) { 3247 eventFns[i].call(element, event); 3248 } 3249 } 3250 }; 3251 3252 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3253 // events on `element` 3254 eventHandler.elem = element; 3255 return eventHandler; 3256} 3257 3258////////////////////////////////////////// 3259// Functions iterating traversal. 3260// These functions chain results into a single 3261// selector. 3262////////////////////////////////////////// 3263forEach({ 3264 removeData: jqLiteRemoveData, 3265 3266 on: function jqLiteOn(element, type, fn, unsupported) {
3267 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3268 3269 // Do not add event handlers to non-elements because they will not be cleaned up. 3270 if (!jqLiteAcceptsData(element)) { 3271 return; 3272 } 3273 3274 var expandoStore = jqLiteExpandoStore(element, true); 3275 var events = expandoStore.events; 3276 var handle = expandoStore.handle; 3277 3278 if (!handle) { 3279 handle = expandoStore.handle = createEventHandler(element, events); 3280 } 3281 3282 // http://jsperf.com/string-indexof-vs-split 3283 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3284 var i = types.length; 3285 3286 while (i--) { 3287 type = types[i]; 3288 var eventFns = events[type]; 3289 3290 if (!eventFns) { 3291 events[type] = []; 3292 3293 if (type === 'mouseenter' || type === 'mouseleave') { 3294 // Refer to jQuery's implementation of mouseenter & mouseleave 3295 // Read about mouseenter and mouseleave: 3296 // http://www.quirksmode.org/js/events_mouse.html#link8 3297 3298 jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) { 3299 var target = this, related = event.relatedTarget; 3300 // For mousenter/leave call the handler if related is outside the target. 3301 // NB: No relatedTarget if the mouse left/entered the browser window 3302 if (!related || (related !== target && !target.contains(related))) { 3303 handle(event, type); 3304 } 3305 }); 3306 3307 } else { 3308 if (type !== '$destroy') { 3309 addEventListenerFn(element, type, handle); 3310 } 3311 } 3312 eventFns = events[type]; 3313 } 3314 eventFns.push(fn); 3315 } 3316 }, 3317 3318 off: jqLiteOff, 3319 3320 one: function(element, type, fn) { 3321 element = jqLite(element); 3322 3323 //add the listener twice so that when it is called 3324 //you can remove the original function and still be 3325 //able to call element.off(ev, fn) normally 3326 element.on(type, function onFn() { 3327 element.off(type, fn); 3328 element.off(type, onFn); 3329 }); 3330 element.on(type, fn); 3331 }, 3332 3333 replaceWith: function(element, replaceNode) { 3334 var index, parent = element.parentNode; 3335 jqLiteDealoc(element); 3336 forEach(new JQLite(replaceNode), function(node) { 3337 if (index) { 3338 parent.insertBefore(node, index.nextSibling); 3339 } else { 3340 parent.replaceChild(node, element); 3341 } 3342 index = node; 3343 }); 3344 }, 3345 3346 children: function(element) { 3347 var children = []; 3348 forEach(element.childNodes, function(element) { 3349 if (element.nodeType === NODE_TYPE_ELEMENT) { 3350 children.push(element); 3351 } 3352 }); 3353 return children; 3354 }, 3355 3356 contents: function(element) { 3357 return element.contentDocument || element.childNodes || []; 3358 }, 3359 3360 append: function(element, node) { 3361 var nodeType = element.nodeType; 3362 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 3363 3364 node = new JQLite(node); 3365 3366 for (var i = 0, ii = node.length; i < ii; i++) { 3367 var child = node[i]; 3368 element.appendChild(child); 3369 } 3370 }, 3371 3372 prepend: function(element, node) { 3373 if (element.nodeType === NODE_TYPE_ELEMENT) { 3374 var index = element.firstChild; 3375 forEach(new JQLite(node), function(child) { 3376 element.insertBefore(child, index); 3377 }); 3378 } 3379 }, 3380 3381 wrap: function(element, wrapNode) { 3382 wrapNode = jqLite(wrapNode).eq(0).clone()[0]; 3383 var parent = element.parentNode; 3384 if (parent) { 3385 parent.replaceChild(wrapNode, element); 3386 } 3387 wrapNode.appendChild(element); 3388 }, 3389 3390 remove: jqLiteRemove, 3391 3392 detach: function(element) { 3393 jqLiteRemove(element, true); 3394 }, 3395 3396 after: function(element, newElement) { 3397 var index = element, parent = element.parentNode; 3398 newElement = new JQLite(newElement); 3399 3400 for (var i = 0, ii = newElement.length; i < ii; i++) { 3401 var node = newElement[i]; 3402 parent.insertBefore(node, index.nextSibling); 3403 index = node; 3404 } 3405 }, 3406 3407 addClass: jqLiteAddClass, 3408 removeClass: jqLiteRemoveClass, 3409 3410 toggleClass: function(element, selector, condition) { 3411 if (selector) {
vendor: 5,441 bytes, lines 3412-3613
3412 forEach(selector.split(' '), function(className) { 3413 var classCondition = condition; 3414 if (isUndefined(classCondition)) { 3415 classCondition = !jqLiteHasClass(element, className); 3416 } 3417 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3418 }); 3419 } 3420 }, 3421 3422 parent: function(element) { 3423 var parent = element.parentNode; 3424 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 3425 }, 3426 3427 next: function(element) { 3428 return element.nextElementSibling; 3429 }, 3430 3431 find: function(element, selector) { 3432 if (element.getElementsByTagName) { 3433 return element.getElementsByTagName(selector); 3434 } else { 3435 return []; 3436 } 3437 }, 3438 3439 clone: jqLiteClone, 3440 3441 triggerHandler: function(element, event, extraParameters) { 3442 3443 var dummyEvent, eventFnsCopy, handlerArgs; 3444 var eventName = event.type || event; 3445 var expandoStore = jqLiteExpandoStore(element); 3446 var events = expandoStore && expandoStore.events; 3447 var eventFns = events && events[eventName]; 3448 3449 if (eventFns) { 3450 // Create a dummy event to pass to the handlers 3451 dummyEvent = { 3452 preventDefault: function() { this.defaultPrevented = true; }, 3453 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3454 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 3455 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 3456 stopPropagation: noop, 3457 type: eventName, 3458 target: element 3459 }; 3460 3461 // If a custom event was provided then extend our dummy event with it 3462 if (event.type) { 3463 dummyEvent = extend(dummyEvent, event); 3464 } 3465 3466 // Copy event handlers in case event handlers array is modified during execution. 3467 eventFnsCopy = shallowCopy(eventFns); 3468 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3469 3470 forEach(eventFnsCopy, function(fn) { 3471 if (!dummyEvent.isImmediatePropagationStopped()) { 3472 fn.apply(element, handlerArgs); 3473 } 3474 }); 3475 } 3476 } 3477}, function(fn, name) { 3478 /** 3479 * chaining functions 3480 */ 3481 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3482 var value; 3483 3484 for (var i = 0, ii = this.length; i < ii; i++) { 3485 if (isUndefined(value)) { 3486 value = fn(this[i], arg1, arg2, arg3); 3487 if (isDefined(value)) { 3488 // any function which returns a value needs to be wrapped 3489 value = jqLite(value); 3490 } 3491 } else { 3492 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3493 } 3494 } 3495 return isDefined(value) ? value : this; 3496 }; 3497 3498 // bind legacy bind/unbind to on/off 3499 JQLite.prototype.bind = JQLite.prototype.on; 3500 JQLite.prototype.unbind = JQLite.prototype.off; 3501}); 3502 3503 3504// Provider for private $$jqLite service 3505function $$jqLiteProvider() { 3506 this.$get = function $$jqLite() { 3507 return extend(JQLite, { 3508 hasClass: function(node, classes) { 3509 if (node.attr) node = node[0]; 3510 return jqLiteHasClass(node, classes); 3511 }, 3512 addClass: function(node, classes) { 3513 if (node.attr) node = node[0]; 3514 return jqLiteAddClass(node, classes); 3515 }, 3516 removeClass: function(node, classes) { 3517 if (node.attr) node = node[0]; 3518 return jqLiteRemoveClass(node, classes); 3519 } 3520 }); 3521 }; 3522} 3523 3524/** 3525 * Computes a hash of an 'obj'. 3526 * Hash of a: 3527 * string is string 3528 * number is number as string 3529 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3530 * that is also assigned to the $$hashKey property of the object. 3531 * 3532 * @param obj 3533 * @returns {string} hash string such that the same input will have the same hash string. 3534 * The resulting string key is in 'type:hashKey' format. 3535 */ 3536function hashKey(obj, nextUidFn) { 3537 var key = obj && obj.$$hashKey; 3538 3539 if (key) { 3540 if (typeof key === 'function') { 3541 key = obj.$$hashKey(); 3542 } 3543 return key; 3544 } 3545 3546 var objType = typeof obj; 3547 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3548 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 3549 } else { 3550 key = objType + ':' + obj; 3551 } 3552 3553 return key; 3554} 3555 3556/** 3557 * HashMap which can use objects as keys 3558 */ 3559function HashMap(array, isolatedUid) { 3560 if (isolatedUid) { 3561 var uid = 0; 3562 this.nextUid = function() { 3563 return ++uid; 3564 }; 3565 } 3566 forEach(array, this.put, this); 3567} 3568HashMap.prototype = { 3569 /** 3570 * Store key value pair 3571 * @param key key to store can be any type 3572 * @param value value to store can be any type 3573 */ 3574 put: function(key, value) { 3575 this[hashKey(key, this.nextUid)] = value; 3576 }, 3577 3578 /** 3579 * @param key 3580 * @returns {Object} the value for the key 3581 */ 3582 get: function(key) { 3583 return this[hashKey(key, this.nextUid)]; 3584 }, 3585 3586 /** 3587 * Remove the key/value pair 3588 * @param key 3589 */ 3590 remove: function(key) { 3591 var value = this[key = hashKey(key, this.nextUid)]; 3592 delete this[key]; 3593 return value; 3594 } 3595}; 3596 3597var $$HashMapProvider = [function() { 3598 this.$get = [function() { 3599 return HashMap; 3600 }]; 3601}]; 3602 3603/** 3604 * @ngdoc function 3605 * @module ng 3606 * @name angular.injector 3607 * @kind function 3608 * 3609 * @description 3610 * Creates an injector object that can be used for retrieving services as well as for 3611 * dependency injection (see {@link guide/di dependency injection}). 3612 * 3613 * @param {Array.<string|Function>}
3613 modules A list of module functions or their aliases. See 3614 * {@link angular.module}. The `ng` module must be explicitly added. 3615 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 3616 * disallows argument name annotation inference. 3617 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3618 * 3619 * @example 3620 * Typical usage 3621 * ```js 3622 * // create an injector 3623 * var $injector = angular.injector(['ng']); 3624 * 3625 * // use the injector to kick off your application 3626 * // use the type inference to auto inject arguments, or use implicit injection 3627 * $injector.invoke(function($rootScope, $compile, $document) { 3628 * $compile($document)($rootScope); 3629 * $rootScope.$digest(); 3630 * }); 3631 * ``` 3632 * 3633 * Sometimes you want to get access to the injector of a currently running Angular app 3634 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3635 * application has been bootstrapped. You can do this using the extra `injector()` added 3636 * to JQuery/jqLite elements. See {@link angular.element}. 3637 * 3638 * *This is fairly rare but could be the case if a third party library is injecting the 3639 * markup.* 3640 * 3641 * In the following example a new block of HTML containing a `ng-controller` 3642 * directive is added to the end of the document body by JQuery. We then compile and link 3643 * it into the current AngularJS scope. 3644 * 3645 * ```js 3646 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3647 * $(document.body).append($div); 3648 * 3649 * angular.element(document).injector().invoke(function($compile) { 3650 * var scope = angular.element($div).scope(); 3651 * $compile($div)(scope); 3652 * }); 3653 * ``` 3654 */ 3655 3656 3657/** 3658 * @ngdoc module 3659 * @name auto 3660 * @description 3661 * 3662 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3663 */ 3664 3665var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3666var FN_ARG_SPLIT = /,/; 3667var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3668var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3669var $injectorMinErr = minErr('$injector'); 3670 3671function anonFn(fn) { 3672 // For anonymous functions, showing at the very least the function signature can help in 3673 // debugging. 3674 var fnText = fn.toString().replace(STRIP_COMMENTS, ''), 3675 args = fnText.match(FN_ARGS); 3676 if (args) { 3677 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 3678 } 3679 return 'fn'; 3680} 3681 3682function annotate(fn, strictDi, name) { 3683 var $inject, 3684 fnText, 3685 argDecl, 3686 last; 3687 3688 if (typeof fn === 'function') { 3689 if (!($inject = fn.$inject)) { 3690 $inject = []; 3691 if (fn.length) { 3692 if (strictDi) { 3693 if (!isString(name) || !name) { 3694 name = fn.name || anonFn(fn); 3695 } 3696 throw $injectorMinErr('strictdi', 3697 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 3698 } 3699 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3700 argDecl = fnText.match(FN_ARGS); 3701 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 3702 arg.replace(FN_ARG, function(all, underscore, name) { 3703 $inject.push(name); 3704 }); 3705 }); 3706 } 3707 fn.$inject = $inject; 3708 } 3709 } else if (isArray(fn)) { 3710 last = fn.length - 1; 3711 assertArgFn(fn[last], 'fn'); 3712 $inject = fn.slice(0, last); 3713 } else { 3714 assertArgFn(fn, 'fn', true); 3715 } 3716 return $inject; 3717} 3718 3719/////////////////////////////////////// 3720 3721/** 3722 * @ngdoc service 3723 * @name $injector 3724 * 3725 * @description 3726 * 3727 * `$injector` is used to retrieve object instances as defined by 3728 * {@link auto.$provide provider}, instantiate types, invoke methods, 3729 * and load modules. 3730 * 3731 * The following always holds true: 3732 * 3733 * ```js 3734 * var $injector = angular.injector(); 3735 * expect($injector.get('$injector')).toBe($injector); 3736 * expect($injector.invoke(function($injector) { 3737 * return $injector; 3738 * })).toBe($injector); 3739 * ``` 3740 * 3741 * # Injection Function Annotation 3742 * 3743 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3744 * following are all valid ways of annotating function with injection arguments and are equivalent. 3745 * 3746 * ```js 3747 * // inferred (only works if code not minified/obfuscated) 3748 * $injector.invoke(function(serviceA){}); 3749 * 3750 * // annotated 3751 * function explicit(serviceA) {}; 3752 * explicit.$inject = ['serviceA']; 3753 * $injector.invoke(explicit); 3754 * 3755 * // inline 3756 * $injector.invoke(['serviceA', function(serviceA){}]); 3757 * ``` 3758 * 3759 * ## Inference 3760 * 3761 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3762 * can then be parsed and the function arguments can be extracted. This method of discovering 3763 * annotations is disallowed when the injector is in strict mode. 3764 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 3765 * argument names. 3766 * 3767 * ## `$inject` Annotation 3768 * By adding an `$inject` property onto a function the injection parameters can be specified. 3769 * 3770 * ## Inline 3771 * As an array of injection names, where the last item in the array is the function to call. 3772 */ 3773 3774/** 3775 * @ngdoc method 3776 * @name $injector#get 3777 * 3778 * @description 3779 * Return an instance of the service. 3780 * 3781 * @param {string} name The name of the instance to retrieve. 3782 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 3783 * @return {*} The instance. 3784 */ 3785 3786/** 3787 * @ngdoc method 3788 * @name $injector#invoke 3789 * 3790 * @description 3791 * Invoke the method and supply the method arguments from the `$injector`. 3792 * 3793 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 3794 * injected according to the {@link guide/di $inject Annotation} rules. 3795 * @param {Object=} self The `this` for the invoked method. 3796 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3797 * object first, before the `$injector` is consulted. 3798 * @returns {*} the value returned by the invoked `fn` function. 3799 */ 3800 3801/** 3802 * @ngdoc method 3803 * @name $injector#has 3804 * 3805 * @description 3806 * Allows the user to query if the particular service exists. 3807 * 3808 * @param {string} name Name of the service to query. 3809 * @returns {boolean} `true` if injector has given service. 3810 */ 3811 3812/** 3813 * @ngdoc method 3814 * @name $injector#instantiate 3815 * @description 3816 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3817 * operator, and supplies all of the arguments to the constructor function as specified by the 3818 * constructor annotation. 3819 * 3820 * @param {Function} Type Annotated constructor function. 3821 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3822 * object first, before the `$injector` is consulted. 3823 * @returns {Object} new instance of `Type`. 3824 */ 3825 3826/** 3827 * @ngdoc method 3828 * @name $injector#annotate 3829 * 3830 * @description 3831 * Returns an array of service names which the function is requesting for injection. This API is 3832 * used by the injector to determine which services need to be injected into the function when the 3833 * function is invoked. There are three ways in which the function can be annotated with the needed 3834 * dependencies. 3835 * 3836 * # Argument names 3837 * 3838 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3839 * by converting the function into a string using `toString()` method and extracting the argument 3840 * names. 3841 * ```js 3842 * // Given 3843 * function MyController($scope, $route) { 3844 * // ... 3845 * } 3846 * 3847 * // Then 3848 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3849 * ``` 3850 * 3851 * You can disallow this method by using strict injection mode. 3852 * 3853 * This method does not work with code minification / obfuscation. For this reason the following 3854 * annotation strategies are supported. 3855 * 3856 * # The `$inject` property 3857 * 3858 * If a function has an `$inject` property and its value is an array of strings, then the strings 3859 * represent names of services to be injected into the function. 3860 * ```js 3861 * // Given 3862 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3863 * // ... 3864 * } 3865 * // Define function dependencies 3866 * MyController['$inject'] = ['$scope', '$route']; 3867 * 3868 * // Then 3869 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3870 * ``` 3871 * 3872 * # The array notation 3873 * 3874 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3875 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3876 * a way that survives minification is a better choice: 3877 * 3878 * ```js 3879 * // We wish to write this (not minification / obfuscation safe) 3880 * injector.invoke(function($compile, $rootScope) { 3881 * // ... 3882 * }); 3883 * 3884 * // We are forced to write break inlining 3885 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3886 * // ... 3887 * }; 3888 * tmpFn.$inject = ['$compile', '$rootScope']; 3889 * injector.invoke(tmpFn); 3890 * 3891 * // To better support inline function the inline annotation is supported 3892 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3893 * // ... 3894 * }]); 3895 * 3896 * // Therefore 3897 * expect(injector.annotate( 3898 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3899 * ).toEqual(['$compile', '$rootScope']); 3900 * ``` 3901 * 3902 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 3903 * be retrieved as described above. 3904 * 3905 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 3906 * 3907 * @returns {Array.<string>} The names of the services which the function requires. 3908 */ 3909 3910 3911 3912 3913/** 3914 * @ngdoc service 3915 * @name $provide 3916 * 3917 * @description 3918 * 3919 * The {@link auto.$provide $provide} service has a number of methods for registering components 3920 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3921 * {@link angular.Module}. 3922 * 3923 * An Angular **service** is a singleton object created by a **service factory**. These **service 3924 * factories** are functions which, in turn, are created by a **service provider**. 3925 * The **service providers** are constructor functions. When instantiated they must contain a 3926 * property called `$get`, which holds the **service factory** function. 3927 * 3928 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3929 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3930 * function to get the instance of the **service**. 3931 * 3932 * Often services have no configuration options and there is no need to add methods to the service 3933 * provider. The provider will be no more than a constructor function with a `$get` property. For 3934 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3935 * services without specifying a provider. 3936 * 3937 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3938 * {@link auto.$injector $injector} 3939 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3940 * providers and services. 3941 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3942 * services, not providers. 3943 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3944 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3945 * given factory function. 3946 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3947 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3948 * a new object using the given constructor function. 3949 * 3950 * See the individual methods for more information and examples. 3951 */ 3952 3953/** 3954 * @ngdoc method 3955 * @name $provide#provider 3956 * @description 3957 * 3958 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 3959 * are constructor functions, whose instances are responsible for "providing" a factory for a 3960 * service. 3961 * 3962 * Service provider names start with the name of the service they provide followed by `Provider`. 3963 * For example, the {@link ng.$log $log} service has a provider called 3964 * {@link ng.$logProvider $logProvider}. 3965 * 3966 * Service provider objects can have additional methods which allow configuration of the provider 3967 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3968 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3969 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3970 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3971 * console or not. 3972 * 3973 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3974 'Provider'` key. 3975 * @param {(Object|function())} provider If the provider is: 3976 * 3977 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3978 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 3979 * - `Constructor`: a new instance of the provider will be created using 3980 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3981 * 3982 * @returns {Object} registered provider instance 3983 3984 * @example 3985 * 3986 * The following example shows how to create a simple event tracking service and register it using 3987 * {@link auto.$provide#provider $provide.provider()}. 3988 * 3989 * ```js 3990 * // Define the eventTracker provider 3991 * function EventTrackerProvider() { 3992 * var trackingUrl = '/track'; 3993 * 3994 * // A provider method for configuring where the tracked events should been saved 3995 * this.setTrackingUrl = function(url) { 3996 * trackingUrl = url; 3997 * }; 3998 * 3999 * // The service factory function 4000 * this.$get = ['$http', function($http) { 4001 * var trackedEvents = {}; 4002 * return { 4003 * // Call this to track an event 4004 * event: function(event) { 4005 * var count = trackedEvents[event] || 0; 4006 * count += 1; 4007 * trackedEvents[event] = count; 4008 * return count; 4009 * }, 4010 * // Call this to save the tracked events to the trackingUrl 4011 * save: function() { 4012 * $http.post(trackingUrl, trackedEvents); 4013 * } 4014 * }; 4015 * }]; 4016 * } 4017 * 4018 * describe('eventTracker', function() { 4019 * var postSpy; 4020 * 4021 * beforeEach(module(function($provide) { 4022 * // Register the eventTracker provider 4023 * $provide.provider('eventTracker', EventTrackerProvider); 4024 * })); 4025 * 4026 * beforeEach(module(function(eventTrackerProvider) { 4027 * // Configure eventTracker provider 4028 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4029 * })); 4030 * 4031 * it('tracks events', inject(function(eventTracker) { 4032 * expect(eventTracker.event('login')).toEqual(1); 4033 * expect(eventTracker.event('login')).toEqual(2); 4034 * })); 4035 * 4036 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4037 * postSpy = spyOn($http, 'post'); 4038 * eventTracker.event('login'); 4039 * eventTracker.save(); 4040 * expect(postSpy).toHaveBeenCalled(); 4041 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4042 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4043 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4044 * })); 4045 * }); 4046 * ``` 4047 */ 4048 4049/** 4050 * @ngdoc method 4051 * @name $provide#factory 4052 * @description 4053 * 4054 * Register a **service factory**, which will be called to return the service instance. 4055 * This is short for registering a service where its provider consists of only a `$get` property, 4056 * which is the given service factory function. 4057 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4058 * configure your service in a provider. 4059 * 4060 * @param {string} name The name of the instance. 4061 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4062 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4063 * @returns {Object} registered provider instance 4064 * 4065 * @example 4066 * Here is an example of registering a service 4067 * ```js 4068 * $provide.factory('ping', ['$http', function($http) { 4069 * return function ping() { 4070 * return $http.send('/ping'); 4071 * }; 4072 * }]); 4073 * ``` 4074 * You would then inject and use this service like this: 4075 * ```js 4076 * someModule.controller('Ctrl', ['ping', function(ping) { 4077 * ping(); 4078 * }]); 4079 * ``` 4080 */ 4081 4082 4083/** 4084 * @ngdoc method 4085 * @name $provide#service 4086 * @description 4087 * 4088 * Register a **service constructor**, which will be invoked with `new` to create the service 4089 * instance. 4090 * This is short for registering a service where its provider's `$get` property is the service 4091 * constructor function that will be used to instantiate the service instance. 4092 * 4093 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4094 * as a type/class. 4095 * 4096 * @param {string} name The name of the instance. 4097 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4098 * that will be instantiated. 4099 * @returns {Object} registered provider instance 4100 * 4101 * @example 4102 * Here is an example of registering a service using 4103 * {@link auto.$provide#service $provide.service(class)}. 4104 * ```js 4105 * var Ping = function($http) { 4106 * this.$http = $http; 4107 * }; 4108 * 4109 * Ping.$inject = ['$http']; 4110 * 4111 * Ping.prototype.send = function() { 4112 * return this.$http.get('/ping'); 4113 * }; 4114 * $provide.service('ping', Ping); 4115 * ``` 4116 * You would then inject and use this service like this: 4117 * ```js 4118 * someModule.controller('Ctrl', ['ping', function(ping) { 4119 * ping.send(); 4120 * }]); 4121 * ``` 4122 */ 4123 4124 4125/** 4126 * @ngdoc method 4127 * @name $provide#value 4128 * @description 4129 * 4130 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4131 * number, an array, an object or a function. This is short for registering a service where its 4132 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4133 * service**. 4134 * 4135 * Value services are similar to constant services, except that they cannot be injected into a 4136 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4137 * an Angular 4138 * {@link auto.$provide#decorator decorator}. 4139 * 4140 * @param {string} name The name of the instance. 4141 * @param {*} value The value. 4142 * @returns {Object} registered provider instance 4143 * 4144 * @example 4145 * Here are some examples of creating value services. 4146 * ```js 4147 * $provide.value('ADMIN_USER', 'admin'); 4148 *
4149 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4150 * 4151 * $provide.value('halfOf', function(value) { 4152 * return value / 2; 4153 * }); 4154 * ``` 4155 */ 4156 4157 4158/** 4159 * @ngdoc method 4160 * @name $provide#constant 4161 * @description 4162 * 4163 * Register a **constant service**, such as a string, a number, an array, an object or a function, 4164 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 4165 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4166 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 4167 * 4168 * @param {string} name The name of the constant. 4169 * @param {*} value The constant value. 4170 * @returns {Object} registered instance 4171 * 4172 * @example 4173 * Here a some examples of creating constants: 4174 * ```js 4175 * $provide.constant('SHARD_HEIGHT', 306); 4176 * 4177 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4178 * 4179 * $provide.constant('double', function(value) { 4180 * return value * 2; 4181 * }); 4182 * ``` 4183 */ 4184 4185 4186/** 4187 * @ngdoc method 4188 * @name $provide#decorator 4189 * @description 4190 * 4191 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 4192 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 4193 * service. The object returned by the decorator may be the original service, or a new service 4194 * object which replaces or wraps and delegates to the original service. 4195 * 4196 * @param {string} name The name of the service to decorate. 4197 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4198 * instantiated and should return the decorated service instance. The function is called using 4199 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4200 * Local injection arguments: 4201 * 4202 * * `$delegate` - The original service instance, which can be monkey patched, configured, 4203 * decorated or delegated to. 4204 * 4205 * @example 4206 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4207 * calls to {@link ng.$log#error $log.warn()}. 4208 * ```js 4209 * $provide.decorator('$log', ['$delegate', function($delegate) { 4210 * $delegate.warn = $delegate.error; 4211 * return $delegate; 4212 * }]); 4213 * ``` 4214 */ 4215 4216 4217function createInjector(modulesToLoad, strictDi) { 4218 strictDi = (strictDi === true); 4219 var INSTANTIATING = {}, 4220 providerSuffix = 'Provider', 4221 path = [], 4222 loadedModules = new HashMap([], true), 4223 providerCache = { 4224 $provide: { 4225 provider: supportObject(provider), 4226 factory: supportObject(factory), 4227 service: supportObject(service), 4228 value: supportObject(value), 4229 constant: supportObject(constant), 4230 decorator: decorator 4231 } 4232 }, 4233 providerInjector = (providerCache.$injector = 4234 createInternalInjector(providerCache, function(serviceName, caller) { 4235 if (angular.isString(caller)) { 4236 path.push(caller); 4237 } 4238 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 4239 })), 4240 instanceCache = {}, 4241 instanceInjector = (instanceCache.$injector = 4242 createInternalInjector(instanceCache, function(serviceName, caller) { 4243 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4244 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); 4245 })); 4246 4247 4248 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 4249 4250 return instanceInjector; 4251 4252 //////////////////////////////////// 4253 // $provider 4254 //////////////////////////////////// 4255 4256 function supportObject(delegate) { 4257 return function(key, value) { 4258 if (isObject(key)) { 4259 forEach(key, reverseParams(delegate)); 4260 } else { 4261 return delegate(key, value); 4262 } 4263 }; 4264 } 4265 4266 function provider(name, provider_) { 4267 assertNotHasOwnProperty(name, 'service'); 4268 if (isFunction(provider_) || isArray(provider_)) { 4269 provider_ = providerInjector.instantiate(provider_); 4270 } 4271 if (!provider_.$get) { 4272 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 4273 } 4274 return providerCache[name + providerSuffix] = provider_; 4275 } 4276 4277 function enforceReturnValue(name, factory) { 4278 return function enforcedReturnValue() { 4279 var result = instanceInjector.invoke(factory, this); 4280 if (isUndefined(result)) { 4281 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 4282 } 4283 return result; 4284 }; 4285 } 4286 4287 function factory(name, factoryFn, enforce) { 4288 return provider(name, { 4289 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 4290 }); 4291 } 4292 4293 function service(name, constructor) { 4294 return factory(name, ['$injector', function($injector) { 4295 return $injector.instantiate(constructor); 4296 }]); 4297 } 4298 4299 function value(name, val) { return factory(name, valueFn(val), false); } 4300 4301 function constant(name, value) { 4302 assertNotHasOwnProperty(name, 'constant'); 4303 providerCache[name] = value; 4304 instanceCache[name] = value; 4305 } 4306 4307 function decorator(serviceName, decorFn) { 4308 var origProvider = providerInjector.get(serviceName + providerSuffix), 4309 orig$get = origProvider.$get; 4310 4311 origProvider.$get = function() { 4312 var origInstance = instanceInjector.invoke(orig$get, origProvider); 4313 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 4314 }; 4315 } 4316 4317 //////////////////////////////////// 4318 // Module Loading 4319 //////////////////////////////////// 4320 function loadModules(modulesToLoad) { 4321 var runBlocks = [], moduleFn;
vendor: 5,290 bytes, lines 4322-4479
4322 forEach(modulesToLoad, function(module) { 4323 if (loadedModules.get(module)) return; 4324 loadedModules.put(module, true); 4325 4326 function runInvokeQueue(queue) { 4327 var i, ii; 4328 for (i = 0, ii = queue.length; i < ii; i++) { 4329 var invokeArgs = queue[i], 4330 provider = providerInjector.get(invokeArgs[0]); 4331 4332 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 4333 } 4334 } 4335 4336 try { 4337 if (isString(module)) { 4338 moduleFn = angularModule(module); 4339 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 4340 runInvokeQueue(moduleFn._invokeQueue); 4341 runInvokeQueue(moduleFn._configBlocks); 4342 } else if (isFunction(module)) { 4343 runBlocks.push(providerInjector.invoke(module)); 4344 } else if (isArray(module)) { 4345 runBlocks.push(providerInjector.invoke(module)); 4346 } else { 4347 assertArgFn(module, 'module'); 4348 } 4349 } catch (e) { 4350 if (isArray(module)) { 4351 module = module[module.length - 1]; 4352 } 4353 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 4354 // Safari & FF's stack traces don't contain error.message content 4355 // unlike those of Chrome and IE 4356 // So if stack doesn't contain message, we create a new string that contains both. 4357 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 4358 /* jshint -W022 */ 4359 e = e.message + '\n' + e.stack; 4360 } 4361 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 4362 module, e.stack || e.message || e); 4363 } 4364 }); 4365 return runBlocks; 4366 } 4367 4368 //////////////////////////////////// 4369 // internal Injector 4370 //////////////////////////////////// 4371 4372 function createInternalInjector(cache, factory) { 4373 4374 function getService(serviceName, caller) { 4375 if (cache.hasOwnProperty(serviceName)) { 4376 if (cache[serviceName] === INSTANTIATING) { 4377 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 4378 serviceName + ' <- ' + path.join(' <- ')); 4379 } 4380 return cache[serviceName]; 4381 } else { 4382 try { 4383 path.unshift(serviceName); 4384 cache[serviceName] = INSTANTIATING; 4385 return cache[serviceName] = factory(serviceName, caller); 4386 } catch (err) { 4387 if (cache[serviceName] === INSTANTIATING) { 4388 delete cache[serviceName]; 4389 } 4390 throw err; 4391 } finally { 4392 path.shift(); 4393 } 4394 } 4395 } 4396 4397 function invoke(fn, self, locals, serviceName) { 4398 if (typeof locals === 'string') { 4399 serviceName = locals; 4400 locals = null; 4401 } 4402 4403 var args = [], 4404 $inject = createInjector.$$annotate(fn, strictDi, serviceName), 4405 length, i, 4406 key; 4407 4408 for (i = 0, length = $inject.length; i < length; i++) { 4409 key = $inject[i]; 4410 if (typeof key !== 'string') { 4411 throw $injectorMinErr('itkn', 4412 'Incorrect injection token! Expected service name as string, got {0}', key); 4413 } 4414 args.push( 4415 locals && locals.hasOwnProperty(key) 4416 ? locals[key] 4417 : getService(key, serviceName) 4418 ); 4419 } 4420 if (isArray(fn)) { 4421 fn = fn[length]; 4422 } 4423 4424 // http://jsperf.com/angularjs-invoke-apply-vs-switch 4425 // #5388 4426 return fn.apply(self, args); 4427 } 4428 4429 function instantiate(Type, locals, serviceName) { 4430 // Check if Type is annotated and use just the given function at n-1 as parameter 4431 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 4432 // Object creation: http://jsperf.com/create-constructor/2 4433 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); 4434 var returnedValue = invoke(Type, instance, locals, serviceName); 4435 4436 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 4437 } 4438 4439 return { 4440 invoke: invoke, 4441 instantiate: instantiate, 4442 get: getService, 4443 annotate: createInjector.$$annotate, 4444 has: function(name) { 4445 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 4446 } 4447 }; 4448 } 4449} 4450 4451createInjector.$$annotate = annotate; 4452 4453/** 4454 * @ngdoc provider 4455 * @name $anchorScrollProvider 4456 * 4457 * @description 4458 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 4459 * {@link ng.$location#hash $location.hash()} changes. 4460 */ 4461function $AnchorScrollProvider() { 4462 4463 var autoScrollingEnabled = true; 4464 4465 /** 4466 * @ngdoc method 4467 * @name $anchorScrollProvider#disableAutoScrolling 4468 * 4469 * @description 4470 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4471 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4472 * Use this method to disable automatic scrolling. 4473 * 4474 * If automatic scrolling is disabled, one must explicitly call 4475 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4476 * current hash. 4477 */ 4478 this.disableAutoScrolling = function() { 4479 autoScrollingEnabled = false;
4480 }; 4481 4482 /** 4483 * @ngdoc service 4484 * @name $anchorScroll 4485 * @kind function 4486 * @requires $window 4487 * @requires $location 4488 * @requires $rootScope 4489 * 4490 * @description 4491 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 4492 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 4493 * in the 4494 * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 4495 * 4496 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 4497 * match any anchor whenever it changes. This can be disabled by calling 4498 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 4499 * 4500 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 4501 * vertical scroll-offset (either fixed or dynamic). 4502 * 4503 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 4504 * {@link ng.$location#hash $location.hash()} will be used. 4505 * 4506 * @property {(number|function|jqLite)} yOffset 4507 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 4508 * positioned elements at the top of the page, such as navbars, headers etc. 4509 * 4510 * `yOffset` can be specified in various ways: 4511 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 4512 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 4513 * a number representing the offset (in pixels).<br /><br /> 4514 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 4515 * the top of the page to the element's bottom will be used as offset.<br /> 4516 * **Note**: The element will be taken into account only as long as its `position` is set to 4517 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 4518 * their height and/or positioning according to the viewport's size. 4519 * 4520 * <br /> 4521 * <div class="alert alert-warning"> 4522 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 4523 * not some child element. 4524 * </div> 4525 * 4526 * @example 4527 <example module="anchorScrollExample"> 4528 <file name="index.html"> 4529 <div id="scrollArea" ng-controller="ScrollController"> 4530 <a ng-click="gotoBottom()">Go to bottom</a> 4531 <a id="bottom"></a> You're at the bottom! 4532 </div> 4533 </file> 4534 <file name="script.js"> 4535 angular.module('anchorScrollExample', []) 4536 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 4537 function ($scope, $location, $anchorScroll) { 4538 $scope.gotoBottom = function() { 4539 // set the location.hash to the id of 4540 // the element you wish to scroll to. 4541 $location.hash('bottom'); 4542 4543 // call $anchorScroll() 4544 $anchorScroll(); 4545 }; 4546 }]); 4547 </file> 4548 <file name="style.css"> 4549 #scrollArea { 4550 height: 280px; 4551 overflow: auto; 4552 } 4553 4554 #bottom { 4555 display: block; 4556 margin-top: 2000px; 4557 } 4558 </file> 4559 </example> 4560 * 4561 * <hr /> 4562 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 4563 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 4564 * 4565 * @example 4566 <example module="anchorScrollOffsetExample"> 4567 <file name="index.html"> 4568 <div class="fixed-header" ng-controller="headerCtrl"> 4569 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 4570 Go to anchor {{x}} 4571 </a> 4572 </div> 4573 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 4574 Anchor {{x}} of 5 4575 </div> 4576 </file> 4577 <file name="script.js"> 4578 angular.module('anchorScrollOffsetExample', []) 4579 .run(['$anchorScroll', function($anchorScroll) { 4580 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 4581 }]) 4582 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 4583 function ($anchorScroll, $location, $scope) {
4584 $scope.gotoAnchor = function(x) { 4585 var newHash = 'anchor' + x; 4586 if ($location.hash() !== newHash) { 4587 // set the $location.hash to `newHash` and 4588 // $anchorScroll will automatically scroll to it 4589 $location.hash('anchor' + x); 4590 } else { 4591 // call $anchorScroll() explicitly, 4592 // since $location.hash hasn't changed 4593 $anchorScroll(); 4594 } 4595 }; 4596 } 4597 ]); 4598 </file> 4599 <file name="style.css"> 4600 body { 4601 padding-top: 50px; 4602 } 4603 4604 .anchor { 4605 border: 2px dashed DarkOrchid; 4606 padding: 10px 10px 200px 10px; 4607 } 4608 4609 .fixed-header { 4610 background-color: rgba(0, 0, 0, 0.2); 4611 height: 50px; 4612 position: fixed; 4613 top: 0; left: 0; right: 0; 4614 } 4615 4616 .fixed-header > a { 4617 display: inline-block; 4618 margin: 5px 15px; 4619 } 4620 </file> 4621 </example> 4622 */ 4623 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4624 var document = $window.document; 4625 4626 // Helper function to get first anchor from a NodeList 4627 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 4628 // and working in all supported browsers.) 4629 function getFirstAnchor(list) { 4630 var result = null; 4631 Array.prototype.some.call(list, function(element) { 4632 if (nodeName_(element) === 'a') { 4633 result = element; 4634 return true; 4635 } 4636 }); 4637 return result; 4638 } 4639 4640 function getYOffset() { 4641 4642 var offset = scroll.yOffset; 4643 4644 if (isFunction(offset)) { 4645 offset = offset(); 4646 } else if (isElement(offset)) { 4647 var elem = offset[0]; 4648 var style = $window.getComputedStyle(elem); 4649 if (style.position !== 'fixed') { 4650 offset = 0; 4651 } else { 4652 offset = elem.getBoundingClientRect().bottom; 4653 } 4654 } else if (!isNumber(offset)) { 4655 offset = 0; 4656 } 4657 4658 return offset; 4659 } 4660 4661 function scrollTo(elem) { 4662 if (elem) { 4663 elem.scrollIntoView(); 4664 4665 var offset = getYOffset(); 4666 4667 if (offset) { 4668 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 4669 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 4670 // top of the viewport. 4671 // 4672 // IF the number of pixels from the top of `elem` to the end of the page's content is less 4673 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 4674 // way down the page. 4675 // 4676 // This is often the case for elements near the bottom of the page. 4677 // 4678 // In such cases we do not need to scroll the whole `offset` up, just the difference between 4679 // the top of the element and the offset, which is enough to align the top of `elem` at the 4680 // desired position. 4681 var elemTop = elem.getBoundingClientRect().top; 4682 $window.scrollBy(0, elemTop - offset); 4683 } 4684 } else { 4685 $window.scrollTo(0, 0); 4686 } 4687 } 4688 4689 function scroll(hash) { 4690 hash = isString(hash) ? hash : $location.hash(); 4691 var elm; 4692 4693 // empty hash, scroll to the top of the page 4694 if (!hash) scrollTo(null); 4695 4696 // element with given id 4697 else if ((elm = document.getElementById(hash))) scrollTo(elm); 4698 4699 // first anchor with given name :-D 4700 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 4701 4702 // no element and hash == 'top', scroll to the top of the page 4703 else if (hash === 'top') scrollTo(null); 4704 } 4705 4706 // does not scroll when user clicks on anchor link that is currently on 4707 // (no url change, no $location.hash() change), browser native does scroll 4708 if (autoScrollingEnabled) { 4709 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4710 function autoScrollWatchAction(newVal, oldVal) { 4711 // skip the initial scroll if $location.hash is empty 4712 if (newVal === oldVal && newVal === '') return; 4713 4714 jqLiteDocumentLoaded(function() { 4715 $rootScope.$evalAsync(scroll); 4716 }); 4717 }); 4718 } 4719 4720 return scroll; 4721 }]; 4722} 4723 4724var $animateMinErr = minErr('$animate'); 4725var ELEMENT_NODE = 1; 4726var NG_ANIMATE_CLASSNAME = 'ng-animate'; 4727 4728function mergeClasses(a,b) { 4729 if (!a && !b) return ''; 4730 if (!a) return b; 4731 if (!b) return a; 4732 if (isArray(a)) a = a.join(' '); 4733 if (isArray(b)) b = b.join(' '); 4734 return a + ' ' + b; 4735} 4736 4737function extractElementNode(element) { 4738 for (var i = 0; i < element.length; i++) { 4739 var elm = element[i]; 4740 if (elm.nodeType === ELEMENT_NODE) { 4741 return elm; 4742 } 4743 } 4744} 4745 4746function splitClasses(classes) { 4747 if (isString(classes)) { 4748 classes = classes.split(' '); 4749 } 4750 4751 // Use createMap() to prevent class assumptions involving property names in 4752 // Object.prototype 4753 var obj = createMap(); 4754 forEach(classes, function(klass) { 4755 // sometimes the split leaves empty string values 4756 // incase extra spaces were applied to the options 4757 if (klass.length) { 4758 obj[klass] = true; 4759 } 4760 }); 4761 return obj; 4762} 4763 4764// if any other type of options value besides an Object value is 4765// passed into the $animate.method() animation then this helper code 4766// will be run which will ignore it. While this patch is not the 4767// greatest solution to this, a lot of existing plugins depend on 4768// $animate to either call the callback (< 1.2) or return a promise 4769// that can be changed. This helper function ensures that the options 4770// are wiped clean incase a callback function is provided. 4771function prepareAnimateOptions(options) { 4772 return isObject(options) 4773 ? options 4774 : {}; 4775} 4776 4777var $$CoreAnimateRunnerProvider = function() { 4778 this.$get = ['$q', '$$rAF', function($q, $$rAF) { 4779 function AnimateRunner() {} 4780 AnimateRunner.all = noop; 4781 AnimateRunner.chain = noop; 4782 AnimateRunner.prototype = { 4783 end: noop, 4784 cancel: noop, 4785 resume: noop, 4786 pause: noop, 4787 complete: noop, 4788 then: function(pass, fail) { 4789 return $q(function(resolve) { 4790 $$rAF(function() { 4791 resolve(); 4792 }); 4793 }).then(pass, fail); 4794 } 4795 }; 4796 return AnimateRunner; 4797 }]; 4798}; 4799 4800// this is prefixed with Core since it conflicts with 4801// the animateQueueProvider defined in ngAnimate/animateQueue.js 4802var $$CoreAnimateQueueProvider = function() { 4803 var postDigestQueue = new HashMap(); 4804 var postDigestElements = []; 4805 4806 this.$get = ['$$AnimateRunner', '$rootScope', 4807 function($$AnimateRunner, $rootScope) { 4808 return { 4809 enabled: noop, 4810 on: noop, 4811 off: noop, 4812 pin: noop, 4813 4814 push: function(element, event, options, domOperation) { 4815 domOperation && domOperation(); 4816 4817 options = options || {}; 4818 options.from && element.css(options.from); 4819 options.to && element.css(options.to); 4820 4821 if (options.addClass || options.removeClass) { 4822 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 4823 } 4824 4825 return new $$AnimateRunner(); // jshint ignore:line 4826 } 4827 }; 4828 4829 function addRemoveClassesPostDigest(element, add, remove) { 4830 var data = postDigestQueue.get(element); 4831 var classVal; 4832 4833 if (!data) { 4834 postDigestQueue.put(element, data = {}); 4835 postDigestElements.push(element); 4836 } 4837 4838 if (add) {
4839 forEach(add.split(' '), function(className) { 4840 if (className) { 4841 data[className] = true; 4842 } 4843 }); 4844 } 4845 4846 if (remove) { 4847 forEach(remove.split(' '), function(className) { 4848 if (className) { 4849 data[className] = false; 4850 } 4851 }); 4852 } 4853 4854 if (postDigestElements.length > 1) return; 4855 4856 $rootScope.$$postDigest(function() { 4857 forEach(postDigestElements, function(element) { 4858 var data = postDigestQueue.get(element); 4859 if (data) { 4860 var existing = splitClasses(element.attr('class')); 4861 var toAdd = ''; 4862 var toRemove = ''; 4863 forEach(data, function(status, className) { 4864 var hasClass = !!existing[className]; 4865 if (status !== hasClass) { 4866 if (status) { 4867 toAdd += (toAdd.length ? ' ' : '') + className; 4868 } else { 4869 toRemove += (toRemove.length ? ' ' : '') + className; 4870 } 4871 } 4872 }); 4873 4874 forEach(element, function(elm) { 4875 toAdd && jqLiteAddClass(elm, toAdd); 4876 toRemove && jqLiteRemoveClass(elm, toRemove); 4877 }); 4878 postDigestQueue.remove(element); 4879 } 4880 }); 4881 4882 postDigestElements.length = 0; 4883 }); 4884 } 4885 }]; 4886}; 4887 4888/** 4889 * @ngdoc provider 4890 * @name $animateProvider 4891 * 4892 * @description 4893 * Default implementation of $animate that doesn't perform any animations, instead just 4894 * synchronously performs DOM updates and resolves the returned runner promise. 4895 * 4896 * In order to enable animations the `ngAnimate` module has to be loaded. 4897 * 4898 * To see the functional implementation check out `src/ngAnimate/animate.js`. 4899 */ 4900var $AnimateProvider = ['$provide', function($provide) { 4901 var provider = this; 4902 4903 this.$$registeredAnimations = Object.create(null); 4904 4905 /** 4906 * @ngdoc method 4907 * @name $animateProvider#register 4908 * 4909 * @description 4910 * Registers a new injectable animation factory function. The factory function produces the 4911 * animation object which contains callback functions for each event that is expected to be 4912 * animated. 4913 * 4914 * * `eventFn`: `function(element, ... , doneFunction, options)` 4915 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 4916 * on the type of animation additional arguments will be injected into the animation function. The 4917 * list below explains the function signatures for the different animation methods: 4918 * 4919 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 4920 * - addClass: function(element, addedClasses, doneFunction, options) 4921 * - removeClass: function(element, removedClasses, doneFunction, options) 4922 * - enter, leave, move: function(element, doneFunction, options) 4923 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 4924 * 4925 * Make sure to trigger the `doneFunction` once the animation is fully complete. 4926 * 4927 * ```js 4928 * return { 4929 * //enter, leave, move signature 4930 * eventFn : function(element, done, options) { 4931 * //code to run the animation 4932 * //once complete, then run done() 4933 * return function endFunction(wasCancelled) { 4934 * //code to cancel the animation 4935 * } 4936 * } 4937 * } 4938 * ``` 4939 * 4940 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 4941 * @param {Function} factory The factory function that will be executed to return the animation 4942 * object. 4943 */ 4944 this.register = function(name, factory) { 4945 if (name && name.charAt(0) !== '.') { 4946 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 4947 } 4948 4949 var key = name + '-animation'; 4950 provider.$$registeredAnimations[name.substr(1)] = key; 4951 $provide.factory(key, factory); 4952 }; 4953 4954 /** 4955 * @ngdoc method 4956 * @name $animateProvider#classNameFilter 4957 * 4958 * @description 4959 * Sets and/or returns the CSS class regular expression that is checked when performing 4960 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 4961 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 4962 * When setting the `classNameFilter` value, animations will only be performed on elements 4963 * that successfully match the filter expression. This in turn can boost performance 4964 * for low-powered devices as well as applications containing a lot of structural operations. 4965 * @param {RegExp=} expression The className expression which will be checked against all animations 4966 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 4967 */ 4968 this.classNameFilter = function(expression) { 4969 if (arguments.length === 1) { 4970 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 4971 if (this.$$classNameFilter) { 4972 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 4973 if (reservedRegex.test(this.$$classNameFilter.toString())) { 4974 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 4975 4976 } 4977 } 4978 } 4979 return this.$$classNameFilter; 4980 }; 4981 4982 this.$get = ['$$animateQueue', function($$animateQueue) { 4983 function domInsert(element, parentElement, afterElement) { 4984 // if for some reason the previous element was removed 4985 // from the dom sometime before this code runs then let's
4986 // just stick to using the parent element as the anchor 4987 if (afterElement) { 4988 var afterNode = extractElementNode(afterElement); 4989 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 4990 afterElement = null; 4991 } 4992 } 4993 afterElement ? afterElement.after(element) : parentElement.prepend(element); 4994 } 4995 4996 /** 4997 * @ngdoc service 4998 * @name $animate 4999 * @description The $animate service exposes a series of DOM utility methods that provide support 5000 * for animation hooks. The default behavior is the application of DOM operations, however, 5001 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5002 * to ensure that animation runs with the triggered DOM operation. 5003 * 5004 * By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't 5005 * included and only when it is active then the animation hooks that `$animate` triggers will be 5006 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5007 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5008 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5009 * 5010 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5011 * 5012 * To learn more about enabling animation support, click here to visit the 5013 * {@link ngAnimate ngAnimate module page}. 5014 */ 5015 return { 5016 // we don't call it directly since non-existant arguments may 5017 // be interpreted as null within the sub enabled function 5018 5019 /** 5020 * 5021 * @ngdoc method 5022 * @name $animate#on 5023 * @kind function 5024 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5025 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5026 * is fired with the following params: 5027 * 5028 * ```js 5029 * $animate.on('enter', container, 5030 * function callback(element, phase) { 5031 * // cool we detected an enter animation within the container 5032 * } 5033 * ); 5034 * ``` 5035 * 5036 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5037 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5038 * as well as among its children 5039 * @param {Function} callback the callback function that will be fired when the listener is triggered 5040 * 5041 * The arguments present in the callback function are: 5042 * * `element` - The captured DOM element that the animation was fired on. 5043 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5044 */ 5045 on: $$animateQueue.on, 5046 5047 /** 5048 * 5049 * @ngdoc method 5050 * @name $animate#off 5051 * @kind function 5052 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5053 * can be used in three different ways depending on the arguments: 5054 * 5055 * ```js 5056 * // remove all the animation event listeners listening for `enter` 5057 * $animate.off('enter'); 5058 * 5059 * // remove all the animation event listeners listening for `enter` on the given element and its children 5060 * $animate.off('enter', container); 5061 * 5062 * // remove the event listener function provided by `listenerFn` that is set 5063 * // to listen for `enter` on the given `element` as well as its children 5064 * $animate.off('enter', container, callback); 5065 * ``` 5066 * 5067 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) 5068 * @param {DOMElement=} container the container element the event listener was placed on 5069 * @param {Function=} callback the callback function that was registered as the listener 5070 */ 5071 off: $$animateQueue.off, 5072 5073 /** 5074 * @ngdoc method 5075 * @name $animate#pin 5076 * @kind function
5077 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5078 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 5079 * element despite being outside the realm of the application or within another application. Say for example if the application 5080 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5081 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5082 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5083 * 5084 * Note that this feature is only active when the `ngAnimate` module is used. 5085 * 5086 * @param {DOMElement} element the external element that will be pinned 5087 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5088 */ 5089 pin: $$animateQueue.pin, 5090 5091 /** 5092 * 5093 * @ngdoc method 5094 * @name $animate#enabled 5095 * @kind function 5096 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5097 * function can be called in four ways: 5098 * 5099 * ```js 5100 * // returns true or false 5101 * $animate.enabled(); 5102 * 5103 * // changes the enabled state for all animations 5104 * $animate.enabled(false); 5105 * $animate.enabled(true); 5106 * 5107 * // returns true or false if animations are enabled for an element 5108 * $animate.enabled(element); 5109 * 5110 * // changes the enabled state for an element and its children 5111 * $animate.enabled(element, true); 5112 * $animate.enabled(element, false); 5113 * ``` 5114 * 5115 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5116 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5117 * 5118 * @return {boolean} whether or not animations are enabled 5119 */ 5120 enabled: $$animateQueue.enabled, 5121 5122 /** 5123 * @ngdoc method 5124 * @name $animate#cancel 5125 * @kind function 5126 * @description Cancels the provided animation. 5127 * 5128 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 5129 */ 5130 cancel: function(runner) { 5131 runner.end && runner.end(); 5132 }, 5133 5134 /** 5135 * 5136 * @ngdoc method 5137 * @name $animate#enter 5138 * @kind function 5139 * @description Inserts the element into the DOM either after the `after` element (if provided) or 5140 * as the first child within the `parent` element and then triggers an animation. 5141 * A promise is returned that will be resolved during the next digest once the animation 5142 * has completed. 5143 * 5144 * @param {DOMElement} element the element which will be inserted into the DOM 5145 * @param {DOMElement} parent the parent element which will append the element as 5146 * a child (so long as the after element is not present) 5147 * @param {DOMElement=} after the sibling element after which the element will be appended 5148 * @param {object=} options an optional collection of options/styles that will be applied to the element 5149 * 5150 * @return {Promise} the animation callback promise 5151 */ 5152 enter: function(element, parent, after, options) { 5153 parent = parent && jqLite(parent); 5154 after = after && jqLite(after); 5155 parent = parent || after.parent(); 5156 domInsert(element, parent, after); 5157 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 5158 }, 5159 5160 /** 5161 * 5162 * @ngdoc method 5163 * @name $animate#move 5164 * @kind function
5165 * @description Inserts (moves) the element into its new position in the DOM either after 5166 * the `after` element (if provided) or as the first child within the `parent` element 5167 * and then triggers an animation. A promise is returned that will be resolved 5168 * during the next digest once the animation has completed. 5169 * 5170 * @param {DOMElement} element the element which will be moved into the new DOM position 5171 * @param {DOMElement} parent the parent element which will append the element as 5172 * a child (so long as the after element is not present) 5173 * @param {DOMElement=} after the sibling element after which the element will be appended 5174 * @param {object=} options an optional collection of options/styles that will be applied to the element 5175 * 5176 * @return {Promise} the animation callback promise 5177 */ 5178 move: function(element, parent, after, options) { 5179 parent = parent && jqLite(parent); 5180 after = after && jqLite(after); 5181 parent = parent || after.parent(); 5182 domInsert(element, parent, after); 5183 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 5184 }, 5185 5186 /** 5187 * @ngdoc method 5188 * @name $animate#leave 5189 * @kind function 5190 * @description Triggers an animation and then removes the element from the DOM. 5191 * When the function is called a promise is returned that will be resolved during the next 5192 * digest once the animation has completed. 5193 * 5194 * @param {DOMElement} element the element which will be removed from the DOM 5195 * @param {object=} options an optional collection of options/styles that will be applied to the element 5196 * 5197 * @return {Promise} the animation callback promise 5198 */ 5199 leave: function(element, options) { 5200 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 5201 element.remove(); 5202 }); 5203 }, 5204 5205 /** 5206 * @ngdoc method 5207 * @name $animate#addClass 5208 * @kind function 5209 * 5210 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 5211 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 5212 * animation if element already contains the CSS class or if the class is removed at a later step. 5213 * Note that class-based animations are treated differently compared to structural animations 5214 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5215 * depending if CSS or JavaScript animations are used. 5216 * 5217 * @param {DOMElement} element the element which the CSS classes will be applied to 5218 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 5219 * @param {object=} options an optional collection of options/styles that will be applied to the element 5220 * 5221 * @return {Promise} the animation callback promise 5222 */ 5223 addClass: function(element, className, options) { 5224 options = prepareAnimateOptions(options); 5225 options.addClass = mergeClasses(options.addclass, className); 5226 return $$animateQueue.push(element, 'addClass', options); 5227 }, 5228 5229 /** 5230 * @ngdoc method 5231 * @name $animate#removeClass 5232 * @kind function 5233 * 5234 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 5235 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 5236 * animation if element does not contain the CSS class or if the class is added at a later step. 5237 * Note that class-based animations are treated differently compared to structural animations 5238 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5239 * depending if CSS or JavaScript animations are used. 5240 * 5241 * @param {DOMElement} element the element which the CSS classes will be applied to 5242 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 5243 * @param {object=} options an optional collection of options/styles that will be applied to the element 5244 * 5245 * @return {Promise} the animation callback promise 5246 */ 5247 removeClass: function(element, className, options) { 5248 options = prepareAnimateOptions(options); 5249 options.removeClass = mergeClasses(options.removeClass, className); 5250 return $$animateQueue.push(element, 'removeClass', options); 5251 }, 5252 5253 /** 5254 * @ngdoc method 5255 * @name $animate#setClass 5256 * @kind function 5257 *
5258 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 5259 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 5260 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 5261 * passed. Note that class-based animations are treated differently compared to structural animations 5262 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5263 * depending if CSS or JavaScript animations are used. 5264 * 5265 * @param {DOMElement} element the element which the CSS classes will be applied to 5266 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 5267 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 5268 * @param {object=} options an optional collection of options/styles that will be applied to the element 5269 * 5270 * @return {Promise} the animation callback promise 5271 */ 5272 setClass: function(element, add, remove, options) { 5273 options = prepareAnimateOptions(options); 5274 options.addClass = mergeClasses(options.addClass, add); 5275 options.removeClass = mergeClasses(options.removeClass, remove); 5276 return $$animateQueue.push(element, 'setClass', options); 5277 }, 5278 5279 /** 5280 * @ngdoc method 5281 * @name $animate#animate 5282 * @kind function 5283 * 5284 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 5285 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take 5286 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and 5287 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles 5288 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter). 5289 * 5290 * @param {DOMElement} element the element which the CSS styles will be applied to 5291 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 5292 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 5293 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 5294 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 5295 * (Note that if no animation is detected then this value will not be appplied to the element.) 5296 * @param {object=} options an optional collection of options/styles that will be applied to the element 5297 * 5298 * @return {Promise} the animation callback promise 5299 */ 5300 animate: function(element, from, to, className, options) { 5301 options = prepareAnimateOptions(options); 5302 options.from = options.from ? extend(options.from, from) : from; 5303 options.to = options.to ? extend(options.to, to) : to; 5304 5305 className = className || 'ng-inline-animate'; 5306 options.tempClasses = mergeClasses(options.tempClasses, className); 5307 return $$animateQueue.push(element, 'animate', options); 5308 } 5309 }; 5310 }]; 5311}]; 5312 5313function $$AsyncCallbackProvider() { 5314 this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { 5315 return $$rAF.supported 5316 ? function(fn) { return $$rAF(fn); } 5317 : function(fn) { 5318 return $timeout(fn, 0, false); 5319 }; 5320 }]; 5321} 5322 5323/* global stripHash: true */ 5324 5325/** 5326 * ! This is a private undocumented service ! 5327 * 5328 * @name $browser 5329 * @requires $log 5330 * @description 5331 * This object has two goals: 5332 * 5333 * - hide all the global state in the browser caused by the window object 5334 * - abstract away all the browser specific features and inconsistencies 5335 * 5336 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5337 * service, which can be used for convenient testing of the application without the interaction with 5338 * the real browser apis. 5339 */ 5340/** 5341 * @param {object} window The global window object. 5342 * @param {object} document jQuery wrapped document. 5343 * @param {object} $log window.console or an object with the same interface. 5344 * @param {object} $sniffer $sniffer service 5345 */ 5346function Browser(window, document, $log, $sniffer) { 5347 var self = this, 5348 rawDocument = document[0], 5349 location = window.location, 5350 history = window.history, 5351 setTimeout = window.setTimeout, 5352 clearTimeout = window.clearTimeout, 5353 pendingDeferIds = {}; 5354 5355 self.isMock = false; 5356 5357 var outstandingRequestCount = 0; 5358 var outstandingRequestCallbacks = []; 5359 5360 // TODO(vojta): remove this temporary api 5361 self.$$completeOutstandingRequest = completeOutstandingRequest; 5362 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 5363 5364 /** 5365 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 5366 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 5367 */ 5368 function completeOutstandingRequest(fn) { 5369 try { 5370 fn.apply(null, sliceArgs(arguments, 1)); 5371 } finally { 5372 outstandingRequestCount--; 5373 if (outstandingRequestCount === 0) { 5374 while (outstandingRequestCallbacks.length) { 5375 try { 5376 outstandingRequestCallbacks.pop()(); 5377 } catch (e) { 5378 $log.error(e); 5379 } 5380 } 5381 } 5382 } 5383 } 5384 5385 function getHash(url) { 5386 var index = url.indexOf('#'); 5387 return index === -1 ? '' : url.substr(index + 1); 5388 } 5389 5390 /** 5391 * @private 5392 * Note: this method is used only by scenario runner 5393 * TODO(vojta): prefix this method with $$ ? 5394 * @param {function()} callback Function that will be called when no outstanding request 5395 */ 5396 self.notifyWhenNoOutstandingRequests = function(callback) { 5397 if (outstandingRequestCount === 0) { 5398 callback(); 5399 } else { 5400 outstandingRequestCallbacks.push(callback); 5401 } 5402 }; 5403 5404 ////////////////////////////////////////////////////////////// 5405 // URL API 5406 ////////////////////////////////////////////////////////////// 5407 5408 var cachedState, lastHistoryState, 5409 lastBrowserUrl = location.href, 5410 baseElement = document.find('base'), 5411 reloadLocation = null; 5412 5413 cacheState(); 5414 lastHistoryState = cachedState; 5415 5416 /** 5417 * @name $browser#url 5418 * 5419 * @description 5420 * GETTER: 5421 * Without any argument, this method just returns current value of location.href. 5422 * 5423 * SETTER: 5424 * With at least one argument, this method sets url to new value. 5425 * If html5 history api supported, pushState/replaceState is used, otherwise 5426 * location.href/location.replace is used. 5427 * Returns its own instance to allow chaining 5428 * 5429 * NOTE: this api is intended for use only by the $location service. Please use the 5430 * {@link ng.$location $location service} to change url. 5431 * 5432 * @param {string} url New url (when used as setter) 5433 * @param {boolean=} replace Should new url replace current history record? 5434 * @param {object=} state object to use with pushState/replaceState 5435 */ 5436 self.url = function(url, replace, state) { 5437 // In modern browsers `history.state` is `null` by default; treating it separately 5438 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 5439 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 5440 if (isUndefined(state)) { 5441 state = null; 5442 } 5443 5444 // Android Browser BFCache causes location, history reference to become stale. 5445 if (location !== window.location) location = window.location; 5446 if (history !== window.history) history = window.history; 5447 5448 // setter 5449 if (url) { 5450 var sameState = lastHistoryState === state; 5451 5452 // Don't change anything if previous and current URLs and states match. This also prevents 5453 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 5454 // See https://github.com/angular/angular.js/commit/ffb2701 5455 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 5456 return self; 5457 } 5458 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 5459 lastBrowserUrl = url; 5460 lastHistoryState = state; 5461 // Don't use history API if only the hash changed 5462 // due to a bug in IE10/IE11 which leads 5463 // to not firing a `hashchange` nor `popstate` event 5464 // in some cases (see #9143). 5465 if ($sniffer.history && (!sameBase || !sameState)) { 5466 history[replace ? 'replaceState' : 'pushState'](state, '', url); 5467 cacheState(); 5468 // Do the assignment again so that those two variables are referentially identical. 5469 lastHistoryState = cachedState; 5470 } else { 5471 if (!sameBase) { 5472 reloadLocation = url; 5473 } 5474 if (replace) { 5475 location.replace(url); 5476 } else if (!sameBase) { 5477 location.href = url; 5478 } else { 5479 location.hash = getHash(url); 5480 } 5481 } 5482 return self; 5483 // getter 5484 } else { 5485 // - reloadLocation is needed as browsers don't allow to read out 5486 // the new location.href if a reload happened.
5487 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 5488 return reloadLocation || location.href.replace(/%27/g,"'"); 5489 } 5490 }; 5491 5492 /** 5493 * @name $browser#state 5494 * 5495 * @description 5496 * This method is a getter. 5497 * 5498 * Return history.state or null if history.state is undefined. 5499 * 5500 * @returns {object} state 5501 */ 5502 self.state = function() { 5503 return cachedState; 5504 }; 5505 5506 var urlChangeListeners = [], 5507 urlChangeInit = false; 5508 5509 function cacheStateAndFireUrlChange() { 5510 cacheState(); 5511 fireUrlChange(); 5512 } 5513 5514 function getCurrentState() { 5515 try { 5516 return history.state; 5517 } catch (e) { 5518 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 5519 } 5520 } 5521 5522 // This variable should be used *only* inside the cacheState function. 5523 var lastCachedState = null; 5524 function cacheState() { 5525 // This should be the only place in $browser where `history.state` is read. 5526 cachedState = getCurrentState(); 5527 cachedState = isUndefined(cachedState) ? null : cachedState; 5528 5529 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 5530 if (equals(cachedState, lastCachedState)) { 5531 cachedState = lastCachedState; 5532 } 5533 lastCachedState = cachedState; 5534 } 5535 5536 function fireUrlChange() { 5537 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 5538 return; 5539 } 5540 5541 lastBrowserUrl = self.url(); 5542 lastHistoryState = cachedState; 5543 forEach(urlChangeListeners, function(listener) { 5544 listener(self.url(), cachedState); 5545 }); 5546 } 5547 5548 /** 5549 * @name $browser#onUrlChange 5550 * 5551 * @description 5552 * Register callback function that will be called, when url changes. 5553 * 5554 * It's only called when the url is changed from outside of angular: 5555 * - user types different url into address bar 5556 * - user clicks on history (forward/back) button 5557 * - user clicks on a link 5558 * 5559 * It's not called when url is changed by $browser.url() method 5560 * 5561 * The listener gets called with new url as parameter. 5562 * 5563 * NOTE: this api is intended for use only by the $location service. Please use the 5564 * {@link ng.$location $location service} to monitor url changes in angular apps. 5565 * 5566 * @param {function(string)} listener Listener function to be called when url changes. 5567 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 5568 */ 5569 self.onUrlChange = function(callback) { 5570 // TODO(vojta): refactor to use node's syntax for events 5571 if (!urlChangeInit) { 5572 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 5573 // don't fire popstate when user change the address bar and don't fire hashchange when url 5574 // changed by push/replaceState 5575 5576 // html5 history api - popstate event 5577 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 5578 // hashchange event 5579 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 5580 5581 urlChangeInit = true; 5582 } 5583 5584 urlChangeListeners.push(callback); 5585 return callback; 5586 }; 5587 5588 /** 5589 * @private 5590 * Remove popstate and hashchange handler from window. 5591 * 5592 * NOTE: this api is intended for use only by $rootScope. 5593 */ 5594 self.$$applicationDestroyed = function() { 5595 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 5596 }; 5597 5598 /** 5599 * Checks whether the url has changed outside of Angular. 5600 * Needs to be exported to be able to check for changes that have been done in sync, 5601 * as hashchange/popstate events fire in async. 5602 */ 5603 self.$$checkUrlChange = fireUrlChange; 5604 5605 ////////////////////////////////////////////////////////////// 5606 // Misc API 5607 ////////////////////////////////////////////////////////////// 5608 5609 /** 5610 * @name $browser#baseHref 5611 * 5612 * @description 5613 * Returns current <base href> 5614 * (always relative - without domain) 5615 * 5616 * @returns {string} The current base href 5617 */ 5618 self.baseHref = function() { 5619 var href = baseElement.attr('href'); 5620 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 5621 }; 5622 5623 /** 5624 * @name $browser#defer 5625 * @param {function()} fn A function, who's execution should be deferred. 5626 * @param {number=} [delay=0] of milliseconds to defer the function execution. 5627 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 5628 * 5629 * @description 5630 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 5631 * 5632 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 5633 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 5634 * via `$browser.defer.flush()`. 5635 * 5636 */ 5637 self.defer = function(fn, delay) { 5638 var timeoutId; 5639 outstandingRequestCount++; 5640 timeoutId = setTimeout(function() {
5641 delete pendingDeferIds[timeoutId]; 5642 completeOutstandingRequest(fn); 5643 }, delay || 0); 5644 pendingDeferIds[timeoutId] = true; 5645 return timeoutId; 5646 }; 5647 5648 5649 /** 5650 * @name $browser#defer.cancel 5651 * 5652 * @description 5653 * Cancels a deferred task identified with `deferId`. 5654 * 5655 * @param {*} deferId Token returned by the `$browser.defer` function. 5656 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 5657 * canceled. 5658 */ 5659 self.defer.cancel = function(deferId) { 5660 if (pendingDeferIds[deferId]) { 5661 delete pendingDeferIds[deferId]; 5662 clearTimeout(deferId); 5663 completeOutstandingRequest(noop); 5664 return true; 5665 } 5666 return false; 5667 }; 5668 5669} 5670 5671function $BrowserProvider() { 5672 this.$get = ['$window', '$log', '$sniffer', '$document', 5673 function($window, $log, $sniffer, $document) { 5674 return new Browser($window, $document, $log, $sniffer); 5675 }]; 5676} 5677 5678/** 5679 * @ngdoc service 5680 * @name $cacheFactory 5681 * 5682 * @description 5683 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 5684 * them. 5685 * 5686 * ```js 5687 * 5688 * var cache = $cacheFactory('cacheId'); 5689 * expect($cacheFactory.get('cacheId')).toBe(cache); 5690 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 5691 * 5692 * cache.put("key", "value"); 5693 * cache.put("another key", "another value"); 5694 * 5695 * // We've specified no options on creation 5696 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 5697 * 5698 * ``` 5699 * 5700 * 5701 * @param {string} cacheId Name or id of the newly created cache. 5702 * @param {object=} options Options object that specifies the cache behavior. Properties: 5703 * 5704 * - `{number=}` `capacity` â turns the cache into LRU cache. 5705 * 5706 * @returns {object} Newly created cache object with the following set of methods: 5707 * 5708 * - `{object}` `info()` â Returns id, size, and options of cache. 5709 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 5710 * it. 5711 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 5712 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 5713 * - `{void}` `removeAll()` â Removes all cached values. 5714 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 5715 * 5716 * @example 5717 <example module="cacheExampleApp"> 5718 <file name="index.html"> 5719 <div ng-controller="CacheController"> 5720 <input ng-model="newCacheKey" placeholder="Key"> 5721 <input ng-model="newCacheValue" placeholder="Value"> 5722 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 5723 5724 <p ng-if="keys.length">Cached Values</p> 5725 <div ng-repeat="key in keys"> 5726 <span ng-bind="key"></span> 5727 <span>: </span> 5728 <b ng-bind="cache.get(key)"></b> 5729 </div> 5730 5731 <p>Cache Info</p> 5732 <div ng-repeat="(key, value) in cache.info()"> 5733 <span ng-bind="key"></span> 5734 <span>: </span> 5735 <b ng-bind="value"></b> 5736 </div> 5737 </div> 5738 </file> 5739 <file name="script.js"> 5740 angular.module('cacheExampleApp', []). 5741 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 5742 $scope.keys = []; 5743 $scope.cache = $cacheFactory('cacheId'); 5744 $scope.put = function(key, value) { 5745 if ($scope.cache.get(key) === undefined) { 5746 $scope.keys.push(key); 5747 } 5748 $scope.cache.put(key, value === undefined ? null : value); 5749 }; 5750 }]); 5751 </file> 5752 <file name="style.css"> 5753 p { 5754 margin: 10px 0 3px; 5755 } 5756 </file> 5757 </example> 5758 */ 5759function $CacheFactoryProvider() { 5760 5761 this.$get = function() { 5762 var caches = {}; 5763 5764 function cacheFactory(cacheId, options) { 5765 if (cacheId in caches) { 5766 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 5767 } 5768 5769 var size = 0, 5770 stats = extend({}, options, {id: cacheId}), 5771 data = {}, 5772 capacity = (options && options.capacity) || Number.MAX_VALUE, 5773 lruHash = {}, 5774 freshEnd = null, 5775 staleEnd = null; 5776 5777 /** 5778 * @ngdoc type 5779 * @name $cacheFactory.Cache 5780 *
5781 * @description 5782 * A cache object used to store and retrieve data, primarily used by 5783 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 5784 * templates and other data. 5785 * 5786 * ```js 5787 * angular.module('superCache') 5788 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 5789 * return $cacheFactory('super-cache'); 5790 * }]); 5791 * ``` 5792 * 5793 * Example test: 5794 * 5795 * ```js 5796 * it('should behave like a cache', inject(function(superCache) { 5797 * superCache.put('key', 'value'); 5798 * superCache.put('another key', 'another value'); 5799 * 5800 * expect(superCache.info()).toEqual({ 5801 * id: 'super-cache', 5802 * size: 2 5803 * }); 5804 * 5805 * superCache.remove('another key'); 5806 * expect(superCache.get('another key')).toBeUndefined(); 5807 * 5808 * superCache.removeAll(); 5809 * expect(superCache.info()).toEqual({ 5810 * id: 'super-cache', 5811 * size: 0 5812 * }); 5813 * })); 5814 * ``` 5815 */ 5816 return caches[cacheId] = { 5817 5818 /** 5819 * @ngdoc method 5820 * @name $cacheFactory.Cache#put 5821 * @kind function 5822 * 5823 * @description 5824 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 5825 * retrieved later, and incrementing the size of the cache if the key was not already 5826 * present in the cache. If behaving like an LRU cache, it will also remove stale 5827 * entries from the set. 5828 * 5829 * It will not insert undefined values into the cache. 5830 * 5831 * @param {string} key the key under which the cached data is stored. 5832 * @param {*} value the value to store alongside the key. If it is undefined, the key 5833 * will not be stored. 5834 * @returns {*} the value stored. 5835 */ 5836 put: function(key, value) { 5837 if (isUndefined(value)) return; 5838 if (capacity < Number.MAX_VALUE) { 5839 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 5840 5841 refresh(lruEntry); 5842 } 5843 5844 if (!(key in data)) size++; 5845 data[key] = value; 5846 5847 if (size > capacity) { 5848 this.remove(staleEnd.key); 5849 } 5850 5851 return value; 5852 }, 5853 5854 /** 5855 * @ngdoc method 5856 * @name $cacheFactory.Cache#get 5857 * @kind function 5858 * 5859 * @description 5860 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 5861 * 5862 * @param {string} key the key of the data to be retrieved 5863 * @returns {*} the value stored. 5864 */ 5865 get: function(key) { 5866 if (capacity < Number.MAX_VALUE) { 5867 var lruEntry = lruHash[key]; 5868 5869 if (!lruEntry) return; 5870 5871 refresh(lruEntry); 5872 } 5873 5874 return data[key]; 5875 }, 5876 5877 5878 /** 5879 * @ngdoc method 5880 * @name $cacheFactory.Cache#remove 5881 * @kind function 5882 * 5883 * @description 5884 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 5885 * 5886 * @param {string} key the key of the entry to be removed 5887 */ 5888 remove: function(key) { 5889 if (capacity < Number.MAX_VALUE) { 5890 var lruEntry = lruHash[key]; 5891 5892 if (!lruEntry) return; 5893 5894 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 5895 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 5896 link(lruEntry.n,lruEntry.p); 5897 5898 delete lruHash[key]; 5899 } 5900 5901 delete data[key]; 5902 size--; 5903 }, 5904 5905 5906 /** 5907 * @ngdoc method 5908 * @name $cacheFactory.Cache#removeAll 5909 * @kind function 5910 * 5911 * @description 5912 * Clears the cache object of any entries. 5913 */ 5914 removeAll: function() { 5915 data = {}; 5916 size = 0; 5917 lruHash = {}; 5918 freshEnd = staleEnd = null; 5919 }, 5920 5921 5922 /** 5923 * @ngdoc method 5924 * @name $cacheFactory.Cache#destroy 5925 * @kind function 5926 *
5927 * @description 5928 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 5929 * removing it from the {@link $cacheFactory $cacheFactory} set. 5930 */ 5931 destroy: function() { 5932 data = null; 5933 stats = null; 5934 lruHash = null; 5935 delete caches[cacheId]; 5936 }, 5937 5938 5939 /** 5940 * @ngdoc method 5941 * @name $cacheFactory.Cache#info 5942 * @kind function 5943 * 5944 * @description 5945 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 5946 * 5947 * @returns {object} an object with the following properties: 5948 * <ul> 5949 * <li>**id**: the id of the cache instance</li> 5950 * <li>**size**: the number of entries kept in the cache instance</li> 5951 * <li>**...**: any additional properties from the options object when creating the 5952 * cache.</li> 5953 * </ul> 5954 */ 5955 info: function() { 5956 return extend({}, stats, {size: size}); 5957 } 5958 }; 5959 5960 5961 /** 5962 * makes the `entry` the freshEnd of the LRU linked list 5963 */ 5964 function refresh(entry) { 5965 if (entry != freshEnd) { 5966 if (!staleEnd) { 5967 staleEnd = entry; 5968 } else if (staleEnd == entry) { 5969 staleEnd = entry.n; 5970 } 5971 5972 link(entry.n, entry.p); 5973 link(entry, freshEnd); 5974 freshEnd = entry; 5975 freshEnd.n = null; 5976 } 5977 } 5978 5979 5980 /** 5981 * bidirectionally links two entries of the LRU linked list 5982 */ 5983 function link(nextEntry, prevEntry) { 5984 if (nextEntry != prevEntry) { 5985 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 5986 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 5987 } 5988 } 5989 } 5990 5991 5992 /** 5993 * @ngdoc method 5994 * @name $cacheFactory#info 5995 * 5996 * @description 5997 * Get information about all the caches that have been created 5998 * 5999 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 6000 */ 6001 cacheFactory.info = function() { 6002 var info = {}; 6003 forEach(caches, function(cache, cacheId) { 6004 info[cacheId] = cache.info(); 6005 }); 6006 return info; 6007 }; 6008 6009 6010 /** 6011 * @ngdoc method 6012 * @name $cacheFactory#get 6013 * 6014 * @description 6015 * Get access to a cache object by the `cacheId` used when it was created. 6016 * 6017 * @param {string} cacheId Name or id of a cache to access. 6018 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 6019 */ 6020 cacheFactory.get = function(cacheId) { 6021 return caches[cacheId]; 6022 }; 6023 6024 6025 return cacheFactory; 6026 }; 6027} 6028 6029/** 6030 * @ngdoc service 6031 * @name $templateCache 6032 * 6033 * @description 6034 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 6035 * can load templates directly into the cache in a `script` tag, or by consuming the 6036 * `$templateCache` service directly. 6037 * 6038 * Adding via the `script` tag: 6039 * 6040 * ```html 6041 * <script type="text/ng-template" id="templateId.html"> 6042 * <p>This is the content of the template</p> 6043 * </script> 6044 * ``` 6045 * 6046 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 6047 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 6048 * element with ng-app attribute), otherwise the template will be ignored. 6049 * 6050 * Adding via the `$templateCache` service: 6051 * 6052 * ```js 6053 * var myApp = angular.module('myApp', []); 6054 * myApp.run(function($templateCache) { 6055 * $templateCache.put('templateId.html', 'This is the content of the template'); 6056 * }); 6057 * ``` 6058 * 6059 * To retrieve the template later, simply use it in your HTML: 6060 * ```html 6061 * <div ng-include=" 'templateId.html' "></div> 6062 * ``` 6063 * 6064 * or get it via Javascript: 6065 * ```js 6066 * $templateCache.get('templateId.html') 6067 * ``` 6068 * 6069 * See {@link ng.$cacheFactory $cacheFactory}. 6070 * 6071 */ 6072function $TemplateCacheProvider() { 6073 this.$get = ['$cacheFactory', function($cacheFactory) { 6074 return $cacheFactory('templates'); 6075 }]; 6076} 6077 6078/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 6079 * Any commits to this file should be reviewed with security in mind. * 6080 * Changes to this file can potentially create security vulnerabilities. * 6081 * An approval from 2 Core members with history of modifying * 6082 * this file is required. * 6083 * * 6084 * Does the change somehow allow for arbitrary javascript to be executed? * 6085 * Or allows for someone to change the prototype of built-in objects? * 6086 * Or gives undesired access to variables likes document or window? * 6087 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 6088
6089/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 6090 * 6091 * DOM-related variables: 6092 * 6093 * - "node" - DOM Node 6094 * - "element" - DOM Element or Node 6095 * - "$node" or "$element" - jqLite-wrapped node or element 6096 * 6097 * 6098 * Compiler related stuff: 6099 * 6100 * - "linkFn" - linking fn of a single directive 6101 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 6102 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 6103 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 6104 */ 6105 6106 6107/** 6108 * @ngdoc service 6109 * @name $compile 6110 * @kind function 6111 * 6112 * @description 6113 * Compiles an HTML string or DOM into a template and produces a template function, which 6114 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 6115 * 6116 * The compilation is a process of walking the DOM tree and matching DOM elements to 6117 * {@link ng.$compileProvider#directive directives}. 6118 * 6119 * <div class="alert alert-warning"> 6120 * **Note:** This document is an in-depth reference of all directive options. 6121 * For a gentle introduction to directives with examples of common use cases, 6122 * see the {@link guide/directive directive guide}. 6123 * </div> 6124 * 6125 * ## Comprehensive Directive API 6126 * 6127 * There are many different options for a directive. 6128 * 6129 * The difference resides in the return value of the factory function. 6130 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 6131 * or just the `postLink` function (all other properties will have the default values). 6132 * 6133 * <div class="alert alert-success"> 6134 * **Best Practice:** It's recommended to use the "directive definition object" form. 6135 * </div> 6136 * 6137 * Here's an example directive declared with a Directive Definition Object: 6138 * 6139 * ```js 6140 * var myModule = angular.module(...); 6141 * 6142 * myModule.directive('directiveName', function factory(injectables) { 6143 * var directiveDefinitionObject = { 6144 * priority: 0, 6145 * template: '<div></div>
6145', // or // function(tElement, tAttrs) { ... }, 6146 * // or 6147 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 6148 * transclude: false, 6149 * restrict: 'A', 6150 * templateNamespace: 'html', 6151 * scope: false, 6152 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 6153 * controllerAs: 'stringIdentifier', 6154 * bindToController: false, 6155 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 6156 * compile: function compile(tElement, tAttrs, transclude) { 6157 * return { 6158 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6159 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 6160 * } 6161 * // or 6162 * // return function postLink( ... ) { ... } 6163 * }, 6164 * // or 6165 * // link: { 6166 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6167 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 6168 * // } 6169 * // or 6170 * // link: function postLink( ... ) { ... } 6171 * }; 6172 * return directiveDefinitionObject; 6173 * }); 6174 * ``` 6175 * 6176 * <div class="alert alert-warning"> 6177 * **Note:** Any unspecified options will use the default value. You can see the default values below. 6178 * </div> 6179 * 6180 * Therefore the above can be simplified as: 6181 * 6182 * ```js 6183 * var myModule = angular.module(...); 6184 * 6185 * myModule.directive('directiveName', function factory(injectables) { 6186 * var directiveDefinitionObject = { 6187 * link: function postLink(scope, iElement, iAttrs) { ... } 6188 * }; 6189 * return directiveDefinitionObject; 6190 * // or 6191 * // return function postLink(scope, iElement, iAttrs) { ... } 6192 * }); 6193 * ``` 6194 * 6195 * 6196 * 6197 * ### Directive Definition Object 6198 * 6199 * The directive definition object provides instructions to the {@link ng.$compile 6200 * compiler}. The attributes are: 6201 * 6202 * #### `multiElement` 6203 * When this property is set to true, the HTML compiler will collect DOM nodes between 6204 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 6205 * together as the directive elements. It is recommended that this feature be used on directives 6206 * which are not strictly behavioural (such as {@link ngClick}), and which 6207 * do not manipulate or replace child nodes (such as {@link ngInclude}). 6208 * 6209 * #### `priority` 6210 * When there are multiple directives defined on a single DOM element, sometimes it 6211 * is necessary to specify the order in which the directives are applied. The `priority` is used 6212 * to sort the directives before their `compile` functions get called. Priority is defined as a 6213 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 6214 * are also run in priority order, but post-link functions are run in reverse order. The order 6215 * of directives with the same priority is undefined. The default priority is `0`. 6216 * 6217 * #### `terminal` 6218 * If set to true then the current `priority` will be the last set of directives 6219 * which will execute (any directives at the current priority will still execute 6220 * as the order of execution on same `priority` is undefined). Note that expressions 6221 * and other directives used in the directive's template will also be excluded from execution. 6222 * 6223 * #### `scope` 6224 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 6225 * same element request a new scope, only one new scope is created. The new scope rule does not 6226 * apply for the root of the template since the root of the template always gets a new scope. 6227 * 6228 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 6229 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 6230 * when creating reusable components, which should not accidentally read or modify data in the 6231 * parent scope. 6232 * 6233 * The 'isolate' scope takes an object hash which defines a set of local scope properties 6234 * derived from the parent scope. These local properties are useful for aliasing values for 6235 * templates. Locals definition is a hash of local scope property to its source: 6236 * 6237 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6238 * always a string since DOM attributes are strings. If no `attr` name is specified then the 6239 * attribute name is assumed to be the same as the local name. 6240 * Given `<widget my-attr="hello {{name}}">` and widget definition 6241 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 6242 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 6243 * `localName` property on the widget scope. The `name` is read from the parent scope (not 6244 * component scope). 6245 * 6246 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 6247 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 6248 * name is specified then the attribute name is assumed to be the same as the local name. 6249 * Given `<widget my-attr="parentModel">` and widget definition of 6250 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 6251 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 6252 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 6253 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 6254 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If 6255 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use 6256 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). 6257 * 6258 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 6259 * If no `attr` name is specified then the attribute name is assumed to be the same as the 6260 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 6261 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 6262 * a function wrapper for the `count = count + value` expression. Often it's desirable to 6263 * pass data from the isolated scope via an expression to the parent scope, this can be 6264 * done by passing a map of local variable names and values into the expression wrapper fn. 6265 * For example, if the expression is `increment(amount)` then we can specify the amount value 6266 * by calling the `localFn` as `localFn({amount: 22})`. 6267 * 6268 * 6269 * #### `bindToController` 6270 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will 6271 * allow a component to have its properties bound to the controller, rather than to scope. When the controller 6272 * is instantiated, the initial values of the isolate scope bindings are already available. 6273 * 6274 * #### `controller` 6275 * Controller constructor function. The controller is instantiated before the 6276 * pre-linking phase and it is shared with other directives (see 6277 * `require` attribute). This allows the directives to communicate with each other and augment 6278 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 6279 * 6280 * * `$scope` - Current scope associated with the element 6281 * * `$element` - Current element 6282 * * `$attrs` - Current attributes object for the element 6283 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 6284 * `function([scope], cloneLinkingFn, futureParentElement)`. 6285 * * `scope`: optional argument to override the scope. 6286 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. 6287 * * `futureParentElement`: 6288 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 6289 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 6290 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 6291 * and when the `cloneLinkinFn` is passed, 6292 * as those elements need to created and cloned in a special way when they are defined outside their 6293 * usual containers (e.g. like `<svg>`). 6294 * * See also the `directive.templateNamespace` property. 6295 * 6296 * 6297 * #### `require` 6298 * Require another directive and inject its controller as the fourth argument to the linking function. The
6299 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 6300 * injected argument will be an array in corresponding order. If no such directive can be 6301 * found, or if the directive does not have a controller, then an error is raised (unless no link function 6302 * is specified, in which case error checking is skipped). The name can be prefixed with: 6303 * 6304 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 6305 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 6306 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 6307 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 6308 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 6309 * `null` to the `link` fn if not found. 6310 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 6311 * `null` to the `link` fn if not found. 6312 * 6313 * 6314 * #### `controllerAs` 6315 * Identifier name for a reference to the controller in the directive's scope. 6316 * This allows the controller to be referenced from the directive template. The directive 6317 * needs to define a scope for this configuration to be used. Useful in the case when 6318 * directive is used as component. 6319 * 6320 * 6321 * #### `restrict` 6322 * String of subset of `EACM` which restricts the directive to a specific directive 6323 * declaration style. If omitted, the defaults (elements and attributes) are used. 6324 * 6325 * * `E` - Element name (default): `<my-directive></my-directive>` 6326 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 6327 * * `C` - Class: `<div class="my-directive: exp;"></div>` 6328 * * `M` - Comment: `<!-- directive: my-directive exp -->` 6329 * 6330 * 6331 * #### `templateNamespace` 6332 * String representing the document type used by the markup in the template. 6333 * AngularJS needs this information as those elements need to be created and cloned 6334 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 6335 * 6336 * * `html` - All root nodes in the template are HTML. Root nodes may also be 6337 * top-level elements such as `<svg>` or `<math>`. 6338 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 6339 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 6340 * 6341 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 6342 * 6343 * #### `template` 6344 * HTML markup that may: 6345 * * Replace the contents of the directive's element (default). 6346 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 6347 * * Wrap the contents of the directive's element (if `transclude` is true). 6348 * 6349 * Value may be: 6350 * 6351 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 6352 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 6353 * function api below) and returns a string value. 6354 * 6355 * 6356 * #### `templateUrl` 6357 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 6358 * 6359 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 6360 * for later when the template has been resolved. In the meantime it will continue to compile and link 6361 * sibling and parent elements as though this element had not contained any directives. 6362 * 6363 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 6364 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 6365 * case when only one deeply nested directive has `templateUrl`. 6366 * 6367 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 6368 * 6369 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 6370 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 6371 * a string value representing the url. In either case, the template URL is passed through {@link 6372 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 6373 * 6374 * 6375 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 6376 * specify what the template should replace. Defaults to `false`. 6377 * 6378 * * `true` - the template will replace the directive's element. 6379 * * `false` - the template will replace the contents of the directive's element. 6380 * 6381 * The replacement process migrates all of the attributes / classes from the old element to the new 6382 * one. See the {@link guide/directive#template-expanding-directive 6383 * Directives Guide} for an example. 6384 * 6385 * There are very few scenarios where element replacement is required for the application function, 6386 * the main one being reusable custom components that are used within SVG contexts 6387 * (because SVG doesn't work with custom elements in the DOM tree). 6388 * 6389 * #### `transclude` 6390 * Extract the contents of the element where the directive appears and make it available to the directive. 6391 * The contents are compiled and provided to the directive as a **transclusion function**. See the 6392 * {@link $compile#transclusion Transclusion} section below. 6393 * 6394 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the 6395 * directive's element or the entire element: 6396 * 6397 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 6398 * * `'element'` - transclude the whole of the directive's element including any directives on this 6399 * element that defined at a lower priority than this directive. When used, the `template` 6400 * property is ignored. 6401 * 6402 * 6403 * #### `compile` 6404 * 6405 * ```js 6406 * function compile(tElement, tAttrs, transclude) { ... } 6407 * ``` 6408 * 6409 * The compile function deals with transforming the template DOM. Since most directives do not do 6410 * template transformation, it is not used often. The compile function takes the following arguments: 6411 * 6412 * * `tElement` - template element - The element where the directive has been declared. It is 6413 * safe to do template transformation on the element and child elements only. 6414 * 6415 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 6416 * between all directive compile functions. 6417 * 6418 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 6419 * 6420 * <div class="alert alert-warning"> 6421 * **Note:** The template instance and the link instance may be different objects if the template has 6422 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 6423 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6424 * should be done in a linking function rather than in a compile function. 6425 * </div> 6426 6427 * <div class="alert alert-warning"> 6428 * **Note:** The compile function cannot handle directives that recursively use themselves in their 6429 * own templates or compile functions. Compiling these directives results in an infinite loop and a 6430 * stack overflow errors. 6431 * 6432 * This can be avoided by manually using $compile in the postLink function to imperatively compile 6433 * a directive's template instead of relying on automatic template compilation via `template` or 6434 * `templateUrl` declaration or manual compilation inside the compile function. 6435 * </div> 6436 * 6437 * <div class="alert alert-danger"> 6438 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 6439 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 6440 * to the link function instead. 6441 * </div> 6442 6443 * A compile function can have a return value which can be either a function or an object. 6444 * 6445 * * returning a (post-link) function - is equivalent to registering the linking function via the 6446 * `link` property of the config object when the compile function is empty. 6447 * 6448 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 6449 * control when a linking function should be called during the linking phase. See info about 6450 * pre-linking and post-linking functions below. 6451 * 6452 * 6453 * #### `link` 6454 * This property is used only if the `compile` property is not defined. 6455 * 6456 * ```js 6457 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 6458 * ``` 6459 * 6460 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 6461 * executed after the template has been cloned. This is where most of the directive logic will be 6462 * put. 6463 * 6464 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 6465 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 6466 * 6467 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 6468 * manipulate the children of the element only in `postLink` function since the children have 6469 * already been linked. 6470 * 6471 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 6472 * between all directive linking functions. 6473 * 6474 * * `controller` - the directive's required controller instance(s) - Instances are shared 6475 * among all directives, which allows the directives to use the controllers as a communication 6476 * channel. The exact value depends on the directive's `require` property: 6477 * * `string`: the controller instance 6478 * * `array`: array of controller instances 6479 * * no controller(s) required: `undefined` 6480 * 6481 * If a required controller cannot be found, and it is optional, the instance is `null`, 6482 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 6483 * 6484 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 6485 * This is the same as the `$transclude` 6486 * parameter of directive controllers, see there for details. 6487 * `function([scope], cloneLinkingFn, futureParentElement)`. 6488 * 6489 * #### Pre-linking function 6490 * 6491 * Executed before the child elements are linked. Not safe to do DOM transformation since the 6492 * compiler linking function will fail to locate the correct elements for linking. 6493 * 6494 * #### Post-linking function 6495 * 6496 * Executed after the child elements are linked. 6497 *
6498 * Note that child elements that contain `templateUrl` directives will not have been compiled 6499 * and linked since they are waiting for their template to load asynchronously and their own 6500 * compilation and linking has been suspended until that occurs. 6501 * 6502 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 6503 * for their async templates to be resolved. 6504 * 6505 * 6506 * ### Transclusion 6507 * 6508 * Transclusion is the process of extracting a collection of DOM element from one part of the DOM and 6509 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 6510 * scope from where they were taken. 6511 * 6512 * Transclusion is used (often with {@link ngTransclude}) to insert the 6513 * original contents of a directive's element into a specified place in the template of the directive. 6514 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 6515 * content has access to the properties on the scope from which it was taken, even if the directive 6516 * has isolated scope. 6517 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 6518 * 6519 * This makes it possible for the widget to have private state for its template, while the transcluded 6520 * content has access to its originating scope. 6521 * 6522 * <div class="alert alert-warning"> 6523 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 6524 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 6525 * Testing Transclusion Directives}. 6526 * </div> 6527 * 6528 * #### Transclusion Functions 6529 * 6530 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 6531 * function** to the directive's `link` function and `controller`. This transclusion function is a special 6532 * **linking function** that will return the compiled contents linked to a new transclusion scope. 6533 * 6534 * <div class="alert alert-info"> 6535 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 6536 * ngTransclude will deal with it for us. 6537 * </div> 6538 * 6539 * If you want to manually control the insertion and removal of the transcluded content in your directive 6540 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery 6541 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 6542 * 6543 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 6544 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 6545 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 6546 * 6547 * <div class="alert alert-info"> 6548 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function 6549 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 6550 * </div> 6551 * 6552 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 6553 * attach function**: 6554 * 6555 * ```js 6556 * var transcludedContent, transclusionScope; 6557 * 6558 * $transclude(function(clone, scope) { 6559 * element.append(clone); 6560 * transcludedContent = clone; 6561 * transclusionScope = scope; 6562 * }); 6563 * ``` 6564 * 6565 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 6566 * associated transclusion scope: 6567 * 6568 * ```js 6569 * transcludedContent.remove(); 6570 * transclusionScope.$destroy(); 6571 * ``` 6572 * 6573 * <div class="alert alert-info"> 6574 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 6575 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 6576 * then you are also responsible for calling `$destroy` on the transclusion scope. 6577 * </div> 6578 * 6579 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 6580 * automatically destroy their transluded clones as necessary so you do not need to worry about this if 6581 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 6582 * 6583 * 6584 * #### Transclusion Scopes 6585 * 6586 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 6587 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 6588 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 6589 * was taken. 6590 * 6591 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 6592 * like this: 6593 * 6594 * ```html 6595 * <div ng-app> 6596 * <div isolate> 6597 * <div transclusion>
6598 * </div> 6599 * </div> 6600 * </div> 6601 * ``` 6602 * 6603 * The `$parent` scope hierarchy will look like this: 6604 * 6605 * ``` 6606 * - $rootScope 6607 * - isolate 6608 * - transclusion 6609 * ``` 6610 * 6611 * but the scopes will inherit prototypically from different scopes to their `$parent`. 6612 * 6613 * ``` 6614 * - $rootScope 6615 * - transclusion 6616 * - isolate 6617 * ``` 6618 * 6619 * 6620 * ### Attributes 6621 * 6622 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 6623 * `link()` or `compile()` functions. It has a variety of uses. 6624 * 6625 * accessing *Normalized attribute names:* 6626 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 6627 * the attributes object allows for normalized access to 6628 * the attributes. 6629 * 6630 * * *Directive inter-communication:* All directives share the same instance of the attributes 6631 * object which allows the directives to use the attributes object as inter directive 6632 * communication. 6633 * 6634 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 6635 * allowing other directives to read the interpolated value. 6636 * 6637 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 6638 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 6639 * the only way to easily get the actual value because during the linking phase the interpolation 6640 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 6641 * 6642 * ```js 6643 * function linkingFn(scope, elm, attrs, ctrl) { 6644 * // get the attribute value 6645 * console.log(attrs.ngModel); 6646 * 6647 * // change the attribute 6648 * attrs.$set('ngModel', 'new value'); 6649 * 6650 * // observe changes to interpolated attribute 6651 * attrs.$observe('ngModel', function(value) { 6652 * console.log('ngModel has changed value to ' + value); 6653 * }); 6654 * } 6655 * ``` 6656 * 6657 * ## Example 6658 * 6659 * <div class="alert alert-warning"> 6660 * **Note**: Typically directives are registered with `module.directive`. The example below is 6661 * to illustrate how `$compile` works. 6662 * </div> 6663 * 6664 <example module="compileExample"> 6665 <file name="index.html"> 6666 <script> 6667 angular.module('compileExample', [], function($compileProvider) { 6668 // configure new 'compile' directive by passing a directive 6669 // factory function. The factory function injects the '$compile' 6670 $compileProvider.directive('compile', function($compile) { 6671 // directive factory creates a link function 6672 return function(scope, element, attrs) { 6673 scope.$watch( 6674 function(scope) { 6675 // watch the 'compile' expression for changes 6676 return scope.$eval(attrs.compile); 6677 }, 6678 function(value) { 6679 // when the 'compile' expression changes 6680 // assign it into the current DOM 6681 element.html(value); 6682 6683 // compile the new DOM and link it to the current 6684 // scope. 6685 // NOTE: we only compile .childNodes so that 6686 // we don't get into infinite loop compiling ourselves 6687 $compile(element.contents())(scope); 6688 } 6689 ); 6690 }; 6691 }); 6692 }) 6693 .controller('GreeterController', ['$scope', function($scope) { 6694 $scope.name = 'Angular'; 6695 $scope.html = 'Hello {{name}}'; 6696 }]); 6697 </script> 6698 <div ng-controller="GreeterController"> 6699 <input ng-model="name"> <br/> 6700 <textarea ng-model="html"></textarea> <br/> 6701 <div compile="html"></div> 6702 </div> 6703 </file> 6704 <file name="protractor.js" type="protractor"> 6705 it('should auto compile', function() { 6706 var textarea = $('textarea'); 6707 var output = $('div[compile]'); 6708 // The initial state reads 'Hello Angular'. 6709 expect(output.getText()).toBe('Hello Angular'); 6710 textarea.clear(); 6711 textarea.sendKeys('{{name}}!'); 6712 expect(output.getText()).toBe('Angular!'); 6713 }); 6714 </file> 6715 </example> 6716 6717 * 6718 * 6719 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 6720 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 6721 * 6722 * <div class="alert alert-danger"> 6723 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 6724 * e.g. will not use the right outer scope. Please pass the transclude function as a 6725 * `parentBoundTranscludeFn` to the link function instead. 6726 * </div> 6727 * 6728 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
6729 * root element(s), not their children) 6730 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 6731 * (a DOM element/tree) to a scope. Where: 6732 * 6733 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 6734 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 6735 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 6736 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 6737 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 6738 * 6739 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 6740 * * `scope` - is the current scope with which the linking function is working with. 6741 * 6742 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 6743 * keys may be used to control linking behavior: 6744 * 6745 * * `parentBoundTranscludeFn` - the transclude function made available to 6746 * directives; if given, it will be passed through to the link functions of 6747 * directives found in `element` during compilation. 6748 * * `transcludeControllers` - an object hash with keys that map controller names 6749 * to controller instances; if given, it will make the controllers 6750 * available to directives. 6751 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 6752 * the cloned elements; only needed for transcludes that are allowed to contain non html 6753 * elements (e.g. SVG elements). See also the directive.controller property. 6754 * 6755 * Calling the linking function returns the element of the template. It is either the original 6756 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 6757 * 6758 * After linking the view is not updated until after a call to $digest which typically is done by 6759 * Angular automatically. 6760 * 6761 * If you need access to the bound view, there are two ways to do it: 6762 * 6763 * - If you are not asking the linking function to clone the template, create the DOM element(s) 6764 * before you send them to the compiler and keep this reference around. 6765 * ```js 6766 * var element = $compile('<p>{{total}}</p>')(scope); 6767 * ``` 6768 * 6769 * - if on the other hand, you need the element to be cloned, the view reference from the original 6770 * example would not point to the clone, but rather to the original template that was cloned. In 6771 * this case, you can access the clone via the cloneAttachFn: 6772 * ```js 6773 * var templateElement = angular.element('<p>{{total}}</p>'), 6774 * scope = ....; 6775 * 6776 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 6777 * //attach the clone to DOM document at the right place 6778 * }); 6779 * 6780 * //now we have reference to the cloned DOM via `clonedElement` 6781 * ``` 6782 * 6783 * 6784 * For information on how the compiler works, see the 6785 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 6786 */ 6787 6788var $compileMinErr = minErr('$compile'); 6789 6790/** 6791 * @ngdoc provider 6792 * @name $compileProvider 6793 * 6794 * @description 6795 */ 6796$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 6797function $CompileProvider($provide, $$sanitizeUriProvider) { 6798 var hasDirectives = {}, 6799 Suffix = 'Directive', 6800 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 6801 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 6802 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 6803 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 6804 6805 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 6806 // The assumption is that future DOM event attribute names will begin with 6807 // 'on' and be composed of only English letters. 6808 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 6809 6810 function parseIsolateBindings(scope, directiveName, isController) { 6811 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; 6812 6813 var bindings = {}; 6814 6815 forEach(scope, function(definition, scopeName) { 6816 var match = definition.match(LOCAL_REGEXP); 6817 6818 if (!match) { 6819 throw $compileMinErr('iscp', 6820 "Invalid {3} for directive '{0}'." + 6821 " Definition: {... {1}: '{2}' ...}", 6822 directiveName, scopeName, definition, 6823 (isController ? "controller bindings definition" : 6824 "isolate scope definition")); 6825 } 6826 6827 bindings[scopeName] = { 6828 mode: match[1][0], 6829 collection: match[2] === '*', 6830 optional: match[3] === '?', 6831 attrName: match[4] || scopeName 6832 }; 6833 }); 6834 6835 return bindings; 6836 } 6837 6838 function parseDirectiveBindings(directive, directiveName) { 6839 var bindings = { 6840 isolateScope: null, 6841 bindToController: null 6842 }; 6843 if (isObject(directive.scope)) { 6844 if (directive.bindToController === true) { 6845 bindings.bindToController = parseIsolateBindings(directive.scope, 6846 directiveName, true); 6847 bindings.isolateScope = {}; 6848 } else { 6849 bindings.isolateScope = parseIsolateBindings(directive.scope, 6850 directiveName, false); 6851 } 6852 } 6853 if (isObject(directive.bindToController)) { 6854 bindings.bindToController = 6855 parseIsolateBindings(directive.bindToController, directiveName, true); 6856 } 6857 if (isObject(bindings.bindToController)) { 6858 var controller = directive.controller; 6859 var controllerAs = directive.controllerAs; 6860 if (!controller) { 6861 // There is no controller, there may or may not be a controllerAs property 6862 throw $compileMinErr('noctrl', 6863 "Cannot bind to controller without directive '{0}'s controller.", 6864 directiveName); 6865 } else if (!identifierForController(controller, controllerAs)) { 6866 // There is a controller, but no identifier or controllerAs property 6867 throw $compileMinErr('noident', 6868 "Cannot bind to controller without identifier for directive '{0}'.", 6869 directiveName); 6870 } 6871 } 6872 return bindings; 6873 } 6874 6875 function assertValidDirectiveName(name) { 6876 var letter = name.charAt(0); 6877 if (!letter || letter !== lowercase(letter)) { 6878 throw $compileMinErr('baddir', "Directive name '{0}
vendor: 4,558 bytes, lines 6878-6989
6878' is invalid. The first character must be a lowercase letter", name); 6879 } 6880 if (name !== name.trim()) { 6881 throw $compileMinErr('baddir', 6882 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 6883 name); 6884 } 6885 } 6886 6887 /** 6888 * @ngdoc method 6889 * @name $compileProvider#directive 6890 * @kind function 6891 * 6892 * @description 6893 * Register a new directive with the compiler. 6894 * 6895 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 6896 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 6897 * names and the values are the factories. 6898 * @param {Function|Array} directiveFactory An injectable directive factory function. See 6899 * {@link guide/directive} for more info. 6900 * @returns {ng.$compileProvider} Self for chaining. 6901 */ 6902 this.directive = function registerDirective(name, directiveFactory) { 6903 assertNotHasOwnProperty(name, 'directive'); 6904 if (isString(name)) { 6905 assertValidDirectiveName(name); 6906 assertArg(directiveFactory, 'directiveFactory'); 6907 if (!hasDirectives.hasOwnProperty(name)) { 6908 hasDirectives[name] = []; 6909 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 6910 function($injector, $exceptionHandler) { 6911 var directives = []; 6912 forEach(hasDirectives[name], function(directiveFactory, index) { 6913 try { 6914 var directive = $injector.invoke(directiveFactory); 6915 if (isFunction(directive)) { 6916 directive = { compile: valueFn(directive) }; 6917 } else if (!directive.compile && directive.link) { 6918 directive.compile = valueFn(directive.link); 6919 } 6920 directive.priority = directive.priority || 0; 6921 directive.index = index; 6922 directive.name = directive.name || name; 6923 directive.require = directive.require || (directive.controller && directive.name); 6924 directive.restrict = directive.restrict || 'EA'; 6925 var bindings = directive.$$bindings = 6926 parseDirectiveBindings(directive, directive.name); 6927 if (isObject(bindings.isolateScope)) { 6928 directive.$$isolateBindings = bindings.isolateScope; 6929 } 6930 directives.push(directive); 6931 } catch (e) { 6932 $exceptionHandler(e); 6933 } 6934 }); 6935 return directives; 6936 }]); 6937 } 6938 hasDirectives[name].push(directiveFactory); 6939 } else { 6940 forEach(name, reverseParams(registerDirective)); 6941 } 6942 return this; 6943 }; 6944 6945 6946 /** 6947 * @ngdoc method 6948 * @name $compileProvider#aHrefSanitizationWhitelist 6949 * @kind function 6950 * 6951 * @description 6952 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 6953 * urls during a[href] sanitization. 6954 * 6955 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 6956 * 6957 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 6958 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 6959 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 6960 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 6961 * 6962 * @param {RegExp=} regexp New regexp to whitelist urls with. 6963 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 6964 * chaining otherwise. 6965 */ 6966 this.aHrefSanitizationWhitelist = function(regexp) { 6967 if (isDefined(regexp)) { 6968 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 6969 return this; 6970 } else { 6971 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 6972 } 6973 }; 6974 6975 6976 /** 6977 * @ngdoc method 6978 * @name $compileProvider#imgSrcSanitizationWhitelist 6979 * @kind function 6980 * 6981 * @description 6982 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 6983 * urls during img[src] sanitization. 6984 * 6985 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 6986 * 6987 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 6988 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 6989 * regular expression. If a match is found, the original url is
6989written into the dom. Otherwise, 6990 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 6991 * 6992 * @param {RegExp=} regexp New regexp to whitelist urls with. 6993 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 6994 * chaining otherwise. 6995 */ 6996 this.imgSrcSanitizationWhitelist = function(regexp) { 6997 if (isDefined(regexp)) { 6998 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 6999 return this; 7000 } else { 7001 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 7002 } 7003 }; 7004 7005 /** 7006 * @ngdoc method 7007 * @name $compileProvider#debugInfoEnabled 7008 * 7009 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 7010 * current debugInfoEnabled state 7011 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7012 * 7013 * @kind function 7014 * 7015 * @description 7016 * Call this method to enable/disable various debug runtime information in the compiler such as adding 7017 * binding information and a reference to the current scope on to DOM elements. 7018 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 7019 * * `ng-binding` CSS class 7020 * * `$binding` data property containing an array of the binding expressions 7021 * 7022 * You may want to disable this in production for a significant performance boost. See 7023 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 7024 * 7025 * The default value is true. 7026 */ 7027 var debugInfoEnabled = true; 7028 this.debugInfoEnabled = function(enabled) { 7029 if (isDefined(enabled)) { 7030 debugInfoEnabled = enabled; 7031 return this; 7032 } 7033 return debugInfoEnabled; 7034 }; 7035 7036 this.$get = [ 7037 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 7038 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 7039 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 7040 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 7041 7042 var Attributes = function(element, attributesToCopy) { 7043 if (attributesToCopy) { 7044 var keys = Object.keys(attributesToCopy); 7045 var i, l, key; 7046 7047 for (i = 0, l = keys.length; i < l; i++) { 7048 key = keys[i]; 7049 this[key] = attributesToCopy[key]; 7050 } 7051 } else { 7052 this.$attr = {}; 7053 } 7054 7055 this.$$element = element; 7056 }; 7057 7058 Attributes.prototype = { 7059 /** 7060 * @ngdoc method 7061 * @name $compile.directive.Attributes#$normalize 7062 * @kind function 7063 * 7064 * @description 7065 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 7066 * `data-`) to its normalized, camelCase form. 7067 * 7068 * Also there is special case for Moz prefix starting with upper case letter. 7069 * 7070 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7071 * 7072 * @param {string} name Name to normalize 7073 */ 7074 $normalize: directiveNormalize, 7075 7076 7077 /** 7078 * @ngdoc method 7079 * @name $compile.directive.Attributes#$addClass 7080 * @kind function 7081 * 7082 * @description 7083 * Adds the CSS class value specified by the classVal parameter to the element. If animations 7084 * are enabled then an animation will be triggered for the class addition. 7085 * 7086 * @param {string} classVal The className value that will be added to the element 7087 */ 7088 $addClass: function(classVal) { 7089 if (classVal && classVal.length > 0) { 7090 $animate.addClass(this.$$element, classVal); 7091 } 7092 }, 7093 7094 /** 7095 * @ngdoc method 7096 * @name $compile.directive.Attributes#$removeClass 7097 * @kind function 7098 * 7099 * @description 7100 * Removes the CSS class value specified by the classVal parameter from the element. If 7101 * animations are enabled then an animation will be triggered for the class removal. 7102 * 7103 * @param {string} classVal The className value that will be removed from the element 7104 */ 7105 $removeClass: function(classVal) { 7106 if (classVal && classVal.length > 0) { 7107 $animate.removeClass(this.$$element, classVal); 7108 } 7109 }, 7110 7111 /** 7112 * @ngdoc method 7113 * @name $compile.directive.Attributes#$updateClass 7114 * @kind function 7115 *
7116 * @description 7117 * Adds and removes the appropriate CSS class values to the element based on the difference 7118 * between the new and old CSS class values (specified as newClasses and oldClasses). 7119 * 7120 * @param {string} newClasses The current CSS className value 7121 * @param {string} oldClasses The former CSS className value 7122 */ 7123 $updateClass: function(newClasses, oldClasses) { 7124 var toAdd = tokenDifference(newClasses, oldClasses); 7125 if (toAdd && toAdd.length) { 7126 $animate.addClass(this.$$element, toAdd); 7127 } 7128 7129 var toRemove = tokenDifference(oldClasses, newClasses); 7130 if (toRemove && toRemove.length) { 7131 $animate.removeClass(this.$$element, toRemove); 7132 } 7133 }, 7134 7135 /** 7136 * Set a normalized attribute on the element in a way such that all directives 7137 * can share the attribute. This function properly handles boolean attributes. 7138 * @param {string} key Normalized key. (ie ngAttribute) 7139 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 7140 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 7141 * Defaults to true. 7142 * @param {string=} attrName Optional none normalized name. Defaults to key. 7143 */ 7144 $set: function(key, value, writeAttr, attrName) { 7145 // TODO: decide whether or not to throw an error if "class" 7146 //is set through this function since it may cause $updateClass to 7147 //become unstable. 7148 7149 var node = this.$$element[0], 7150 booleanKey = getBooleanAttrName(node, key), 7151 aliasedKey = getAliasedAttrName(node, key), 7152 observer = key, 7153 nodeName; 7154 7155 if (booleanKey) { 7156 this.$$element.prop(key, value); 7157 attrName = booleanKey; 7158 } else if (aliasedKey) { 7159 this[aliasedKey] = value; 7160 observer = aliasedKey; 7161 } 7162 7163 this[key] = value; 7164 7165 // translate normalized key to actual key 7166 if (attrName) { 7167 this.$attr[key] = attrName; 7168 } else { 7169 attrName = this.$attr[key]; 7170 if (!attrName) { 7171 this.$attr[key] = attrName = snake_case(key, '-'); 7172 } 7173 } 7174 7175 nodeName = nodeName_(this.$$element); 7176 7177 if ((nodeName === 'a' && key === 'href') || 7178 (nodeName === 'img' && key === 'src')) { 7179 // sanitize a[href] and img[src] values 7180 this[key] = value = $$sanitizeUri(value, key === 'src'); 7181 } else if (nodeName === 'img' && key === 'srcset') { 7182 // sanitize img[srcset] values 7183 var result = ""; 7184 7185 // first check if there are spaces because it's not the same pattern 7186 var trimmedSrcset = trim(value); 7187 // ( 999x ,| 999w ,| ,|, ) 7188 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 7189 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 7190 7191 // split srcset into tuple of uri and descriptor except for the last item 7192 var rawUris = trimmedSrcset.split(pattern); 7193 7194 // for each tuples 7195 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 7196 for (var i = 0; i < nbrUrisWith2parts; i++) { 7197 var innerIdx = i * 2; 7198 // sanitize the uri 7199 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 7200 // add the descriptor 7201 result += (" " + trim(rawUris[innerIdx + 1])); 7202 } 7203 7204 // split the last item into uri and descriptor 7205 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 7206 7207 // sanitize the last uri 7208 result += $$sanitizeUri(trim(lastTuple[0]), true); 7209 7210 // and add the last descriptor if any 7211 if (lastTuple.length === 2) { 7212 result += (" " + trim(lastTuple[1])); 7213 } 7214 this[key] = value = result; 7215 } 7216 7217 if (writeAttr !== false) { 7218 if (value === null || value === undefined) { 7219 this.$$element.removeAttr(attrName); 7220 } else { 7221 this.$$element.attr(attrName, value); 7222 } 7223 } 7224 7225 // fire observers 7226 var $$observers = this.$$observers;
7227 $$observers && forEach($$observers[observer], function(fn) { 7228 try { 7229 fn(value); 7230 } catch (e) { 7231 $exceptionHandler(e); 7232 } 7233 }); 7234 }, 7235 7236 7237 /** 7238 * @ngdoc method 7239 * @name $compile.directive.Attributes#$observe 7240 * @kind function 7241 * 7242 * @description 7243 * Observes an interpolated attribute. 7244 * 7245 * The observer function will be invoked once during the next `$digest` following 7246 * compilation. The observer is then invoked whenever the interpolated value 7247 * changes. 7248 * 7249 * @param {string} key Normalized key. (ie ngAttribute) . 7250 * @param {function(interpolatedValue)} fn Function that will be called whenever 7251 the interpolated value of the attribute changes. 7252 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info. 7253 * @returns {function()} Returns a deregistration function for this observer. 7254 */ 7255 $observe: function(key, fn) { 7256 var attrs = this, 7257 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 7258 listeners = ($$observers[key] || ($$observers[key] = [])); 7259 7260 listeners.push(fn); 7261 $rootScope.$evalAsync(function() { 7262 if (!listeners.$$inter && attrs.hasOwnProperty(key)) { 7263 // no one registered attribute interpolation function, so lets call it manually 7264 fn(attrs[key]); 7265 } 7266 }); 7267 7268 return function() { 7269 arrayRemove(listeners, fn); 7270 }; 7271 } 7272 }; 7273 7274 7275 function safeAddClass($element, className) { 7276 try { 7277 $element.addClass(className); 7278 } catch (e) { 7279 // ignore, since it means that we are trying to set class on 7280 // SVG element, where class name is read-only. 7281 } 7282 } 7283 7284 7285 var startSymbol = $interpolate.startSymbol(), 7286 endSymbol = $interpolate.endSymbol(), 7287 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 7288 ? identity 7289 : function denormalizeTemplate(template) { 7290 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 7291 }, 7292 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 7293 7294 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 7295 var bindings = $element.data('$binding') || []; 7296 7297 if (isArray(binding)) { 7298 bindings = bindings.concat(binding); 7299 } else { 7300 bindings.push(binding); 7301 } 7302 7303 $element.data('$binding', bindings); 7304 } : noop; 7305 7306 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 7307 safeAddClass($element, 'ng-binding'); 7308 } : noop; 7309 7310 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 7311 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 7312 $element.data(dataName, scope); 7313 } : noop; 7314 7315 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 7316 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 7317 } : noop; 7318 7319 return compile; 7320 7321 //================================ 7322 7323 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 7324 previousCompileContext) { 7325 if (!($compileNodes instanceof jqLite)) { 7326 // jquery always rewraps, whereas we need to preserve the original selector so that we can 7327 // modify it. 7328 $compileNodes = jqLite($compileNodes); 7329 } 7330 // We can not compile top level text elements since text nodes can be merged and we will 7331 // not be able to attach scope data to them, so we will wrap them in <span> 7332 forEach($compileNodes, function(node, index) { 7333 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { 7334 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; 7335 } 7336 }); 7337 var compositeLinkFn = 7338 compileNodes($compileNodes, transcludeFn, $compileNodes, 7339 maxPriority, ignoreDirective, previousCompileContext); 7340 compile.$$addScopeClass($compileNodes); 7341 var namespace = null; 7342 return function publicLinkFn(scope, cloneConnectFn, options) { 7343 assertArg(scope, 'scope'); 7344 7345 options = options || {}; 7346 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 7347 transcludeControllers = options.transcludeControllers, 7348 futureParentElement = options.futureParentElement; 7349 7350 // When `parentBoundTranscludeFn` is passed, it is a 7351 // `controllersBoundTransclude` function (it was previously passed 7352 // as `transclude` to directive.link) so we must unwrap it to get 7353 // its `boundTranscludeFn` 7354 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 7355 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 7356 } 7357 7358 if (!namespace) { 7359 namespace = detectNamespaceForChildElements(futureParentElement); 7360 } 7361 var $linkNode; 7362 if (namespace !== 'html') { 7363 // When using a directive with replace:true and templateUrl the $compileNodes 7364 // (or a child element inside of them) 7365 // might change, so we need to recreate the namespace adapted compileNodes 7366 // for call to the link function. 7367 // Note: This will already clone the nodes... 7368 $linkNode = jqLite( 7369 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 7370 ); 7371 } else if (cloneConnectFn) { 7372 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
vendor: 8,070 bytes, lines 7373-7568
7373 // and sometimes changes the structure of the DOM. 7374 $linkNode = JQLitePrototype.clone.call($compileNodes); 7375 } else { 7376 $linkNode = $compileNodes; 7377 } 7378 7379 if (transcludeControllers) { 7380 for (var controllerName in transcludeControllers) { 7381 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 7382 } 7383 } 7384 7385 compile.$$addScopeInfo($linkNode, scope); 7386 7387 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 7388 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 7389 return $linkNode; 7390 }; 7391 } 7392 7393 function detectNamespaceForChildElements(parentElement) { 7394 // TODO: Make this detect MathML as well... 7395 var node = parentElement && parentElement[0]; 7396 if (!node) { 7397 return 'html'; 7398 } else { 7399 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; 7400 } 7401 } 7402 7403 /** 7404 * Compile function matches each node in nodeList against the directives. Once all directives 7405 * for a particular node are collected their compile functions are executed. The compile 7406 * functions return values - the linking functions - are combined into a composite linking 7407 * function, which is the a linking function for the node. 7408 * 7409 * @param {NodeList} nodeList an array of nodes or NodeList to compile 7410 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7411 * scope argument is auto-generated to the new child of the transcluded parent scope. 7412 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 7413 * the rootElement must be set the jqLite collection of the compile root. This is 7414 * needed so that the jqLite collection items can be replaced with widgets. 7415 * @param {number=} maxPriority Max directive priority. 7416 * @returns {Function} A composite linking function of all of the matched directives or null. 7417 */ 7418 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 7419 previousCompileContext) { 7420 var linkFns = [], 7421 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 7422 7423 for (var i = 0; i < nodeList.length; i++) { 7424 attrs = new Attributes(); 7425 7426 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 7427 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 7428 ignoreDirective); 7429 7430 nodeLinkFn = (directives.length) 7431 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 7432 null, [], [], previousCompileContext) 7433 : null; 7434 7435 if (nodeLinkFn && nodeLinkFn.scope) { 7436 compile.$$addScopeClass(attrs.$$element); 7437 } 7438 7439 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 7440 !(childNodes = nodeList[i].childNodes) || 7441 !childNodes.length) 7442 ? null 7443 : compileNodes(childNodes, 7444 nodeLinkFn ? ( 7445 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 7446 && nodeLinkFn.transclude) : transcludeFn); 7447 7448 if (nodeLinkFn || childLinkFn) { 7449 linkFns.push(i, nodeLinkFn, childLinkFn); 7450 linkFnFound = true; 7451 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 7452 } 7453 7454 //use the previous context only for the first element in the virtual group 7455 previousCompileContext = null; 7456 } 7457 7458 // return a linking function if we have found anything, null otherwise 7459 return linkFnFound ? compositeLinkFn : null; 7460 7461 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 7462 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 7463 var stableNodeList; 7464 7465 7466 if (nodeLinkFnFound) { 7467 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 7468 // offsets don't get screwed up 7469 var nodeListLength = nodeList.length; 7470 stableNodeList = new Array(nodeListLength); 7471 7472 // create a sparse array by only copying the elements which have a linkFn 7473 for (i = 0; i < linkFns.length; i+=3) { 7474 idx = linkFns[i]; 7475 stableNodeList[idx] = nodeList[idx]; 7476 } 7477 } else { 7478 stableNodeList = nodeList; 7479 } 7480 7481 for (i = 0, ii = linkFns.length; i < ii;) { 7482 node = stableNodeList[linkFns[i++]]; 7483 nodeLinkFn = linkFns[i++]; 7484 childLinkFn = linkFns[i++]; 7485 7486 if (nodeLinkFn) { 7487 if (nodeLinkFn.scope) { 7488 childScope = scope.$new(); 7489 compile.$$addScopeInfo(jqLite(node), childScope); 7490 var destroyBindings = nodeLinkFn.$$destroyBindings; 7491 if (destroyBindings) { 7492 nodeLinkFn.$$destroyBindings = null; 7493 childScope.$on('$destroyed', destroyBindings); 7494 } 7495 } else { 7496 childScope = scope; 7497 } 7498 7499 if (nodeLinkFn.transcludeOnThisElement) { 7500 childBoundTranscludeFn = createBoundTranscludeFn( 7501 scope, nodeLinkFn.transclude, parentBoundTranscludeFn, 7502 nodeLinkFn.elementTranscludeOnThisElement); 7503 7504 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 7505 childBoundTranscludeFn = parentBoundTranscludeFn; 7506 7507 } else if (!parentBoundTranscludeFn && transcludeFn) { 7508 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 7509 7510 } else { 7511 childBoundTranscludeFn = null; 7512 } 7513 7514 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn, 7515 nodeLinkFn); 7516 7517 } else if (childLinkFn) { 7518 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 7519 } 7520 } 7521 } 7522 } 7523 7524 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) { 7525 7526 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 7527 7528 if (!transcludedScope) { 7529 transcludedScope = scope.$new(false, containingScope); 7530 transcludedScope.$$transcluded = true; 7531 } 7532 7533 return transcludeFn(transcludedScope, cloneFn, { 7534 parentBoundTranscludeFn: previousBoundTranscludeFn, 7535 transcludeControllers: controllers, 7536 futureParentElement: futureParentElement 7537 }); 7538 }; 7539 7540 return boundTranscludeFn; 7541 } 7542 7543 /** 7544 * Looks for directives on the given node and adds them to the directive collection which is 7545 * sorted. 7546 * 7547 * @param node Node to search. 7548 * @param directives An array to which the directives are added to. This array is sorted before 7549 * the function returns. 7550 * @param attrs The shared attrs object which is used to populate the normalized attributes. 7551 * @param {number=} maxPriority Max directive priority. 7552 */ 7553 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 7554 var nodeType = node.nodeType, 7555 attrsMap = attrs.$attr, 7556 match, 7557 className; 7558 7559 switch (nodeType) { 7560 case NODE_TYPE_ELEMENT: /* Element */ 7561 // use the node name: <directive> 7562 addDirective(directives, 7563 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 7564 7565 // iterate over the attributes 7566 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 7567 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 7568 var attrStartName = false;
7569 var attrEndName = false; 7570 7571 attr = nAttrs[j]; 7572 name = attr.name; 7573 value = trim(attr.value); 7574 7575 // support ngAttr attribute binding 7576 ngAttrName = directiveNormalize(name); 7577 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 7578 name = name.replace(PREFIX_REGEXP, '') 7579 .substr(8).replace(/_(.)/g, function(match, letter) { 7580 return letter.toUpperCase(); 7581 }); 7582 } 7583 7584 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 7585 if (directiveIsMultiElement(directiveNName)) { 7586 if (ngAttrName === directiveNName + 'Start') { 7587 attrStartName = name; 7588 attrEndName = name.substr(0, name.length - 5) + 'end'; 7589 name = name.substr(0, name.length - 6); 7590 } 7591 } 7592 7593 nName = directiveNormalize(name.toLowerCase()); 7594 attrsMap[nName] = name; 7595 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 7596 attrs[nName] = value; 7597 if (getBooleanAttrName(node, nName)) { 7598 attrs[nName] = true; // presence means true 7599 } 7600 } 7601 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 7602 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 7603 attrEndName); 7604 } 7605 7606 // use class as directive 7607 className = node.className; 7608 if (isObject(className)) { 7609 // Maybe SVGAnimatedString 7610 className = className.animVal; 7611 } 7612 if (isString(className) && className !== '') { 7613 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 7614 nName = directiveNormalize(match[2]); 7615 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 7616 attrs[nName] = trim(match[3]); 7617 } 7618 className = className.substr(match.index + match[0].length); 7619 } 7620 } 7621 break; 7622 case NODE_TYPE_TEXT: /* Text Node */ 7623 addTextInterpolateDirective(directives, node.nodeValue); 7624 break; 7625 case NODE_TYPE_COMMENT: /* Comment */ 7626 try { 7627 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 7628 if (match) { 7629 nName = directiveNormalize(match[1]); 7630 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 7631 attrs[nName] = trim(match[2]); 7632 } 7633 } 7634 } catch (e) { 7635 // turns out that under some circumstances IE9 throws errors when one attempts to read 7636 // comment's node value. 7637 // Just ignore it and continue. (Can't seem to reproduce in test case.) 7638 } 7639 break; 7640 } 7641 7642 directives.sort(byPriority); 7643 return directives; 7644 } 7645 7646 /** 7647 * Given a node with an directive-start it collects all of the siblings until it finds 7648 * directive-end. 7649 * @param node 7650 * @param attrStart 7651 * @param attrEnd 7652 * @returns {*} 7653 */ 7654 function groupScan(node, attrStart, attrEnd) { 7655 var nodes = []; 7656 var depth = 0; 7657 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 7658 do { 7659 if (!node) { 7660 throw $compileMinErr('uterdir', 7661 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 7662 attrStart, attrEnd); 7663 } 7664 if (node.nodeType == NODE_TYPE_ELEMENT) { 7665 if (node.hasAttribute(attrStart)) depth++; 7666 if (node.hasAttribute(attrEnd)) depth--; 7667 } 7668 nodes.push(node); 7669 node = node.nextSibling; 7670 } while (depth > 0); 7671 } else { 7672 nodes.push(node); 7673 } 7674 7675 return jqLite(nodes); 7676 } 7677 7678 /** 7679 * Wrapper for linking function which converts normal linking function into a grouped 7680 * linking function. 7681 * @param linkFn 7682 * @param attrStart 7683 * @param attrEnd 7684 * @returns {Function} 7685 */ 7686 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 7687 return function(scope, element, attrs, controllers, transcludeFn) { 7688 element = groupScan(element[0], attrStart, attrEnd); 7689 return linkFn(scope, element, attrs, controllers, transcludeFn); 7690 }; 7691 } 7692 7693 /** 7694 * Once the directives have been collected, their compile functions are executed. This method 7695 * is responsible for inlining directive templates as well as terminating the application 7696 * of the directives if the terminal directive has been reached. 7697 * 7698 * @param {Array} directives Array of collected directives to execute their compile function. 7699 * this needs to be pre-sorted by priority order. 7700 * @param {Node} compileNode The raw DOM node to apply the compile functions to 7701 * @param {Object} templateAttrs The shared attribute function 7702 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7703 * scope argument is auto-generated to the new 7704 * child of the transcluded parent scope. 7705 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 7706 * argument has the root jqLite array so that we can replace nodes 7707 * on it. 7708 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 7709 * compiling the transclusion. 7710 * @param {Array.<Function>} preLinkFns 7711 * @param {Array.<Function>} postLinkFns 7712 * @param {Object} previousCompileContext Context used for previous compilation of the current 7713 * node 7714 * @returns {Function} linkFn 7715 */ 7716 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 7717 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 7718 previousCompileContext) { 7719 previousCompileContext = previousCompileContext || {}; 7720 7721 var terminalPriority = -Number.MAX_VALUE, 7722 newScopeDirective, 7723 controllerDirectives = previousCompileContext.controllerDirectives, 7724 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 7725 templateDirective = previousCompileContext.templateDirective, 7726 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 7727 hasTranscludeDirective = false, 7728 hasTemplate = false, 7729 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 7730 $compileNode = templateAttrs.$$element = jqLite(compileNode), 7731 directive, 7732 directiveName, 7733 $template, 7734 replaceDirective = originalReplaceDirective, 7735 childTranscludeFn = transcludeFn, 7736 linkFn, 7737 directiveValue; 7738 7739 // executes all directives on the current element 7740 for (var i = 0, ii = directives.length; i < ii; i++) { 7741 directive = directives[i]; 7742 var attrStart = directive.$$start; 7743 var attrEnd = directive.$$end; 7744 7745 // collect multiblock sections 7746 if (attrStart) { 7747 $compileNode = groupScan(compileNode, attrStart, attrEnd); 7748 } 7749 $template = undefined; 7750 7751 if (terminalPriority > directive.priority) { 7752 break; // prevent further processing of directives 7753 } 7754 7755 if (directiveValue = directive.scope) { 7756 7757 // skip the check for directives with async templates, we'll check the derived sync 7758 // directive when the template arrives 7759 if (!directive.templateUrl) { 7760 if (isObject(directiveValue)) { 7761 // This directive is trying to add an isolated scope. 7762 // Check that there is no scope of any kind already 7763 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 7764 directive, $compileNode); 7765 newIsolateScopeDirective = directive; 7766 } else { 7767 // This directive is trying to add a child scope. 7768 // Check that there is no isolated scope already 7769 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 7770 $compileNode); 7771 } 7772 } 7773 7774 newScopeDirective = newScopeDirective || directive; 7775 } 7776 7777 directiveName = directive.name; 7778 7779 if (!directive.templateUrl && directive.controller) { 7780 directiveValue = directive.controller; 7781 controllerDirectives = controllerDirectives || createMap(); 7782 assertNoDuplicate("'" + directiveName + "' controller", 7783 controllerDirectives[directiveName], directive, $compileNode); 7784 controllerDirectives[directiveName] = directive; 7785 } 7786 7787 if (directiveValue = directive.transclude) { 7788 hasTranscludeDirective = true; 7789 7790 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 7791 // This option should only be used by directives that know how to safely handle element transclusion, 7792 // where the transcluded nodes are added or replaced after linking. 7793 if (!directive.$$tlb) { 7794 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 7795 nonTlbTranscludeDirective = directive; 7796 } 7797 7798 if (directiveValue == 'element') { 7799 hasElementTranscludeDirective = true; 7800 terminalPriority = directive.priority; 7801 $template = $compileNode; 7802 $compileNode = templateAttrs.$$element = 7803 jqLite(document.createComment(' ' + directiveName + ': ' + 7804 templateAttrs[directiveName] + ' ')); 7805 compileNode = $compileNode[0]; 7806 replaceWith(jqCollection, sliceArgs($template), compileNode); 7807 7808 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 7809 replaceDirective && replaceDirective.name, { 7810 // Don't pass in: 7811 // - controllerDirectives - otherwise we'll create duplicates controllers 7812 // - newIsolateScopeDirective or templateDirective - comb
vendor: 4,751 bytes, lines 7812-7920
7812ining templates with 7813 // element transclusion doesn't make sense. 7814 // 7815 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 7816 // on the same element more than once. 7817 nonTlbTranscludeDirective: nonTlbTranscludeDirective 7818 }); 7819 } else { 7820 $template = jqLite(jqLiteClone(compileNode)).contents(); 7821 $compileNode.empty(); // clear contents 7822 childTranscludeFn = compile($template, transcludeFn); 7823 } 7824 } 7825 7826 if (directive.template) { 7827 hasTemplate = true; 7828 assertNoDuplicate('template', templateDirective, directive, $compileNode); 7829 templateDirective = directive; 7830 7831 directiveValue = (isFunction(directive.template)) 7832 ? directive.template($compileNode, templateAttrs) 7833 : directive.template; 7834 7835 directiveValue = denormalizeTemplate(directiveValue); 7836 7837 if (directive.replace) { 7838 replaceDirective = directive; 7839 if (jqLiteIsTextNode(directiveValue)) { 7840 $template = []; 7841 } else { 7842 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 7843 } 7844 compileNode = $template[0]; 7845 7846 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 7847 throw $compileMinErr('tplrt', 7848 "Template for directive '{0}' must have exactly one root element. {1}", 7849 directiveName, ''); 7850 } 7851 7852 replaceWith(jqCollection, $compileNode, compileNode); 7853 7854 var newTemplateAttrs = {$attr: {}}; 7855 7856 // combine directives from the original node and from the template: 7857 // - take the array of directives for this element 7858 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 7859 // - collect directives from the template and sort them by priority 7860 // - combine directives as: processed + template + unprocessed 7861 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 7862 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 7863 7864 if (newIsolateScopeDirective) { 7865 markDirectivesAsIsolate(templateDirectives); 7866 } 7867 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 7868 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 7869 7870 ii = directives.length; 7871 } else { 7872 $compileNode.html(directiveValue); 7873 } 7874 } 7875 7876 if (directive.templateUrl) { 7877 hasTemplate = true; 7878 assertNoDuplicate('template', templateDirective, directive, $compileNode); 7879 templateDirective = directive; 7880 7881 if (directive.replace) { 7882 replaceDirective = directive; 7883 } 7884 7885 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 7886 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 7887 controllerDirectives: controllerDirectives, 7888 newIsolateScopeDirective: newIsolateScopeDirective, 7889 templateDirective: templateDirective, 7890 nonTlbTranscludeDirective: nonTlbTranscludeDirective 7891 }); 7892 ii = directives.length; 7893 } else if (directive.compile) { 7894 try { 7895 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 7896 if (isFunction(linkFn)) { 7897 addLinkFns(null, linkFn, attrStart, attrEnd); 7898 } else if (linkFn) { 7899 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 7900 } 7901 } catch (e) { 7902 $exceptionHandler(e, startingTag($compileNode)); 7903 } 7904 } 7905 7906 if (directive.terminal) { 7907 nodeLinkFn.terminal = true; 7908 terminalPriority = Math.max(terminalPriority, directive.priority); 7909 } 7910 7911 } 7912 7913 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 7914 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 7915 nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective; 7916 nodeLinkFn.templateOnThisElement = hasTemplate; 7917 nodeLinkFn.transclude = childTranscludeFn; 7918 7919 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 7920
7921 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 7922 return nodeLinkFn; 7923 7924 //////////////////// 7925 7926 function addLinkFns(pre, post, attrStart, attrEnd) { 7927 if (pre) { 7928 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 7929 pre.require = directive.require; 7930 pre.directiveName = directiveName; 7931 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 7932 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 7933 } 7934 preLinkFns.push(pre); 7935 } 7936 if (post) { 7937 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 7938 post.require = directive.require; 7939 post.directiveName = directiveName; 7940 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 7941 post = cloneAndAnnotateFn(post, {isolateScope: true}); 7942 } 7943 postLinkFns.push(post); 7944 } 7945 } 7946 7947 7948 function getControllers(directiveName, require, $element, elementControllers) { 7949 var value; 7950 7951 if (isString(require)) { 7952 var match = require.match(REQUIRE_PREFIX_REGEXP); 7953 var name = require.substring(match[0].length); 7954 var inheritType = match[1] || match[3]; 7955 var optional = match[2] === '?'; 7956 7957 //If only parents then start at the parent element 7958 if (inheritType === '^^') { 7959 $element = $element.parent(); 7960 //Otherwise attempt getting the controller from elementControllers in case 7961 //the element is transcluded (and has no data) and to avoid .data if possible 7962 } else { 7963 value = elementControllers && elementControllers[name]; 7964 value = value && value.instance; 7965 } 7966 7967 if (!value) { 7968 var dataName = '$' + name + 'Controller'; 7969 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 7970 } 7971 7972 if (!value && !optional) { 7973 throw $compileMinErr('ctreq', 7974 "Controller '{0}', required by directive '{1}', can't be found!", 7975 name, directiveName); 7976 } 7977 } else if (isArray(require)) { 7978 value = []; 7979 for (var i = 0, ii = require.length; i < ii; i++) { 7980 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 7981 } 7982 } 7983 7984 return value || null; 7985 } 7986 7987 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) { 7988 var elementControllers = createMap(); 7989 for (var controllerKey in controllerDirectives) { 7990 var directive = controllerDirectives[controllerKey]; 7991 var locals = { 7992 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 7993 $element: $element, 7994 $attrs: attrs, 7995 $transclude: transcludeFn 7996 }; 7997 7998 var controller = directive.controller; 7999 if (controller == '@') { 8000 controller = attrs[directive.name]; 8001 } 8002 8003 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 8004 8005 // For directives with element transclusion the element is a comment, 8006 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 8007 // clean up (http://bugs.jquery.com/ticket/8335). 8008 // Instead, we save the controllers for the element in a local hash and attach to .data 8009 // later, once we have the actual element. 8010 elementControllers[directive.name] = controllerInstance; 8011 if (!hasElementTranscludeDirective) { 8012 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 8013 } 8014 } 8015 return elementControllers; 8016 } 8017 8018 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn, 8019 thisLinkFn) { 8020 var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element, 8021 attrs; 8022 8023 if (compileNode === linkNode) { 8024 attrs = templateAttrs; 8025 $element = templateAttrs.$$element; 8026 } else { 8027 $element = jqLite(linkNode); 8028 attrs = new Attributes($element, templateAttrs); 8029 } 8030 8031 if (newIsolateScopeDirective) { 8032 isolateScope = scope.$new(true); 8033 } 8034 8035 if (boundTranscludeFn) { 8036 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 8037 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 8038 transcludeFn = controllersBoundTransclude; 8039 transcludeFn.$$boundTransclude = boundTranscludeFn; 8040 } 8041 8042 if (controllerDirectives) { 8043 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope); 8044 } 8045 8046 if (newIsolateScopeDirective) { 8047 // Initialize isolate scope bindings for new isolate scope directive. 8048 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 8049 templateDirective === newIsolateScopeDirective.$$originalDirective))); 8050 compile.$$addScopeClass($element, true); 8051 isolateScope.$$isolateBindings = 8052 newIsolateScopeDirective.$$isolateBindings; 8053 initializeDirectiveBindings(scope, attrs, isolateScope, 8054 isolateScope.$$isolateBindings, 8055 newIsolateScopeDirective, isolateScope); 8056 } 8057 if (elementControllers) { 8058 // Initialize bindToController bindings for new/isolate scopes 8059 var scopeDirective = newIsolateScopeDirective || newScopeDirective; 8060 var bindings; 8061 var controllerForBindings; 8062 if (scopeDirective && elementControllers[scopeDirective.name]) { 8063 bindings = scopeDirective.$$bindings.bindToController; 8064 controller = elementControllers[scopeDirective.name]; 8065 8066 if (controller && controller.identifier && bindings) { 8067 controllerForBindings = controller; 8068 thisLinkFn.$$destroyBindings = 8069 initializeDirectiveBindings(scope, attrs, controller.instance, 8070 bindings, scopeDirective); 8071 } 8072 } 8073 for (i in elementControllers) { 8074 controller = elementControllers[i]; 8075 var controllerResult = controller(); 8076 if (controllerResult !== controller.instance) { 8077 controller.instance = controllerResult; 8078 $element.data('$' + directive.name + 'Controller', controllerResult); 8079 if (controller === controllerForBindings) { 8080 // Remove and re-install bindToController bindings 8081 thisLinkFn.$$destroyBindings(); 8082 thisLinkFn.$$destroyBindings = 8083 initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective); 8084 } 8085 } 8086 } 8087 } 8088 8089 // PRELINKING 8090 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 8091 linkFn = preLinkFns[i]; 8092 invokeLinkFn(linkFn, 8093 linkFn.isolateScope ? isolateScope : scope, 8094 $element, 8095 attrs, 8096 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8097 transcludeFn 8098 ); 8099 } 8100 8101 // RECURSION 8102 // We only pass the isolate scope, if the isolate directive has a template, 8103 // otherwise the child elements do not belong to the isolate directive. 8104 var scopeToChild = scope; 8105 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 8106 scopeToChild = isolateScope; 8107 } 8108 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 8109 8110 // POSTLINKING 8111 for (i = postLinkFns.length - 1; i >= 0; i--) { 8112 linkFn = postLinkFns[i]; 8113 invokeLinkFn(linkFn, 8114 linkFn.isolateScope ? isolateScope : scope, 8115 $element, 8116 attrs, 8117 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8118 transcludeFn 8119 ); 8120 } 8121 8122 // This is the function that is injected as `$transclude`. 8123 // Note: all arguments are optional! 8124 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { 8125 var transcludeControllers; 8126 8127 // No scope passed in: 8128 if (!isScope(scope)) { 8129 futureParentElement = cloneAttachFn; 8130 cloneAttachFn = scope; 8131 scope = undefined; 8132 } 8133 8134 if (hasElementTranscludeDirective) { 8135 transcludeControllers = elementControllers; 8136 } 8137 if (!futureParentElement) { 8138 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 8139 } 8140 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 8141 } 8142 } 8143 } 8144 8145 function markDirectivesAsIsolate(directives) { 8146 // mark all directives as needing isolate scope. 8147 for (var j = 0, jj = directives.length; j < jj; j++) { 8148 directives[j] = inherit(directives[j], {$$isolateScope: true}); 8149 } 8150 } 8151 8152 /** 8153 * looks up the directive and decorates it with exception handling and proper parameters. We 8154 * call this the boundDirective. 8155 * 8156 * @param {string} name name of the directive to look up. 8157 * @param {string} location The directive must be found in specific format. 8158 * String containing any of theses characters: 8159 * 8160 * * `E`: element name 8161 * * `A': attribute 8162 * * `C`: class 8163 * * `M`: comment 8164 * @returns {boolean} true if directive was added. 8165 */ 8166 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 8167 endAttrName) { 8168 if (name === ignoreDirective) return null; 8169 var match = null; 8170 if (hasDirectives.hasOwnProperty(name)) { 8171 for (var directive, directives = $injector.get(name + Suffix), 8172 i = 0, ii = directives.length; i < ii; i++) { 8173 try { 8174 directive = directives[i]; 8175 if ((maxPriority === undefined || maxPriority > directive.priority) && 8176 directive.restrict.indexOf(location) != -1) { 8177 if (startAttrName) { 8178 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 8179 } 8180 tDirectives.push(directive); 8181 match = directive; 8182 } 8183 } catch (e) { $exceptionHandler(e); } 8184 } 8185 } 8186 return match; 8187 } 8188 8189 8190 /** 8191 * looks up the directive and returns true if it is a multi-element directive, 8192 * and therefore requires DOM nodes between -start and -end markers to be grouped 8193 * together. 8194 * 8195 * @param {string} name name of the directive to look up. 8196 * @returns true if directive was registered as multi-element. 8197 */ 8198 function directiveIsMultiElement(name) { 8199 if (hasDirectives.hasOwnProperty(name)) { 8200 for (var directive, directives = $injector.get(name + Suffix), 8201 i = 0, ii = directives.length; i < ii; i++) { 8202 directive = directives[i]; 8203 if (directive.multiElement) { 8204 return true; 8205 } 8206 } 8207 } 8208 return false;
8209 } 8210 8211 /** 8212 * When the element is replaced with HTML template then the new attributes 8213 * on the template need to be merged with the existing attributes in the DOM. 8214 * The desired effect is to have both of the attributes present. 8215 * 8216 * @param {object} dst destination attributes (original DOM) 8217 * @param {object} src source attributes (from the directive template) 8218 */ 8219 function mergeTemplateAttributes(dst, src) { 8220 var srcAttr = src.$attr, 8221 dstAttr = dst.$attr, 8222 $element = dst.$$element; 8223 8224 // reapply the old attributes to the new element 8225 forEach(dst, function(value, key) { 8226 if (key.charAt(0) != '$') { 8227 if (src[key] && src[key] !== value) { 8228 value += (key === 'style' ? ';' : ' ') + src[key]; 8229 } 8230 dst.$set(key, value, true, srcAttr[key]); 8231 } 8232 }); 8233 8234 // copy the new attributes on the old attrs object 8235 forEach(src, function(value, key) { 8236 if (key == 'class') { 8237 safeAddClass($element, value); 8238 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 8239 } else if (key == 'style') { 8240 $element.attr('style', $element.attr('style') + ';' + value); 8241 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 8242 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 8243 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 8244 // have an attribute like "has-own-property" or "data-has-own-property", etc. 8245 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 8246 dst[key] = value; 8247 dstAttr[key] = srcAttr[key]; 8248 } 8249 }); 8250 } 8251 8252 8253 function compileTemplateUrl(directives, $compileNode, tAttrs, 8254 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 8255 var linkQueue = [], 8256 afterTemplateNodeLinkFn, 8257 afterTemplateChildLinkFn, 8258 beforeTemplateCompileNode = $compileNode[0], 8259 origAsyncDirective = directives.shift(), 8260 derivedSyncDirective = inherit(origAsyncDirective, { 8261 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 8262 }), 8263 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 8264 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 8265 : origAsyncDirective.templateUrl, 8266 templateNamespace = origAsyncDirective.templateNamespace; 8267 8268 $compileNode.empty(); 8269 8270 $templateRequest($sce.getTrustedResourceUrl(templateUrl)) 8271 .then(function(content) { 8272 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 8273 8274 content = denormalizeTemplate(content); 8275 8276 if (origAsyncDirective.replace) { 8277 if (jqLiteIsTextNode(content)) { 8278 $template = []; 8279 } else { 8280 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 8281 } 8282 compileNode = $template[0]; 8283 8284 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8285 throw $compileMinErr('tplrt', 8286 "Template for directive '{0}' must have exactly one root element. {1}", 8287 origAsyncDirective.name, templateUrl); 8288 } 8289 8290 tempTemplateAttrs = {$attr: {}}; 8291 replaceWith($rootElement, $compileNode, compileNode); 8292 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 8293 8294 if (isObject(origAsyncDirective.scope)) { 8295 markDirectivesAsIsolate(templateDirectives); 8296 } 8297 directives = templateDirectives.concat(directives); 8298 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 8299 } else { 8300 compileNode = beforeTemplateCompileNode; 8301 $compileNode.html(content); 8302 } 8303 8304 directives.unshift(derivedSyncDirective); 8305 8306 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 8307 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 8308 previousCompileContext);
vendor: 11,498 bytes, lines 8309-8578
8309 forEach($rootElement, function(node, i) { 8310 if (node == compileNode) { 8311 $rootElement[i] = $compileNode[0]; 8312 } 8313 }); 8314 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 8315 8316 while (linkQueue.length) { 8317 var scope = linkQueue.shift(), 8318 beforeTemplateLinkNode = linkQueue.shift(), 8319 linkRootElement = linkQueue.shift(), 8320 boundTranscludeFn = linkQueue.shift(), 8321 linkNode = $compileNode[0]; 8322 8323 if (scope.$$destroyed) continue; 8324 8325 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 8326 var oldClasses = beforeTemplateLinkNode.className; 8327 8328 if (!(previousCompileContext.hasElementTranscludeDirective && 8329 origAsyncDirective.replace)) { 8330 // it was cloned therefore we have to clone as well. 8331 linkNode = jqLiteClone(compileNode); 8332 } 8333 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 8334 8335 // Copy in CSS classes from original node 8336 safeAddClass(jqLite(linkNode), oldClasses); 8337 } 8338 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8339 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8340 } else { 8341 childBoundTranscludeFn = boundTranscludeFn; 8342 } 8343 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 8344 childBoundTranscludeFn, afterTemplateNodeLinkFn); 8345 } 8346 linkQueue = null; 8347 }); 8348 8349 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 8350 var childBoundTranscludeFn = boundTranscludeFn; 8351 if (scope.$$destroyed) return; 8352 if (linkQueue) { 8353 linkQueue.push(scope, 8354 node, 8355 rootElement, 8356 childBoundTranscludeFn); 8357 } else { 8358 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8359 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8360 } 8361 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn, 8362 afterTemplateNodeLinkFn); 8363 } 8364 }; 8365 } 8366 8367 8368 /** 8369 * Sorting function for bound directives. 8370 */ 8371 function byPriority(a, b) { 8372 var diff = b.priority - a.priority; 8373 if (diff !== 0) return diff; 8374 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 8375 return a.index - b.index; 8376 } 8377 8378 8379 function assertNoDuplicate(what, previousDirective, directive, element) { 8380 if (previousDirective) { 8381 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 8382 previousDirective.name, directive.name, what, startingTag(element)); 8383 } 8384 } 8385 8386 8387 function addTextInterpolateDirective(directives, text) { 8388 var interpolateFn = $interpolate(text, true); 8389 if (interpolateFn) { 8390 directives.push({ 8391 priority: 0, 8392 compile: function textInterpolateCompileFn(templateNode) { 8393 var templateNodeParent = templateNode.parent(), 8394 hasCompileParent = !!templateNodeParent.length; 8395 8396 // When transcluding a template that has bindings in the root 8397 // we don't have a parent and thus need to add the class during linking fn. 8398 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 8399 8400 return function textInterpolateLinkFn(scope, node) { 8401 var parent = node.parent(); 8402 if (!hasCompileParent) compile.$$addBindingClass(parent); 8403 compile.$$addBindingInfo(parent, interpolateFn.expressions); 8404 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 8405 node[0].nodeValue = value; 8406 }); 8407 }; 8408 } 8409 }); 8410 } 8411 } 8412 8413 8414 function wrapTemplate(type, template) { 8415 type = lowercase(type || 'html'); 8416 switch (type) { 8417 case 'svg': 8418 case 'math': 8419 var wrapper = document.createElement('div'); 8420 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 8421 return wrapper.childNodes[0].childNodes; 8422 default: 8423 return template; 8424 } 8425 } 8426 8427 8428 function getTrustedContext(node, attrNormalizedName) { 8429 if (attrNormalizedName == "srcdoc") { 8430 return $sce.HTML; 8431 } 8432 var tag = nodeName_(node); 8433 // maction[xlink:href] can source SVG. It's not limited to <maction>. 8434 if (attrNormalizedName == "xlinkHref" || 8435 (tag == "form" && attrNormalizedName == "action") || 8436 (tag != "img" && (attrNormalizedName == "src" || 8437 attrNormalizedName == "ngSrc"))) { 8438 return $sce.RESOURCE_URL; 8439 } 8440 } 8441 8442 8443 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 8444 var trustedContext = getTrustedContext(node, name); 8445 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 8446 8447 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 8448 8449 // no interpolation found -> ignore 8450 if (!interpolateFn) return; 8451 8452 8453 if (name === "multiple" && nodeName_(node) === "select") { 8454 throw $compileMinErr("selmulti", 8455 "Binding to the 'multiple' attribute is not supported. Element: {0}", 8456 startingTag(node)); 8457 } 8458 8459 directives.push({ 8460 priority: 100, 8461 compile: function() { 8462 return { 8463 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 8464 var $$observers = (attr.$$observers || (attr.$$observers = {})); 8465 8466 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 8467 throw $compileMinErr('nodomevents', 8468 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 8469 "ng- versions (such as ng-click instead of onclick) instead."); 8470 } 8471 8472 // If the attribute has changed since last $interpolate()ed 8473 var newValue = attr[name]; 8474 if (newValue !== value) { 8475 // we need to interpolate again since the attribute value has been updated 8476 // (e.g. by another directive's compile function) 8477 // ensure unset/empty values make interpolateFn falsy 8478 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 8479 value = newValue; 8480 } 8481 8482 // if attribute was updated so that there is no interpolation going on we don't want to 8483 // register any observers 8484 if (!interpolateFn) return; 8485 8486 // initialize attr object so that it's ready in case we need the value for isolate 8487 // scope initialization, otherwise the value would not be available from isolate 8488 // directive's linking fn during linking phase 8489 attr[name] = interpolateFn(scope); 8490 8491 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 8492 (attr.$$observers && attr.$$observers[name].$$scope || scope). 8493 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 8494 //special case for class attribute addition + removal 8495 //so that class changes can tap into the animation 8496 //hooks provided by the $animate service. Be sure to 8497 //skip animations when the first digest occurs (when 8498 //both the new and the old values are the same) since 8499 //the CSS classes are the non-interpolated values 8500 if (name === 'class' && newValue != oldValue) { 8501 attr.$updateClass(newValue, oldValue); 8502 } else { 8503 attr.$set(name, newValue); 8504 } 8505 }); 8506 } 8507 }; 8508 } 8509 }); 8510 } 8511 8512 8513 /** 8514 * This is a special jqLite.replaceWith, which can replace items which 8515 * have no parents, provided that the containing jqLite collection is provided. 8516 * 8517 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 8518 * in the root of the tree. 8519 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 8520 * the shell, but replace its DOM node reference. 8521 * @param {Node} newNode The new DOM node. 8522 */ 8523 function replaceWith($rootElement, elementsToRemove, newNode) { 8524 var firstElementToRemove = elementsToRemove[0], 8525 removeCount = elementsToRemove.length, 8526 parent = firstElementToRemove.parentNode, 8527 i, ii; 8528 8529 if ($rootElement) { 8530 for (i = 0, ii = $rootElement.length; i < ii; i++) { 8531 if ($rootElement[i] == firstElementToRemove) { 8532 $rootElement[i++] = newNode; 8533 for (var j = i, j2 = j + removeCount - 1, 8534 jj = $rootElement.length; 8535 j < jj; j++, j2++) { 8536 if (j2 < jj) { 8537 $rootElement[j] = $rootElement[j2]; 8538 } else { 8539 delete $rootElement[j]; 8540 } 8541 } 8542 $rootElement.length -= removeCount - 1; 8543 8544 // If the replaced element is also the jQuery .context then replace it 8545 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 8546 // http://api.jquery.com/context/ 8547 if ($rootElement.context === firstElementToRemove) { 8548 $rootElement.context = newNode; 8549 } 8550 break; 8551 } 8552 } 8553 } 8554 8555 if (parent) { 8556 parent.replaceChild(newNode, firstElementToRemove); 8557 } 8558 8559 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? 8560 var fragment = document.createDocumentFragment(); 8561 fragment.appendChild(firstElementToRemove); 8562 8563 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 8564 // data here because there's no public interface in jQuery to do that and copying over 8565 // event listeners (which is the main use of private data) wouldn't work anyway. 8566 jqLite(newNode).data(jqLite(firstElementToRemove).data()); 8567 8568 // Remove data of the replaced element. We cannot just call .remove() 8569 // on the element it since that would deallocate scope that is needed 8570 // for the new node. Instead, remove the data "manually". 8571 if (!jQuery) { 8572 delete jqLite.cache[firstElementToRemove[jqLite.expando]]; 8573 } else { 8574 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after 8575 // the replaced element. The cleanData version monkey-patched by Angular would cause 8576 // the scope to be trashed and we do need the very same scope to work with the new 8577 // element. However, we cannot just cache the non-patched version and use it here as 8578 // that would break if another library patches the method after Angular does (one
8579 // example is jQuery UI). Instead, set a flag indicating scope destroying should be 8580 // skipped this one time. 8581 skipDestroyOnNextJQueryCleanData = true; 8582 jQuery.cleanData([firstElementToRemove]); 8583 } 8584 8585 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 8586 var element = elementsToRemove[k]; 8587 jqLite(element).remove(); // must do this way to clean up expando 8588 fragment.appendChild(element); 8589 delete elementsToRemove[k]; 8590 } 8591 8592 elementsToRemove[0] = newNode; 8593 elementsToRemove.length = 1; 8594 } 8595 8596 8597 function cloneAndAnnotateFn(fn, annotation) { 8598 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 8599 } 8600 8601 8602 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 8603 try { 8604 linkFn(scope, $element, attrs, controllers, transcludeFn); 8605 } catch (e) { 8606 $exceptionHandler(e, startingTag($element)); 8607 } 8608 } 8609 8610 8611 // Set up $watches for isolate scope and controller bindings. This process 8612 // only occurs for isolate scopes and new scopes with controllerAs. 8613 function initializeDirectiveBindings(scope, attrs, destination, bindings, 8614 directive, newScope) { 8615 var onNewScopeDestroyed; 8616 forEach(bindings, function(definition, scopeName) { 8617 var attrName = definition.attrName, 8618 optional = definition.optional, 8619 mode = definition.mode, // @, =, or & 8620 lastValue, 8621 parentGet, parentSet, compare; 8622 8623 switch (mode) { 8624 8625 case '@': 8626 attrs.$observe(attrName, function(value) { 8627 destination[scopeName] = value; 8628 }); 8629 attrs.$$observers[attrName].$$scope = scope; 8630 if (attrs[attrName]) { 8631 // If the attribute has been provided then we trigger an interpolation to ensure 8632 // the value is there for use in the link fn 8633 destination[scopeName] = $interpolate(attrs[attrName])(scope); 8634 } 8635 break; 8636 8637 case '=': 8638 if (optional && !attrs[attrName]) { 8639 return; 8640 } 8641 parentGet = $parse(attrs[attrName]); 8642 if (parentGet.literal) { 8643 compare = equals; 8644 } else { 8645 compare = function(a, b) { return a === b || (a !== a && b !== b); }; 8646 } 8647 parentSet = parentGet.assign || function() { 8648 // reset the change, or we will throw this exception on every $digest 8649 lastValue = destination[scopeName] = parentGet(scope); 8650 throw $compileMinErr('nonassign', 8651 "Expression '{0}' used with directive '{1}' is non-assignable!", 8652 attrs[attrName], directive.name); 8653 }; 8654 lastValue = destination[scopeName] = parentGet(scope); 8655 var parentValueWatch = function parentValueWatch(parentValue) { 8656 if (!compare(parentValue, destination[scopeName])) { 8657 // we are out of sync and need to copy 8658 if (!compare(parentValue, lastValue)) { 8659 // parent changed and it has precedence 8660 destination[scopeName] = parentValue; 8661 } else { 8662 // if the parent can be assigned then do so 8663 parentSet(scope, parentValue = destination[scopeName]); 8664 } 8665 } 8666 return lastValue = parentValue; 8667 }; 8668 parentValueWatch.$stateful = true; 8669 var unwatch; 8670 if (definition.collection) { 8671 unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 8672 } else { 8673 unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 8674 } 8675 onNewScopeDestroyed = (onNewScopeDestroyed || []); 8676 onNewScopeDestroyed.push(unwatch); 8677 break; 8678 8679 case '&': 8680 // Don't assign Object.prototype method to scope 8681 if (!attrs.hasOwnProperty(attrName) && optional) break; 8682 8683 parentGet = $parse(attrs[attrName]); 8684 8685 // Don't assign noop to destination if expression is not val
8685id 8686 if (parentGet === noop && optional) break; 8687 8688 destination[scopeName] = function(locals) { 8689 return parentGet(scope, locals); 8690 }; 8691 break; 8692 } 8693 }); 8694 var destroyBindings = onNewScopeDestroyed ? function destroyBindings() { 8695 for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) { 8696 onNewScopeDestroyed[i](); 8697 } 8698 } : noop; 8699 if (newScope && destroyBindings !== noop) { 8700 newScope.$on('$destroy', destroyBindings); 8701 return noop; 8702 } 8703 return destroyBindings; 8704 } 8705 }]; 8706} 8707 8708var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 8709/** 8710 * Converts all accepted directives format into proper directive name. 8711 * @param name Name to normalize 8712 */ 8713function directiveNormalize(name) { 8714 return camelCase(name.replace(PREFIX_REGEXP, '')); 8715} 8716 8717/** 8718 * @ngdoc type 8719 * @name $compile.directive.Attributes 8720 * 8721 * @description 8722 * A shared object between directive compile / linking functions which contains normalized DOM 8723 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 8724 * needed since all of these are treated as equivalent in Angular: 8725 * 8726 * ``` 8727 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 8728 * ``` 8729 */ 8730 8731/** 8732 * @ngdoc property 8733 * @name $compile.directive.Attributes#$attr 8734 * 8735 * @description 8736 * A map of DOM element attribute names to the normalized name. This is 8737 * needed to do reverse lookup from normalized name back to actual name. 8738 */ 8739 8740 8741/** 8742 * @ngdoc method 8743 * @name $compile.directive.Attributes#$set 8744 * @kind function 8745 * 8746 * @description 8747 * Set DOM element attribute value. 8748 * 8749 * 8750 * @param {string} name Normalized element attribute name of the property to modify. The name is 8751 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 8752 * property to the original name. 8753 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 8754 */ 8755 8756 8757 8758/** 8759 * Closure compiler type information 8760 */ 8761 8762function nodesetLinkingFn( 8763 /* angular.Scope */ scope, 8764 /* NodeList */ nodeList, 8765 /* Element */ rootElement, 8766 /* function(Function) */ boundTranscludeFn 8767) {} 8768 8769function directiveLinkingFn( 8770 /* nodesetLinkingFn */ nodesetLinkingFn, 8771 /* angular.Scope */ scope, 8772 /* Node */ node, 8773 /* Element */ rootElement, 8774 /* function(Function) */ boundTranscludeFn 8775) {} 8776 8777function tokenDifference(str1, str2) { 8778 var values = '', 8779 tokens1 = str1.split(/\s+/), 8780 tokens2 = str2.split(/\s+/); 8781 8782 outer: 8783 for (var i = 0; i < tokens1.length; i++) { 8784 var token = tokens1[i]; 8785 for (var j = 0; j < tokens2.length; j++) { 8786 if (token == tokens2[j]) continue outer; 8787 } 8788 values += (values.length > 0 ? ' ' : '') + token; 8789 } 8790 return values; 8791} 8792 8793function removeComments(jqNodes) { 8794 jqNodes = jqLite(jqNodes); 8795 var i = jqNodes.length; 8796 8797 if (i <= 1) { 8798 return jqNodes; 8799 } 8800 8801 while (i--) { 8802 var node = jqNodes[i]; 8803 if (node.nodeType === NODE_TYPE_COMMENT) { 8804 splice.call(jqNodes, i, 1); 8805 } 8806 } 8807 return jqNodes; 8808} 8809 8810var $controllerMinErr = minErr('$controller'); 8811 8812 8813var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 8814function identifierForController(controller, ident) { 8815 if (ident && isString(ident)) return ident; 8816 if (isString(controller)) { 8817 var match = CNTRL_REG.exec(controller); 8818 if (match) return match[3]; 8819 } 8820} 8821 8822 8823/** 8824 * @ngdoc provider 8825 * @name $controllerProvider 8826 * @description 8827 * The {@link ng.$controller $controller service} is used by Angular to create new 8828 * controllers. 8829 * 8830 * This provider allows controller registration via the 8831 * {@link ng.$controllerProvider#register register} method. 8832 */ 8833function $ControllerProvider() { 8834 var controllers = {}, 8835 globals = false; 8836 8837 /** 8838 * @ngdoc method 8839 * @name $controllerProvider#register 8840 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 8841 * the names and the values are the constructors. 8842 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 8843 * annotations in the array notation). 8844 */ 8845 this.register = function(name, constructor) { 8846 assertNotHasOwnProperty(name, 'controller'); 8847 if (isObject(name)) { 8848 extend(controllers, name); 8849 } else { 8850 controllers[name] = constructor; 8851 } 8852 }; 8853 8854 /** 8855 * @ngdoc method 8856 * @name $controllerProvider#allowGlobals 8857 * @description If called, allows `$controller` to find controller constructors on `window` 8858 */ 8859 this.allowGlobals = function() { 8860 globals = true; 8861 }; 8862 8863 8864 this.$get = ['$injector', '$window', function($injector, $window) { 8865 8866 /** 8867 * @ngdoc service 8868 * @name $controller 8869 * @requires $injector 8870 * 8871 * @param {Function|string} constructor If called with a function then it's considered to be the 8872 * controller constructor function. Otherwise it's considered to be a string which is used 8873 * to retrieve the controller constructor using the following steps: 8874 * 8875 * * check if a controller with given name is registered via `$controllerProvider` 8876 * * check if evaluating the string on the current scope returns a constructor 8877 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 8878 * `window` object (not recommended) 8879 * 8880 * The string can use the `controller as property` syntax, where the controller instance is published 8881 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 8882 * to work correctly. 8883 * 8884 * @param {Object} locals Injection locals for Controller. 8885 * @return {Object} Instance of given controller. 8886 *
vendor: 4,024 bytes, lines 8887-8991
8887 * @description 8888 * `$controller` service is responsible for instantiating controllers. 8889 * 8890 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 8891 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 8892 */ 8893 return function(expression, locals, later, ident) { 8894 // PRIVATE API: 8895 // param `later` --- indicates that the controller's constructor is invoked at a later time. 8896 // If true, $controller will allocate the object with the correct 8897 // prototype chain, but will not invoke the controller until a returned 8898 // callback is invoked. 8899 // param `ident` --- An optional label which overrides the label parsed from the controller 8900 // expression, if any. 8901 var instance, match, constructor, identifier; 8902 later = later === true; 8903 if (ident && isString(ident)) { 8904 identifier = ident; 8905 } 8906 8907 if (isString(expression)) { 8908 match = expression.match(CNTRL_REG); 8909 if (!match) { 8910 throw $controllerMinErr('ctrlfmt', 8911 "Badly formed controller string '{0}'. " + 8912 "Must match `__name__ as __id__` or `__name__`.", expression); 8913 } 8914 constructor = match[1], 8915 identifier = identifier || match[3]; 8916 expression = controllers.hasOwnProperty(constructor) 8917 ? controllers[constructor] 8918 : getter(locals.$scope, constructor, true) || 8919 (globals ? getter($window, constructor, true) : undefined); 8920 8921 assertArgFn(expression, constructor, true); 8922 } 8923 8924 if (later) { 8925 // Instantiate controller later: 8926 // This machinery is used to create an instance of the object before calling the 8927 // controller's constructor itself. 8928 // 8929 // This allows properties to be added to the controller before the constructor is 8930 // invoked. Primarily, this is used for isolate scope bindings in $compile. 8931 // 8932 // This feature is not intended for use by applications, and is thus not documented 8933 // publicly. 8934 // Object creation: http://jsperf.com/create-constructor/2 8935 var controllerPrototype = (isArray(expression) ? 8936 expression[expression.length - 1] : expression).prototype; 8937 instance = Object.create(controllerPrototype || null); 8938 8939 if (identifier) { 8940 addIdentifier(locals, identifier, instance, constructor || expression.name); 8941 } 8942 8943 var instantiate; 8944 return instantiate = extend(function() { 8945 var result = $injector.invoke(expression, instance, locals, constructor); 8946 if (result !== instance && (isObject(result) || isFunction(result))) { 8947 instance = result; 8948 if (identifier) { 8949 // If result changed, re-assign controllerAs value to scope. 8950 addIdentifier(locals, identifier, instance, constructor || expression.name); 8951 } 8952 } 8953 return instance; 8954 }, { 8955 instance: instance, 8956 identifier: identifier 8957 }); 8958 } 8959 8960 instance = $injector.instantiate(expression, locals, constructor); 8961 8962 if (identifier) { 8963 addIdentifier(locals, identifier, instance, constructor || expression.name); 8964 } 8965 8966 return instance; 8967 }; 8968 8969 function addIdentifier(locals, identifier, instance, name) { 8970 if (!(locals && isObject(locals.$scope))) { 8971 throw minErr('$controller')('noscp', 8972 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 8973 name, identifier); 8974 } 8975 8976 locals.$scope[identifier] = instance; 8977 } 8978 }]; 8979} 8980 8981/** 8982 * @ngdoc service 8983 * @name $document 8984 * @requires $window 8985 * 8986 * @description 8987 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 8988 * 8989 * @example 8990 <example module="documentExample"> 8991 <file name="index.html">
8992 <div ng-controller="ExampleController"> 8993 <p>$document title: <b ng-bind="title"></b></p> 8994 <p>window.document title: <b ng-bind="windowTitle"></b></p> 8995 </div> 8996 </file> 8997 <file name="script.js"> 8998 angular.module('documentExample', []) 8999 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 9000 $scope.title = $document[0].title; 9001 $scope.windowTitle = angular.element(window.document)[0].title; 9002 }]); 9003 </file> 9004 </example> 9005 */ 9006function $DocumentProvider() { 9007 this.$get = ['$window', function(window) { 9008 return jqLite(window.document); 9009 }]; 9010} 9011 9012/** 9013 * @ngdoc service 9014 * @name $exceptionHandler 9015 * @requires ng.$log 9016 * 9017 * @description 9018 * Any uncaught exception in angular expressions is delegated to this service. 9019 * The default implementation simply delegates to `$log.error` which logs it into 9020 * the browser console. 9021 * 9022 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 9023 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 9024 * 9025 * ## Example: 9026 * 9027 * ```js 9028 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 9029 * return function(exception, cause) { 9030 * exception.message += ' (caused by "' + cause + '")'; 9031 * throw exception; 9032 * }; 9033 * }); 9034 * ``` 9035 * 9036 * This example will override the normal action of `$exceptionHandler`, to make angular 9037 * exceptions fail hard when they happen, instead of just logging to the console. 9038 * 9039 * <hr /> 9040 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 9041 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 9042 * (unless executed during a digest). 9043 * 9044 * If you wish, you can manually delegate exceptions, e.g. 9045 * `try { ... } catch(e) { $exceptionHandler(e); }` 9046 * 9047 * @param {Error} exception Exception associated with the error. 9048 * @param {string=} cause optional information about the context in which 9049 * the error was thrown. 9050 * 9051 */ 9052function $ExceptionHandlerProvider() { 9053 this.$get = ['$log', function($log) { 9054 return function(exception, cause) { 9055 $log.error.apply($log, arguments); 9056 }; 9057 }]; 9058} 9059 9060var APPLICATION_JSON = 'application/json'; 9061var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 9062var JSON_START = /^\[|^\{(?!\{)/; 9063var JSON_ENDS = { 9064 '[': /]$/, 9065 '{': /}$/ 9066}; 9067var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 9068 9069function serializeValue(v) { 9070 if (isObject(v)) { 9071 return isDate(v) ? v.toISOString() : toJson(v); 9072 } 9073 return v; 9074} 9075 9076 9077function $HttpParamSerializerProvider() { 9078 /** 9079 * @ngdoc service 9080 * @name $httpParamSerializer 9081 * @description 9082 * 9083 * Default $http params serializer that converts objects to a part of a request URL 9084 * according to the following rules: 9085 * * `{'foo': 'bar'}` results in `foo=bar` 9086 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 9087 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 9088 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 9089 * */ 9090 this.$get = function() { 9091 return function ngParamSerializer(params) { 9092 if (!params) return ''; 9093 var parts = []; 9094 forEachSorted(params, function(value, key) { 9095 if (value === null || isUndefined(value)) return; 9096 if (isArray(value)) { 9097 forEach(value, function(v, k) { 9098 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 9099 }); 9100 } else { 9101 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 9102 } 9103 }); 9104 9105 return parts.join('&'); 9106 }; 9107 }; 9108} 9109 9110function $HttpParamSerializerJQLikeProvider() { 9111 /** 9112 * @ngdoc service 9113 * @name $httpParamSerializerJQLike 9114 * @description 9115 * 9116 * Alternative $http params serializer that follows jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 9117 * */ 9118 this.$get = function() { 9119 return function jQueryLikeParamSerializer(params) { 9120 if (!params) return ''; 9121 var parts = []; 9122 serialize(params, '', true); 9123 return parts.join('&'); 9124 9125 function serialize(toSerialize, prefix, topLevel) { 9126 if (toSerialize === null || isUndefined(toSerialize)) return; 9127 if (isArray(toSerialize)) {
9128 forEach(toSerialize, function(value) { 9129 serialize(value, prefix + '[]'); 9130 }); 9131 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 9132 forEachSorted(toSerialize, function(value, key) { 9133 serialize(value, prefix + 9134 (topLevel ? '' : '[') + 9135 key + 9136 (topLevel ? '' : ']')); 9137 }); 9138 } else { 9139 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 9140 } 9141 } 9142 }; 9143 }; 9144} 9145 9146function defaultHttpResponseTransform(data, headers) { 9147 if (isString(data)) { 9148 // Strip json vulnerability protection prefix and trim whitespace 9149 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 9150 9151 if (tempData) { 9152 var contentType = headers('Content-Type'); 9153 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 9154 data = fromJson(tempData); 9155 } 9156 } 9157 } 9158 9159 return data; 9160} 9161 9162function isJsonLike(str) { 9163 var jsonStart = str.match(JSON_START); 9164 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 9165} 9166 9167/** 9168 * Parse headers into key value object 9169 * 9170 * @param {string} headers Raw headers as a string 9171 * @returns {Object} Parsed headers as key value object 9172 */ 9173function parseHeaders(headers) { 9174 var parsed = createMap(), i; 9175 9176 function fillInParsed(key, val) { 9177 if (key) { 9178 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 9179 } 9180 } 9181 9182 if (isString(headers)) { 9183 forEach(headers.split('\n'), function(line) { 9184 i = line.indexOf(':');
vendor: 4,242 bytes, lines 9185-9321
9185 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 9186 }); 9187 } else if (isObject(headers)) { 9188 forEach(headers, function(headerVal, headerKey) { 9189 fillInParsed(lowercase(headerKey), trim(headerVal)); 9190 }); 9191 } 9192 9193 return parsed; 9194} 9195 9196 9197/** 9198 * Returns a function that provides access to parsed headers. 9199 * 9200 * Headers are lazy parsed when first requested. 9201 * @see parseHeaders 9202 * 9203 * @param {(string|Object)} headers Headers to provide access to. 9204 * @returns {function(string=)} Returns a getter function which if called with: 9205 * 9206 * - if called with single an argument returns a single header value or null 9207 * - if called with no arguments returns an object containing all headers. 9208 */ 9209function headersGetter(headers) { 9210 var headersObj; 9211 9212 return function(name) { 9213 if (!headersObj) headersObj = parseHeaders(headers); 9214 9215 if (name) { 9216 var value = headersObj[lowercase(name)]; 9217 if (value === void 0) { 9218 value = null; 9219 } 9220 return value; 9221 } 9222 9223 return headersObj; 9224 }; 9225} 9226 9227 9228/** 9229 * Chain all given functions 9230 * 9231 * This function is used for both request and response transforming 9232 * 9233 * @param {*} data Data to transform. 9234 * @param {function(string=)} headers HTTP headers getter fn. 9235 * @param {number} status HTTP status code of the response. 9236 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 9237 * @returns {*} Transformed data. 9238 */ 9239function transformData(data, headers, status, fns) { 9240 if (isFunction(fns)) { 9241 return fns(data, headers, status); 9242 } 9243 9244 forEach(fns, function(fn) { 9245 data = fn(data, headers, status); 9246 }); 9247 9248 return data; 9249} 9250 9251 9252function isSuccess(status) { 9253 return 200 <= status && status < 300; 9254} 9255 9256 9257/** 9258 * @ngdoc provider 9259 * @name $httpProvider 9260 * @description 9261 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 9262 * */ 9263function $HttpProvider() { 9264 /** 9265 * @ngdoc property 9266 * @name $httpProvider#defaults 9267 * @description 9268 * 9269 * Object containing default values for all {@link ng.$http $http} requests. 9270 * 9271 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} 9272 * that will provide the cache for all requests who set their `cache` property to `true`. 9273 * If you set the `default.cache = false` then only requests that specify their own custom 9274 * cache object will be cached. See {@link $http#caching $http Caching} for more information. 9275 * 9276 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 9277 * Defaults value is `'XSRF-TOKEN'`. 9278 * 9279 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 9280 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 9281 * 9282 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 9283 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 9284 * setting default headers. 9285 * - **`defaults.headers.common`** 9286 * - **`defaults.headers.post`** 9287 * - **`defaults.headers.put`** 9288 * - **`defaults.headers.patch`** 9289 * 9290 * - **`defaults.paramSerializer`** - {string|function(Object<string,string>):string} - A function used to prepare string representation 9291 * of request parameters (specified as an object). 9292 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 9293 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 9294 * 9295 **/ 9296 var defaults = this.defaults = { 9297 // transform incoming response data 9298 transformResponse: [defaultHttpResponseTransform], 9299 9300 // transform outgoing request data 9301 transformRequest: [function(d) { 9302 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 9303 }], 9304 9305 // default headers 9306 headers: { 9307 common: { 9308 'Accept': 'application/json, text/plain, */*' 9309 }, 9310 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9311 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9312 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 9313 }, 9314 9315 xsrfCookieName: 'XSRF-TOKEN', 9316 xsrfHeaderName: 'X-XSRF-TOKEN', 9317 9318 paramSerializer: '$httpParamSerializer' 9319 }; 9320 9321 var useApplyAsync = false;
9322 /** 9323 * @ngdoc method 9324 * @name $httpProvider#useApplyAsync 9325 * @description 9326 * 9327 * Configure $http service to combine processing of multiple http responses received at around 9328 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 9329 * significant performance improvement for bigger applications that make many HTTP requests 9330 * concurrently (common during application bootstrap). 9331 * 9332 * Defaults to false. If no value is specified, returns the current configured value. 9333 * 9334 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 9335 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 9336 * to load and share the same digest cycle. 9337 * 9338 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 9339 * otherwise, returns the current configured value. 9340 **/ 9341 this.useApplyAsync = function(value) { 9342 if (isDefined(value)) { 9343 useApplyAsync = !!value; 9344 return this; 9345 } 9346 return useApplyAsync; 9347 }; 9348 9349 /** 9350 * @ngdoc property 9351 * @name $httpProvider#interceptors 9352 * @description 9353 * 9354 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 9355 * pre-processing of request or postprocessing of responses. 9356 * 9357 * These service factories are ordered by request, i.e. they are applied in the same order as the 9358 * array, on request, but reverse order, on response. 9359 * 9360 * {@link ng.$http#interceptors Interceptors detailed info} 9361 **/ 9362 var interceptorFactories = this.interceptors = []; 9363 9364 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 9365 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 9366 9367 var defaultCache = $cacheFactory('$http'); 9368 9369 /** 9370 * Make sure that default param serializer is exposed as a function 9371 */ 9372 defaults.paramSerializer = isString(defaults.paramSerializer) ? 9373 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 9374 9375 /** 9376 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 9377 * The reversal is needed so that we can build up the interception chain around the 9378 * server request. 9379 */ 9380 var reversedInterceptors = []; 9381 9382 forEach(interceptorFactories, function(interceptorFactory) { 9383 reversedInterceptors.unshift(isString(interceptorFactory) 9384 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 9385 }); 9386 9387 /** 9388 * @ngdoc service 9389 * @kind function 9390 * @name $http 9391 * @requires ng.$httpBackend 9392 * @requires $cacheFactory 9393 * @requires $rootScope 9394 * @requires $q 9395 * @requires $injector 9396 * 9397 * @description 9398 * The `$http` service is a core Angular service that facilitates communication with the remote 9399 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 9400 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 9401 * 9402 * For unit testing applications that use `$http` service, see 9403 * {@link ngMock.$httpBackend $httpBackend mock}. 9404 * 9405 * For a higher level of abstraction, please check out the {@link ngResource.$resource 9406 * $resource} service. 9407 * 9408 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 9409 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 9410 * it is important to familiarize yourself with these APIs and the guarantees they provide. 9411 * 9412 * 9413 * ## General usage 9414 * The `$http` service is a function which takes a single argument â a configuration object â 9415 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 9416 * with two $http specific methods: `success` and `error`. 9417 * 9418 * ```js 9419 * // Simple GET request example : 9420 * $http.get('/someUrl'). 9421 * success(function(data, status, headers, config) { 9422 * // this callback will be called asynchronously 9423 * // when the response is available 9424 * }). 9425 * error(function(data, status, headers, config) { 9426 * // called asynchronously if an error occurs 9427 * // or server returns response with an error status. 9428 * }); 9429 * ``` 9430 * 9431 * ```js 9432 * // Simple POST request example (passing data) : 9433 * $http.post('/someUrl', {msg:'hello word!'}). 9434 * success(function(data, status, headers, config) { 9435 * // this callback will be called asynchronously 9436 * // when the response is available 9437 * }). 9438 * error(function(data, status, headers, config) { 9439 * // called asynchronously if an error occurs 9440 * // or server returns response with an error status. 9441 * }); 9442 * ``` 9443 * 9444 * 9445 * Since the returned value of calling the $http function is a `promise`, you can also use 9446 * the `then` method to register callbacks, and these callbacks will receive a single argument â 9447 * an object representing the response. See the API signature and type info below for more 9448 * details. 9449 * 9450 * A response status code between 200 and 299 is considered a success status and 9451 * will result in the success callback being called. Note that if the response is a redirect, 9452 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 9453 * called for such responses. 9454 * 9455 * ## Writing Unit Tests that use $http 9456 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 9457 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 9458 * request using trained responses. 9459 * 9460 * ``` 9461 * $httpBackend.expectGET(...); 9462 * $http.get(...); 9463 * $httpBackend.flush(); 9464 * ``` 9465 * 9466 * ## Shortcut methods 9467 * 9468 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 9469 * request data must be passed in for POST/PUT requests. 9470 * 9471 * ```js 9472 * $http.get('/someUrl').success(successCallback); 9473 * $http.post('/someUrl', data).success(successCallback); 9474 * ``` 9475 * 9476 * Complete list of shortcut methods: 9477 * 9478 * - {@link ng.$http#get $http.get} 9479 * - {@link ng.$http#head $http.head} 9480 * - {@link ng.$http#post $http.post} 9481 * - {@link ng.$http#put $http.put} 9482 * - {@link ng.$http#delete $http.delete} 9483 * - {@link ng.$http#jsonp $http.jsonp} 9484 * - {@link ng.$http#patch $http.patch} 9485 * 9486 * 9487 * ## Setting HTTP Headers 9488 * 9489 * The $http service will automatically add certain HTTP headers to all requests. These defaults 9490 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 9491 * object, which currently contains this default configuration: 9492 * 9493 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 9494 * - `Accept: application/json, text/plain, * / *` 9495 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 9496 * - `Content-Type: application/json` 9497 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 9498 * - `Content-Type: application/json` 9499 *
9500 * To add or overwrite these defaults, simply add or remove a property from these configuration 9501 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 9502 * with the lowercased HTTP method name as the key, e.g. 9503 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 9504 * 9505 * The defaults can also be set at runtime via the `$http.defaults` object in the same 9506 * fashion. For example: 9507 * 9508 * ``` 9509 * module.run(function($http) { 9510 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 9511 * }); 9512 * ``` 9513 * 9514 * In addition, you can supply a `headers` property in the config object passed when 9515 * calling `$http(config)`, which overrides the defaults without changing them globally. 9516 * 9517 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 9518 * Use the `headers` property, setting the desired header to `undefined`. For example: 9519 * 9520 * ```js 9521 * var req = { 9522 * method: 'POST', 9523 * url: 'http://example.com', 9524 * headers: { 9525 * 'Content-Type': undefined 9526 * }, 9527 * data: { test: 'test' } 9528 * } 9529 * 9530 * $http(req).success(function(){...}).error(function(){...}); 9531 * ``` 9532 * 9533 * ## Transforming Requests and Responses 9534 * 9535 * Both requests and responses can be transformed using transformation functions: `transformRequest` 9536 * and `transformResponse`. These properties can be a single function that returns 9537 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 9538 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 9539 * 9540 * ### Default Transformations 9541 * 9542 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 9543 * `defaults.transformResponse` properties. If a request does not provide its own transformations 9544 * then these will be applied. 9545 * 9546 * You can augment or replace the default transformations by modifying these properties by adding to or 9547 * replacing the array. 9548 * 9549 * Angular provides the following default transformations: 9550 * 9551 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 9552 * 9553 * - If the `data` property of the request configuration object contains an object, serialize it 9554 * into JSON format. 9555 * 9556 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 9557 * 9558 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 9559 * - If JSON response is detected, deserialize it using a JSON parser. 9560 * 9561 * 9562 * ### Overriding the Default Transformations Per Request 9563 * 9564 * If you wish override the request/response transformations only for a single request then provide 9565 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 9566 * into `$http`. 9567 * 9568 * Note that if you provide these properties on the config object the default transformations will be 9569 * overwritten. If you wish to augment the default transformations then you must include them in your 9570 * local transformation array. 9571 * 9572 * The following code demonstrates adding a new response transformation to be run after the default response 9573 * transformations have been run. 9574 * 9575 * ```js 9576 * function appendTransform(defaults, transform) { 9577 * 9578 * // We can't guarantee that the default transformation is an array 9579 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 9580 * 9581 * // Append the new transformation to the defaults 9582 * return defaults.concat(transform); 9583 * } 9584 * 9585 * $http({ 9586 * url: '...', 9587 * method: 'GET', 9588 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 9589 * return doTransform(value); 9590 * }) 9591 * }); 9592 * ``` 9593 * 9594 * 9595 * ## Caching 9596 *
9597 * To enable caching, set the request configuration `cache` property to `true` (to use default 9598 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 9599 * When the cache is enabled, `$http` stores the response from the server in the specified 9600 * cache. The next time the same request is made, the response is served from the cache without 9601 * sending a request to the server. 9602 * 9603 * Note that even if the response is served from cache, delivery of the data is asynchronous in 9604 * the same way that real requests are. 9605 * 9606 * If there are multiple GET requests for the same URL that should be cached using the same 9607 * cache, but the cache is not populated yet, only one request to the server will be made and 9608 * the remaining requests will be fulfilled using the response from the first request. 9609 * 9610 * You can change the default cache to a new object (built with 9611 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 9612 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set 9613 * their `cache` property to `true` will now use this cache object. 9614 * 9615 * If you set the default cache to `false` then only requests that specify their own custom 9616 * cache object will be cached. 9617 * 9618 * ## Interceptors 9619 * 9620 * Before you start creating interceptors, be sure to understand the 9621 * {@link ng.$q $q and deferred/promise APIs}. 9622 * 9623 * For purposes of global error handling, authentication, or any kind of synchronous or 9624 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 9625 * able to intercept requests before they are handed to the server and 9626 * responses before they are handed over to the application code that 9627 * initiated these requests. The interceptors leverage the {@link ng.$q 9628 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 9629 * 9630 * The interceptors are service factories that are registered with the `$httpProvider` by 9631 * adding them to the `$httpProvider.interceptors` array. The factory is called and 9632 * injected with dependencies (if specified) and returns the interceptor. 9633 * 9634 * There are two kinds of interceptors (and two kinds of rejection interceptors): 9635 * 9636 * * `request`: interceptors get called with a http `config` object. The function is free to 9637 * modify the `config` object or create a new one. The function needs to return the `config` 9638 * object directly, or a promise containing the `config` or a new `config` object. 9639 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 9640 * resolved with a rejection. 9641 * * `response`: interceptors get called with http `response` object. The function is free to 9642 * modify the `response` object or create a new one. The function needs to return the `response` 9643 * object directly, or as a promise containing the `response` or a new `response` object. 9644 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 9645 * resolved with a rejection. 9646 * 9647 * 9648 * ```js 9649 * // register the interceptor as a service 9650 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 9651 * return { 9652 * // optional method 9653 * 'request': function(config) { 9654 * // do something on success 9655 * return config; 9656 * }, 9657 * 9658 * // optional method 9659 * 'requestError': function(rejection) { 9660 * // do something on error 9661 * if (canRecover(rejection)) { 9662 * return responseOrNewPromise 9663 * } 9664 * return $q.reject(rejection); 9665 * }, 9666 * 9667 * 9668 * 9669 * // optional method 9670 * 'response': function(response) { 9671 * // do something on success 9672 * return response; 9673 * }, 9674 * 9675 * // optional method 9676 * 'responseError': function(rejection) {
9677 * // do something on error 9678 * if (canRecover(rejection)) { 9679 * return responseOrNewPromise 9680 * } 9681 * return $q.reject(rejection); 9682 * } 9683 * }; 9684 * }); 9685 * 9686 * $httpProvider.interceptors.push('myHttpInterceptor'); 9687 * 9688 * 9689 * // alternatively, register the interceptor via an anonymous factory 9690 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 9691 * return { 9692 * 'request': function(config) { 9693 * // same as above 9694 * }, 9695 * 9696 * 'response': function(response) { 9697 * // same as above 9698 * } 9699 * }; 9700 * }); 9701 * ``` 9702 * 9703 * ## Security Considerations 9704 * 9705 * When designing web applications, consider security threats from: 9706 * 9707 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 9708 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 9709 * 9710 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 9711 * pre-configured with strategies that address these issues, but for this to work backend server 9712 * cooperation is required. 9713 * 9714 * ### JSON Vulnerability Protection 9715 * 9716 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 9717 * allows third party website to turn your JSON resource URL into 9718 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 9719 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 9720 * Angular will automatically strip the prefix before processing it as JSON. 9721 * 9722 * For example if your server needs to return: 9723 * ```js 9724 * ['one','two'] 9725 * ``` 9726 * 9727 * which is vulnerable to attack, your server can return: 9728 * ```js 9729 * )]}', 9730 * ['one','two'] 9731 * ``` 9732 * 9733 * Angular will strip the prefix, before processing the JSON. 9734 * 9735 * 9736 * ### Cross Site Request Forgery (XSRF) Protection 9737 * 9738 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 9739 * an unauthorized site can gain your user's private data. Angular provides a mechanism 9740 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 9741 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 9742 * JavaScript that runs on your domain could read the cookie, your server can be assured that 9743 * the XHR came from JavaScript running on your domain. The header will not be set for 9744 * cross-domain requests. 9745 * 9746 * To take advantage of this, your server needs to set a token in a JavaScript readable session 9747 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 9748 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 9749 * that only JavaScript running on your domain could have sent the request. The token must be 9750 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 9751 * making up its own tokens). We recommend that the token is a digest of your site's 9752 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 9753 * for added security. 9754 * 9755 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 9756 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 9757 * or the per-request config object. 9758 * 9759 * 9760 * @param {object} config Object describing the request to be made and how it should be 9761 * processed. The object has following properties: 9762 * 9763 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 9764 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 9765 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 9766 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 9767 * JSONified. 9768 * - **data** â `{string|Object}` â Data to be sent as the request message data. 9769 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 9770 * HTTP headers to send to the server. If the return value of a function is null, the 9771 * header will not be sent. Functions accept a config object as an argument. 9772 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 9773 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 9774 * - **transformRequest** â 9775 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
9775}` â 9776 * transform function or an array of such functions. The transform function takes the http 9777 * request body and headers and returns its transformed (typically serialized) version. 9778 * See {@link ng.$http#overriding-the-default-transformations-per-request 9779 * Overriding the Default Transformations} 9780 * - **transformResponse** â 9781 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 9782 * transform function or an array of such functions. The transform function takes the http 9783 * response body, headers and status and returns its transformed (typically deserialized) version. 9784 * See {@link ng.$http#overriding-the-default-transformations-per-request 9785 * Overriding the Default Transformations} 9786 * - **paramSerializer** - {string|function(Object<string,string>):string} - A function used to prepare string representation 9787 * of request parameters (specified as an object). 9788 * Is specified as string, it is interpreted as function registered in with the {$injector}. 9789 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 9790 * GET request, otherwise if a cache instance built with 9791 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 9792 * caching. 9793 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 9794 * that should abort the request when resolved. 9795 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 9796 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 9797 * for more information. 9798 * - **responseType** - `{string}` - see 9799 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 9800 * 9801 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 9802 * standard `then` method and two http specific methods: `success` and `error`. The `then` 9803 * method takes two arguments a success and an error callback which will be called with a 9804 * response object. The `success` and `error` methods take a single argument - a function that 9805 * will be called when the request succeeds or fails respectively. The arguments passed into 9806 * these functions are destructured representation of the response object passed into the 9807 * `then` method. The response object has these properties: 9808 * 9809 * - **data** â `{string|Object}` â The response body transformed with the transform 9810 * functions. 9811 * - **status** â `{number}` â HTTP status code of the response. 9812 * - **headers** â `{function([headerName])}` â Header getter function. 9813 * - **config** â `{Object}` â The configuration object that was used to generate the request. 9814 * - **statusText** â `{string}` â HTTP status text of the response. 9815 * 9816 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 9817 * requests. This is primarily meant to be used for debugging purposes. 9818 * 9819 * 9820 * @example 9821<example module="httpExample"> 9822<file name="index.html"> 9823 <div ng-controller="FetchController"> 9824 <select ng-model="method" aria-label="Request method"> 9825 <option>GET</option> 9826 <option>JSONP</option> 9827 </select> 9828 <input type="text" ng-model="url" size="80" aria-label="URL" /> 9829 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 9830 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 9831 <button id="samplejsonpbtn" 9832 ng-click="updateModel('JSONP', 9833 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 9834 Sample JSONP 9835 </button> 9836 <button id="invalidjsonpbtn" 9837 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 9838 Invalid JSONP 9839 </button> 9840 <pre>http status code: {{status}}</pre> 9841 <pre>http response data: {{data}}</pre> 9842 </div> 9843</file> 9844<file name="script.js"> 9845 angular.module('httpExample', []) 9846 .controller('FetchController', ['$scope', '$http', '$templateCache', 9847 function($scope, $http, $templateCache) { 9848 $scope.method = 'GET'; 9849 $scope.url = 'http-hello.html'; 9850 9851 $scope.fetch = function() { 9852 $scope.code = null; 9853 $scope.response = null; 9854 9855 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 9856 success(function(data, status) { 9857 $scope.status = status; 9858 $scope.data = data; 9859 }). 9860 error(function(data, status) { 9861 $scope.data = data || "Request failed"; 9862 $scope.status = status; 9863 }); 9864 }; 9865 9866 $scope.updateModel = function(method, url) { 9867 $scope.method = method; 9868 $scope.url = url; 9869 }; 9870 }]); 9871</file> 9872<file name="http-hello.html"> 9873 Hello, $http! 9874</file> 9875<file name="protractor.js" type="protractor"> 9876 var status = element(by.binding('status')); 9877 var data = element(by.binding('data')); 9878 var fetchBtn = element(by.id('fetchbtn')); 9879 var sampleGetBtn = element(by.id('samplegetbtn')); 9880 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 9881 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 9882 9883 it('should make an xhr GET request', function() { 9884 sampleGetBtn.click(); 9885 fetchBtn.click(); 9886 expect(status.getText()).toMatch('200'); 9887 expect(data.getText()).toMatch(/Hello, \$http!/); 9888 }); 9889 9890// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 9891// it('should make a JSONP request to angularjs.org', function() { 9892// sampleJsonpBtn.click(); 9893// fetchBtn.click(); 9894// expect(status.getText()).toMatch('200'); 9895// expect(data.getText()).toMatch(/Super Hero!/); 9896// }); 9897
9898 it('should make JSONP request to invalid URL and invoke the error handler', 9899 function() { 9900 invalidJsonpBtn.click(); 9901 fetchBtn.click(); 9902 expect(status.getText()).toMatch('0'); 9903 expect(data.getText()).toMatch('Request failed'); 9904 }); 9905</file> 9906</example> 9907 */ 9908 function $http(requestConfig) { 9909 9910 if (!angular.isObject(requestConfig)) { 9911 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 9912 } 9913 9914 var config = extend({ 9915 method: 'get', 9916 transformRequest: defaults.transformRequest, 9917 transformResponse: defaults.transformResponse, 9918 paramSerializer: defaults.paramSerializer 9919 }, requestConfig); 9920 9921 config.headers = mergeHeaders(requestConfig); 9922 config.method = uppercase(config.method); 9923 config.paramSerializer = isString(config.paramSerializer) ? 9924 $injector.get(config.paramSerializer) : config.paramSerializer; 9925 9926 var serverRequest = function(config) { 9927 var headers = config.headers; 9928 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 9929 9930 // strip content-type if data is undefined 9931 if (isUndefined(reqData)) { 9932 forEach(headers, function(value, header) { 9933 if (lowercase(header) === 'content-type') { 9934 delete headers[header]; 9935 } 9936 }); 9937 } 9938 9939 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 9940 config.withCredentials = defaults.withCredentials; 9941 } 9942 9943 // send request 9944 return sendReq(config, reqData).then(transformResponse, transformResponse); 9945 }; 9946 9947 var chain = [serverRequest, undefined]; 9948 var promise = $q.when(config); 9949 9950 // apply interceptors 9951 forEach(reversedInterceptors, function(interceptor) { 9952 if (interceptor.request || interceptor.requestError) { 9953 chain.unshift(interceptor.request, interceptor.requestError); 9954 } 9955 if (interceptor.response || interceptor.responseError) { 9956 chain.push(interceptor.response, interceptor.responseError); 9957 } 9958 }); 9959 9960 while (chain.length) { 9961 var thenFn = chain.shift(); 9962 var rejectFn = chain.shift(); 9963 9964 promise = promise.then(thenFn, rejectFn); 9965 } 9966 9967 promise.success = function(fn) { 9968 assertArgFn(fn, 'fn'); 9969 9970 promise.then(function(response) { 9971 fn(response.data, response.status, response.headers, config); 9972 }); 9973 return promise; 9974 }; 9975 9976 promise.error = function(fn) { 9977 assertArgFn(fn, 'fn'); 9978 9979 promise.then(null, function(response) { 9980 fn(response.data, response.status, response.headers, config); 9981 }); 9982 return promise; 9983 }; 9984 9985 return promise; 9986 9987 function transformResponse(response) { 9988 // make a copy since the response must be cacheable 9989 var resp = extend({}, response); 9990 if (!response.data) { 9991 resp.data = response.data; 9992 } else { 9993 resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); 9994 } 9995 return (isSuccess(response.status)) 9996 ? resp 9997 : $q.reject(resp); 9998 } 9999 10000 function executeHeaderFns(headers, config) { 10001 var headerContent, processedHeaders = {}; 10002 10003 forEach(headers, function(headerFn, header) { 10004 if (isFunction(headerFn)) { 10005 headerContent = headerFn(config); 10006 if (headerContent != null) { 10007 processedHeaders[header] = headerContent; 10008 } 10009 } else { 10010 processedHeaders[header] = headerFn; 10011 } 10012 }); 10013 10014 return processedHeaders; 10015 } 10016 10017 function mergeHeaders(config) { 10018 var defHeaders = defaults.headers, 10019 reqHeaders = extend({}, config.headers), 10020 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 10021 10022 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 10023
vendor: 4,228 bytes, lines 10024-10163
10024 // using for-in instead of forEach to avoid unecessary iteration after header has been found 10025 defaultHeadersIteration: 10026 for (defHeaderName in defHeaders) { 10027 lowercaseDefHeaderName = lowercase(defHeaderName); 10028 10029 for (reqHeaderName in reqHeaders) { 10030 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 10031 continue defaultHeadersIteration; 10032 } 10033 } 10034 10035 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 10036 } 10037 10038 // execute if header value is a function for merged headers 10039 return executeHeaderFns(reqHeaders, shallowCopy(config)); 10040 } 10041 } 10042 10043 $http.pendingRequests = []; 10044 10045 /** 10046 * @ngdoc method 10047 * @name $http#get 10048 * 10049 * @description 10050 * Shortcut method to perform `GET` request. 10051 * 10052 * @param {string} url Relative or absolute URL specifying the destination of the request 10053 * @param {Object=} config Optional configuration object 10054 * @returns {HttpPromise} Future object 10055 */ 10056 10057 /** 10058 * @ngdoc method 10059 * @name $http#delete 10060 * 10061 * @description 10062 * Shortcut method to perform `DELETE` request. 10063 * 10064 * @param {string} url Relative or absolute URL specifying the destination of the request 10065 * @param {Object=} config Optional configuration object 10066 * @returns {HttpPromise} Future object 10067 */ 10068 10069 /** 10070 * @ngdoc method 10071 * @name $http#head 10072 * 10073 * @description 10074 * Shortcut method to perform `HEAD` request. 10075 * 10076 * @param {string} url Relative or absolute URL specifying the destination of the request 10077 * @param {Object=} config Optional configuration object 10078 * @returns {HttpPromise} Future object 10079 */ 10080 10081 /** 10082 * @ngdoc method 10083 * @name $http#jsonp 10084 * 10085 * @description 10086 * Shortcut method to perform `JSONP` request. 10087 * 10088 * @param {string} url Relative or absolute URL specifying the destination of the request. 10089 * The name of the callback should be the string `JSON_CALLBACK`. 10090 * @param {Object=} config Optional configuration object 10091 * @returns {HttpPromise} Future object 10092 */ 10093 createShortMethods('get', 'delete', 'head', 'jsonp'); 10094 10095 /** 10096 * @ngdoc method 10097 * @name $http#post 10098 * 10099 * @description 10100 * Shortcut method to perform `POST` request. 10101 * 10102 * @param {string} url Relative or absolute URL specifying the destination of the request 10103 * @param {*} data Request content 10104 * @param {Object=} config Optional configuration object 10105 * @returns {HttpPromise} Future object 10106 */ 10107 10108 /** 10109 * @ngdoc method 10110 * @name $http#put 10111 * 10112 * @description 10113 * Shortcut method to perform `PUT` request. 10114 * 10115 * @param {string} url Relative or absolute URL specifying the destination of the request 10116 * @param {*} data Request content 10117 * @param {Object=} config Optional configuration object 10118 * @returns {HttpPromise} Future object 10119 */ 10120 10121 /** 10122 * @ngdoc method 10123 * @name $http#patch 10124 * 10125 * @description 10126 * Shortcut method to perform `PATCH` request. 10127 * 10128 * @param {string} url Relative or absolute URL specifying the destination of the request 10129 * @param {*} data Request content 10130 * @param {Object=} config Optional configuration object 10131 * @returns {HttpPromise} Future object 10132 */ 10133 createShortMethodsWithData('post', 'put', 'patch'); 10134 10135 /** 10136 * @ngdoc property 10137 * @name $http#defaults 10138 * 10139 * @description 10140 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 10141 * default headers, withCredentials as well as request and response transformations. 10142 * 10143 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 10144 */ 10145 $http.defaults = defaults; 10146 10147 10148 return $http; 10149 10150 10151 function createShortMethods(names) { 10152 forEach(arguments, function(name) { 10153 $http[name] = function(url, config) { 10154 return $http(extend({}, config || {}, { 10155 method: name, 10156 url: url 10157 })); 10158 }; 10159 }); 10160 } 10161 10162 10163 function createShortMethodsWithData(name) {
10164 forEach(arguments, function(name) { 10165 $http[name] = function(url, data, config) { 10166 return $http(extend({}, config || {}, { 10167 method: name, 10168 url: url, 10169 data: data 10170 })); 10171 }; 10172 }); 10173 } 10174 10175 10176 /** 10177 * Makes the request. 10178 * 10179 * !!! ACCESSES CLOSURE VARS: 10180 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 10181 */ 10182 function sendReq(config, reqData) { 10183 var deferred = $q.defer(), 10184 promise = deferred.promise, 10185 cache, 10186 cachedResp, 10187 reqHeaders = config.headers, 10188 url = buildUrl(config.url, config.paramSerializer(config.params)); 10189 10190 $http.pendingRequests.push(config); 10191 promise.then(removePendingReq, removePendingReq); 10192 10193 10194 if ((config.cache || defaults.cache) && config.cache !== false && 10195 (config.method === 'GET' || config.method === 'JSONP')) { 10196 cache = isObject(config.cache) ? config.cache 10197 : isObject(defaults.cache) ? defaults.cache 10198 : defaultCache; 10199 } 10200 10201 if (cache) { 10202 cachedResp = cache.get(url); 10203 if (isDefined(cachedResp)) { 10204 if (isPromiseLike(cachedResp)) { 10205 // cached request has already been sent, but there is no response yet 10206 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 10207 } else { 10208 // serving from cache 10209 if (isArray(cachedResp)) { 10210 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 10211 } else { 10212 resolvePromise(cachedResp, 200, {}, 'OK'); 10213 } 10214 } 10215 } else { 10216 // put the promise for the non-transformed response into cache as a placeholder 10217 cache.put(url, promise); 10218 } 10219 } 10220 10221 10222 // if we won't have the response in cache, set the xsrf headers and 10223 // send the request to the backend 10224 if (isUndefined(cachedResp)) { 10225 var xsrfValue = urlIsSameOrigin(config.url) 10226 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 10227 : undefined; 10228 if (xsrfValue) { 10229 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 10230 } 10231 10232 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 10233 config.withCredentials, config.responseType); 10234 } 10235 10236 return promise; 10237 10238 10239 /** 10240 * Callback registered to $httpBackend(): 10241 * - caches the response if desired 10242 * - resolves the raw $http promise 10243 * - calls $apply 10244 */ 10245 function done(status, response, headersString, statusText) { 10246 if (cache) { 10247 if (isSuccess(status)) { 10248 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 10249 } else { 10250 // remove promise from the cache 10251 cache.remove(url); 10252 } 10253 } 10254 10255 function resolveHttpPromise() { 10256 resolvePromise(response, status, headersString, statusText); 10257 } 10258 10259 if (useApplyAsync) { 10260 $rootScope.$applyAsync(resolveHttpPromise); 10261 } else { 10262 resolveHttpPromise(); 10263 if (!$rootScope.$$phase) $rootScope.$apply(); 10264 } 10265 } 10266 10267 10268 /** 10269 * Resolves the raw $http promise. 10270 */ 10271 function resolvePromise(response, status, headers, statusText) { 10272 // normalize internal statuses to 0 10273 status = Math.max(status, 0); 10274 10275 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 10276 data: response, 10277 status: status, 10278 headers: headersGetter(headers), 10279 config: config, 10280 statusText: statusText 10281 }); 10282 } 10283 10284 function resolvePromiseWithResult(result) { 10285 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 10286 } 10287 10288 function removePendingReq() { 10289 var idx = $http.pendingRequests.indexOf(config); 10290 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 10291 } 10292 } 10293 10294 10295 function buildUrl(url, serializedParams) { 10296 if (serializedParams.length > 0) { 10297 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 10298 } 10299 return url; 10300 } 10301 }]; 10302} 10303 10304function createXhr() { 10305 return new window.XMLHttpRequest(); 10306} 10307 10308/** 10309 * @ngdoc service 10310 * @name $httpBackend 10311 * @requires $window 10312 * @requires $document 10313 * 10314 * @description 10315 * HTTP backend used by the {@link ng.$http service} that delegates to 10316 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 10317 * 10318 * You should never need to use this service directly, instead use the higher-level abstractions: 10319 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 10320 * 10321 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 10322 * $httpBackend} which can be trained with responses. 10323 */ 10324function $HttpBackendProvider() { 10325 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 10326 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 10327 }]; 10328} 10329 10330function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 10331 // TODO(vojta): fix the signature 10332 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 10333 $browser.$$incOutstandingRequestCount(); 10334 url = url || $browser.url(); 10335 10336 if (lowercase(method) == 'jsonp') { 10337 var callbackId = '_' + (callbacks.counter++).toString(36); 10338 callbacks[callbackId] = function(data) { 10339 callbacks[callbackId].data = data; 10340 callbacks[callbackId].called = true; 10341 }; 10342 10343 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 10344 callbackId, function(status, text) {
10345 completeRequest(callback, status, callbacks[callbackId].data, "", text); 10346 callbacks[callbackId] = noop; 10347 }); 10348 } else { 10349 10350 var xhr = createXhr(); 10351 10352 xhr.open(method, url, true); 10353 forEach(headers, function(value, key) { 10354 if (isDefined(value)) { 10355 xhr.setRequestHeader(key, value); 10356 } 10357 }); 10358 10359 xhr.onload = function requestLoaded() { 10360 var statusText = xhr.statusText || ''; 10361 10362 // responseText is the old-school way of retrieving response (supported by IE8 & 9) 10363 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 10364 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 10365 10366 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 10367 var status = xhr.status === 1223 ? 204 : xhr.status; 10368 10369 // fix status code when it is 0 (0 status is undocumented). 10370 // Occurs when accessing file resources or on Android 4.1 stock browser 10371 // while retrieving files from application cache. 10372 if (status === 0) { 10373 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 10374 } 10375 10376 completeRequest(callback, 10377 status, 10378 response, 10379 xhr.getAllResponseHeaders(), 10380 statusText); 10381 }; 10382 10383 var requestError = function() { 10384 // The response is always empty 10385 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 10386 completeRequest(callback, -1, null, null, ''); 10387 }; 10388 10389 xhr.onerror = requestError; 10390 xhr.onabort = requestError; 10391 10392 if (withCredentials) { 10393 xhr.withCredentials = true; 10394 } 10395 10396 if (responseType) { 10397 try { 10398 xhr.responseType = responseType; 10399 } catch (e) { 10400 // WebKit added support for the json responseType value on 09/03/2013 10401 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 10402 // known to throw when setting the value "json" as the response type. Other older 10403 // browsers implementing the responseType 10404 // 10405 // The json response type can be ignored if not supported, because JSON payloads are 10406 // parsed on the client-side regardless. 10407 if (responseType !== 'json') { 10408 throw e; 10409 } 10410 } 10411 } 10412 10413 xhr.send(post); 10414 } 10415 10416 if (timeout > 0) { 10417 var timeoutId = $browserDefer(timeoutRequest, timeout); 10418 } else if (isPromiseLike(timeout)) { 10419 timeout.then(timeoutRequest); 10420 } 10421 10422 10423 function timeoutRequest() { 10424 jsonpDone && jsonpDone(); 10425 xhr && xhr.abort(); 10426 } 10427 10428 function completeRequest(callback, status, response, headersString, statusText) { 10429 // cancel timeout and subsequent timeout promise resolution 10430 if (timeoutId !== undefined) { 10431 $browserDefer.cancel(timeoutId); 10432 } 10433 jsonpDone = xhr = null; 10434 10435 callback(status, response, headersString, statusText); 10436 $browser.$$completeOutstandingRequest(noop); 10437 } 10438 }; 10439 10440 function jsonpReq(url, callbackId, done) { 10441 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 10442 // - fetches local scripts via XHR and evals them 10443 // - adds and immediately removes script elements from the document 10444 var script = rawDocument.createElement('script'), callback = null; 10445 script.type = "text/javascript"; 10446 script.src = url; 10447 script.async = true; 10448 10449 callback = function(event) { 10450 removeEventListenerFn(script, "load", callback); 10451 removeEventListenerFn(script, "error", callback); 10452 rawDocument.body.removeChild(script); 10453 script = null; 10454 var status = -1; 10455 var text = "unknown"; 10456 10457 if (event) { 10458 if (event.type === "load" && !callbacks[callbackId].called) { 10459 event = { type: "error" }; 10460 } 10461 text = event.type; 10462 status = event.type === "error" ? 404 : 200; 10463 } 10464 10465 if (done) { 10466 done(status, text); 10467 } 10468 }; 10469 10470 addEventListenerFn(script, "load", callback); 10471 addEventListenerFn(script, "error", callback); 10472 rawDocument.body.appendChild(script); 10473 return callback; 10474 } 10475} 10476 10477var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 10478$interpolateMinErr.throwNoconcat = function(text) { 10479 throw $interpolateMinErr('noconcat', 10480 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 10481 "interpolations that concatenate multiple expressions when a trusted value is " + 10482 "required. See http://docs.angularjs.org/api/ng.$sce", text); 10483}; 10484 10485$interpolateMinErr.interr = function(text, err) { 10486 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 10487}; 10488 10489/** 10490 * @ngdoc provider 10491 * @name $interpolateProvider 10492 * 10493 * @description 10494 *
10495 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 10496 * 10497 * @example 10498<example module="customInterpolationApp"> 10499<file name="index.html"> 10500<script> 10501 var customInterpolationApp = angular.module('customInterpolationApp', []); 10502 10503 customInterpolationApp.config(function($interpolateProvider) { 10504 $interpolateProvider.startSymbol('//'); 10505 $interpolateProvider.endSymbol('//'); 10506 }); 10507 10508 10509 customInterpolationApp.controller('DemoController', function() { 10510 this.label = "This binding is brought you by // interpolation symbols."; 10511 }); 10512</script> 10513<div ng-app="App" ng-controller="DemoController as demo"> 10514 //demo.label// 10515</div> 10516</file> 10517<file name="protractor.js" type="protractor"> 10518 it('should interpolate binding with custom symbols', function() { 10519 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 10520 }); 10521</file> 10522</example> 10523 */ 10524function $InterpolateProvider() { 10525 var startSymbol = '{{'; 10526 var endSymbol = '}}'; 10527 10528 /** 10529 * @ngdoc method 10530 * @name $interpolateProvider#startSymbol 10531 * @description 10532 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 10533 * 10534 * @param {string=} value new value to set the starting symbol to. 10535 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 10536 */ 10537 this.startSymbol = function(value) { 10538 if (value) { 10539 startSymbol = value; 10540 return this; 10541 } else { 10542 return startSymbol; 10543 } 10544 }; 10545 10546 /** 10547 * @ngdoc method 10548 * @name $interpolateProvider#endSymbol 10549 * @description 10550 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 10551 * 10552 * @param {string=} value new value to set the ending symbol to. 10553 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 10554 */ 10555 this.endSymbol = function(value) { 10556 if (value) { 10557 endSymbol = value; 10558 return this; 10559 } else { 10560 return endSymbol; 10561 } 10562 }; 10563 10564 10565 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 10566 var startSymbolLength = startSymbol.length, 10567 endSymbolLength = endSymbol.length, 10568 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 10569 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 10570 10571 function escape(ch) { 10572 return '\\\\\\' + ch; 10573 } 10574 10575 function unescapeText(text) { 10576 return text.replace(escapedStartRegexp, startSymbol). 10577 replace(escapedEndRegexp, endSymbol); 10578 } 10579 10580 function stringify(value) { 10581 if (value == null) { // null || undefined 10582 return ''; 10583 } 10584 switch (typeof value) { 10585 case 'string': 10586 break; 10587 case 'number': 10588 value = '' + value; 10589 break; 10590 default: 10591 value = toJson(value); 10592 } 10593 10594 return value; 10595 } 10596 10597 /** 10598 * @ngdoc service 10599 * @name $interpolate 10600 * @kind function 10601 * 10602 * @requires $parse 10603 * @requires $sce 10604 * 10605 * @description 10606 * 10607 * Compiles a string with markup into an interpolation function. This service is used by the 10608 * HTML {@link ng.$compile $compile} service for data binding. See 10609 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 10610 * interpolation markup. 10611 * 10612 * 10613 * ```js 10614 * var $interpolate = ...; // injected 10615 * var exp = $interpolate('Hello {{name | uppercase}}!'); 10616 * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 10617 * ``` 10618 * 10619 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 10620 * `true`, the interpolation function will return `undefined` unless all embedded expressions 10621 * evaluate to a value other than `undefined`. 10622 * 10623 * ```js 10624 * var $interpolate = ...; // injected 10625 * var context = {greeting: 'Hello', name: undefined }; 10626 * 10627 * // default "forgiving" mode 10628 * var exp = $interpolate('{{greeting}} {{name}}!'); 10629 * expect(exp(context)).toEqual('Hello !'); 10630 * 10631 * // "allOrNothing" mode 10632 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 10633 * expect(exp(context)).toBeUndefined(); 10634 * context.name = 'Angular'; 10635 * expect(exp(context)).toEqual('Hello Angular!'); 10636 * ``` 10637 * 10638 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 10639 * 10640 * ####Escaped Interpolation 10641 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 10642 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 10643 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 10644 * or binding. 10645 * 10646 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 10647 * degree, while also enabling code examples to work without relying on the 10648 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 10649 * 10650 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 10651 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 10652 * interpolation start/end markers with their escaped counterparts.** 10653 * 10654 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 10655 * output when the $interpolate service processes the text. So, for HTML elements interpolated 10656 * by {@link ng.$compile $compile}
10656, or otherwise interpolated with the `mustHaveExpression` parameter 10657 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 10658 * this is typically useful only when user-data is used in rendering a template from the server, or 10659 * when otherwise untrusted data is used by a directive. 10660 * 10661 * <example> 10662 * <file name="index.html"> 10663 * <div ng-init="username='A user'"> 10664 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 10665 * </p> 10666 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 10667 * application, but fails to accomplish their task, because the server has correctly 10668 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 10669 * characters.</p> 10670 * <p>Instead, the result of the attempted script injection is visible, and can be removed 10671 * from the database by an administrator.</p> 10672 * </div> 10673 * </file> 10674 * </example> 10675 * 10676 * @param {string} text The text with markup to interpolate. 10677 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 10678 * embedded expression in order to return an interpolation function. Strings with no 10679 * embedded expression will return null for the interpolation function. 10680 * @param {string=} trustedContext when provided, the returned function passes the interpolated 10681 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 10682 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 10683 * provides Strict Contextual Escaping for details. 10684 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 10685 * unless all embedded expressions evaluate to a value other than `undefined`. 10686 * @returns {function(context)} an interpolation function which is used to compute the 10687 * interpolated string. The function has these parameters: 10688 * 10689 * - `context`: evaluation context for all expressions embedded in the interpolated text 10690 */ 10691 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 10692 allOrNothing = !!allOrNothing; 10693 var startIndex, 10694 endIndex, 10695 index = 0, 10696 expressions = [], 10697 parseFns = [], 10698 textLength = text.length, 10699 exp, 10700 concat = [], 10701 expressionPositions = []; 10702 10703 while (index < textLength) { 10704 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 10705 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 10706 if (index !== startIndex) { 10707 concat.push(unescapeText(text.substring(index, startIndex))); 10708 } 10709 exp = text.substring(startIndex + startSymbolLength, endIndex); 10710 expressions.push(exp); 10711 parseFns.push($parse(exp, parseStringifyInterceptor)); 10712 index = endIndex + endSymbolLength; 10713 expressionPositions.push(concat.length); 10714 concat.push(''); 10715 } else { 10716 // we did not find an interpolation, so we have to add the remainder to the separators array 10717 if (index !== textLength) { 10718 concat.push(unescapeText(text.substring(index))); 10719 } 10720 break; 10721 } 10722 } 10723 10724 // Concatenating expressions makes it hard to reason about whether some combination of 10725 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 10726 // single expression be used for iframe[src], object[src], etc., we ensure that the value 10727 // that's used is assigned or constructed by some JS code somewhere that is more testable or 10728 // make it obvious that you bound the value to some user controlled value. This helps reduce 10729 // the load when auditing for XSS issues. 10730 if (trustedContext && concat.length > 1) { 10731 $interpolateMinErr.throwNoconcat(text); 10732 } 10733 10734 if (!mustHaveExpression || expressions.length) { 10735 var compute = function(values) { 10736 for (var i = 0, ii = expressions.length; i < ii; i++) { 10737 if (allOrNothing && isUndefined(values[i])) return; 10738 concat[expressionPositions[i]] = values[i]; 10739 } 10740 return concat.join(''); 10741 }; 10742 10743 var getValue = function(value) { 10744 return trustedContext ? 10745 $sce.getTrusted(trustedContext, value) : 10746 $sce.valueOf(value); 10747 }; 10748 10749 return extend(function interpolationFn(context) { 10750 var i = 0; 10751 var ii = expressions.length; 10752 var values = new Array(ii); 10753 10754 try { 10755 for (; i < ii; i++) { 10756 values[i] = parseFns[i](context); 10757 } 10758 10759 return compute(values); 10760 } catch (err) { 10761 $exceptionHandler($interpolateMinErr.interr(text, err)); 10762 } 10763 10764 }, { 10765 // all of these properties are undocumented for now 10766 exp: text, //just for compatibility with regular watchers created via $watch 10767 expressions: expressions, 10768 $$watchDelegate: function(scope, listener) { 10769 var lastValue; 10770 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 10771 var currValue = compute(values); 10772 if (isFunction(listener)) { 10773 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 10774 }
10775 lastValue = currValue; 10776 }); 10777 } 10778 }); 10779 } 10780 10781 function parseStringifyInterceptor(value) { 10782 try { 10783 value = getValue(value); 10784 return allOrNothing && !isDefined(value) ? value : stringify(value); 10785 } catch (err) { 10786 $exceptionHandler($interpolateMinErr.interr(text, err)); 10787 } 10788 } 10789 } 10790 10791 10792 /** 10793 * @ngdoc method 10794 * @name $interpolate#startSymbol 10795 * @description 10796 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 10797 * 10798 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 10799 * the symbol. 10800 * 10801 * @returns {string} start symbol. 10802 */ 10803 $interpolate.startSymbol = function() { 10804 return startSymbol; 10805 }; 10806 10807 10808 /** 10809 * @ngdoc method 10810 * @name $interpolate#endSymbol 10811 * @description 10812 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 10813 * 10814 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 10815 * the symbol. 10816 * 10817 * @returns {string} end symbol. 10818 */ 10819 $interpolate.endSymbol = function() { 10820 return endSymbol; 10821 }; 10822 10823 return $interpolate; 10824 }]; 10825} 10826 10827function $IntervalProvider() { 10828 this.$get = ['$rootScope', '$window', '$q', '$$q', 10829 function($rootScope, $window, $q, $$q) { 10830 var intervals = {}; 10831 10832 10833 /** 10834 * @ngdoc service 10835 * @name $interval 10836 * 10837 * @description 10838 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 10839 * milliseconds. 10840 * 10841 * The return value of registering an interval function is a promise. This promise will be 10842 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 10843 * run indefinitely if `count` is not defined. The value of the notification will be the 10844 * number of iterations that have run. 10845 * To cancel an interval, call `$interval.cancel(promise)`. 10846 * 10847 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 10848 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 10849 * time. 10850 * 10851 * <div class="alert alert-warning"> 10852 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 10853 * with them. In particular they are not automatically destroyed when a controller's scope or a 10854 * directive's element are destroyed. 10855 * You should take this into consideration and make sure to always cancel the interval at the 10856 * appropriate moment. See the example below for more details on how and when to do this. 10857 * </div> 10858 * 10859 * @param {function()} fn A function that should be called repeatedly. 10860 * @param {number} delay Number of milliseconds between each function call. 10861 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 10862 * indefinitely. 10863 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 10864 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 10865 * @param {...*=} Pass additional parameters to the executed function. 10866 * @returns {promise} A promise which will be notified on each iteration. 10867 * 10868 * @example 10869 * <example module="intervalExample"> 10870 * <file name="index.html"> 10871 * <script> 10872 * angular.module('intervalExample', []) 10873 * .controller('ExampleController', ['$scope', '$interval', 10874 * function($scope, $interval) { 10875 * $scope.format = 'M/d/yy h:mm:ss a'; 10876 * $scope.blood_1 = 100; 10877 * $scope.blood_2 = 120; 10878 * 10879 * var stop; 10880 * $scope.fight = function() { 10881 * // Don't start a new fight if we are already fighting 10882 * if ( angular.isDefined(stop) ) return; 10883 * 10884 * stop = $interval(function() { 10885 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 10886 * $scope.blood_1 = $scope.blood_1 - 3; 10887 * $scope.blood_2 = $scope.blood_2 - 4; 10888 * } else { 10889 * $scope.stopFight(); 10890 * } 10891 * }, 100); 10892 * }; 10893 * 10894 * $scope.stopFight = function() { 10895 * if (angular.isDefined(stop)) { 10896 * $interval.cancel(stop); 10897 * stop = undefined; 10898 * } 10899 * }; 10900 * 10901 * $scope.resetFight = function() { 10902 * $scope.blood_1 = 100; 10903 * $scope.blood_2 = 120; 10904 * }; 10905 * 10906 * $scope.$on('$destroy', function() { 10907 * // Make sure that the interval is destroyed too 10908 * $scope.stopFight(); 10909 * }); 10910 * }]) 10911 * // Register the 'myCurrentTime' directive factory method. 10912 * // We inject $interval and dateFilter service since the factory method is DI. 10913 * .directive('myCurrentTime', ['$interval', 'dateFilter', 10914 * function($interval, dateFilter) { 10915 * // return the directive link function. (compile function not needed) 10916 * return function(scope, element, attrs) { 10917 * var format, // date format 10918 * stopTime; // so that we can cancel the time updates 10919 * 10920 * // used to update the UI 10921 * function updateTime() { 10922 * element.text(dateFilter(new Date(), format)); 10923 * } 10924 * 10925 * // watch the expression, and update the UI on change. 10926 * scope.$watch(attrs.myCurrentTime, function(value) { 10927 * format = value; 10928 * updateTime(); 10929 * }); 10930 * 10931 * stopTime = $interval(updateTime, 1000); 10932 * 10933 * // listen on DOM destroy (removal) event, and cancel the next UI update 10934 * // to prevent updating time after the DOM element was removed. 10935 * element.on('$destroy', function() { 10936 * $interval.cancel(stopTime); 10937 * }); 10938 * } 10939 * }]); 10940 * </script> 10941 * 10942 * <div>
10943 * <div ng-controller="ExampleController"> 10944 * <label>Date format: <input ng-model="format"></label> <hr/> 10945 * Current time is: <span my-current-time="format"></span> 10946 * <hr/> 10947 * Blood 1 : <font color='red'>{{blood_1}}</font> 10948 * Blood 2 : <font color='red'>{{blood_2}}</font> 10949 * <button type="button" data-ng-click="fight()">Fight</button> 10950 * <button type="button" data-ng-click="stopFight()">StopFight</button> 10951 * <button type="button" data-ng-click="resetFight()">resetFight</button> 10952 * </div> 10953 * </div> 10954 * 10955 * </file> 10956 * </example> 10957 */ 10958 function interval(fn, delay, count, invokeApply) { 10959 var hasParams = arguments.length > 4, 10960 args = hasParams ? sliceArgs(arguments, 4) : [], 10961 setInterval = $window.setInterval, 10962 clearInterval = $window.clearInterval, 10963 iteration = 0, 10964 skipApply = (isDefined(invokeApply) && !invokeApply), 10965 deferred = (skipApply ? $$q : $q).defer(), 10966 promise = deferred.promise; 10967 10968 count = isDefined(count) ? count : 0; 10969 10970 promise.then(null, null, (!hasParams) ? fn : function() { 10971 fn.apply(null, args); 10972 }); 10973 10974 promise.$$intervalId = setInterval(function tick() { 10975 deferred.notify(iteration++); 10976 10977 if (count > 0 && iteration >= count) { 10978 deferred.resolve(iteration); 10979 clearInterval(promise.$$intervalId); 10980 delete intervals[promise.$$intervalId]; 10981 } 10982 10983 if (!skipApply) $rootScope.$apply(); 10984 10985 }, delay); 10986 10987 intervals[promise.$$intervalId] = deferred; 10988 10989 return promise; 10990 } 10991 10992 10993 /** 10994 * @ngdoc method 10995 * @name $interval#cancel 10996 * 10997 * @description 10998 * Cancels a task associated with the `promise`. 10999 * 11000 * @param {promise} promise returned by the `$interval` function. 11001 * @returns {boolean} Returns `true` if the task was successfully canceled. 11002 */ 11003 interval.cancel = function(promise) { 11004 if (promise && promise.$$intervalId in intervals) { 11005 intervals[promise.$$intervalId].reject('canceled'); 11006 $window.clearInterval(promise.$$intervalId); 11007 delete intervals[promise.$$intervalId]; 11008 return true; 11009 } 11010 return false; 11011 }; 11012 11013 return interval; 11014 }]; 11015} 11016 11017/** 11018 * @ngdoc service 11019 * @name $locale 11020 * 11021 * @description 11022 * $locale service provides localization rules for various Angular components. As of right now the 11023 * only public api is: 11024 * 11025 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 11026 */ 11027function $LocaleProvider() { 11028 this.$get = function() { 11029 return { 11030 id: 'en-us', 11031 11032 NUMBER_FORMATS: { 11033 DECIMAL_SEP: '.', 11034 GROUP_SEP: ',', 11035 PATTERNS: [ 11036 { // Decimal Pattern 11037 minInt: 1, 11038 minFrac: 0, 11039 maxFrac: 3, 11040 posPre: '', 11041 posSuf: '', 11042 negPre: '-', 11043 negSuf: '', 11044 gSize: 3, 11045 lgSize: 3 11046 },{ //Currency Pattern 11047 minInt: 1, 11048 minFrac: 2, 11049 maxFrac: 2, 11050 posPre: '\u00A4', 11051 posSuf: '', 11052 negPre: '(\u00A4', 11053 negSuf: ')', 11054 gSize: 3, 11055 lgSize: 3 11056 } 11057 ], 11058 CURRENCY_SYM: '$' 11059 }, 11060 11061 DATETIME_FORMATS: { 11062 MONTH: 11063 'January,February,March,April,May,June,July,August,September,October,November,December' 11064 .split(','), 11065 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 11066 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 11067 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 11068 AMPMS: ['AM','PM'], 11069 medium: 'MMM d, y h:mm:ss a', 11070 'short': 'M/d/yy h:mm a', 11071 fullDate: 'EEEE, MMMM d, y', 11072 longDate: 'MMMM d, y', 11073 mediumDate: 'MMM d, y', 11074 shortDate: 'M/d/yy', 11075 mediumTime: 'h:mm:ss a', 11076 shortTime: 'h:mm a', 11077 ERANAMES: [ 11078 "Before Christ", 11079 "Anno Domini" 11080 ], 11081 ERAS: [ 11082 "BC", 11083 "AD" 11084 ] 11085 }, 11086 11087 pluralCat: function(num) { 11088 if (num === 1) { 11089 return 'one'; 11090 } 11091 return 'other'; 11092 } 11093 }; 11094 }; 11095} 11096 11097var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 11098 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 11099var $locationMinErr = minErr('$location'); 11100 11101 11102/** 11103 * Encode path using encodeUriSegment, ignoring forward slashes 11104 * 11105 * @param {string} path Path to encode 11106 * @returns {string} 11107 */ 11108function encodePath(path) { 11109 var segments = path.split('/'), 11110 i = segments.length; 11111 11112 while (i--) { 11113 segments[i] = encodeUriSegment(segments[i]); 11114 } 11115 11116 return segments.join('/'); 11117} 11118 11119function parseAbsoluteUrl(absoluteUrl, locationObj) {
11120 var parsedUrl = urlResolve(absoluteUrl); 11121 11122 locationObj.$$protocol = parsedUrl.protocol; 11123 locationObj.$$host = parsedUrl.hostname; 11124 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 11125} 11126 11127 11128function parseAppUrl(relativeUrl, locationObj) { 11129 var prefixed = (relativeUrl.charAt(0) !== '/'); 11130 if (prefixed) { 11131 relativeUrl = '/' + relativeUrl; 11132 } 11133 var match = urlResolve(relativeUrl); 11134 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 11135 match.pathname.substring(1) : match.pathname); 11136 locationObj.$$search = parseKeyValue(match.search); 11137 locationObj.$$hash = decodeURIComponent(match.hash); 11138 11139 // make sure path starts with '/'; 11140 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 11141 locationObj.$$path = '/' + locationObj.$$path; 11142 } 11143} 11144 11145 11146/** 11147 * 11148 * @param {string} begin 11149 * @param {string} whole 11150 * @returns {string} returns text from whole after begin or undefined if it does not begin with 11151 * expected string. 11152 */ 11153function beginsWith(begin, whole) { 11154 if (whole.indexOf(begin) === 0) { 11155 return whole.substr(begin.length); 11156 } 11157} 11158 11159 11160function stripHash(url) { 11161 var index = url.indexOf('#'); 11162 return index == -1 ? url : url.substr(0, index); 11163} 11164 11165function trimEmptyHash(url) { 11166 return url.replace(/(#.+)|#$/, '$1'); 11167} 11168 11169 11170function stripFile(url) { 11171 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 11172} 11173 11174/* return the server only (scheme://host:port) */ 11175function serverBase(url) { 11176 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 11177} 11178 11179 11180/** 11181 * LocationHtml5Url represents an url 11182 * This object is exposed as $location service when HTML5 mode is enabled and supported 11183 * 11184 * @constructor 11185 * @param {string} appBase application base URL 11186 * @param {string} basePrefix url path prefix 11187 */ 11188function LocationHtml5Url(appBase, basePrefix) { 11189 this.$$html5 = true; 11190 basePrefix = basePrefix || ''; 11191 var appBaseNoFile = stripFile(appBase); 11192 parseAbsoluteUrl(appBase, this); 11193 11194 11195 /** 11196 * Parse given html5 (regular) url string into properties 11197 * @param {string} url HTML5 url 11198 * @private 11199 */ 11200 this.$$parse = function(url) { 11201 var pathUrl = beginsWith(appBaseNoFile, url); 11202 if (!isString(pathUrl)) { 11203 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 11204 appBaseNoFile); 11205 } 11206 11207 parseAppUrl(pathUrl, this); 11208 11209 if (!this.$$path) { 11210 this.$$path = '/'; 11211 } 11212 11213 this.$$compose(); 11214 }; 11215 11216 /** 11217 * Compose url and update `absUrl` property 11218 * @private 11219 */ 11220 this.$$compose = function() { 11221 var search = toKeyValue(this.$$search), 11222 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11223 11224 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11225 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 11226 }; 11227 11228 this.$$parseLinkUrl = function(url, relHref) { 11229 if (relHref && relHref[0] === '#') { 11230 // special case for links to hash fragments: 11231 // keep the old url and only replace the hash fragment 11232 this.hash(relHref.slice(1)); 11233 return true; 11234 } 11235 var appUrl, prevAppUrl; 11236 var rewrittenUrl; 11237 11238 if ((appUrl = beginsWith(appBase, url)) !== undefined) { 11239 prevAppUrl = appUrl; 11240 if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) { 11241 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 11242 } else { 11243 rewrittenUrl = appBase + prevAppUrl; 11244 } 11245 } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) { 11246 rewrittenUrl = appBaseNoFile + appUrl; 11247 } else if (appBaseNoFile == url + '/') { 11248 rewrittenUrl = appBaseNoFile; 11249 } 11250 if (rewrittenUrl) { 11251 this.$$parse(rewrittenUrl); 11252 } 11253 return !!rewrittenUrl; 11254 }; 11255} 11256 11257 11258/** 11259 * LocationHashbangUrl represents url 11260 * This object is exposed as $location service when developer doesn't opt into html5 mode. 11261 * It also serves as the base class for html5 mode fallback on legacy browsers. 11262 * 11263 * @constructor 11264 * @param {string} appBase application base URL 11265 * @param {string} hashPrefix hashbang prefix 11266 */ 11267function LocationHashbangUrl(appBase, hashPrefix) { 11268 var appBaseNoFile = stripFile(appBase); 11269 11270 parseAbsoluteUrl(appBase, this); 11271 11272 11273 /** 11274 * Parse given hashbang url into properties 11275 * @param {string} url Hashbang url 11276 * @private 11277 */ 11278 this.$$parse = function(url) { 11279 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 11280 var withoutHashUrl; 11281 11282 if (withoutBaseUrl.charAt(0) === '#') { 11283 11284 // The rest of the url starts with a hash so we have 11285 // got either a hashbang path or a plain hash fragment 11286 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 11287 if (isUndefined(withoutHashUrl)) { 11288 // There was no hashbang prefix so we just have a hash fragment 11289 withoutHashUrl = withoutBaseUrl; 11290 } 11291 11292 } else { 11293 // There was no hashbang path nor hash fragment: 11294 // If we are in HTML5 mode we use what is left as the path; 11295 // Otherwise we ignore what is left 11296 withoutHashUrl = this.$$html5 ? withoutBaseUrl : ''; 11297 } 11298 11299 parseAppUrl(withoutHashUrl, this); 11300 11301 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 11302 11303 this.$$compose(); 11304 11305 /* 11306 * In Windows, on an anchor node on documents loaded from 11307 * the filesystem, the browser will return a pathname
11308 * prefixed with the drive name ('/C:/path') when a 11309 * pathname without a drive is set: 11310 * * a.setAttribute('href', '/foo') 11311 * * a.pathname === '/C:/foo' //true 11312 * 11313 * Inside of Angular, we're always using pathnames that 11314 * do not include drive names for routing. 11315 */ 11316 function removeWindowsDriveName(path, url, base) { 11317 /* 11318 Matches paths for file protocol on windows, 11319 such as /C:/foo/bar, and captures only /foo/bar. 11320 */ 11321 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 11322 11323 var firstPathSegmentMatch; 11324 11325 //Get the relative path from the input URL. 11326 if (url.indexOf(base) === 0) { 11327 url = url.replace(base, ''); 11328 } 11329 11330 // The input URL intentionally contains a first path segment that ends with a colon. 11331 if (windowsFilePathExp.exec(url)) { 11332 return path; 11333 } 11334 11335 firstPathSegmentMatch = windowsFilePathExp.exec(path); 11336 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 11337 } 11338 }; 11339 11340 /** 11341 * Compose hashbang url and update `absUrl` property 11342 * @private 11343 */ 11344 this.$$compose = function() { 11345 var search = toKeyValue(this.$$search), 11346 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11347 11348 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11349 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 11350 }; 11351 11352 this.$$parseLinkUrl = function(url, relHref) { 11353 if (stripHash(appBase) == stripHash(url)) { 11354 this.$$parse(url); 11355 return true; 11356 } 11357 return false; 11358 }; 11359} 11360 11361 11362/** 11363 * LocationHashbangUrl represents url 11364 * This object is exposed as $location service when html5 history api is enabled but the browser 11365 * does not support it. 11366 * 11367 * @constructor 11368 * @param {string} appBase application base URL 11369 * @param {string} hashPrefix hashbang prefix 11370 */ 11371function LocationHashbangInHtml5Url(appBase, hashPrefix) { 11372 this.$$html5 = true; 11373 LocationHashbangUrl.apply(this, arguments); 11374 11375 var appBaseNoFile = stripFile(appBase); 11376 11377 this.$$parseLinkUrl = function(url, relHref) { 11378 if (relHref && relHref[0] === '#') { 11379 // special case for links to hash fragments: 11380 // keep the old url and only replace the hash fragment 11381 this.hash(relHref.slice(1)); 11382 return true; 11383 } 11384 11385 var rewrittenUrl; 11386 var appUrl; 11387 11388 if (appBase == stripHash(url)) { 11389 rewrittenUrl = url; 11390 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 11391 rewrittenUrl = appBase + hashPrefix + appUrl; 11392 } else if (appBaseNoFile === url + '/') { 11393 rewrittenUrl = appBaseNoFile; 11394 } 11395 if (rewrittenUrl) { 11396 this.$$parse(rewrittenUrl); 11397 } 11398 return !!rewrittenUrl; 11399 }; 11400 11401 this.$$compose = function() { 11402 var search = toKeyValue(this.$$search), 11403 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11404 11405 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11406 // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' 11407 this.$$absUrl = appBase + hashPrefix + this.$$url; 11408 }; 11409 11410} 11411 11412 11413var locationPrototype = { 11414 11415 /** 11416 * Are we in html5 mode? 11417 * @private 11418 */ 11419 $$html5: false, 11420 11421 /** 11422 * Has any change been replacing? 11423 * @private 11424 */ 11425 $$replace: false, 11426 11427 /** 11428 * @ngdoc method 11429 * @name $location#absUrl 11430 * 11431 * @description 11432 * This method is getter only. 11433 * 11434 * Return full url representation with all segments encoded according to rules specified in 11435 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 11436 * 11437 * 11438 * ```js 11439 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11440 * var absUrl = $location.absUrl(); 11441 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 11442 * ``` 11443 * 11444 * @return {string} full url 11445 */ 11446 absUrl: locationGetter('$$absUrl'), 11447 11448 /** 11449 * @ngdoc method 11450 * @name $location#url 11451 * 11452 * @description 11453 * This method is getter / setter. 11454 * 11455 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 11456 * 11457 * Change path, search and hash, when called with parameter and return `$location`. 11458 * 11459 * 11460 * ```js
11461 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11462 * var url = $location.url(); 11463 * // => "/some/path?foo=bar&baz=xoxo" 11464 * ``` 11465 * 11466 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 11467 * @return {string} url 11468 */ 11469 url: function(url) { 11470 if (isUndefined(url)) { 11471 return this.$$url; 11472 } 11473 11474 var match = PATH_MATCH.exec(url); 11475 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 11476 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 11477 this.hash(match[5] || ''); 11478 11479 return this; 11480 }, 11481 11482 /** 11483 * @ngdoc method 11484 * @name $location#protocol 11485 * 11486 * @description 11487 * This method is getter only. 11488 * 11489 * Return protocol of current url. 11490 * 11491 * 11492 * ```js 11493 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11494 * var protocol = $location.protocol(); 11495 * // => "http" 11496 * ``` 11497 * 11498 * @return {string} protocol of current url 11499 */ 11500 protocol: locationGetter('$$protocol'), 11501 11502 /** 11503 * @ngdoc method 11504 * @name $location#host 11505 * 11506 * @description 11507 * This method is getter only. 11508 * 11509 * Return host of current url. 11510 * 11511 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 11512 * 11513 * 11514 * ```js 11515 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11516 * var host = $location.host(); 11517 * // => "example.com" 11518 * 11519 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 11520 * host = $location.host(); 11521 * // => "example.com" 11522 * host = location.host; 11523 * // => "example.com:8080" 11524 * ``` 11525 * 11526 * @return {string} host of current url. 11527 */ 11528 host: locationGetter('$$host'), 11529 11530 /** 11531 * @ngdoc method 11532 * @name $location#port 11533 * 11534 * @description 11535 * This method is getter only. 11536 * 11537 * Return port of current url. 11538 * 11539 * 11540 * ```js 11541 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11542 * var port = $location.port(); 11543 * // => 80 11544 * ``` 11545 * 11546 * @return {Number} port 11547 */ 11548 port: locationGetter('$$port'), 11549 11550 /** 11551 * @ngdoc method 11552 * @name $location#path 11553 * 11554 * @description 11555 * This method is getter / setter. 11556 * 11557 * Return path of current url when called without any parameter. 11558 * 11559 * Change path when called with parameter and return `$location`. 11560 * 11561 * Note: Path should always begin with forward slash (/), this method will add the forward slash 11562 * if it is missing. 11563 * 11564 * 11565 * ```js 11566 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11567 * var path = $location.path(); 11568 * // => "/some/path" 11569 * ``` 11570 * 11571 * @param {(string|number)=} path New path 11572 * @return {string} path 11573 */ 11574 path: locationGetterSetter('$$path', function(path) { 11575 path = path !== null ? path.toString() : ''; 11576 return path.charAt(0) == '/' ? path : '/' + path; 11577 }), 11578 11579 /** 11580 * @ngdoc method 11581 * @name $location#search 11582 * 11583 * @description 11584 * This method is getter / setter. 11585 * 11586 * Return search part (as object) of current url when called without any parameter. 11587 * 11588 * Change search part when called with parameter and return `$location`. 11589 * 11590 * 11591 * ```js 11592 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11593 * var searchObject = $location.search(); 11594 * // => {foo: 'bar', baz: 'xoxo'} 11595 * 11596 * // set foo to 'yipee' 11597 * $location.search('foo', 'yipee'); 11598 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 11599 * ``` 11600 * 11601 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 11602 * hash object. 11603 * 11604 * When called with a single argument the method acts as a setter, setting the `search` component 11605 * of `$location` to the specified value. 11606 * 11607 * If the argument is a hash object containing an array of values, these values will be encoded 11608 * as duplicate search parameters in the url. 11609 * 11610 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 11611 * will override only a single search property. 11612 * 11613 * If `paramValue` is an array, it will override the property of the `search` component of 11614 * `$location` specified via the first argument. 11615 * 11616 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 11617 * 11618 * If `paramValue` is `true`, the property specified via the first argument will be added with
vendor: 18,948 bytes, lines 11618-12207
11618no 11619 * value nor trailing equal sign. 11620 * 11621 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 11622 * one or more arguments returns `$location` object itself. 11623 */ 11624 search: function(search, paramValue) { 11625 switch (arguments.length) { 11626 case 0: 11627 return this.$$search; 11628 case 1: 11629 if (isString(search) || isNumber(search)) { 11630 search = search.toString(); 11631 this.$$search = parseKeyValue(search); 11632 } else if (isObject(search)) { 11633 search = copy(search, {}); 11634 // remove object undefined or null properties 11635 forEach(search, function(value, key) { 11636 if (value == null) delete search[key]; 11637 }); 11638 11639 this.$$search = search; 11640 } else { 11641 throw $locationMinErr('isrcharg', 11642 'The first argument of the `$location#search()` call must be a string or an object.'); 11643 } 11644 break; 11645 default: 11646 if (isUndefined(paramValue) || paramValue === null) { 11647 delete this.$$search[search]; 11648 } else { 11649 this.$$search[search] = paramValue; 11650 } 11651 } 11652 11653 this.$$compose(); 11654 return this; 11655 }, 11656 11657 /** 11658 * @ngdoc method 11659 * @name $location#hash 11660 * 11661 * @description 11662 * This method is getter / setter. 11663 * 11664 * Return hash fragment when called without any parameter. 11665 * 11666 * Change hash fragment when called with parameter and return `$location`. 11667 * 11668 * 11669 * ```js 11670 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 11671 * var hash = $location.hash(); 11672 * // => "hashValue" 11673 * ``` 11674 * 11675 * @param {(string|number)=} hash New hash fragment 11676 * @return {string} hash 11677 */ 11678 hash: locationGetterSetter('$$hash', function(hash) { 11679 return hash !== null ? hash.toString() : ''; 11680 }), 11681 11682 /** 11683 * @ngdoc method 11684 * @name $location#replace 11685 * 11686 * @description 11687 * If called, all changes to $location during current `$digest` will be replacing current history 11688 * record, instead of adding new one. 11689 */ 11690 replace: function() { 11691 this.$$replace = true; 11692 return this; 11693 } 11694}; 11695 11696forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 11697 Location.prototype = Object.create(locationPrototype); 11698 11699 /** 11700 * @ngdoc method 11701 * @name $location#state 11702 * 11703 * @description 11704 * This method is getter / setter. 11705 * 11706 * Return the history state object when called without any parameter. 11707 * 11708 * Change the history state object when called with one parameter and return `$location`. 11709 * The state object is later passed to `pushState` or `replaceState`. 11710 * 11711 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 11712 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 11713 * older browsers (like IE9 or Android < 4.0), don't use this method. 11714 * 11715 * @param {object=} state State object for pushState or replaceState 11716 * @return {object} state 11717 */ 11718 Location.prototype.state = function(state) { 11719 if (!arguments.length) { 11720 return this.$$state; 11721 } 11722 11723 if (Location !== LocationHtml5Url || !this.$$html5) { 11724 throw $locationMinErr('nostate', 'History API state support is available only ' + 11725 'in HTML5 mode and only in browsers supporting HTML5 History API'); 11726 } 11727 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 11728 // but we're changing the $$state reference to $browser.state() during the $digest 11729 // so the modification window is narrow. 11730 this.$$state = isUndefined(state) ? null : state; 11731 11732 return this; 11733 }; 11734}); 11735 11736 11737function locationGetter(property) { 11738 return function() { 11739 return this[property]; 11740 }; 11741} 11742 11743 11744function locationGetterSetter(property, preprocess) { 11745 return function(value) { 11746 if (isUndefined(value)) { 11747 return this[property]; 11748 } 11749 11750 this[property] = preprocess(value); 11751 this.$$compose(); 11752 11753 return this; 11754 }; 11755} 11756 11757 11758/** 11759 * @ngdoc service 11760 * @name $location 11761 * 11762 * @requires $rootElement 11763 * 11764 * @description 11765 * The $location service parses the URL in the browser address bar (based on the 11766 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 11767 * available to your application. Changes to the URL in the address bar are reflected into 11768 * $location service and changes to $location are reflected into the browser address bar. 11769 * 11770 * **The $location service:** 11771 * 11772 * - Exposes the current URL in the browser address bar, so you can 11773 * - Watch and observe the URL. 11774 * - Change the URL. 11775 * - Synchronizes the URL with the browser when the user 11776 * - Changes the address bar. 11777 * - Clicks the back or forward button (or clicks a History link). 11778 * - Clicks on a link. 11779 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 11780 * 11781 * For more information see {@link guide/$location Developer Guide: Using $location} 11782 */ 11783 11784/** 11785 * @ngdoc provider 11786 * @name $locationProvider 11787 * @description 11788 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 11789 */ 11790function $LocationProvider() { 11791 var hashPrefix = '', 11792 html5Mode = { 11793 enabled: false, 11794 requireBase: true, 11795 rewriteLinks: true 11796 }; 11797 11798 /** 11799 * @ngdoc method 11800 * @name $locationProvider#hashPrefix 11801 * @description 11802 * @param {string=} prefix Prefix for hash part (containing path and search) 11803 * @returns {*} current value if used as getter or itself (chaining) if used as setter 11804 */ 11805 this.hashPrefix = function(prefix) { 11806 if (isDefined(prefix)) { 11807 hashPrefix = prefix; 11808 return this; 11809 } else { 11810 return hashPrefix; 11811 } 11812 }; 11813 11814 /** 11815 * @ngdoc method 11816 * @name $locationProvider#html5Mode 11817 * @description 11818 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 11819 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 11820 * properties: 11821 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 11822 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 11823 * support `pushState`. 11824 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 11825 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 11826 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 11827 * See the {@link guide/$location $location guide for more information} 11828 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 11829 * enables/disables url rewriting for relative links. 11830 * 11831 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 11832 */ 11833 this.html5Mode = function(mode) { 11834 if (isBoolean(mode)) { 11835 html5Mode.enabled = mode; 11836 return this; 11837 } else if (isObject(mode)) { 11838 11839 if (isBoolean(mode.enabled)) { 11840 html5Mode.enabled = mode.enabled; 11841 } 11842 11843 if (isBoolean(mode.requireBase)) { 11844 html5Mode.requireBase = mode.requireBase; 11845 } 11846 11847 if (isBoolean(mode.rewriteLinks)) { 11848 html5Mode.rewriteLinks = mode.rewriteLinks; 11849 } 11850 11851 return this; 11852 } else { 11853 return html5Mode; 11854 } 11855 }; 11856 11857 /** 11858 * @ngdoc event 11859 * @name $location#$locationChangeStart 11860 * @eventType broadcast on root scope 11861 * @description 11862 * Broadcasted before a URL will change. 11863 * 11864 * This change can be prevented by calling 11865 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 11866 * details about event object. Upon successful change 11867 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 11868 * 11869 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 11870 * the browser supports the HTML5 History API. 11871 * 11872 * @param {Object} angularEvent Synthetic event object. 11873 * @param {string} newUrl New URL 11874 * @param {string=} oldUrl URL that was before it was changed. 11875 * @param {string=} newState New history state object 11876 * @param {string=} oldState History state object that was before it was changed. 11877 */ 11878 11879 /** 11880 * @ngdoc event 11881 * @name $location#$locationChangeSuccess 11882 * @eventType broadcast on root scope 11883 * @description 11884 * Broadcasted after a URL was changed. 11885 * 11886 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 11887 * the browser supports the HTML5 History API. 11888 * 11889 * @param {Object} angularEvent Synthetic event object. 11890 * @param {string} newUrl New URL 11891 * @param {string=} oldUrl URL that was before it was changed. 11892 * @param {string=} newState New history state object 11893 * @param {string=} oldState History state object that was before it was changed. 11894 */ 11895 11896 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 11897 function($rootScope, $browser, $sniffer, $rootElement, $window) { 11898 var $location, 11899 LocationMode, 11900 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 11901 initialUrl = $browser.url(), 11902 appBase; 11903 11904 if (html5Mode.enabled) { 11905 if (!baseHref && html5Mode.requireBase) { 11906 throw $locationMinErr('nobase', 11907 "$location in HTML5 mode requires a <base> tag to be present!"); 11908 } 11909 appBase = serverBase(initialUrl) + (baseHref || '/'); 11910 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 11911 } else { 11912 appBase = stripHash(initialUrl); 11913 LocationMode = LocationHashbangUrl; 11914 } 11915 $location = new LocationMode(appBase, '#' + hashPrefix); 11916 $location.$$parseLinkUrl(initialUrl, initialUrl); 11917 11918 $location.$$state = $browser.state(); 11919 11920 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 11921 11922 function setBrowserUrlWithFallback(url, replace, state) { 11923 var oldUrl = $location.url(); 11924 var oldState = $location.$$state; 11925 try { 11926 $browser.url(url, replace, state); 11927 11928 // Make sure $location.state() returns referentially identical (not just deeply equal) 11929 // state object; this makes possible quick checking if the state changed in the digest 11930 // loop. Checking deep equality would be too expensive. 11931 $location.$$state = $browser.state(); 11932 } catch (e) { 11933 // Restore old values if pushState fails 11934 $location.url(oldUrl); 11935 $location.$$state = oldState; 11936 11937 throw e; 11938 } 11939 } 11940 11941 $rootElement.on('click', function(event) { 11942 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 11943 // currently we open nice url link and redirect then 11944 11945 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 11946 11947 var elm = jqLite(event.target); 11948 11949 // traverse the DOM up to find first A tag 11950 while (nodeName_(elm[0]) !== 'a') { 11951 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 11952 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 11953 } 11954 11955 var absHref = elm.prop('href'); 11956 // get the actual href attribute - see 11957 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 11958 var relHref = elm.attr('href') || elm.attr('xlink:href'); 11959 11960 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 11961 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 11962 // an animation. 11963 absHref = urlResolve(absHref.animVal).href; 11964 } 11965 11966 // Ignore when url is started with javascript: or mailto: 11967 if (IGNORE_URI_REGEXP.test(absHref)) return; 11968 11969 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 11970 if ($location.$$parseLinkUrl(absHref, relHref)) { 11971 // We do a preventDefault for all urls that are part of the angular application, 11972 // in html5mode and also without, so that we are able to abort navigation without 11973 // getting double entries in the location history. 11974 event.preventDefault(); 11975 // update location manually 11976 if ($location.absUrl() != $browser.url()) { 11977 $rootScope.$apply(); 11978 // hack to work around FF6 bug 684208 when scenario runner clicks on links 11979 $window.angular['ff-684208-preventDefault'] = true; 11980 } 11981 } 11982 } 11983 }); 11984 11985 11986 // rewrite hashbang url <> html5 url 11987 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 11988 $browser.url($location.absUrl(), true); 11989 } 11990 11991 var initializing = true; 11992 11993 // update $location when $browser url changes 11994 $browser.onUrlChange(function(newUrl, newState) { 11995 $rootScope.$evalAsync(function() { 11996 var oldUrl = $location.absUrl(); 11997 var oldState = $location.$$state; 11998 var defaultPrevented; 11999 12000 $location.$$parse(newUrl); 12001 $location.$$state = newState; 12002 12003 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12004 newState, oldState).defaultPrevented; 12005 12006 // if the location was changed by a `$locationChangeStart` handler then stop 12007 // processing this location change 12008 if ($location.absUrl() !== newUrl) return; 12009 12010 if (defaultPrevented) { 12011 $location.$$parse(oldUrl); 12012 $location.$$state = oldState; 12013 setBrowserUrlWithFallback(oldUrl, false, oldState); 12014 } else { 12015 initializing = false; 12016 afterLocationChange(oldUrl, oldState); 12017 } 12018 }); 12019 if (!$rootScope.$$phase) $rootScope.$digest(); 12020 }); 12021 12022 // update browser 12023 $rootScope.$watch(function $locationWatch() { 12024 var oldUrl = trimEmptyHash($browser.url()); 12025 var newUrl = trimEmptyHash($location.absUrl()); 12026 var oldState = $browser.state(); 12027 var currentReplace = $location.$$replace; 12028 var urlOrStateChanged = oldUrl !== newUrl || 12029 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 12030 12031 if (initializing || urlOrStateChanged) { 12032 initializing = false; 12033 12034 $rootScope.$evalAsync(function() { 12035 var newUrl = $location.absUrl(); 12036 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12037 $location.$$state, oldState).defaultPrevented; 12038 12039 // if the location was changed by a `$locationChangeStart` handler then stop 12040 // processing this location change 12041 if ($location.absUrl() !== newUrl) return; 12042 12043 if (defaultPrevented) { 12044 $location.$$parse(oldUrl); 12045 $location.$$state = oldState; 12046 } else { 12047 if (urlOrStateChanged) { 12048 setBrowserUrlWithFallback(newUrl, currentReplace, 12049 oldState === $location.$$state ? null : $location.$$state); 12050 } 12051 afterLocationChange(oldUrl, oldState); 12052 } 12053 }); 12054 } 12055 12056 $location.$$replace = false; 12057 12058 // we don't need to return anything because $evalAsync will make the digest loop dirty when 12059 // there is a change 12060 }); 12061 12062 return $location; 12063 12064 function afterLocationChange(oldUrl, oldState) { 12065 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 12066 $location.$$state, oldState); 12067 } 12068}]; 12069} 12070 12071/** 12072 * @ngdoc service 12073 * @name $log 12074 * @requires $window 12075 * 12076 * @description 12077 * Simple service for logging. Default implementation safely writes the message 12078 * into the browser's console (if present). 12079 * 12080 * The main purpose of this service is to simplify debugging and troubleshooting. 12081 * 12082 * The default is to log `debug` messages. You can use 12083 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 12084 * 12085 * @example 12086 <example module="logExample"> 12087 <file name="script.js"> 12088 angular.module('logExample', []) 12089 .controller('LogController', ['$scope', '$log', function($scope, $log) { 12090 $scope.$log = $log; 12091 $scope.message = 'Hello World!'; 12092 }]); 12093 </file> 12094 <file name="index.html"> 12095 <div ng-controller="LogController"> 12096 <p>Reload this page with open console, enter text and hit the log button...</p> 12097 <label>Message: 12098 <input type="text" ng-model="message" /></label> 12099 <button ng-click="$log.log(message)">log</button> 12100 <button ng-click="$log.warn(message)">warn</button> 12101 <button ng-click="$log.info(message)">info</button> 12102 <button ng-click="$log.error(message)">error</button> 12103 <button ng-click="$log.debug(message)">debug</button> 12104 </div> 12105 </file> 12106 </example> 12107 */ 12108 12109/** 12110 * @ngdoc provider 12111 * @name $logProvider 12112 * @description 12113 * Use the `$logProvider` to configure how the application logs messages 12114 */ 12115function $LogProvider() { 12116 var debug = true, 12117 self = this; 12118 12119 /** 12120 * @ngdoc method 12121 * @name $logProvider#debugEnabled 12122 * @description 12123 * @param {boolean=} flag enable or disable debug level messages 12124 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12125 */ 12126 this.debugEnabled = function(flag) { 12127 if (isDefined(flag)) { 12128 debug = flag; 12129 return this; 12130 } else { 12131 return debug; 12132 } 12133 }; 12134 12135 this.$get = ['$window', function($window) { 12136 return { 12137 /** 12138 * @ngdoc method 12139 * @name $log#log 12140 * 12141 * @description 12142 * Write a log message 12143 */ 12144 log: consoleLog('log'), 12145 12146 /** 12147 * @ngdoc method 12148 * @name $log#info 12149 * 12150 * @description 12151 * Write an information message 12152 */ 12153 info: consoleLog('info'), 12154 12155 /** 12156 * @ngdoc method 12157 * @name $log#warn 12158 * 12159 * @description 12160 * Write a warning message 12161 */ 12162 warn: consoleLog('warn'), 12163 12164 /** 12165 * @ngdoc method 12166 * @name $log#error 12167 * 12168 * @description 12169 * Write an error message 12170 */ 12171 error: consoleLog('error'), 12172 12173 /** 12174 * @ngdoc method 12175 * @name $log#debug 12176 * 12177 * @description 12178 * Write a debug message 12179 */ 12180 debug: (function() { 12181 var fn = consoleLog('debug'); 12182 12183 return function() { 12184 if (debug) { 12185 fn.apply(self, arguments); 12186 } 12187 }; 12188 }()) 12189 }; 12190 12191 function formatError(arg) { 12192 if (arg instanceof Error) { 12193 if (arg.stack) { 12194 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 12195 ? 'Error: ' + arg.message + '\n' + arg.stack 12196 : arg.stack; 12197 } else if (arg.sourceURL) { 12198 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 12199 } 12200 } 12201 return arg; 12202 } 12203 12204 function consoleLog(type) { 12205 var console = $window.console || {}, 12206 logFn = console[type] || console.log || noop, 12207 hasApply = false;
12208 12209 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 12210 // The reason behind this is that console.log has type "object" in IE8... 12211 try { 12212 hasApply = !!logFn.apply; 12213 } catch (e) {} 12214 12215 if (hasApply) { 12216 return function() { 12217 var args = []; 12218 forEach(arguments, function(arg) { 12219 args.push(formatError(arg)); 12220 }); 12221 return logFn.apply(console, args); 12222 }; 12223 } 12224 12225 // we are IE which either doesn't have window.console => this is noop and we do nothing, 12226 // or we are IE where console.log doesn't have apply so we log at least first 2 args 12227 return function(arg1, arg2) { 12228 logFn(arg1, arg2 == null ? '' : arg2); 12229 }; 12230 } 12231 }]; 12232} 12233 12234/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 12235 * Any commits to this file should be reviewed with security in mind. * 12236 * Changes to this file can potentially create security vulnerabilities. * 12237 * An approval from 2 Core members with history of modifying * 12238 * this file is required. * 12239 * * 12240 * Does the change somehow allow for arbitrary javascript to be executed? * 12241 * Or allows for someone to change the prototype of built-in objects? * 12242 * Or gives undesired access to variables likes document or window? * 12243 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 12244 12245var $parseMinErr = minErr('$parse'); 12246 12247// Sandboxing Angular Expressions 12248// ------------------------------ 12249// Angular expressions are generally considered safe because these expressions only have direct 12250// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 12251// obtaining a reference to native JS functions such as the Function constructor. 12252// 12253// As an example, consider the following Angular expression: 12254// 12255// {}.toString.constructor('alert("evil JS code")') 12256// 12257// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 12258// against the expression language, but not to prevent exploits that were enabled by exposing 12259// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 12260// practice and therefore we are not even trying to protect against interaction with an object 12261// explicitly exposed in this way. 12262// 12263// In general, it is not possible to access a Window object from an angular expression unless a 12264// window or some DOM object that has a reference to window is published onto a Scope. 12265// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 12266// native objects. 12267// 12268// See https://docs.angularjs.org/guide/security 12269 12270 12271function ensureSafeMemberName(name, fullExpression) { 12272 if (name === "__defineGetter__" || name === "__defineSetter__" 12273 || name === "__lookupGetter__" || name === "__lookupSetter__" 12274 || name === "__proto__") { 12275 throw $parseMinErr('isecfld', 12276 'Attempting to access a disallowed field in Angular expressions! ' 12277 + 'Expression: {0}', fullExpression); 12278 } 12279 return name; 12280} 12281 12282function ensureSafeObject(obj, fullExpression) { 12283 // nifty check if obj is Function that is fast and works across iframes and other contexts 12284 if (obj) { 12285 if (obj.constructor === obj) { 12286 throw $parseMinErr('isecfn', 12287 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12288 fullExpression); 12289 } else if (// isWindow(obj) 12290 obj.window === obj) { 12291 throw $parseMinErr('isecwindow', 12292 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 12293 fullExpression); 12294 } else if (// isElement(obj) 12295 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 12296 throw $parseMinErr('isecdom', 12297 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 12298 fullExpression); 12299 } else if (// block Object so that we can't get hold of dangerous Object.* methods 12300 obj === Object) { 12301 throw $parseMinErr('isecobj', 12302 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 12303 fullExpression); 12304 } 12305 } 12306 return obj; 12307} 12308 12309var CALL = Function.prototype.call; 12310var APPLY = Function.prototype.apply; 12311var BIND = Function.prototype.bind; 12312 12313function ensureSafeFunction(obj, fullExpression) { 12314 if (obj) { 12315 if (obj.constructor === obj) { 12316 throw $parseMinErr('isecfn', 12317 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12318 fullExpression); 12319 } else if (obj === CALL || obj === APPLY || obj === BIND) { 12320 throw $parseMinErr('isecff', 12321 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 12322 fullExpression); 12323 } 12324 } 12325} 12326 12327var OPERATORS = createMap(); 12328forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 12329var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 12330 12331
vendor: 47,060 bytes, lines 12332-13813
12332///////////////////////////////////////// 12333 12334 12335/** 12336 * @constructor 12337 */ 12338var Lexer = function(options) { 12339 this.options = options; 12340}; 12341 12342Lexer.prototype = { 12343 constructor: Lexer, 12344 12345 lex: function(text) { 12346 this.text = text; 12347 this.index = 0; 12348 this.tokens = []; 12349 12350 while (this.index < this.text.length) { 12351 var ch = this.text.charAt(this.index); 12352 if (ch === '"' || ch === "'") { 12353 this.readString(ch); 12354 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 12355 this.readNumber(); 12356 } else if (this.isIdent(ch)) { 12357 this.readIdent(); 12358 } else if (this.is(ch, '(){}[].,;:?')) { 12359 this.tokens.push({index: this.index, text: ch}); 12360 this.index++; 12361 } else if (this.isWhitespace(ch)) { 12362 this.index++; 12363 } else { 12364 var ch2 = ch + this.peek(); 12365 var ch3 = ch2 + this.peek(2); 12366 var op1 = OPERATORS[ch]; 12367 var op2 = OPERATORS[ch2]; 12368 var op3 = OPERATORS[ch3]; 12369 if (op1 || op2 || op3) { 12370 var token = op3 ? ch3 : (op2 ? ch2 : ch); 12371 this.tokens.push({index: this.index, text: token, operator: true}); 12372 this.index += token.length; 12373 } else { 12374 this.throwError('Unexpected next character ', this.index, this.index + 1); 12375 } 12376 } 12377 } 12378 return this.tokens; 12379 }, 12380 12381 is: function(ch, chars) { 12382 return chars.indexOf(ch) !== -1; 12383 }, 12384 12385 peek: function(i) { 12386 var num = i || 1; 12387 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 12388 }, 12389 12390 isNumber: function(ch) { 12391 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 12392 }, 12393 12394 isWhitespace: function(ch) { 12395 // IE treats non-breaking space as \u00A0 12396 return (ch === ' ' || ch === '\r' || ch === '\t' || 12397 ch === '\n' || ch === '\v' || ch === '\u00A0'); 12398 }, 12399 12400 isIdent: function(ch) { 12401 return ('a' <= ch && ch <= 'z' || 12402 'A' <= ch && ch <= 'Z' || 12403 '_' === ch || ch === '$'); 12404 }, 12405 12406 isExpOperator: function(ch) { 12407 return (ch === '-' || ch === '+' || this.isNumber(ch)); 12408 }, 12409 12410 throwError: function(error, start, end) { 12411 end = end || this.index; 12412 var colStr = (isDefined(start) 12413 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 12414 : ' ' + end); 12415 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 12416 error, colStr, this.text); 12417 }, 12418 12419 readNumber: function() { 12420 var number = ''; 12421 var start = this.index; 12422 while (this.index < this.text.length) { 12423 var ch = lowercase(this.text.charAt(this.index)); 12424 if (ch == '.' || this.isNumber(ch)) { 12425 number += ch; 12426 } else { 12427 var peekCh = this.peek(); 12428 if (ch == 'e' && this.isExpOperator(peekCh)) { 12429 number += ch; 12430 } else if (this.isExpOperator(ch) && 12431 peekCh && this.isNumber(peekCh) && 12432 number.charAt(number.length - 1) == 'e') { 12433 number += ch; 12434 } else if (this.isExpOperator(ch) && 12435 (!peekCh || !this.isNumber(peekCh)) && 12436 number.charAt(number.length - 1) == 'e') { 12437 this.throwError('Invalid exponent'); 12438 } else { 12439 break; 12440 } 12441 } 12442 this.index++; 12443 } 12444 this.tokens.push({ 12445 index: start, 12446 text: number, 12447 constant: true, 12448 value: Number(number) 12449 }); 12450 }, 12451 12452 readIdent: function() { 12453 var start = this.index; 12454 while (this.index < this.text.length) { 12455 var ch = this.text.charAt(this.index); 12456 if (!(this.isIdent(ch) || this.isNumber(ch))) { 12457 break; 12458 } 12459 this.index++; 12460 } 12461 this.tokens.push({ 12462 index: start, 12463 text: this.text.slice(start, this.index), 12464 identifier: true 12465 }); 12466 }, 12467 12468 readString: function(quote) { 12469 var start = this.index; 12470 this.index++; 12471 var string = ''; 12472 var rawString = quote; 12473 var escape = false; 12474 while (this.index < this.text.length) { 12475 var ch = this.text.charAt(this.index); 12476 rawString += ch; 12477 if (escape) { 12478 if (ch === 'u') { 12479 var hex = this.text.substring(this.index + 1, this.index + 5); 12480 if (!hex.match(/[\da-f]{4}/i)) { 12481 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 12482 } 12483 this.index += 4; 12484 string += String.fromCharCode(parseInt(hex, 16)); 12485 } else { 12486 var rep = ESCAPE[ch]; 12487 string = string + (rep || ch); 12488 } 12489 escape = false; 12490 } else if (ch === '\\') { 12491 escape = true; 12492 } else if (ch === quote) { 12493 this.index++; 12494 this.tokens.push({ 12495 index: start, 12496 text: rawString, 12497 constant: true, 12498 value: string 12499 }); 12500 return; 12501 } else { 12502 string += ch; 12503 } 12504 this.index++; 12505 } 12506 this.throwError('Unterminated quote', start); 12507 } 12508}; 12509 12510var AST = function(lexer, options) { 12511 this.lexer = lexer; 12512 this.options = options; 12513}; 12514 12515AST.Program = 'Program'; 12516AST.ExpressionStatement = 'ExpressionStatement'; 12517AST.AssignmentExpression = 'AssignmentExpression'; 12518AST.ConditionalExpression = 'ConditionalExpression'; 12519AST.LogicalExpression = 'LogicalExpression'; 12520AST.BinaryExpression = 'BinaryExpression'; 12521AST.UnaryExpression = 'UnaryExpression'; 12522AST.CallExpression = 'CallExpression'; 12523AST.MemberExpression = 'MemberExpression'; 12524AST.Identifier = 'Identifier'; 12525AST.Literal = 'Literal'; 12526AST.ArrayExpression = 'ArrayExpression'; 12527AST.Property = 'Property'; 12528AST.ObjectExpression = 'ObjectExpression'; 12529AST.ThisExpression = 'ThisExpression'; 12530 12531// Internal use only 12532AST.NGValueParameter = 'NGValueParameter'; 12533 12534AST.prototype = { 12535 ast: function(text) { 12536 this.text = text; 12537 this.tokens = this.lexer.lex(text); 12538 12539 var value = this.program(); 12540 12541 if (this.tokens.length !== 0) { 12542 this.throwError('is an unexpected token', this.tokens[0]); 12543 } 12544 12545 return value; 12546 }, 12547 12548 program: function() { 12549 var body = []; 12550 while (true) { 12551 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 12552 body.push(this.expressionStatement()); 12553 if (!this.expect(';')) { 12554 return { type: AST.Program, body: body}; 12555 } 12556 } 12557 }, 12558 12559 expressionStatement: function() { 12560 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 12561 }, 12562 12563 filterChain: function() { 12564 var left = this.expression(); 12565 var token; 12566 while ((token = this.expect('|'))) { 12567 left = this.filter(left); 12568 } 12569 return left; 12570 }, 12571 12572 expression: function() { 12573 return this.assignment(); 12574 }, 12575 12576 assignment: function() { 12577 var result = this.ternary(); 12578 if (this.expect('=')) { 12579 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 12580 } 12581 return result; 12582 }, 12583 12584 ternary: function() { 12585 var test = this.logicalOR(); 12586 var alternate; 12587 var consequent; 12588 if (this.expect('?')) { 12589 alternate = this.expression(); 12590 if (this.consume(':')) { 12591 consequent = this.expression(); 12592 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 12593 } 12594 } 12595 return test; 12596 }, 12597 12598 logicalOR: function() { 12599 var left = this.logicalAND(); 12600 while (this.expect('||')) { 12601 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 12602 } 12603 return left; 12604 }, 12605 12606 logicalAND: function() { 12607 var left = this.equality(); 12608 while (this.expect('&&')) { 12609 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 12610 } 12611 return left; 12612 }, 12613 12614 equality: function() { 12615 var left = this.relational(); 12616 var token; 12617 while ((token = this.expect('==','!=','===','!=='))) { 12618 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 12619 } 12620 return left; 12621 }, 12622 12623 relational: function() { 12624 var left = this.additive(); 12625 var token; 12626 while ((token = this.expect('<', '>', '<=', '>='))) { 12627 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 12628 } 12629 return left; 12630 }, 12631 12632 additive: function() { 12633 var left = this.multiplicative(); 12634 var token; 12635 while ((token = this.expect('+','-'))) { 12636 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 12637 } 12638 return left; 12639 }, 12640 12641 multiplicative: function() { 12642 var left = this.unary(); 12643 var token; 12644 while ((token = this.expect('*','/','%'))) { 12645 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 12646 } 12647 return left; 12648 }, 12649 12650 unary: function() { 12651 var token; 12652 if ((token = this.expect('+', '-', '!'))) { 12653 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 12654 } else { 12655 return this.primary(); 12656 } 12657 }, 12658 12659 primary: function() { 12660 var primary; 12661 if (this.expect('(')) { 12662 primary = this.filterChain(); 12663 this.consume(')'); 12664 } else if (this.expect('[')) { 12665 primary = this.arrayDeclaration(); 12666 } else if (this.expect('{')) { 12667 primary = this.object(); 12668 } else if (this.constants.hasOwnProperty(this.peek().text)) { 12669 primary = copy(this.constants[this.consume().text]); 12670 } else if (this.peek().identifier) { 12671 primary = this.identifier(); 12672 } else if (this.peek().constant) { 12673 primary = this.constant(); 12674 } else { 12675 this.throwError('not a primary expression', this.peek()); 12676 } 12677 12678 var next; 12679 while ((next = this.expect('(', '[', '.'))) { 12680 if (next.text === '(') { 12681 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 12682 this.consume(')'); 12683 } else if (next.text === '[') { 12684 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 12685 this.consume(']'); 12686 } else if (next.text === '.') { 12687 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 12688 } else { 12689 this.throwError('IMPOSSIBLE'); 12690 } 12691 } 12692 return primary; 12693 }, 12694 12695 filter: function(baseExpression) { 12696 var args = [baseExpression]; 12697 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 12698 12699 while (this.expect(':')) { 12700 args.push(this.expression()); 12701 } 12702 12703 return result; 12704 }, 12705 12706 parseArguments: function() { 12707 var args = []; 12708 if (this.peekToken().text !== ')') { 12709 do { 12710 args.push(this.expression()); 12711 } while (this.expect(',')); 12712 } 12713 return args; 12714 }, 12715 12716 identifier: function() { 12717 var token = this.consume(); 12718 if (!token.identifier) { 12719 this.throwError('is not a valid identifier', token); 12720 } 12721 return { type: AST.Identifier, name: token.text }; 12722 }, 12723 12724 constant: function() { 12725 // TODO check that it is a constant 12726 return { type: AST.Literal, value: this.consume().value }; 12727 }, 12728 12729 arrayDeclaration: function() { 12730 var elements = []; 12731 if (this.peekToken().text !== ']') { 12732 do { 12733 if (this.peek(']')) { 12734 // Support trailing commas per ES5.1. 12735 break; 12736 } 12737 elements.push(this.expression()); 12738 } while (this.expect(',')); 12739 } 12740 this.consume(']'); 12741 12742 return { type: AST.ArrayExpression, elements: elements }; 12743 }, 12744 12745 object: function() { 12746 var properties = [], property; 12747 if (this.peekToken().text !== '}') { 12748 do { 12749 if (this.peek('}')) { 12750 // Support trailing commas per ES5.1. 12751 break; 12752 } 12753 property = {type: AST.Property, kind: 'init'}; 12754 if (this.peek().constant) { 12755 property.key = this.constant(); 12756 } else if (this.peek().identifier) { 12757 property.key = this.identifier(); 12758 } else { 12759 this.throwError("invalid key", this.peek()); 12760 } 12761 this.consume(':'); 12762 property.value = this.expression(); 12763 properties.push(property); 12764 } while (this.expect(',')); 12765 } 12766 this.consume('}'); 12767 12768 return {type: AST.ObjectExpression, properties: properties }; 12769 }, 12770 12771 throwError: function(msg, token) { 12772 throw $parseMinErr('syntax', 12773 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 12774 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 12775 }, 12776 12777 consume: function(e1) { 12778 if (this.tokens.length === 0) { 12779 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 12780 } 12781 12782 var token = this.expect(e1); 12783 if (!token) { 12784 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 12785 } 12786 return token; 12787 }, 12788 12789 peekToken: function() { 12790 if (this.tokens.length === 0) { 12791 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 12792 } 12793 return this.tokens[0]; 12794 }, 12795 12796 peek: function(e1, e2, e3, e4) { 12797 return this.peekAhead(0, e1, e2, e3, e4); 12798 }, 12799 12800 peekAhead: function(i, e1, e2, e3, e4) { 12801 if (this.tokens.length > i) { 12802 var token = this.tokens[i]; 12803 var t = token.text; 12804 if (t === e1 || t === e2 || t === e3 || t === e4 || 12805 (!e1 && !e2 && !e3 && !e4)) { 12806 return token; 12807 } 12808 } 12809 return false; 12810 }, 12811 12812 expect: function(e1, e2, e3, e4) { 12813 var token = this.peek(e1, e2, e3, e4); 12814 if (token) { 12815 this.tokens.shift(); 12816 return token; 12817 } 12818 return false; 12819 }, 12820 12821 12822 /* `undefined` is not a constant, it is an identifier, 12823 * but using it as an identifier is not supported 12824 */ 12825 constants: { 12826 'true': { type: AST.Literal, value: true }, 12827 'false': { type: AST.Literal, value: false }, 12828 'null': { type: AST.Literal, value: null }, 12829 'undefined': {type: AST.Literal, value: undefined }, 12830 'this': {type: AST.ThisExpression } 12831 } 12832}; 12833 12834function ifDefined(v, d) { 12835 return typeof v !== 'undefined' ? v : d; 12836} 12837 12838function plusFn(l, r) { 12839 if (typeof l === 'undefined') return r; 12840 if (typeof r === 'undefined') return l; 12841 return l + r; 12842} 12843 12844function isStateless($filter, filterName) { 12845 var fn = $filter(filterName); 12846 return !fn.$stateful; 12847} 12848 12849function findConstantAndWatchExpressions(ast, $filter) { 12850 var allConstants; 12851 var argsToWatch; 12852 switch (ast.type) { 12853 case AST.Program: 12854 allConstants = true; 12855 forEach(ast.body, function(expr) { 12856 findConstantAndWatchExpressions(expr.expression, $filter); 12857 allConstants = allConstants && expr.expression.constant; 12858 }); 12859 ast.constant = allConstants; 12860 break; 12861 case AST.Literal: 12862 ast.constant = true; 12863 ast.toWatch = []; 12864 break; 12865 case AST.UnaryExpression: 12866 findConstantAndWatchExpressions(ast.argument, $filter); 12867 ast.constant = ast.argument.constant; 12868 ast.toWatch = ast.argument.toWatch; 12869 break; 12870 case AST.BinaryExpression: 12871 findConstantAndWatchExpressions(ast.left, $filter); 12872 findConstantAndWatchExpressions(ast.right, $filter); 12873 ast.constant = ast.left.constant && ast.right.constant; 12874 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 12875 break; 12876 case AST.LogicalExpression: 12877 findConstantAndWatchExpressions(ast.left, $filter); 12878 findConstantAndWatchExpressions(ast.right, $filter); 12879 ast.constant = ast.left.constant && ast.right.constant; 12880 ast.toWatch = ast.constant ? [] : [ast]; 12881 break; 12882 case AST.ConditionalExpression: 12883 findConstantAndWatchExpressions(ast.test, $filter); 12884 findConstantAndWatchExpressions(ast.alternate, $filter); 12885 findConstantAndWatchExpressions(ast.consequent, $filter); 12886 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 12887 ast.toWatch = ast.constant ? [] : [ast]; 12888 break; 12889 case AST.Identifier: 12890 ast.constant = false; 12891 ast.toWatch = [ast]; 12892 break; 12893 case AST.MemberExpression: 12894 findConstantAndWatchExpressions(ast.object, $filter); 12895 if (ast.computed) { 12896 findConstantAndWatchExpressions(ast.property, $filter); 12897 } 12898 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 12899 ast.toWatch = [ast]; 12900 break; 12901 case AST.CallExpression: 12902 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 12903 argsToWatch = []; 12904 forEach(ast.arguments, function(expr) { 12905 findConstantAndWatchExpressions(expr, $filter); 12906 allConstants = allConstants && expr.constant; 12907 if (!expr.constant) { 12908 argsToWatch.push.apply(argsToWatch, expr.toWatch); 12909 } 12910 }); 12911 ast.constant = allConstants; 12912 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 12913 break; 12914 case AST.AssignmentExpression: 12915 findConstantAndWatchExpressions(ast.left, $filter); 12916 findConstantAndWatchExpressions(ast.right, $filter); 12917 ast.constant = ast.left.constant && ast.right.constant; 12918 ast.toWatch = [ast]; 12919 break; 12920 case AST.ArrayExpression: 12921 allConstants = true; 12922 argsToWatch = []; 12923 forEach(ast.elements, function(expr) { 12924 findConstantAndWatchExpressions(expr, $filter); 12925 allConstants = allConstants && expr.constant; 12926 if (!expr.constant) { 12927 argsToWatch.push.apply(argsToWatch, expr.toWatch); 12928 } 12929 }); 12930 ast.constant = allConstants; 12931 ast.toWatch = argsToWatch; 12932 break; 12933 case AST.ObjectExpression: 12934 allConstants = true; 12935 argsToWatch = []; 12936 forEach(ast.properties, function(property) { 12937 findConstantAndWatchExpressions(property.value, $filter); 12938 allConstants = allConstants && property.value.constant; 12939 if (!property.value.constant) { 12940 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 12941 } 12942 }); 12943 ast.constant = allConstants; 12944 ast.toWatch = argsToWatch; 12945 break; 12946 case AST.ThisExpression: 12947 ast.constant = false; 12948 ast.toWatch = []; 12949 break; 12950 } 12951} 12952 12953function getInputs(body) { 12954 if (body.length != 1) return; 12955 var lastExpression = body[0].expression; 12956 var candidate = lastExpression.toWatch; 12957 if (candidate.length !== 1) return candidate; 12958 return candidate[0] !== lastExpression ? candidate : undefined; 12959} 12960 12961function isAssignable(ast) { 12962 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 12963} 12964 12965function assignableAST(ast) { 12966 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 12967 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 12968 } 12969} 12970 12971function isLiteral(ast) { 12972 return ast.body.length === 0 || 12973 ast.body.length === 1 && ( 12974 ast.body[0].expression.type === AST.Literal || 12975 ast.body[0].expression.type === AST.ArrayExpression || 12976 ast.body[0].expression.type === AST.ObjectExpression); 12977} 12978 12979function isConstant(ast) { 12980 return ast.constant; 12981} 12982 12983function ASTCompiler(astBuilder, $filter) { 12984 this.astBuilder = astBuilder; 12985 this.$filter = $filter; 12986} 12987 12988ASTCompiler.prototype = { 12989 compile: function(expression, expensiveChecks) { 12990 var self = this; 12991 var ast = this.astBuilder.ast(expression); 12992 this.state = { 12993 nextId: 0, 12994 filters: {}, 12995 expensiveChecks: expensiveChecks, 12996 fn: {vars: [], body: [], own: {}}, 12997 assign: {vars: [], body: [], own: {}}, 12998 inputs: [] 12999 }; 13000 findConstantAndWatchExpressions(ast, self.$filter); 13001 var extra = ''; 13002 var assignable; 13003 this.stage = 'assign'; 13004 if ((assignable = assignableAST(ast))) { 13005 this.state.computing = 'assign'; 13006 var result = this.nextId(); 13007 this.recurse(assignable, result); 13008 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 13009 } 13010 var toWatch = getInputs(ast.body); 13011 self.stage = 'inputs'; 13012 forEach(toWatch, function(watch, key) { 13013 var fnKey = 'fn' + key; 13014 self.state[fnKey] = {vars: [], body: [], own: {}}; 13015 self.state.computing = fnKey; 13016 var intoId = self.nextId(); 13017 self.recurse(watch, intoId); 13018 self.return_(intoId); 13019 self.state.inputs.push(fnKey); 13020 watch.watchId = key; 13021 }); 13022 this.state.computing = 'fn'; 13023 this.stage = 'main'; 13024 this.recurse(ast); 13025 var fnString = 13026 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 13027 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 13028 '"' + this.USE + ' ' + this.STRICT + '";\n' + 13029 this.filterPrefix() + 13030 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 13031 extra + 13032 this.watchFns() + 13033 'return fn;'; 13034 13035 /* jshint -W054 */ 13036 var fn = (new Function('$filter', 13037 'ensureSafeMemberName', 13038 'ensureSafeObject', 13039 'ensureSafeFunction', 13040 'ifDefined', 13041 'plus', 13042 'text', 13043 fnString))( 13044 this.$filter, 13045 ensureSafeMemberName, 13046 ensureSafeObject, 13047 ensureSafeFunction, 13048 ifDefined, 13049 plusFn, 13050 expression); 13051 /* jshint +W054 */ 13052 this.state = this.stage = undefined; 13053 fn.literal = isLiteral(ast); 13054 fn.constant = isConstant(ast); 13055 return fn; 13056 }, 13057 13058 USE: 'use', 13059 13060 STRICT: 'strict', 13061 13062 watchFns: function() { 13063 var result = []; 13064 var fns = this.state.inputs; 13065 var self = this; 13066 forEach(fns, function(name) { 13067 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 13068 }); 13069 if (fns.length) { 13070 result.push('fn.inputs=[' + fns.join(',') + '];'); 13071 } 13072 return result.join(''); 13073 }, 13074 13075 generateFunction: function(name, params) { 13076 return 'function(' + params + '){' + 13077 this.varsPrefix(name) + 13078 this.body(name) + 13079 '};'; 13080 }, 13081 13082 filterPrefix: function() { 13083 var parts = []; 13084 var self = this; 13085 forEach(this.state.filters, function(id, filter) { 13086 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 13087 }); 13088 if (parts.length) return 'var ' + parts.join(',') + ';'; 13089 return ''; 13090 }, 13091 13092 varsPrefix: function(section) { 13093 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 13094 }, 13095 13096 body: function(section) { 13097 return this.state[section].body.join(''); 13098 }, 13099 13100 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13101 var left, right, self = this, args, expression; 13102 recursionFn = recursionFn || noop; 13103 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 13104 intoId = intoId || this.nextId(); 13105 this.if_('i', 13106 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 13107 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 13108 ); 13109 return; 13110 } 13111 switch (ast.type) { 13112 case AST.Program: 13113 forEach(ast.body, function(expression, pos) { 13114 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 13115 if (pos !== ast.body.length - 1) { 13116 self.current().body.push(right, ';'); 13117 } else { 13118 self.return_(right); 13119 } 13120 }); 13121 break; 13122 case AST.Literal: 13123 expression = this.escape(ast.value); 13124 this.assign(intoId, expression); 13125 recursionFn(expression); 13126 break; 13127 case AST.UnaryExpression: 13128 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 13129 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 13130 this.assign(intoId, expression); 13131 recursionFn(expression); 13132 break; 13133 case AST.BinaryExpression: 13134 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 13135 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 13136 if (ast.operator === '+') { 13137 expression = this.plus(left, right); 13138 } else if (ast.operator === '-') { 13139 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 13140 } else { 13141 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 13142 } 13143 this.assign(intoId, expression); 13144 recursionFn(expression); 13145 break; 13146 case AST.LogicalExpression: 13147 intoId = intoId || this.nextId(); 13148 self.recurse(ast.left, intoId); 13149 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 13150 recursionFn(intoId); 13151 break; 13152 case AST.ConditionalExpression: 13153 intoId = intoId || this.nextId(); 13154 self.recurse(ast.test, intoId); 13155 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 13156 recursionFn(intoId); 13157 break; 13158 case AST.Identifier: 13159 intoId = intoId || this.nextId(); 13160 if (nameId) { 13161 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 13162 nameId.computed = false; 13163 nameId.name = ast.name; 13164 } 13165 ensureSafeMemberName(ast.name); 13166 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 13167 function() { 13168 self.if_(self.stage === 'inputs' || 's', function() { 13169 if (create && create !== 1) { 13170 self.if_( 13171 self.not(self.nonComputedMember('s', ast.name)), 13172 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 13173 } 13174 self.assign(intoId, self.nonComputedMember('s', ast.name)); 13175 }); 13176 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 13177 ); 13178 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 13179 self.addEnsureSafeObject(intoId); 13180 } 13181 recursionFn(intoId); 13182 break; 13183 case AST.MemberExpression: 13184 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 13185 intoId = intoId || this.nextId(); 13186 self.recurse(ast.object, left, undefined, function() { 13187 self.if_(self.notNull(left), function() { 13188 if (ast.computed) { 13189 right = self.nextId(); 13190 self.recurse(ast.property, right); 13191 self.addEnsureSafeMemberName(right); 13192 if (create && create !== 1) { 13193 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 13194 } 13195 expression = self.ensureSafeObject(self.computedMember(left, right)); 13196 self.assign(intoId, expression); 13197 if (nameId) { 13198 nameId.computed = true; 13199 nameId.name = right; 13200 } 13201 } else { 13202 ensureSafeMemberName(ast.property.name); 13203 if (create && create !== 1) { 13204 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 13205 } 13206 expression = self.nonComputedMember(left, ast.property.name); 13207 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 13208 expression = self.ensureSafeObject(expression); 13209 } 13210 self.assign(intoId, expression); 13211 if (nameId) { 13212 nameId.computed = false; 13213 nameId.name = ast.property.name; 13214 } 13215 } 13216 recursionFn(intoId); 13217 }); 13218 }, !!create); 13219 break; 13220 case AST.CallExpression: 13221 intoId = intoId || this.nextId(); 13222 if (ast.filter) { 13223 right = self.filter(ast.callee.name); 13224 args = []; 13225 forEach(ast.arguments, function(expr) { 13226 var argument = self.nextId(); 13227 self.recurse(expr, argument); 13228 args.push(argument); 13229 }); 13230 expression = right + '(' + args.join(',') + ')'; 13231 self.assign(intoId, expression); 13232 recursionFn(intoId); 13233 } else { 13234 right = self.nextId(); 13235 left = {}; 13236 args = []; 13237 self.recurse(ast.callee, right, left, function() { 13238 self.if_(self.notNull(right), function() { 13239 self.addEnsureSafeFunction(right); 13240 forEach(ast.arguments, function(expr) { 13241 self.recurse(expr, self.nextId(), undefined, function(argument) { 13242 args.push(self.ensureSafeObject(argument)); 13243 }); 13244 }); 13245 if (left.name) { 13246 if (!self.state.expensiveChecks) { 13247 self.addEnsureSafeObject(left.context); 13248 } 13249 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 13250 } else { 13251 expression = right + '(' + args.join(',') + ')'; 13252 } 13253 expression = self.ensureSafeObject(expression); 13254 self.assign(intoId, expression); 13255 recursionFn(intoId); 13256 }); 13257 }); 13258 } 13259 break; 13260 case AST.AssignmentExpression: 13261 right = this.nextId(); 13262 left = {}; 13263 if (!isAssignable(ast.left)) { 13264 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value'); 13265 } 13266 this.recurse(ast.left, undefined, left, function() { 13267 self.if_(self.notNull(left.context), function() { 13268 self.recurse(ast.right, right); 13269 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 13270 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 13271 self.assign(intoId, expression); 13272 recursionFn(intoId || expression); 13273 }); 13274 }, 1); 13275 break; 13276 case AST.ArrayExpression: 13277 args = []; 13278 forEach(ast.elements, function(expr) { 13279 self.recurse(expr, self.nextId(), undefined, function(argument) { 13280 args.push(argument); 13281 }); 13282 }); 13283 expression = '[' + args.join(',') + ']'; 13284 this.assign(intoId, expression); 13285 recursionFn(expression); 13286 break; 13287 case AST.ObjectExpression: 13288 args = []; 13289 forEach(ast.properties, function(property) { 13290 self.recurse(property.value, self.nextId(), undefined, function(expr) { 13291 args.push(self.escape( 13292 property.key.type === AST.Identifier ? property.key.name : 13293 ('' + property.key.value)) + 13294 ':' + expr); 13295 }); 13296 }); 13297 expression = '{' + args.join(',') + '}'; 13298 this.assign(intoId, expression); 13299 recursionFn(expression); 13300 break; 13301 case AST.ThisExpression: 13302 this.assign(intoId, 's'); 13303 recursionFn('s'); 13304 break; 13305 case AST.NGValueParameter: 13306 this.assign(intoId, 'v'); 13307 recursionFn('v'); 13308 break; 13309 } 13310 }, 13311 13312 getHasOwnProperty: function(element, property) { 13313 var key = element + '.' + property; 13314 var own = this.current().own; 13315 if (!own.hasOwnProperty(key)) { 13316 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 13317 } 13318 return own[key]; 13319 }, 13320 13321 assign: function(id, value) { 13322 if (!id) return; 13323 this.current().body.push(id, '=', value, ';'); 13324 return id; 13325 }, 13326 13327 filter: function(filterName) { 13328 if (!this.state.filters.hasOwnProperty(filterName)) { 13329 this.state.filters[filterName] = this.nextId(true); 13330 } 13331 return this.state.filters[filterName]; 13332 }, 13333 13334 ifDefined: function(id, defaultValue) { 13335 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 13336 }, 13337 13338 plus: function(left, right) { 13339 return 'plus(' + left + ',' + right + ')'; 13340 }, 13341 13342 return_: function(id) { 13343 this.current().body.push('return ', id, ';'); 13344 }, 13345 13346 if_: function(test, alternate, consequent) { 13347 if (test === true) { 13348 alternate(); 13349 } else { 13350 var body = this.current().body; 13351 body.push('if(', test, '){'); 13352 alternate(); 13353 body.push('}'); 13354 if (consequent) { 13355 body.push('else{'); 13356 consequent(); 13357 body.push('}'); 13358 } 13359 } 13360 }, 13361 13362 not: function(expression) { 13363 return '!(' + expression + ')'; 13364 }, 13365 13366 notNull: function(expression) { 13367 return expression + '!=null'; 13368 }, 13369 13370 nonComputedMember: function(left, right) { 13371 return left + '.' + right; 13372 }, 13373 13374 computedMember: function(left, right) { 13375 return left + '[' + right + ']'; 13376 }, 13377 13378 member: function(left, right, computed) { 13379 if (computed) return this.computedMember(left, right); 13380 return this.nonComputedMember(left, right); 13381 }, 13382 13383 addEnsureSafeObject: function(item) { 13384 this.current().body.push(this.ensureSafeObject(item), ';'); 13385 }, 13386 13387 addEnsureSafeMemberName: function(item) { 13388 this.current().body.push(this.ensureSafeMemberName(item), ';'); 13389 }, 13390 13391 addEnsureSafeFunction: function(item) { 13392 this.current().body.push(this.ensureSafeFunction(item), ';'); 13393 }, 13394 13395 ensureSafeObject: function(item) { 13396 return 'ensureSafeObject(' + item + ',text)'; 13397 }, 13398 13399 ensureSafeMemberName: function(item) { 13400 return 'ensureSafeMemberName(' + item + ',text)'; 13401 }, 13402 13403 ensureSafeFunction: function(item) { 13404 return 'ensureSafeFunction(' + item + ',text)'; 13405 }, 13406 13407 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13408 var self = this; 13409 return function() { 13410 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 13411 }; 13412 }, 13413 13414 lazyAssign: function(id, value) { 13415 var self = this; 13416 return function() { 13417 self.assign(id, value); 13418 }; 13419 }, 13420 13421 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 13422 13423 stringEscapeFn: function(c) { 13424 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 13425 }, 13426 13427 escape: function(value) { 13428 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 13429 if (isNumber(value)) return value.toString(); 13430 if (value === true) return 'true'; 13431 if (value === false) return 'false'; 13432 if (value === null) return 'null'; 13433 if (typeof value === 'undefined') return 'undefined'; 13434 13435 throw $parseMinErr('esc', 'IMPOSSIBLE'); 13436 }, 13437 13438 nextId: function(skip, init) { 13439 var id = 'v' + (this.state.nextId++); 13440 if (!skip) { 13441 this.current().vars.push(id + (init ? '=' + init : '')); 13442 } 13443 return id; 13444 }, 13445 13446 current: function() { 13447 return this.state[this.state.computing]; 13448 } 13449}; 13450 13451 13452function ASTInterpreter(astBuilder, $filter) { 13453 this.astBuilder = astBuilder; 13454 this.$filter = $filter; 13455} 13456 13457ASTInterpreter.prototype = { 13458 compile: function(expression, expensiveChecks) { 13459 var self = this; 13460 var ast = this.astBuilder.ast(expression); 13461 this.expression = expression; 13462 this.expensiveChecks = expensiveChecks; 13463 findConstantAndWatchExpressions(ast, self.$filter); 13464 var assignable; 13465 var assign; 13466 if ((assignable = assignableAST(ast))) { 13467 assign = this.recurse(assignable); 13468 } 13469 var toWatch = getInputs(ast.body); 13470 var inputs; 13471 if (toWatch) { 13472 inputs = []; 13473 forEach(toWatch, function(watch, key) { 13474 var input = self.recurse(watch); 13475 watch.input = input; 13476 inputs.push(input); 13477 watch.watchId = key; 13478 }); 13479 } 13480 var expressions = []; 13481 forEach(ast.body, function(expression) { 13482 expressions.push(self.recurse(expression.expression)); 13483 }); 13484 var fn = ast.body.length === 0 ? function() {} : 13485 ast.body.length === 1 ? expressions[0] : 13486 function(scope, locals) { 13487 var lastValue; 13488 forEach(expressions, function(exp) { 13489 lastValue = exp(scope, locals); 13490 }); 13491 return lastValue; 13492 }; 13493 if (assign) { 13494 fn.assign = function(scope, value, locals) { 13495 return assign(scope, locals, value); 13496 }; 13497 } 13498 if (inputs) { 13499 fn.inputs = inputs; 13500 } 13501 fn.literal = isLiteral(ast); 13502 fn.constant = isConstant(ast); 13503 return fn; 13504 }, 13505 13506 recurse: function(ast, context, create) { 13507 var left, right, self = this, args, expression; 13508 if (ast.input) { 13509 return this.inputs(ast.input, ast.watchId); 13510 } 13511 switch (ast.type) { 13512 case AST.Literal: 13513 return this.value(ast.value, context); 13514 case AST.UnaryExpression: 13515 right = this.recurse(ast.argument); 13516 return this['unary' + ast.operator](right, context); 13517 case AST.BinaryExpression: 13518 left = this.recurse(ast.left); 13519 right = this.recurse(ast.right); 13520 return this['binary' + ast.operator](left, right, context); 13521 case AST.LogicalExpression: 13522 left = this.recurse(ast.left); 13523 right = this.recurse(ast.right); 13524 return this['binary' + ast.operator](left, right, context); 13525 case AST.ConditionalExpression: 13526 return this['ternary?:']( 13527 this.recurse(ast.test), 13528 this.recurse(ast.alternate), 13529 this.recurse(ast.consequent), 13530 context 13531 ); 13532 case AST.Identifier: 13533 ensureSafeMemberName(ast.name, self.expression); 13534 return self.identifier(ast.name, 13535 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 13536 context, create, self.expression); 13537 case AST.MemberExpression: 13538 left = this.recurse(ast.object, false, !!create); 13539 if (!ast.computed) { 13540 ensureSafeMemberName(ast.property.name, self.expression); 13541 right = ast.property.name; 13542 } 13543 if (ast.computed) right = this.recurse(ast.property); 13544 return ast.computed ? 13545 this.computedMember(left, right, context, create, self.expression) : 13546 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 13547 case AST.CallExpression: 13548 args = []; 13549 forEach(ast.arguments, function(expr) { 13550 args.push(self.recurse(expr)); 13551 }); 13552 if (ast.filter) right = this.$filter(ast.callee.name); 13553 if (!ast.filter) right = this.recurse(ast.callee, true); 13554 return ast.filter ? 13555 function(scope, locals, assign, inputs) { 13556 var values = []; 13557 for (var i = 0; i < args.length; ++i) { 13558 values.push(args[i](scope, locals, assign, inputs)); 13559 } 13560 var value = right.apply(undefined, values, inputs); 13561 return context ? {context: undefined, name: undefined, value: value} : value; 13562 } : 13563 function(scope, locals, assign, inputs) { 13564 var rhs = right(scope, locals, assign, inputs); 13565 var value; 13566 if (rhs.value != null) { 13567 ensureSafeObject(rhs.context, self.expression); 13568 ensureSafeFunction(rhs.value, self.expression); 13569 var values = []; 13570 for (var i = 0; i < args.length; ++i) { 13571 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 13572 } 13573 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 13574 } 13575 return context ? {value: value} : value; 13576 }; 13577 case AST.AssignmentExpression: 13578 left = this.recurse(ast.left, true, 1); 13579 right = this.recurse(ast.right); 13580 return function(scope, locals, assign, inputs) { 13581 var lhs = left(scope, locals, assign, inputs); 13582 var rhs = right(scope, locals, assign, inputs); 13583 ensureSafeObject(lhs.value, self.expression); 13584 lhs.context[lhs.name] = rhs; 13585 return context ? {value: rhs} : rhs; 13586 }; 13587 case AST.ArrayExpression: 13588 args = []; 13589 forEach(ast.elements, function(expr) { 13590 args.push(self.recurse(expr)); 13591 }); 13592 return function(scope, locals, assign, inputs) { 13593 var value = []; 13594 for (var i = 0; i < args.length; ++i) { 13595 value.push(args[i](scope, locals, assign, inputs)); 13596 } 13597 return context ? {value: value} : value; 13598 }; 13599 case AST.ObjectExpression: 13600 args = []; 13601 forEach(ast.properties, function(property) { 13602 args.push({key: property.key.type === AST.Identifier ? 13603 property.key.name : 13604 ('' + property.key.value), 13605 value: self.recurse(property.value) 13606 }); 13607 }); 13608 return function(scope, locals, assign, inputs) { 13609 var value = {}; 13610 for (var i = 0; i < args.length; ++i) { 13611 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 13612 } 13613 return context ? {value: value} : value; 13614 }; 13615 case AST.ThisExpression: 13616 return function(scope) { 13617 return context ? {value: scope} : scope; 13618 }; 13619 case AST.NGValueParameter: 13620 return function(scope, locals, assign, inputs) { 13621 return context ? {value: assign} : assign; 13622 }; 13623 } 13624 }, 13625 13626 'unary+': function(argument, context) { 13627 return function(scope, locals, assign, inputs) { 13628 var arg = argument(scope, locals, assign, inputs); 13629 if (isDefined(arg)) { 13630 arg = +arg; 13631 } else { 13632 arg = 0; 13633 } 13634 return context ? {value: arg} : arg; 13635 }; 13636 }, 13637 'unary-': function(argument, context) { 13638 return function(scope, locals, assign, inputs) { 13639 var arg = argument(scope, locals, assign, inputs); 13640 if (isDefined(arg)) { 13641 arg = -arg; 13642 } else { 13643 arg = 0; 13644 } 13645 return context ? {value: arg} : arg; 13646 }; 13647 }, 13648 'unary!': function(argument, context) { 13649 return function(scope, locals, assign, inputs) { 13650 var arg = !argument(scope, locals, assign, inputs); 13651 return context ? {value: arg} : arg; 13652 }; 13653 }, 13654 'binary+': function(left, right, context) { 13655 return function(scope, locals, assign, inputs) { 13656 var lhs = left(scope, locals, assign, inputs); 13657 var rhs = right(scope, locals, assign, inputs); 13658 var arg = plusFn(lhs, rhs); 13659 return context ? {value: arg} : arg; 13660 }; 13661 }, 13662 'binary-': function(left, right, context) { 13663 return function(scope, locals, assign, inputs) { 13664 var lhs = left(scope, locals, assign, inputs); 13665 var rhs = right(scope, locals, assign, inputs); 13666 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 13667 return context ? {value: arg} : arg; 13668 }; 13669 }, 13670 'binary*': function(left, right, context) { 13671 return function(scope, locals, assign, inputs) { 13672 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 13673 return context ? {value: arg} : arg; 13674 }; 13675 }, 13676 'binary/': function(left, right, context) { 13677 return function(scope, locals, assign, inputs) { 13678 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 13679 return context ? {value: arg} : arg; 13680 }; 13681 }, 13682 'binary%': function(left, right, context) { 13683 return function(scope, locals, assign, inputs) { 13684 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 13685 return context ? {value: arg} : arg; 13686 }; 13687 }, 13688 'binary===': function(left, right, context) { 13689 return function(scope, locals, assign, inputs) { 13690 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 13691 return context ? {value: arg} : arg; 13692 }; 13693 }, 13694 'binary!==': function(left, right, context) { 13695 return function(scope, locals, assign, inputs) { 13696 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 13697 return context ? {value: arg} : arg; 13698 }; 13699 }, 13700 'binary==': function(left, right, context) { 13701 return function(scope, locals, assign, inputs) { 13702 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 13703 return context ? {value: arg} : arg; 13704 }; 13705 }, 13706 'binary!=': function(left, right, context) { 13707 return function(scope, locals, assign, inputs) { 13708 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 13709 return context ? {value: arg} : arg; 13710 }; 13711 }, 13712 'binary<': function(left, right, context) { 13713 return function(scope, locals, assign, inputs) { 13714 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 13715 return context ? {value: arg} : arg; 13716 }; 13717 }, 13718 'binary>': function(left, right, context) { 13719 return function(scope, locals, assign, inputs) { 13720 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 13721 return context ? {value: arg} : arg; 13722 }; 13723 }, 13724 'binary<=': function(left, right, context) { 13725 return function(scope, locals, assign, inputs) { 13726 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 13727 return context ? {value: arg} : arg; 13728 }; 13729 }, 13730 'binary>=': function(left, right, context) { 13731 return function(scope, locals, assign, inputs) { 13732 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 13733 return context ? {value: arg} : arg; 13734 }; 13735 }, 13736 'binary&&': function(left, right, context) { 13737 return function(scope, locals, assign, inputs) { 13738 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 13739 return context ? {value: arg} : arg; 13740 }; 13741 }, 13742 'binary||': function(left, right, context) { 13743 return function(scope, locals, assign, inputs) { 13744 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 13745 return context ? {value: arg} : arg; 13746 }; 13747 }, 13748 'ternary?:': function(test, alternate, consequent, context) { 13749 return function(scope, locals, assign, inputs) { 13750 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 13751 return context ? {value: arg} : arg; 13752 }; 13753 }, 13754 value: function(value, context) { 13755 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 13756 }, 13757 identifier: function(name, expensiveChecks, context, create, expression) { 13758 return function(scope, locals, assign, inputs) { 13759 var base = locals && (name in locals) ? locals : scope; 13760 if (create && create !== 1 && base && !(base[name])) { 13761 base[name] = {}; 13762 } 13763 var value = base ? base[name] : undefined; 13764 if (expensiveChecks) { 13765 ensureSafeObject(value, expression); 13766 } 13767 if (context) { 13768 return {context: base, name: name, value: value}; 13769 } else { 13770 return value; 13771 } 13772 }; 13773 }, 13774 computedMember: function(left, right, context, create, expression) { 13775 return function(scope, locals, assign, inputs) { 13776 var lhs = left(scope, locals, assign, inputs); 13777 var rhs; 13778 var value; 13779 if (lhs != null) { 13780 rhs = right(scope, locals, assign, inputs); 13781 ensureSafeMemberName(rhs, expression); 13782 if (create && create !== 1 && lhs && !(lhs[rhs])) { 13783 lhs[rhs] = {}; 13784 } 13785 value = lhs[rhs]; 13786 ensureSafeObject(value, expression); 13787 } 13788 if (context) { 13789 return {context: lhs, name: rhs, value: value}; 13790 } else { 13791 return value; 13792 } 13793 }; 13794 }, 13795 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 13796 return function(scope, locals, assign, inputs) { 13797 var lhs = left(scope, locals, assign, inputs); 13798 if (create && create !== 1 && lhs && !(lhs[right])) { 13799 lhs[right] = {}; 13800 } 13801 var value = lhs != null ? lhs[right] : undefined; 13802 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 13803 ensureSafeObject(value, expression); 13804 } 13805 if (context) { 13806 return {context: lhs, name: right, value: value}; 13807 } else { 13808 return value; 13809 } 13810 }; 13811 }, 13812 inputs: function(input, watchId) { 13813 return function(scope, value, locals, inputs) {
13814 if (inputs) return inputs[watchId]; 13815 return input(scope, value, locals); 13816 }; 13817 } 13818}; 13819 13820/** 13821 * @constructor 13822 */ 13823var Parser = function(lexer, $filter, options) { 13824 this.lexer = lexer; 13825 this.$filter = $filter; 13826 this.options = options; 13827 this.ast = new AST(this.lexer); 13828 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 13829 new ASTCompiler(this.ast, $filter); 13830}; 13831 13832Parser.prototype = { 13833 constructor: Parser, 13834 13835 parse: function(text) { 13836 return this.astCompiler.compile(text, this.options.expensiveChecks); 13837 } 13838}; 13839 13840////////////////////////////////////////////////// 13841// Parser helper functions 13842////////////////////////////////////////////////// 13843 13844function setter(obj, path, setValue, fullExp) { 13845 ensureSafeObject(obj, fullExp); 13846 13847 var element = path.split('.'), key; 13848 for (var i = 0; element.length > 1; i++) { 13849 key = ensureSafeMemberName(element.shift(), fullExp); 13850 var propertyObj = ensureSafeObject(obj[key], fullExp); 13851 if (!propertyObj) { 13852 propertyObj = {}; 13853 obj[key] = propertyObj; 13854 } 13855 obj = propertyObj; 13856 } 13857 key = ensureSafeMemberName(element.shift(), fullExp); 13858 ensureSafeObject(obj[key], fullExp); 13859 obj[key] = setValue; 13860 return setValue; 13861} 13862 13863var getterFnCacheDefault = createMap(); 13864var getterFnCacheExpensive = createMap(); 13865 13866function isPossiblyDangerousMemberName(name) { 13867 return name == 'constructor'; 13868} 13869 13870var objectValueOf = Object.prototype.valueOf; 13871 13872function getValueOf(value) { 13873 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 13874} 13875 13876/////////////////////////////////// 13877 13878/** 13879 * @ngdoc service 13880 * @name $parse 13881 * @kind function 13882 * 13883 * @description 13884 * 13885 * Converts Angular {@link guide/expression expression} into a function. 13886 * 13887 * ```js 13888 * var getter = $parse('user.name'); 13889 * var setter = getter.assign; 13890 * var context = {user:{name:'angular'}}; 13891 * var locals = {user:{name:'local'}}; 13892 * 13893 * expect(getter(context)).toEqual('angular'); 13894 * setter(context, 'newValue'); 13895 * expect(context.user.name).toEqual('newValue'); 13896 * expect(getter(context, locals)).toEqual('local'); 13897 * ``` 13898 * 13899 * 13900 * @param {string} expression String expression to compile. 13901 * @returns {function(context, locals)} a function which represents the compiled expression: 13902 * 13903 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13904 * are evaluated against (typically a scope object). 13905 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13906 * `context`. 13907 * 13908 * The returned function also has the following properties: 13909 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 13910 * literal. 13911 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 13912 * constant literals. 13913 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 13914 * set to a function to change its value on the given context. 13915 * 13916 */ 13917 13918 13919/** 13920 * @ngdoc provider 13921 * @name $parseProvider 13922 * 13923 * @description 13924 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 13925 * service. 13926 */ 13927function $ParseProvider() { 13928 var cacheDefault = createMap(); 13929 var cacheExpensive = createMap(); 13930 13931 this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { 13932 var $parseOptions = { 13933 csp: $sniffer.csp, 13934 expensiveChecks: false 13935 }, 13936 $parseOptionsExpensive = { 13937 csp: $sniffer.csp, 13938 expensiveChecks: true 13939 }; 13940 13941 return function $parse(exp, interceptorFn, expensiveChecks) { 13942 var parsedExpression, oneTime, cacheKey; 13943 13944 switch (typeof exp) { 13945 case 'string': 13946 exp = exp.trim(); 13947 cacheKey = exp; 13948 13949 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 13950 parsedExpression = cache[cacheKey]; 13951 13952 if (!parsedExpression) { 13953 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 13954 oneTime = true; 13955 exp = exp.substring(2); 13956 } 13957 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 13958 var lexer = new Lexer(parseOptions); 13959 var parser = new Parser(lexer, $filter, parseOptions); 13960 parsedExpression = parser.parse(exp); 13961 if (parsedExpression.constant) { 13962 parsedExpression.$$watchDelegate = constantWatchDelegate; 13963 } else if (oneTime) { 13964 parsedExpression.$$watchDelegate = parsedExpression.literal ? 13965 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 13966 } else if (parsedExpression.inputs) { 13967 parsedExpression.$$watchDelegate = inputsWatchDelegate; 13968 } 13969 cache[cacheKey] = parsedExpression; 13970 } 13971 return addInterceptor(parsedExpression, interceptorFn); 13972 13973 case 'function': 13974 return addInterceptor(exp, interceptorFn); 13975 13976 default: 13977 return noop; 13978 } 13979 }; 13980 13981 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 13982 13983 if (newValue == null || oldValueOfValue == null) { // null/undefined 13984 return newValue === oldValueOfValue; 13985 } 13986 13987 if (typeof newValue === 'object') { 13988 13989 // attempt to convert the value to a primitive type 13990 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 13991 // be cheaply dirty-checked 13992 newValue = getValueOf(newValue); 13993 13994 if (typeof newValue === 'object') { 13995 // objects/arrays are not supported - deep-watching them would be too expensive 13996 return false;
13997 } 13998 13999 // fall-through to the primitive equality check 14000 } 14001 14002 //Primitive or NaN 14003 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 14004 } 14005 14006 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 14007 var inputExpressions = parsedExpression.inputs; 14008 var lastResult; 14009 14010 if (inputExpressions.length === 1) { 14011 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 14012 inputExpressions = inputExpressions[0]; 14013 return scope.$watch(function expressionInputWatch(scope) { 14014 var newInputValue = inputExpressions(scope); 14015 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 14016 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 14017 oldInputValueOf = newInputValue && getValueOf(newInputValue); 14018 } 14019 return lastResult; 14020 }, listener, objectEquality, prettyPrintExpression); 14021 } 14022 14023 var oldInputValueOfValues = []; 14024 var oldInputValues = []; 14025 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14026 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 14027 oldInputValues[i] = null; 14028 } 14029 14030 return scope.$watch(function expressionInputsWatch(scope) { 14031 var changed = false; 14032 14033 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14034 var newInputValue = inputExpressions[i](scope); 14035 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 14036 oldInputValues[i] = newInputValue; 14037 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 14038 } 14039 } 14040 14041 if (changed) { 14042 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 14043 } 14044 14045 return lastResult; 14046 }, listener, objectEquality, prettyPrintExpression); 14047 } 14048 14049 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14050 var unwatch, lastValue; 14051 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14052 return parsedExpression(scope); 14053 }, function oneTimeListener(value, old, scope) { 14054 lastValue = value; 14055 if (isFunction(listener)) { 14056 listener.apply(this, arguments); 14057 } 14058 if (isDefined(value)) { 14059 scope.$$postDigest(function() { 14060 if (isDefined(lastValue)) { 14061 unwatch(); 14062 } 14063 }); 14064 } 14065 }, objectEquality); 14066 } 14067 14068 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14069 var unwatch, lastValue; 14070 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14071 return parsedExpression(scope); 14072 }, function oneTimeListener(value, old, scope) { 14073 lastValue = value; 14074 if (isFunction(listener)) { 14075 listener.call(this, value, old, scope); 14076 } 14077 if (isAllDefined(value)) { 14078 scope.$$postDigest(function() { 14079 if (isAllDefined(lastValue)) unwatch(); 14080 }); 14081 } 14082 }, objectEquality); 14083 14084 function isAllDefined(value) { 14085 var allDefined = true; 14086 forEach(value, function(val) { 14087 if (!isDefined(val)) allDefined = false; 14088 }); 14089 return allDefined; 14090 } 14091 } 14092 14093 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14094 var unwatch; 14095 return unwatch = scope.$watch(function constantWatch(scope) { 14096 return parsedExpression(scope); 14097 }, function constantListener(value, old, scope) { 14098 if (isFunction(listener)) { 14099 listener.apply(this, arguments); 14100 } 14101 unwatch(); 14102 }, objectEquality); 14103 } 14104 14105 function addInterceptor(parsedExpression, interceptorFn) { 14106 if (!interceptorFn) return parsedExpression; 14107 var watchDelegate = parsedExpression.$$watchDelegate; 14108 14109 var regularWatch = 14110 watchDelegate !== oneTimeLiteralWatchDelegate && 14111 watchDelegate !== oneTimeWatchDelegate; 14112 14113 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { 14114 var value = parsedExpression(scope, locals, assign, inputs); 14115 return interceptorFn(value, scope, locals); 14116 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 14117 var value = parsedExpression(scope, locals, assign, inputs); 14118 var result = interceptorFn(value, scope, locals); 14119 // we only return the interceptor's result if the 14120 // initial value is defined (for bind-once) 14121 return isDefined(value) ? result : value; 14122 }; 14123
14124 // Propagate $$watchDelegates other then inputsWatchDelegate 14125 if (parsedExpression.$$watchDelegate && 14126 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 14127 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 14128 } else if (!interceptorFn.$stateful) { 14129 // If there is an interceptor, but no watchDelegate then treat the interceptor like 14130 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 14131 fn.$$watchDelegate = inputsWatchDelegate; 14132 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 14133 } 14134 14135 return fn; 14136 } 14137 }]; 14138} 14139 14140/** 14141 * @ngdoc service 14142 * @name $q 14143 * @requires $rootScope 14144 * 14145 * @description 14146 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 14147 * when they are done processing. 14148 * 14149 * This is an implementation of promises/deferred objects inspired by 14150 * [Kris Kowal's Q](https://github.com/kriskowal/q). 14151 * 14152 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 14153 * implementations, and the other which resembles ES6 promises to some degree. 14154 * 14155 * # $q constructor 14156 * 14157 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 14158 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, 14159 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 14160 * 14161 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are 14162 * available yet. 14163 * 14164 * It can be used like so: 14165 * 14166 * ```js 14167 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14168 * // are available in the current lexical scope (they could have been injected or passed in). 14169 * 14170 * function asyncGreet(name) { 14171 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 14172 * return $q(function(resolve, reject) { 14173 * setTimeout(function() { 14174 * if (okToGreet(name)) { 14175 * resolve('Hello, ' + name + '!'); 14176 * } else { 14177 * reject('Greeting ' + name + ' is not allowed.'); 14178 * } 14179 * }, 1000); 14180 * }); 14181 * } 14182 * 14183 * var promise = asyncGreet('Robin Hood'); 14184 * promise.then(function(greeting) { 14185 * alert('Success: ' + greeting); 14186 * }, function(reason) { 14187 * alert('Failed: ' + reason); 14188 * }); 14189 * ``` 14190 * 14191 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 14192 * 14193 * However, the more traditional CommonJS-style usage is still available, and documented below. 14194 * 14195 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 14196 * interface for interacting with an object that represents the result of an action that is 14197 * performed asynchronously, and may or may not be finished at any given point in time. 14198 * 14199 * From the perspective of dealing with error handling, deferred and promise APIs are to 14200 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 14201 * 14202 * ```js 14203 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14204 * // are available in the current lexical scope (they could have been injected or passed in). 14205 * 14206 * function asyncGreet(name) { 14207 * var deferred = $q.defer(); 14208 * 14209 * setTimeout(function() { 14210 * deferred.notify('About to greet ' + name + '.'); 14211 * 14212 * if (okToGreet(name)) { 14213 * deferred.resolve('Hello, ' + name + '!'); 14214 * } else { 14215 * deferred.reject('Greeting ' + name + ' is not allowed.'); 14216 * } 14217 * }, 1000); 14218 * 14219 * return deferred.promise; 14220 * } 14221 * 14222 * var promise = asyncGreet('Robin Hood'); 14223 * promise.then(function(greeting) { 14224 * alert('Success: ' + greeting); 14225 * }, function(reason) { 14226 * alert('Failed: ' + reason); 14227 * }, function(update) { 14228 * alert('Got notification: ' + update); 14229 * }); 14230 * ``` 14231 * 14232 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 14233 * comes in the way of guarantees that promise and deferred APIs make, see 14234 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 14235 *
14236 * Additionally the promise api allows for composition that is very hard to do with the 14237 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 14238 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 14239 * section on serial or parallel joining of promises. 14240 * 14241 * # The Deferred API 14242 * 14243 * A new instance of deferred is constructed by calling `$q.defer()`. 14244 * 14245 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 14246 * that can be used for signaling the successful or unsuccessful completion, as well as the status 14247 * of the task. 14248 * 14249 * **Methods** 14250 * 14251 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 14252 * constructed via `$q.reject`, the promise will be rejected instead. 14253 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 14254 * resolving it with a rejection constructed via `$q.reject`. 14255 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 14256 * multiple times before the promise is either resolved or rejected. 14257 * 14258 * **Properties** 14259 * 14260 * - promise â `{Promise}` â promise object associated with this deferred. 14261 * 14262 * 14263 * # The Promise API 14264 * 14265 * A new promise instance is created when a deferred instance is created and can be retrieved by 14266 * calling `deferred.promise`. 14267 * 14268 * The purpose of the promise object is to allow for interested parties to get access to the result 14269 * of the deferred task when it completes. 14270 * 14271 * **Methods** 14272 * 14273 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 14274 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 14275 * as soon as the result is available. The callbacks are called with a single argument: the result 14276 * or rejection reason. Additionally, the notify callback may be called zero or more times to 14277 * provide a progress indication, before the promise is resolved or rejected. 14278 * 14279 * This method *returns a new promise* which is resolved or rejected via the return value of the 14280 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14281 * with the value which is resolved in that promise using 14282 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 14283 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 14284 * resolved or rejected from the notifyCallback method. 14285 * 14286 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 14287 * 14288 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 14289 * but to do so without modifying the final value. This is useful to release resources or do some 14290 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 14291 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 14292 * more information. 14293 * 14294 * # Chaining promises 14295 * 14296 * Because calling the `then` method of a promise returns a new derived promise, it is easily 14297 * possible to create a chain of promises: 14298 * 14299 * ```js 14300 * promiseB = promiseA.then(function(result) { 14301 * return result + 1; 14302 * }); 14303 * 14304 * // promiseB will be resolved immediately after promiseA is resolved and its value 14305 * // will be the result of promiseA incremented by 1 14306 * ``` 14307 * 14308 * It is possible to create chains of any length and since a promise can be resolved with another 14309 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 14310 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 14311 * $http's response interceptors. 14312 * 14313 * 14314 * # Differences between Kris Kowal's Q and $q 14315 * 14316 * There are two main differences: 14317 * 14318 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 14319 * mechanism in angular, which means faster propagation of resolution or rejection into your 14320 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 14321 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14322 * all the important functionality needed for common async tasks. 14323 * 14324 * # Testing 14325 * 14326 * ```js 14327 * it('should simulate promise', inject(function($q, $rootScope) { 14328 * var deferred = $q.defer(); 14329 * var promise = deferred.promise; 14330 * var resolvedValue; 14331 * 14332 * promise.then(function(value) { resolvedValue = value; }); 14333 * expect(resolvedValue).toBeUndefined(); 14334 * 14335 * // Simulate resolving of promise 14336 * deferred.resolve(123); 14337 * // Note that the 'then' function does not get called synchronously. 14338 * // This is because we want the promise API to always be async, whether or not 14339 * // it got called synchronously or asynchronously. 14340 * expect(resolvedValue).toBeUndefined(); 14341 * 14342 * // Propagate promise resolution to 'then' functions using $apply(). 14343 * $rootScope.$apply(); 14344 * expect(resolvedValue).toEqual(123); 14345 * })); 14346 * ``` 14347 * 14348 * @param {function(function, function)} resolver Function which is responsible for resolving or 14349 * rejecting the newly created promise. The first parameter is a function which resolves the 14350 * promise, the second parameter is a function which rejects the promise. 14351 * 14352 * @returns {Promise} The newly created promise. 14353 */ 14354function $QProvider() { 14355 14356 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 14357 return qFactory(function(callback) { 14358 $rootScope.$evalAsync(callback); 14359 }, $exceptionHandler); 14360 }]; 14361} 14362 14363function $$QProvider() { 14364 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 14365 return qFactory(function(callback) { 14366 $browser.defer(callback); 14367 }, $exceptionHandler); 14368 }]; 14369} 14370 14371/** 14372 * Constructs a promise manager. 14373 * 14374 * @param {function(function)} nextTick Function for executing functions in the next turn. 14375 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 14376 * debugging purposes. 14377 * @returns {object} Promise manager. 14378 */ 14379function qFactory(nextTick, exceptionHandler) { 14380 var $qMinErr = minErr('$q', TypeError); 14381 function callOnce(self, resolveFn, rejectFn) { 14382 var called = false; 14383 function wrap(fn) { 14384 return function(value) { 14385 if (called) return; 14386 called = true; 14387 fn.call(self, value); 14388 }; 14389 } 14390 14391 return [wrap(resolveFn), wrap(rejectFn)]; 14392 } 14393 14394 /** 14395 * @ngdoc method 14396 * @name ng.$q#defer 14397 * @kind function 14398 * 14399 * @description 14400 * Creates a `Deferred` object which represents a task which will finish in the future. 14401 * 14402 * @returns {Deferred} Returns a new instance of deferred. 14403 */ 14404 var defer = function() { 14405 return new Deferred(); 14406 }; 14407 14408 function Promise() { 14409 this.$$state = { status: 0 }; 14410 } 14411 14412 Promise.prototype = { 14413 then: function(onFulfilled, onRejected, progressBack) { 14414 var result = new Deferred(); 14415 14416 this.$$state.pending = this.$$state.pending || []; 14417 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 14418 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 14419 14420 return result.promise; 14421 }, 14422 14423 "catch": function(callback) { 14424 return this.then(null, callback); 14425 }, 14426 14427 "finally": function(callback, progressBack) { 14428 return this.then(function(value) { 14429 return handleCallback(value, true, callback); 14430 }, function(error) { 14431 return handleCallback(error, false, callback); 14432 }, progressBack); 14433 } 14434 }; 14435 14436 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 14437 function simpleBind(context, fn) { 14438 return function(value) { 14439 fn.call(context, value); 14440 }; 14441 } 14442 14443 function processQueue(state) { 14444 var fn, deferred, pending; 14445 14446 pending = state.pending; 14447 state.processScheduled = false; 14448 state.pending = undefined; 14449 for (var i = 0, ii = pending.length; i < ii; ++i) { 14450 deferred = pending[i][0]; 14451 fn = pending[i][state.status]; 14452 try { 14453 if (isFunction(fn)) { 14454 deferred.resolve(fn(state.value)); 14455 } else if (state.status === 1) { 14456 deferred.resolve(state.value); 14457 } else { 14458 deferred.reject(state.value); 14459 } 14460 } catch (e) { 14461 deferred.reject(e); 14462 exceptionHandler(e); 14463 } 14464 } 14465 } 14466 14467 function scheduleProcessQueue(state) { 14468 if (state.processScheduled || !state.pending) return; 14469 state.processScheduled = true; 14470 nextTick(function() { processQueue(state); }); 14471 } 14472 14473 function Deferred() { 14474 this.promise = new Promise(); 14475 //Necessary to support unbound execution :/ 14476 this.resolve = simpleBind(this, this.resolve); 14477 this.reject = simpleBind(this, this.reject); 14478 this.notify = simpleBind(this, this.notify); 14479 } 14480 14481 Deferred.prototype = { 14482 resolve: function(val) { 14483 if (this.promise.$$state.status) return; 14484 if (val === this.promise) { 14485 this.$$reject($qMinErr( 14486 'qcycle', 14487 "Expected promise to be resolved with value other than
14487itself '{0}'", 14488 val)); 14489 } else { 14490 this.$$resolve(val); 14491 } 14492 14493 }, 14494 14495 $$resolve: function(val) { 14496 var then, fns; 14497 14498 fns = callOnce(this, this.$$resolve, this.$$reject); 14499 try { 14500 if ((isObject(val) || isFunction(val))) then = val && val.then; 14501 if (isFunction(then)) { 14502 this.promise.$$state.status = -1; 14503 then.call(val, fns[0], fns[1], this.notify); 14504 } else { 14505 this.promise.$$state.value = val; 14506 this.promise.$$state.status = 1; 14507 scheduleProcessQueue(this.promise.$$state); 14508 } 14509 } catch (e) { 14510 fns[1](e); 14511 exceptionHandler(e); 14512 } 14513 }, 14514 14515 reject: function(reason) { 14516 if (this.promise.$$state.status) return; 14517 this.$$reject(reason); 14518 }, 14519 14520 $$reject: function(reason) { 14521 this.promise.$$state.value = reason; 14522 this.promise.$$state.status = 2; 14523 scheduleProcessQueue(this.promise.$$state); 14524 }, 14525 14526 notify: function(progress) { 14527 var callbacks = this.promise.$$state.pending; 14528 14529 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 14530 nextTick(function() { 14531 var callback, result; 14532 for (var i = 0, ii = callbacks.length; i < ii; i++) { 14533 result = callbacks[i][0]; 14534 callback = callbacks[i][3]; 14535 try { 14536 result.notify(isFunction(callback) ? callback(progress) : progress); 14537 } catch (e) { 14538 exceptionHandler(e); 14539 } 14540 } 14541 }); 14542 } 14543 } 14544 }; 14545 14546 /** 14547 * @ngdoc method 14548 * @name $q#reject 14549 * @kind function 14550 * 14551 * @description 14552 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 14553 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 14554 * a promise chain, you don't need to worry about it. 14555 * 14556 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 14557 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 14558 * a promise error callback and you want to forward the error to the promise derived from the 14559 * current promise, you have to "rethrow" the error by returning a rejection constructed via 14560 * `reject`. 14561 * 14562 * ```js 14563 * promiseB = promiseA.then(function(result) { 14564 * // success: do something and resolve promiseB 14565 * // with the old or a new result 14566 * return result; 14567 * }, function(reason) { 14568 * // error: handle the error if possible and 14569 * // resolve promiseB with newPromiseOrValue, 14570 * // otherwise forward the rejection to promiseB 14571 * if (canHandle(reason)) { 14572 * // handle the error and recover 14573 * return newPromiseOrValue; 14574 * } 14575 * return $q.reject(reason); 14576 * }); 14577 * ``` 14578 * 14579 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 14580 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 14581 */ 14582 var reject = function(reason) { 14583 var result = new Deferred(); 14584 result.reject(reason); 14585 return result.promise; 14586 }; 14587 14588 var makePromise = function makePromise(value, resolved) { 14589 var result = new Deferred(); 14590 if (resolved) { 14591 result.resolve(value); 14592 } else { 14593 result.reject(value); 14594 } 14595 return result.promise; 14596 }; 14597 14598 var handleCallback = function handleCallback(value, isResolved, callback) { 14599 var callbackOutput = null; 14600 try { 14601 if (isFunction(callback)) callbackOutput = callback(); 14602 } catch (e) { 14603 return makePromise(e, false); 14604 } 14605 if (isPromiseLike(callbackOutput)) { 14606 return callbackOutput.then(function() { 14607 return makePromise(value, isResolved); 14608 }, function(error) { 14609 return makePromise(error, false); 14610 }); 14611 } else { 14612 return makePromise(value, isResolved); 14613 } 14614 }; 14615 14616 /** 14617 * @ngdoc method 14618 * @name $q#when 14619 * @kind function 14620 * 14621 * @description 14622 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 14623 * This is useful when you are dealing with an object that might or might not be a promise, or if 14624 * the promise comes from a source that can't be trusted. 14625 * 14626 * @param {*} value Value or a promise 14627 * @returns {Promise} Returns a promise of the passed value or promise 14628 */ 14629 14630 14631 var when = function(value, callback, errback, progressBack) { 14632 var result = new Deferred(); 14633 result.resolve(value); 14634 return result.promise.then(callback, errback, progressBack); 14635 }; 14636 14637 /** 14638 * @ngdoc method 14639 * @name $q#all 14640 * @kind function 14641 * 14642 * @description 14643 * Combines multiple promises into a single promise that is resolved when all of the input 14644 * promises are resolved. 14645 * 14646 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 14647 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 14648 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 14649 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 14650 * with the same rejection value. 14651 */ 14652 14653 function all(promises) { 14654 var deferred = new Deferred(), 14655 counter = 0, 14656 results = isArray(promises) ? [] : {}; 14657
14658 forEach(promises, function(promise, key) { 14659 counter++; 14660 when(promise).then(function(value) { 14661 if (results.hasOwnProperty(key)) return; 14662 results[key] = value; 14663 if (!(--counter)) deferred.resolve(results); 14664 }, function(reason) { 14665 if (results.hasOwnProperty(key)) return; 14666 deferred.reject(reason); 14667 }); 14668 }); 14669 14670 if (counter === 0) { 14671 deferred.resolve(results); 14672 } 14673 14674 return deferred.promise; 14675 } 14676 14677 var $Q = function Q(resolver) { 14678 if (!isFunction(resolver)) { 14679 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 14680 } 14681 14682 if (!(this instanceof Q)) { 14683 // More useful when $Q is the Promise itself. 14684 return new Q(resolver); 14685 } 14686 14687 var deferred = new Deferred(); 14688 14689 function resolveFn(value) { 14690 deferred.resolve(value); 14691 } 14692 14693 function rejectFn(reason) { 14694 deferred.reject(reason); 14695 } 14696 14697 resolver(resolveFn, rejectFn); 14698 14699 return deferred.promise; 14700 }; 14701 14702 $Q.defer = defer; 14703 $Q.reject = reject; 14704 $Q.when = when; 14705 $Q.all = all; 14706 14707 return $Q; 14708} 14709 14710function $$RAFProvider() { //rAF 14711 this.$get = ['$window', '$timeout', function($window, $timeout) { 14712 var requestAnimationFrame = $window.requestAnimationFrame || 14713 $window.webkitRequestAnimationFrame; 14714 14715 var cancelAnimationFrame = $window.cancelAnimationFrame || 14716 $window.webkitCancelAnimationFrame || 14717 $window.webkitCancelRequestAnimationFrame; 14718 14719 var rafSupported = !!requestAnimationFrame; 14720 var rafFn = rafSupported 14721 ? function(fn) { 14722 var id = requestAnimationFrame(fn); 14723 return function() { 14724 cancelAnimationFrame(id); 14725 }; 14726 } 14727 : function(fn) { 14728 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 14729 return function() { 14730 $timeout.cancel(timer); 14731 }; 14732 }; 14733 14734 queueFn.supported = rafSupported; 14735 14736 var cancelLastRAF; 14737 var taskCount = 0; 14738 var taskQueue = []; 14739 return queueFn; 14740 14741 function flush() { 14742 for (var i = 0; i < taskQueue.length; i++) { 14743 var task = taskQueue[i]; 14744 if (task) { 14745 taskQueue[i] = null; 14746 task(); 14747 } 14748 } 14749 taskCount = taskQueue.length = 0; 14750 } 14751 14752 function queueFn(asyncFn) { 14753 var index = taskQueue.length; 14754 14755 taskCount++; 14756 taskQueue.push(asyncFn); 14757 14758 if (index === 0) { 14759 cancelLastRAF = rafFn(flush); 14760 } 14761 14762 return function cancelQueueFn() { 14763 if (index >= 0) { 14764 taskQueue[index] = null; 14765 index = null; 14766 14767 if (--taskCount === 0 && cancelLastRAF) { 14768 cancelLastRAF(); 14769 cancelLastRAF = null; 14770 taskQueue.length = 0; 14771 } 14772 } 14773 }; 14774 } 14775 }]; 14776} 14777 14778/** 14779 * DESIGN NOTES 14780 * 14781 * The design decisions behind the scope are heavily favored for speed and memory consumption. 14782 * 14783 * The typical use of scope is to watch the expressions, which most of the time return the same 14784 * value as last time so we optimize the operation. 14785 * 14786 * Closures construction is expensive in terms of speed as well as memory: 14787 * - No closures, instead use prototypical inheritance for API 14788 * - Internal state needs to be stored on scope directly, which means that private state is 14789 * exposed as $$____ properties 14790 * 14791 * Loop operations are optimized by using while(count--) { ... } 14792 * - this means that in order to keep the same order of execution as addition we have to add 14793 * items to the array at the beginning (unshift) instead of at the end (push) 14794 * 14795 * Child scopes are created and removed often 14796 * - Using an array would be slow since inserts in middle are expensive so we use linked list 14797 * 14798 * There are few watches then a lot of observers. This is why you don't want the observer to be 14799 * implemented in the same way as watch. Watch requires return of initialization function which 14800 * are expensive to construct. 14801 */ 14802 14803 14804/** 14805 * @ngdoc provider 14806 * @name $rootScopeProvider 14807 * @description 14808 * 14809 * Provider for the $rootScope service. 14810 */ 14811 14812/** 14813 * @ngdoc method 14814 * @name $rootScopeProvider#digestTtl 14815 * @description 14816 * 14817 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 14818 * assuming that the model is unstable. 14819 * 14820 * The current default is 10 iterations. 14821 * 14822 * In complex applications it's possible that the dependencies between `$watch`s will result in 14823 * several digest iterations. However if an application needs more than the default 10 digest 14824 * iterations for its model to stabilize then you should investigate what is causing the model to 14825 * continuously change during the digest. 14826 * 14827 * Increasing the TTL could have performance implications, so you should not change it without 14828 * proper justification. 14829 * 14830 * @param {number} limit The number of digest iterations. 14831 */ 14832 14833 14834/** 14835 * @ngdoc service 14836 * @name $rootScope 14837 * @description 14838 * 14839 * Every application has a single root {@link ng.$rootScope.Scope scope}. 14840 * All other scopes are descendant scopes of the root scope. Scopes provide separation 14841 * between the model and the view, via a mechanism for watching the model for changes. 14842 * They also provide an event emission/broadcast and subscription facility. See the 14843 * {@link guide/scope developer guide on scopes}. 14844 */ 14845function $RootScopeProvider() { 14846 var TTL = 10; 14847 var $rootScopeMinErr = minErr('$rootScope'); 14848 var lastDirtyWatch = null; 14849 var applyAsyncId = null; 14850 14851 this.digestTtl = function(value) { 14852 if (arguments.length) { 14853 TTL = value; 14854 } 14855 return TTL; 14856 }; 14857 14858 function createChildScopeClass(parent) { 14859 function ChildScope() {
vendor: 4,153 bytes, lines 14860-14976
14860 this.$$watchers = this.$$nextSibling = 14861 this.$$childHead = this.$$childTail = null; 14862 this.$$listeners = {}; 14863 this.$$listenerCount = {}; 14864 this.$$watchersCount = 0; 14865 this.$id = nextUid(); 14866 this.$$ChildScope = null; 14867 } 14868 ChildScope.prototype = parent; 14869 return ChildScope; 14870 } 14871 14872 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 14873 function($injector, $exceptionHandler, $parse, $browser) { 14874 14875 function destroyChildScope($event) { 14876 $event.currentScope.$$destroyed = true; 14877 } 14878 14879 /** 14880 * @ngdoc type 14881 * @name $rootScope.Scope 14882 * 14883 * @description 14884 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 14885 * {@link auto.$injector $injector}. Child scopes are created using the 14886 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 14887 * compiled HTML template is executed.) 14888 * 14889 * Here is a simple scope snippet to show how you can interact with the scope. 14890 * ```html 14891 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 14892 * ``` 14893 * 14894 * # Inheritance 14895 * A scope can inherit from a parent scope, as in this example: 14896 * ```js 14897 var parent = $rootScope; 14898 var child = parent.$new(); 14899 14900 parent.salutation = "Hello"; 14901 expect(child.salutation).toEqual('Hello'); 14902 14903 child.salutation = "Welcome"; 14904 expect(child.salutation).toEqual('Welcome'); 14905 expect(parent.salutation).toEqual('Hello'); 14906 * ``` 14907 * 14908 * When interacting with `Scope` in tests, additional helper methods are available on the 14909 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 14910 * details. 14911 * 14912 * 14913 * @param {Object.<string, function()>=} providers Map of service factory which need to be 14914 * provided for the current scope. Defaults to {@link ng}. 14915 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 14916 * append/override services provided by `providers`. This is handy 14917 * when unit-testing and having the need to override a default 14918 * service. 14919 * @returns {Object} Newly created scope. 14920 * 14921 */ 14922 function Scope() { 14923 this.$id = nextUid(); 14924 this.$$phase = this.$parent = this.$$watchers = 14925 this.$$nextSibling = this.$$prevSibling = 14926 this.$$childHead = this.$$childTail = null; 14927 this.$root = this; 14928 this.$$destroyed = false; 14929 this.$$listeners = {}; 14930 this.$$listenerCount = {}; 14931 this.$$watchersCount = 0; 14932 this.$$isolateBindings = null; 14933 } 14934 14935 /** 14936 * @ngdoc property 14937 * @name $rootScope.Scope#$id 14938 * 14939 * @description 14940 * Unique scope ID (monotonically increasing) useful for debugging. 14941 */ 14942 14943 /** 14944 * @ngdoc property 14945 * @name $rootScope.Scope#$parent 14946 * 14947 * @description 14948 * Reference to the parent scope. 14949 */ 14950 14951 /** 14952 * @ngdoc property 14953 * @name $rootScope.Scope#$root 14954 * 14955 * @description 14956 * Reference to the root scope. 14957 */ 14958 14959 Scope.prototype = { 14960 constructor: Scope, 14961 /** 14962 * @ngdoc method 14963 * @name $rootScope.Scope#$new 14964 * @kind function 14965 * 14966 * @description 14967 * Creates a new child {@link ng.$rootScope.Scope scope}. 14968 * 14969 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 14970 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 14971 * 14972 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 14973 * desired for the scope and its child scopes to be permanently detached from the parent and 14974 * thus stop participating in model change detection and listener notification by invoking. 14975 * 14976 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
14977 * parent scope. The scope is isolated, as it can not see parent scope properties. 14978 * When creating widgets, it is useful for the widget to not accidentally read parent 14979 * state. 14980 * 14981 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 14982 * of the newly created scope. Defaults to `this` scope if not provided. 14983 * This is used when creating a transclude scope to correctly place it 14984 * in the scope hierarchy while maintaining the correct prototypical 14985 * inheritance. 14986 * 14987 * @returns {Object} The newly created child scope. 14988 * 14989 */ 14990 $new: function(isolate, parent) { 14991 var child; 14992 14993 parent = parent || this; 14994 14995 if (isolate) { 14996 child = new Scope(); 14997 child.$root = this.$root; 14998 } else { 14999 // Only create a child scope class if somebody asks for one, 15000 // but cache it to allow the VM to optimize lookups. 15001 if (!this.$$ChildScope) { 15002 this.$$ChildScope = createChildScopeClass(this); 15003 } 15004 child = new this.$$ChildScope(); 15005 } 15006 child.$parent = parent; 15007 child.$$prevSibling = parent.$$childTail; 15008 if (parent.$$childHead) { 15009 parent.$$childTail.$$nextSibling = child; 15010 parent.$$childTail = child; 15011 } else { 15012 parent.$$childHead = parent.$$childTail = child; 15013 } 15014 15015 // When the new scope is not isolated or we inherit from `this`, and 15016 // the parent scope is destroyed, the property `$$destroyed` is inherited 15017 // prototypically. In all other cases, this property needs to be set 15018 // when the parent scope is destroyed. 15019 // The listener needs to be added after the parent is set 15020 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 15021 15022 return child; 15023 }, 15024 15025 /** 15026 * @ngdoc method 15027 * @name $rootScope.Scope#$watch 15028 * @kind function 15029 * 15030 * @description 15031 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 15032 * 15033 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 15034 * $digest()} and should return the value that will be watched. (Since 15035 * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the 15036 * `watchExpression` can execute multiple times per 15037 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 15038 * - The `listener` is called only when the value from the current `watchExpression` and the 15039 * previous call to `watchExpression` are not equal (with the exception of the initial run, 15040 * see below). Inequality is determined according to reference inequality, 15041 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 15042 * via the `!==` Javascript operator, unless `objectEquality == true` 15043 * (see next point) 15044 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 15045 * according to the {@link angular.equals} function. To save the value of the object for 15046 * later comparison, the {@link angular.copy} function is used. This therefore means that 15047 * watching complex objects will have adverse memory and performance implications. 15048 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 15049 * This is achieved by rerunning the watchers until no changes are detected. The rerun 15050 * iteration limit is 10 to prevent an infinite loop deadlock. 15051 * 15052 * 15053 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 15054 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 15055 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a 15056 * change is detected, be prepared for multiple calls to your listener.) 15057 * 15058 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 15059 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 15060 * watcher. In rare cases, this is undesirable because the listener is called when the result 15061 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 15062 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 15063 * listener was called due to initialization. 15064 * 15065 * 15066 * 15067 * # Example 15068 * ```js 15069 // let's assume that scope was dependency injected as the $rootScope 15070 var scope = $rootScope; 15071 scope.name = 'misko'; 15072 scope.counter = 0; 15073 15074 expect(scope.counter).toEqual(0); 15075 scope.$watch('name', function(newValue, oldValue) { 15076 scope.counter = scope.counter + 1; 15077 }); 15078 expect(scope.counter).toEqual(0); 15079 15080 scope.$digest(); 15081 // the listener is always called during the first $digest loop after it was registered 15082 expect(scope.counter).toEqual(1); 15083 15084 scope.$digest(); 15085 // but now it will not be called unless the value changes 15086 expect(scope.counter).toEqual(1); 15087 15088 scope.name = 'adam'; 15089 scope.$digest(); 15090 expect(scope.counter).toEqual(2); 15091 15092 15093
15094 // Using a function as a watchExpression 15095 var food; 15096 scope.foodCounter = 0; 15097 expect(scope.foodCounter).toEqual(0); 15098 scope.$watch( 15099 // This function returns the value being watched. It is called for each turn of the $digest loop 15100 function() { return food; }, 15101 // This is the change listener, called when the value returned from the above function changes 15102 function(newValue, oldValue) { 15103 if ( newValue !== oldValue ) { 15104 // Only increment the counter if the value changed 15105 scope.foodCounter = scope.foodCounter + 1; 15106 } 15107 } 15108 ); 15109 // No digest has been run so the counter will be zero 15110 expect(scope.foodCounter).toEqual(0); 15111 15112 // Run the digest but since food has not changed count will still be zero 15113 scope.$digest(); 15114 expect(scope.foodCounter).toEqual(0); 15115 15116 // Update food and run digest. Now the counter will increment 15117 food = 'cheeseburger'; 15118 scope.$digest(); 15119 expect(scope.foodCounter).toEqual(1); 15120 15121 * ``` 15122 * 15123 * 15124 * 15125 * @param {(function()|string)} watchExpression Expression that is evaluated on each 15126 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 15127 * a call to the `listener`. 15128 * 15129 * - `string`: Evaluated as {@link guide/expression expression} 15130 * - `function(scope)`: called with current `scope` as a parameter. 15131 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 15132 * of `watchExpression` changes. 15133 * 15134 * - `newVal` contains the current value of the `watchExpression` 15135 * - `oldVal` contains the previous value of the `watchExpression` 15136 * - `scope` refers to the current scope 15137 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 15138 * comparing for reference equality. 15139 * @returns {function()} Returns a deregistration function for this listener. 15140 */ 15141 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 15142 var get = $parse(watchExp); 15143 15144 if (get.$$watchDelegate) { 15145 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 15146 } 15147 var scope = this, 15148 array = scope.$$watchers, 15149 watcher = { 15150 fn: listener, 15151 last: initWatchVal, 15152 get: get, 15153 exp: prettyPrintExpression || watchExp, 15154 eq: !!objectEquality 15155 }; 15156 15157 lastDirtyWatch = null; 15158 15159 if (!isFunction(listener)) { 15160 watcher.fn = noop; 15161 } 15162 15163 if (!array) { 15164 array = scope.$$watchers = []; 15165 } 15166 // we use unshift since we use a while loop in $digest for speed. 15167 // the while loop reads in reverse order. 15168 array.unshift(watcher); 15169 incrementWatchersCount(this, 1); 15170 15171 return function deregisterWatch() { 15172 if (arrayRemove(array, watcher) >= 0) { 15173 incrementWatchersCount(scope, -1); 15174 } 15175 lastDirtyWatch = null; 15176 }; 15177 }, 15178 15179 /** 15180 * @ngdoc method 15181 * @name $rootScope.Scope#$watchGroup 15182 * @kind function 15183 * 15184 * @description 15185 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 15186 * If any one expression in the collection changes the `listener` is executed. 15187 * 15188 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 15189 * call to $digest() to see if any items changes. 15190 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 15191 * 15192 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 15193 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 15194 * 15195 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 15196 * expression in `watchExpressions` changes 15197 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 15198 * those of `watchExpression` 15199 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 15200 * those of `watchExpression` 15201 * The `scope` refers to the current scope. 15202 * @returns {function()} Returns a de-registration function for all listeners. 15203 */ 15204 $watchGroup: function(watchExpressions, listener) { 15205 var oldValues = new Array(watchExpressions.length); 15206 var newValues = new Array(watchExpressions.length); 15207 var deregisterFns = []; 15208 var self = this; 15209 var changeReactionScheduled = false;
vendor: 10,180 bytes, lines 15210-15472
15210 var firstRun = true; 15211 15212 if (!watchExpressions.length) { 15213 // No expressions means we call the listener ASAP 15214 var shouldCall = true; 15215 self.$evalAsync(function() { 15216 if (shouldCall) listener(newValues, newValues, self); 15217 }); 15218 return function deregisterWatchGroup() { 15219 shouldCall = false; 15220 }; 15221 } 15222 15223 if (watchExpressions.length === 1) { 15224 // Special case size of one 15225 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 15226 newValues[0] = value; 15227 oldValues[0] = oldValue; 15228 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 15229 }); 15230 } 15231 15232 forEach(watchExpressions, function(expr, i) { 15233 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 15234 newValues[i] = value; 15235 oldValues[i] = oldValue; 15236 if (!changeReactionScheduled) { 15237 changeReactionScheduled = true; 15238 self.$evalAsync(watchGroupAction); 15239 } 15240 }); 15241 deregisterFns.push(unwatchFn); 15242 }); 15243 15244 function watchGroupAction() { 15245 changeReactionScheduled = false; 15246 15247 if (firstRun) { 15248 firstRun = false; 15249 listener(newValues, newValues, self); 15250 } else { 15251 listener(newValues, oldValues, self); 15252 } 15253 } 15254 15255 return function deregisterWatchGroup() { 15256 while (deregisterFns.length) { 15257 deregisterFns.shift()(); 15258 } 15259 }; 15260 }, 15261 15262 15263 /** 15264 * @ngdoc method 15265 * @name $rootScope.Scope#$watchCollection 15266 * @kind function 15267 * 15268 * @description 15269 * Shallow watches the properties of an object and fires whenever any of the properties change 15270 * (for arrays, this implies watching the array items; for object maps, this implies watching 15271 * the properties). If a change is detected, the `listener` callback is fired. 15272 * 15273 * - The `obj` collection is observed via standard $watch operation and is examined on every 15274 * call to $digest() to see if any items have been added, removed, or moved. 15275 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 15276 * adding, removing, and moving items belonging to an object or array. 15277 * 15278 * 15279 * # Example 15280 * ```js 15281 $scope.names = ['igor', 'matias', 'misko', 'james']; 15282 $scope.dataCount = 4; 15283 15284 $scope.$watchCollection('names', function(newNames, oldNames) { 15285 $scope.dataCount = newNames.length; 15286 }); 15287 15288 expect($scope.dataCount).toEqual(4); 15289 $scope.$digest(); 15290 15291 //still at 4 ... no changes 15292 expect($scope.dataCount).toEqual(4); 15293 15294 $scope.names.pop(); 15295 $scope.$digest(); 15296 15297 //now there's been a change 15298 expect($scope.dataCount).toEqual(3); 15299 * ``` 15300 * 15301 * 15302 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 15303 * expression value should evaluate to an object or an array which is observed on each 15304 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 15305 * collection will trigger a call to the `listener`. 15306 * 15307 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 15308 * when a change is detected. 15309 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 15310 * - The `oldCollection` object is a copy of the former collection data. 15311 * Due to performance considerations, the`oldCollection` value is computed only if the 15312 * `listener` function declares two or more arguments. 15313 * - The `scope` argument refers to the current scope. 15314 * 15315 * @returns {function()} Returns a de-registration function for this listener. When the 15316 * de-registration function is executed, the internal watch operation is terminated. 15317 */ 15318 $watchCollection: function(obj, listener) { 15319 $watchCollectionInterceptor.$stateful = true; 15320 15321 var self = this; 15322 // the current value, updated on each dirty-check run 15323 var newValue; 15324 // a shallow copy of the newValue from the last dirty-check run, 15325 // updated to match newValue during dirty-check run 15326 var oldValue; 15327 // a shallow copy of the newValue from when the last change happened 15328 var veryOldValue; 15329 // only track veryOldValue if the listener is asking for it 15330 var trackVeryOldValue = (listener.length > 1); 15331 var changeDetected = 0; 15332 var changeDetector = $parse(obj, $watchCollectionInterceptor); 15333 var internalArray = []; 15334 var internalObject = {}; 15335 var initRun = true; 15336 var oldLength = 0; 15337 15338 function $watchCollectionInterceptor(_value) { 15339 newValue = _value; 15340 var newLength, key, bothNaN, newItem, oldItem; 15341 15342 // If the new value is undefined, then return undefined as the watch may be a one-time watch 15343 if (isUndefined(newValue)) return; 15344 15345 if (!isObject(newValue)) { // if primitive 15346 if (oldValue !== newValue) { 15347 oldValue = newValue; 15348 changeDetected++; 15349 } 15350 } else if (isArrayLike(newValue)) { 15351 if (oldValue !== internalArray) { 15352 // we are transitioning from something which was not an array into array. 15353 oldValue = internalArray; 15354 oldLength = oldValue.length = 0; 15355 changeDetected++; 15356 } 15357 15358 newLength = newValue.length; 15359 15360 if (oldLength !== newLength) { 15361 // if lengths do not match we need to trigger change notification 15362 changeDetected++; 15363 oldValue.length = oldLength = newLength; 15364 } 15365 // copy the items to oldValue and look for changes. 15366 for (var i = 0; i < newLength; i++) { 15367 oldItem = oldValue[i]; 15368 newItem = newValue[i]; 15369 15370 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15371 if (!bothNaN && (oldItem !== newItem)) { 15372 changeDetected++; 15373 oldValue[i] = newItem; 15374 } 15375 } 15376 } else { 15377 if (oldValue !== internalObject) { 15378 // we are transitioning from something which was not an object into object. 15379 oldValue = internalObject = {}; 15380 oldLength = 0; 15381 changeDetected++; 15382 } 15383 // copy the items to oldValue and look for changes. 15384 newLength = 0; 15385 for (key in newValue) { 15386 if (newValue.hasOwnProperty(key)) { 15387 newLength++; 15388 newItem = newValue[key]; 15389 oldItem = oldValue[key]; 15390 15391 if (key in oldValue) { 15392 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15393 if (!bothNaN && (oldItem !== newItem)) { 15394 changeDetected++; 15395 oldValue[key] = newItem; 15396 } 15397 } else { 15398 oldLength++; 15399 oldValue[key] = newItem; 15400 changeDetected++; 15401 } 15402 } 15403 } 15404 if (oldLength > newLength) { 15405 // we used to have more keys, need to find them and destroy them. 15406 changeDetected++; 15407 for (key in oldValue) { 15408 if (!newValue.hasOwnProperty(key)) { 15409 oldLength--; 15410 delete oldValue[key]; 15411 } 15412 } 15413 } 15414 } 15415 return changeDetected; 15416 } 15417 15418 function $watchCollectionAction() { 15419 if (initRun) { 15420 initRun = false; 15421 listener(newValue, newValue, self); 15422 } else { 15423 listener(newValue, veryOldValue, self); 15424 } 15425 15426 // make a copy for the next time a collection is changed 15427 if (trackVeryOldValue) { 15428 if (!isObject(newValue)) { 15429 //primitive 15430 veryOldValue = newValue; 15431 } else if (isArrayLike(newValue)) { 15432 veryOldValue = new Array(newValue.length); 15433 for (var i = 0; i < newValue.length; i++) { 15434 veryOldValue[i] = newValue[i]; 15435 } 15436 } else { // if object 15437 veryOldValue = {}; 15438 for (var key in newValue) { 15439 if (hasOwnProperty.call(newValue, key)) { 15440 veryOldValue[key] = newValue[key]; 15441 } 15442 } 15443 } 15444 } 15445 } 15446 15447 return this.$watch(changeDetector, $watchCollectionAction); 15448 }, 15449 15450 /** 15451 * @ngdoc method 15452 * @name $rootScope.Scope#$digest 15453 * @kind function 15454 * 15455 * @description 15456 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 15457 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 15458 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 15459 * until no more listeners are firing. This means that it is possible to get into an infinite 15460 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 15461 * iterations exceeds 10. 15462 * 15463 * Usually, you don't call `$digest()` directly in 15464 * {@link ng.directive:ngController controllers} or in 15465 * {@link ng.$compileProvider#directive directives}. 15466 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 15467 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 15468 * 15469 * If you want to be notified whenever `$digest()` is called, 15470 * you can register a `watchExpression` function with 15471 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 15472 *
15473 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 15474 * 15475 * # Example 15476 * ```js 15477 var scope = ...; 15478 scope.name = 'misko'; 15479 scope.counter = 0; 15480 15481 expect(scope.counter).toEqual(0); 15482 scope.$watch('name', function(newValue, oldValue) { 15483 scope.counter = scope.counter + 1; 15484 }); 15485 expect(scope.counter).toEqual(0); 15486 15487 scope.$digest(); 15488 // the listener is always called during the first $digest loop after it was registered 15489 expect(scope.counter).toEqual(1); 15490 15491 scope.$digest(); 15492 // but now it will not be called unless the value changes 15493 expect(scope.counter).toEqual(1); 15494 15495 scope.name = 'adam'; 15496 scope.$digest(); 15497 expect(scope.counter).toEqual(2); 15498 * ``` 15499 * 15500 */ 15501 $digest: function() { 15502 var watch, value, last, 15503 watchers, 15504 length, 15505 dirty, ttl = TTL, 15506 next, current, target = this, 15507 watchLog = [], 15508 logIdx, logMsg, asyncTask; 15509 15510 beginPhase('$digest'); 15511 // Check for changes to browser url that happened in sync before the call to $digest 15512 $browser.$$checkUrlChange(); 15513 15514 if (this === $rootScope && applyAsyncId !== null) { 15515 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 15516 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 15517 $browser.defer.cancel(applyAsyncId); 15518 flushApplyAsync(); 15519 } 15520 15521 lastDirtyWatch = null; 15522 15523 do { // "while dirty" loop 15524 dirty = false; 15525 current = target; 15526 15527 while (asyncQueue.length) { 15528 try { 15529 asyncTask = asyncQueue.shift(); 15530 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 15531 } catch (e) { 15532 $exceptionHandler(e); 15533 } 15534 lastDirtyWatch = null; 15535 } 15536 15537 traverseScopesLoop: 15538 do { // "traverse the scopes" loop 15539 if ((watchers = current.$$watchers)) { 15540 // process our watches 15541 length = watchers.length; 15542 while (length--) { 15543 try { 15544 watch = watchers[length]; 15545 // Most common watches are on primitives, in which case we can short 15546 // circuit it with === operator, only when === fails do we use .equals 15547 if (watch) { 15548 if ((value = watch.get(current)) !== (last = watch.last) && 15549 !(watch.eq 15550 ? equals(value, last) 15551 : (typeof value === 'number' && typeof last === 'number' 15552 && isNaN(value) && isNaN(last)))) { 15553 dirty = true; 15554 lastDirtyWatch = watch; 15555 watch.last = watch.eq ? copy(value, null) : value; 15556 watch.fn(value, ((last === initWatchVal) ? value : last), current); 15557 if (ttl < 5) { 15558 logIdx = 4 - ttl; 15559 if (!watchLog[logIdx]) watchLog[logIdx] = []; 15560 watchLog[logIdx].push({ 15561 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 15562 newVal: value, 15563 oldVal: last 15564 }); 15565 } 15566 } else if (watch === lastDirtyWatch) { 15567 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 15568 // have already been tested. 15569 dirty = false; 15570 break traverseScopesLoop; 15571 } 15572 } 15573 } catch (e) { 15574 $exceptionHandler(e); 15575 } 15576 } 15577 } 15578 15579 // Insanity Warning: scope depth-first traversal 15580 // yes, this code is a bit crazy, but it works and we have tests to prove it! 15581 // this piece should be kept in sync with the traversal in $broadcast 15582 if (!(next = ((current.$$watchersCount && current.$$childHead) || 15583 (current !== target && current.$$nextSibling)))) { 15584 while (current !== target && !(next = current.$$nextSibling)) { 15585 current = current.$parent; 15586 } 15587 } 15588 } while ((current = next)); 15589 15590 // `break traverseScopesLoop;` takes us to here 15591 15592 if ((dirty || asyncQueue.length) && !(ttl--)) { 15593 clearPhase(); 15594 throw $rootScopeMinErr('infdig', 15595 '{0} $digest() iterations reached. Aborting!\n' + 15596 'Watchers fired in the last 5 iterations: {1}', 15597 TTL, watchLog); 15598 } 15599 15600 }
vendor: 5,032 bytes, lines 15600-15726
15600 while (dirty || asyncQueue.length); 15601 15602 clearPhase(); 15603 15604 while (postDigestQueue.length) { 15605 try { 15606 postDigestQueue.shift()(); 15607 } catch (e) { 15608 $exceptionHandler(e); 15609 } 15610 } 15611 }, 15612 15613 15614 /** 15615 * @ngdoc event 15616 * @name $rootScope.Scope#$destroy 15617 * @eventType broadcast on scope being destroyed 15618 * 15619 * @description 15620 * Broadcasted when a scope and its children are being destroyed. 15621 * 15622 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 15623 * clean up DOM bindings before an element is removed from the DOM. 15624 */ 15625 15626 /** 15627 * @ngdoc method 15628 * @name $rootScope.Scope#$destroy 15629 * @kind function 15630 * 15631 * @description 15632 * Removes the current scope (and all of its children) from the parent scope. Removal implies 15633 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 15634 * propagate to the current scope and its children. Removal also implies that the current 15635 * scope is eligible for garbage collection. 15636 * 15637 * The `$destroy()` is usually used by directives such as 15638 * {@link ng.directive:ngRepeat ngRepeat} for managing the 15639 * unrolling of the loop. 15640 * 15641 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 15642 * Application code can register a `$destroy` event handler that will give it a chance to 15643 * perform any necessary cleanup. 15644 * 15645 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 15646 * clean up DOM bindings before an element is removed from the DOM. 15647 */ 15648 $destroy: function() { 15649 // We can't destroy a scope that has been already destroyed. 15650 if (this.$$destroyed) return; 15651 var parent = this.$parent; 15652 15653 this.$broadcast('$destroy'); 15654 this.$$destroyed = true; 15655 15656 if (this === $rootScope) { 15657 //Remove handlers attached to window when $rootScope is removed 15658 $browser.$$applicationDestroyed(); 15659 } 15660 15661 incrementWatchersCount(this, -this.$$watchersCount); 15662 for (var eventName in this.$$listenerCount) { 15663 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 15664 } 15665 15666 // sever all the references to parent scopes (after this cleanup, the current scope should 15667 // not be retained by any of our references and should be eligible for garbage collection) 15668 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 15669 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 15670 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 15671 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 15672 15673 // Disable listeners, watchers and apply/digest methods 15674 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 15675 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 15676 this.$$listeners = {}; 15677 15678 // All of the code below is bogus code that works around V8's memory leak via optimized code 15679 // and inline caches. 15680 // 15681 // see: 15682 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 15683 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 15684 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 15685 15686 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 15687 this.$$childTail = this.$root = this.$$watchers = null; 15688 }, 15689 15690 /** 15691 * @ngdoc method 15692 * @name $rootScope.Scope#$eval 15693 * @kind function 15694 * 15695 * @description 15696 * Executes the `expression` on the current scope and returns the result. Any exceptions in 15697 * the expression are propagated (uncaught). This is useful when evaluating Angular 15698 * expressions. 15699 * 15700 * # Example 15701 * ```js 15702 var scope = ng.$rootScope.Scope(); 15703 scope.a = 1; 15704 scope.b = 2; 15705 15706 expect(scope.$eval('a+b')).toEqual(3); 15707 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 15708 * ``` 15709 * 15710 * @param {(string|function())=} expression An angular expression to be executed. 15711 * 15712 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15713 * - `function(scope)`: execute the function with the current `scope` parameter. 15714 * 15715 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 15716 * @returns {*} The result of evaluating the expression. 15717 */ 15718 $eval: function(expr, locals) { 15719 return $parse(expr)(this, locals); 15720 }, 15721 15722 /** 15723 * @ngdoc method 15724 * @name $rootScope.Scope#$evalAsync 15725 * @kind function 15726 *
15727 * @description 15728 * Executes the expression on the current scope at a later point in time. 15729 * 15730 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 15731 * that: 15732 * 15733 * - it will execute after the function that scheduled the evaluation (preferably before DOM 15734 * rendering). 15735 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 15736 * `expression` execution. 15737 * 15738 * Any exceptions from the execution of the expression are forwarded to the 15739 * {@link ng.$exceptionHandler $exceptionHandler} service. 15740 * 15741 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 15742 * will be scheduled. However, it is encouraged to always call code that changes the model 15743 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 15744 * 15745 * @param {(string|function())=} expression An angular expression to be executed. 15746 * 15747 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15748 * - `function(scope)`: execute the function with the current `scope` parameter. 15749 * 15750 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 15751 */ 15752 $evalAsync: function(expr, locals) { 15753 // if we are outside of an $digest loop and this is the first time we are scheduling async 15754 // task also schedule async auto-flush 15755 if (!$rootScope.$$phase && !asyncQueue.length) { 15756 $browser.defer(function() { 15757 if (asyncQueue.length) { 15758 $rootScope.$digest(); 15759 } 15760 }); 15761 } 15762 15763 asyncQueue.push({scope: this, expression: expr, locals: locals}); 15764 }, 15765 15766 $$postDigest: function(fn) { 15767 postDigestQueue.push(fn); 15768 }, 15769 15770 /** 15771 * @ngdoc method 15772 * @name $rootScope.Scope#$apply 15773 * @kind function 15774 * 15775 * @description 15776 * `$apply()` is used to execute an expression in angular from outside of the angular 15777 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 15778 * Because we are calling into the angular framework we need to perform proper scope life 15779 * cycle of {@link ng.$exceptionHandler exception handling}, 15780 * {@link ng.$rootScope.Scope#$digest executing watches}. 15781 * 15782 * ## Life cycle 15783 * 15784 * # Pseudo-Code of `$apply()` 15785 * ```js 15786 function $apply(expr) { 15787 try { 15788 return $eval(expr); 15789 } catch (e) { 15790 $exceptionHandler(e); 15791 } finally { 15792 $root.$digest(); 15793 } 15794 } 15795 * ``` 15796 * 15797 * 15798 * Scope's `$apply()` method transitions through the following stages: 15799 * 15800 * 1. The {@link guide/expression expression} is executed using the 15801 * {@link ng.$rootScope.Scope#$eval $eval()} method. 15802 * 2. Any exceptions from the execution of the expression are forwarded to the 15803 * {@link ng.$exceptionHandler $exceptionHandler} service. 15804 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 15805 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 15806 * 15807 * 15808 * @param {(string|function())=} exp An angular expression to be executed. 15809 * 15810 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15811 * - `function(scope)`: execute the function with current `scope` parameter. 15812 * 15813 * @returns {*} The result of evaluating the expression. 15814 */ 15815 $apply: function(expr) { 15816 try { 15817 beginPhase('$apply'); 15818 return this.$eval(expr); 15819 } catch (e) { 15820 $exceptionHandler(e); 15821 } finally { 15822 clearPhase(); 15823 try { 15824 $rootScope.$digest(); 15825 } catch (e) { 15826 $exceptionHandler(e); 15827 throw e; 15828 } 15829 } 15830 }, 15831 15832 /** 15833 * @ngdoc method 15834 * @name $rootScope.Scope#$applyAsync 15835 * @kind function 15836 *
15837 * @description 15838 * Schedule the invocation of $apply to occur at a later time. The actual time difference 15839 * varies across browsers, but is typically around ~10 milliseconds. 15840 * 15841 * This can be used to queue up multiple expressions which need to be evaluated in the same 15842 * digest. 15843 * 15844 * @param {(string|function())=} exp An angular expression to be executed. 15845 * 15846 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15847 * - `function(scope)`: execute the function with current `scope` parameter. 15848 */ 15849 $applyAsync: function(expr) { 15850 var scope = this; 15851 expr && applyAsyncQueue.push($applyAsyncExpression); 15852 scheduleApplyAsync(); 15853 15854 function $applyAsyncExpression() { 15855 scope.$eval(expr); 15856 } 15857 }, 15858 15859 /** 15860 * @ngdoc method 15861 * @name $rootScope.Scope#$on 15862 * @kind function 15863 * 15864 * @description 15865 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 15866 * discussion of event life cycle. 15867 * 15868 * The event listener function format is: `function(event, args...)`. The `event` object 15869 * passed into the listener has the following attributes: 15870 * 15871 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 15872 * `$broadcast`-ed. 15873 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 15874 * event propagates through the scope hierarchy, this property is set to null. 15875 * - `name` - `{string}`: name of the event. 15876 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 15877 * further event propagation (available only for events that were `$emit`-ed). 15878 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 15879 * to true. 15880 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 15881 * 15882 * @param {string} name Event name to listen on. 15883 * @param {function(event, ...args)} listener Function to call when the event is emitted. 15884 * @returns {function()} Returns a deregistration function for this listener. 15885 */ 15886 $on: function(name, listener) { 15887 var namedListeners = this.$$listeners[name]; 15888 if (!namedListeners) { 15889 this.$$listeners[name] = namedListeners = []; 15890 } 15891 namedListeners.push(listener); 15892 15893 var current = this; 15894 do { 15895 if (!current.$$listenerCount[name]) { 15896 current.$$listenerCount[name] = 0; 15897 } 15898 current.$$listenerCount[name]++; 15899 } while ((current = current.$parent)); 15900 15901 var self = this; 15902 return function() { 15903 var indexOfListener = namedListeners.indexOf(listener); 15904 if (indexOfListener !== -1) { 15905 namedListeners[indexOfListener] = null; 15906 decrementListenerCount(self, 1, name); 15907 } 15908 }; 15909 }, 15910 15911 15912 /** 15913 * @ngdoc method 15914 * @name $rootScope.Scope#$emit 15915 * @kind function 15916 * 15917 * @description 15918 * Dispatches an event `name` upwards through the scope hierarchy notifying the 15919 * registered {@link ng.$rootScope.Scope#$on} listeners. 15920 * 15921 * The event life cycle starts at the scope on which `$emit` was called. All 15922 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 15923 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 15924 * registered listeners along the way. The event will stop propagating if one of the listeners 15925 * cancels it. 15926 * 15927 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 15928 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 15929 * 15930 * @param {string} name Event name to emit. 15931 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 15932 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 15933 */ 15934 $emit: function(name, args) { 15935 var empty = [], 15936 namedListeners, 15937 scope = this, 15938 stopPropagation = false, 15939 event = { 15940 name: name, 15941 targetScope: scope, 15942 stopPropagation: function() {stopPropagation = true;}, 15943 preventDefault: function() { 15944 event.defaultPrevented = true; 15945 }, 15946 defaultPrevented: false 15947 }, 15948 listenerArgs = concat([event], arguments, 1), 15949 i, length; 15950 15951 do { 15952 namedListeners = scope.$$listeners[name] || empty; 15953 event.currentScope = scope; 15954 for (i = 0, length = namedListeners.length; i < length; i++) { 15955 15956 // if listeners were deregistered, defragment the array 15957 if (!namedListeners[i]) { 15958 namedListeners.splice(i, 1); 15959 i--; 15960 length--; 15961 continue; 15962 } 15963 try { 15964 //allow all listeners attached to the current scope to run 15965 namedListeners[i].apply(null, listenerArgs); 15966 } catch (e) { 15967 $exceptionHandler(e); 15968 } 15969 } 15970 //if any listener on the current scope stops propagation, prevent bubbling 15971 if (stopPropagation) { 15972 event.currentScope = null; 15973 return event; 15974 } 15975 //traverse upwards 15976 scope = scope.$parent; 15977 } while (scope); 15978 15979 event.currentScope = null; 15980 15981 return event; 15982 }, 15983 15984 15985 /** 15986 * @ngdoc method 15987 * @name $rootScope.Scope#$broadcast 15988 * @kind function 15989 *
15990 * @description 15991 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 15992 * registered {@link ng.$rootScope.Scope#$on} listeners. 15993 * 15994 * The event life cycle starts at the scope on which `$broadcast` was called. All 15995 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 15996 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 15997 * scope and calls all registered listeners along the way. The event cannot be canceled. 15998 * 15999 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16000 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16001 * 16002 * @param {string} name Event name to broadcast. 16003 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16004 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 16005 */ 16006 $broadcast: function(name, args) { 16007 var target = this, 16008 current = target, 16009 next = target, 16010 event = { 16011 name: name, 16012 targetScope: target, 16013 preventDefault: function() { 16014 event.defaultPrevented = true; 16015 }, 16016 defaultPrevented: false 16017 }; 16018 16019 if (!target.$$listenerCount[name]) return event; 16020 16021 var listenerArgs = concat([event], arguments, 1), 16022 listeners, i, length; 16023 16024 //down while you can, then up and next sibling or up and next sibling until back at root 16025 while ((current = next)) { 16026 event.currentScope = current; 16027 listeners = current.$$listeners[name] || []; 16028 for (i = 0, length = listeners.length; i < length; i++) { 16029 // if listeners were deregistered, defragment the array 16030 if (!listeners[i]) { 16031 listeners.splice(i, 1); 16032 i--; 16033 length--; 16034 continue; 16035 } 16036 16037 try { 16038 listeners[i].apply(null, listenerArgs); 16039 } catch (e) { 16040 $exceptionHandler(e); 16041 } 16042 } 16043 16044 // Insanity Warning: scope depth-first traversal 16045 // yes, this code is a bit crazy, but it works and we have tests to prove it! 16046 // this piece should be kept in sync with the traversal in $digest 16047 // (though it differs due to having the extra check for $$listenerCount) 16048 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 16049 (current !== target && current.$$nextSibling)))) { 16050 while (current !== target && !(next = current.$$nextSibling)) { 16051 current = current.$parent; 16052 } 16053 } 16054 } 16055 16056 event.currentScope = null; 16057 return event; 16058 } 16059 }; 16060 16061 var $rootScope = new Scope(); 16062 16063 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 16064 var asyncQueue = $rootScope.$$asyncQueue = []; 16065 var postDigestQueue = $rootScope.$$postDigestQueue = []; 16066 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 16067 16068 return $rootScope; 16069 16070 16071 function beginPhase(phase) { 16072 if ($rootScope.$$phase) { 16073 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 16074 } 16075 16076 $rootScope.$$phase = phase; 16077 } 16078 16079 function clearPhase() { 16080 $rootScope.$$phase = null; 16081 } 16082 16083 function incrementWatchersCount(current, count) { 16084 do { 16085 current.$$watchersCount += count; 16086 } while ((current = current.$parent)); 16087 } 16088 16089 function decrementListenerCount(current, count, name) { 16090 do { 16091 current.$$listenerCount[name] -= count; 16092 16093 if (current.$$listenerCount[name] === 0) { 16094 delete current.$$listenerCount[name]; 16095 } 16096 } while ((current = current.$parent)); 16097 } 16098 16099 /** 16100 * function used as an initial value for watchers. 16101 * because it's unique we can easily tell it apart from other values 16102 */ 16103 function initWatchVal() {} 16104 16105 function flushApplyAsync() {
vendor: 4,838 bytes, lines 16106-16233
16106 while (applyAsyncQueue.length) { 16107 try { 16108 applyAsyncQueue.shift()(); 16109 } catch (e) { 16110 $exceptionHandler(e); 16111 } 16112 } 16113 applyAsyncId = null; 16114 } 16115 16116 function scheduleApplyAsync() { 16117 if (applyAsyncId === null) { 16118 applyAsyncId = $browser.defer(function() { 16119 $rootScope.$apply(flushApplyAsync); 16120 }); 16121 } 16122 } 16123 }]; 16124} 16125 16126/** 16127 * @description 16128 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 16129 */ 16130function $$SanitizeUriProvider() { 16131 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 16132 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 16133 16134 /** 16135 * @description 16136 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16137 * urls during a[href] sanitization. 16138 * 16139 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16140 * 16141 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 16142 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 16143 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16144 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16145 * 16146 * @param {RegExp=} regexp New regexp to whitelist urls with. 16147 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16148 * chaining otherwise. 16149 */ 16150 this.aHrefSanitizationWhitelist = function(regexp) { 16151 if (isDefined(regexp)) { 16152 aHrefSanitizationWhitelist = regexp; 16153 return this; 16154 } 16155 return aHrefSanitizationWhitelist; 16156 }; 16157 16158 16159 /** 16160 * @description 16161 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16162 * urls during img[src] sanitization. 16163 * 16164 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16165 * 16166 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 16167 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 16168 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16169 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16170 * 16171 * @param {RegExp=} regexp New regexp to whitelist urls with. 16172 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16173 * chaining otherwise. 16174 */ 16175 this.imgSrcSanitizationWhitelist = function(regexp) { 16176 if (isDefined(regexp)) { 16177 imgSrcSanitizationWhitelist = regexp; 16178 return this; 16179 } 16180 return imgSrcSanitizationWhitelist; 16181 }; 16182 16183 this.$get = function() { 16184 return function sanitizeUri(uri, isImage) { 16185 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 16186 var normalizedVal; 16187 normalizedVal = urlResolve(uri).href; 16188 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 16189 return 'unsafe:' + normalizedVal; 16190 } 16191 return uri; 16192 }; 16193 }; 16194} 16195 16196/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16197 * Any commits to this file should be reviewed with security in mind. * 16198 * Changes to this file can potentially create security vulnerabilities. * 16199 * An approval from 2 Core members with history of modifying * 16200 * this file is required. * 16201 * * 16202 * Does the change somehow allow for arbitrary javascript to be executed? * 16203 * Or allows for someone to change the prototype of built-in objects? * 16204 * Or gives undesired access to variables likes document or window? * 16205 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 16206 16207var $sceMinErr = minErr('$sce'); 16208 16209var SCE_CONTEXTS = { 16210 HTML: 'html', 16211 CSS: 'css', 16212 URL: 'url', 16213 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 16214 // url. (e.g. ng-include, script src, templateUrl) 16215 RESOURCE_URL: 'resourceUrl', 16216 JS: 'js' 16217}; 16218 16219// Helper functions follow. 16220 16221function adjustMatcher(matcher) { 16222 if (matcher === 'self') { 16223 return matcher; 16224 } else if (isString(matcher)) { 16225 // Strings match exactly except for 2 wildcards - '*' and '**'. 16226 // '*' matches any character except those from the set ':/.?&'. 16227 // '**' matches any character (like .* in a RegExp). 16228 // More than 2 *'s raises an error as it's ill defined. 16229 if (matcher.indexOf('***') > -1) { 16230 throw $sceMinErr('iwcard', 16231 'Illegal sequence *** in string matcher. String: {0}', matcher); 16232 } 16233 matcher = escapeForRegexp(matcher).
16234 replace('\\*\\*', '.*'). 16235 replace('\\*', '[^:/.?&;]*'); 16236 return new RegExp('^' + matcher + '$'); 16237 } else if (isRegExp(matcher)) { 16238 // The only other type of matcher allowed is a Regexp. 16239 // Match entire URL / disallow partial matches. 16240 // Flags are reset (i.e. no global, ignoreCase or multiline) 16241 return new RegExp('^' + matcher.source + '$'); 16242 } else { 16243 throw $sceMinErr('imatcher', 16244 'Matchers may only be "self", string patterns or RegExp objects'); 16245 } 16246} 16247 16248 16249function adjustMatchers(matchers) { 16250 var adjustedMatchers = []; 16251 if (isDefined(matchers)) { 16252 forEach(matchers, function(matcher) { 16253 adjustedMatchers.push(adjustMatcher(matcher)); 16254 }); 16255 } 16256 return adjustedMatchers; 16257} 16258 16259 16260/** 16261 * @ngdoc service 16262 * @name $sceDelegate 16263 * @kind function 16264 * 16265 * @description 16266 * 16267 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 16268 * Contextual Escaping (SCE)} services to AngularJS. 16269 * 16270 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 16271 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 16272 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 16273 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 16274 * work because `$sce` delegates to `$sceDelegate` for these operations. 16275 * 16276 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 16277 * 16278 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 16279 * can override it completely to change the behavior of `$sce`, the common case would 16280 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 16281 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 16282 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 16283 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 16284 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16285 */ 16286 16287/** 16288 * @ngdoc provider 16289 * @name $sceDelegateProvider 16290 * @description 16291 * 16292 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 16293 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 16294 * that the URLs used for sourcing Angular templates are safe. Refer {@link 16295 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 16296 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16297 * 16298 * For the general details about this service in Angular, read the main page for {@link ng.$sce 16299 * Strict Contextual Escaping (SCE)}. 16300 * 16301 * **Example**: Consider the following case. <a name="example"></a> 16302 * 16303 * - your app is hosted at url `http://myapp.example.com/` 16304 * - but some of your templates are hosted on other domains you control such as 16305 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 16306 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 16307 * 16308 * Here is what a secure configuration for this scenario might look like: 16309 * 16310 * ``` 16311 * angular.module('myApp', []).config(function($sceDelegateProvider) { 16312 * $sceDelegateProvider.resourceUrlWhitelist([ 16313 * // Allow same origin resource loads. 16314 * 'self', 16315 * // Allow loading from our assets domain. Notice the difference between * and **. 16316 * 'http://srv*.assets.example.com/**' 16317 * ]); 16318 * 16319 * // The blacklist overrides the whitelist so the open redirect here is blocked. 16320 * $sceDelegateProvider.resourceUrlBlacklist([ 16321 * 'http://myapp.example.com/clickThru**' 16322 * ]); 16323 * }); 16324 * ``` 16325 */ 16326 16327function $SceDelegateProvider() { 16328 this.SCE_CONTEXTS = SCE_CONTEXTS; 16329 16330 // Resource URLs can also be trusted by policy. 16331 var resourceUrlWhitelist = ['self'], 16332 resourceUrlBlacklist = []; 16333 16334 /** 16335 * @ngdoc method 16336 * @name $sceDelegateProvider#resourceUrlWhitelist 16337 * @kind function 16338 * 16339 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 16340 * provided. This must be an array or null. A snapshot of this array is used so further 16341 * changes to the array are ignored. 16342 * 16343 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16344 * allowed in this array.
vendor: 4,148 bytes, lines 16345-16469
16345 * 16346 * Note: **an empty whitelist array will block all URLs**! 16347 * 16348 * @return {Array} the currently set whitelist array. 16349 * 16350 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 16351 * same origin resource requests. 16352 * 16353 * @description 16354 * Sets/Gets the whitelist of trusted resource URLs. 16355 */ 16356 this.resourceUrlWhitelist = function(value) { 16357 if (arguments.length) { 16358 resourceUrlWhitelist = adjustMatchers(value); 16359 } 16360 return resourceUrlWhitelist; 16361 }; 16362 16363 /** 16364 * @ngdoc method 16365 * @name $sceDelegateProvider#resourceUrlBlacklist 16366 * @kind function 16367 * 16368 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 16369 * provided. This must be an array or null. A snapshot of this array is used so further 16370 * changes to the array are ignored. 16371 * 16372 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16373 * allowed in this array. 16374 * 16375 * The typical usage for the blacklist is to **block 16376 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 16377 * these would otherwise be trusted but actually return content from the redirected domain. 16378 * 16379 * Finally, **the blacklist overrides the whitelist** and has the final say. 16380 * 16381 * @return {Array} the currently set blacklist array. 16382 * 16383 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 16384 * is no blacklist.) 16385 * 16386 * @description 16387 * Sets/Gets the blacklist of trusted resource URLs. 16388 */ 16389 16390 this.resourceUrlBlacklist = function(value) { 16391 if (arguments.length) { 16392 resourceUrlBlacklist = adjustMatchers(value); 16393 } 16394 return resourceUrlBlacklist; 16395 }; 16396 16397 this.$get = ['$injector', function($injector) { 16398 16399 var htmlSanitizer = function htmlSanitizer(html) { 16400 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 16401 }; 16402 16403 if ($injector.has('$sanitize')) { 16404 htmlSanitizer = $injector.get('$sanitize'); 16405 } 16406 16407 16408 function matchUrl(matcher, parsedUrl) { 16409 if (matcher === 'self') { 16410 return urlIsSameOrigin(parsedUrl); 16411 } else { 16412 // definitely a regex. See adjustMatchers() 16413 return !!matcher.exec(parsedUrl.href); 16414 } 16415 } 16416 16417 function isResourceUrlAllowedByPolicy(url) { 16418 var parsedUrl = urlResolve(url.toString()); 16419 var i, n, allowed = false; 16420 // Ensure that at least one item from the whitelist allows this url. 16421 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 16422 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 16423 allowed = true; 16424 break; 16425 } 16426 } 16427 if (allowed) { 16428 // Ensure that no item from the blacklist blocked this url. 16429 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 16430 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 16431 allowed = false; 16432 break; 16433 } 16434 } 16435 } 16436 return allowed; 16437 } 16438 16439 function generateHolderType(Base) { 16440 var holderType = function TrustedValueHolderType(trustedValue) { 16441 this.$$unwrapTrustedValue = function() { 16442 return trustedValue; 16443 }; 16444 }; 16445 if (Base) { 16446 holderType.prototype = new Base(); 16447 } 16448 holderType.prototype.valueOf = function sceValueOf() { 16449 return this.$$unwrapTrustedValue(); 16450 }; 16451 holderType.prototype.toString = function sceToString() { 16452 return this.$$unwrapTrustedValue().toString(); 16453 }; 16454 return holderType; 16455 } 16456 16457 var trustedValueHolderBase = generateHolderType(), 16458 byType = {}; 16459 16460 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 16461 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 16462 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 16463 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 16464 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 16465 16466 /** 16467 * @ngdoc method 16468 * @name $sceDelegate#trustAs 16469 *
16470 * @description 16471 * Returns an object that is trusted by angular for use in specified strict 16472 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 16473 * attribute interpolation, any dom event binding attribute interpolation 16474 * such as for onclick, etc.) that uses the provided value. 16475 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 16476 * 16477 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 16478 * resourceUrl, html, js and css. 16479 * @param {*} value The value that that should be considered trusted/safe. 16480 * @returns {*} A value that can be used to stand in for the provided `value` in places 16481 * where Angular expects a $sce.trustAs() return value. 16482 */ 16483 function trustAs(type, trustedValue) { 16484 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 16485 if (!Constructor) { 16486 throw $sceMinErr('icontext', 16487 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 16488 type, trustedValue); 16489 } 16490 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 16491 return trustedValue; 16492 } 16493 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 16494 // mutable objects, we ensure here that the value passed in is actually a string. 16495 if (typeof trustedValue !== 'string') { 16496 throw $sceMinErr('itype', 16497 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 16498 type); 16499 } 16500 return new Constructor(trustedValue); 16501 } 16502 16503 /** 16504 * @ngdoc method 16505 * @name $sceDelegate#valueOf 16506 * 16507 * @description 16508 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 16509 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 16510 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 16511 * 16512 * If the passed parameter is not a value that had been returned by {@link 16513 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 16514 * 16515 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 16516 * call or anything else. 16517 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 16518 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 16519 * `value` unchanged. 16520 */ 16521 function valueOf(maybeTrusted) { 16522 if (maybeTrusted instanceof trustedValueHolderBase) { 16523 return maybeTrusted.$$unwrapTrustedValue(); 16524 } else { 16525 return maybeTrusted; 16526 } 16527 } 16528 16529 /** 16530 * @ngdoc method 16531 * @name $sceDelegate#getTrusted 16532 * 16533 * @description 16534 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 16535 * returns the originally supplied value if the queried context type is a supertype of the 16536 * created type. If this condition isn't satisfied, throws an exception. 16537 * 16538 * @param {string} type The kind of context in which this value is to be used. 16539 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 16540 * `$sceDelegate.trustAs`} call. 16541 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 16542 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 16543 */ 16544 function getTrusted(type, maybeTrusted) { 16545 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 16546 return maybeTrusted; 16547 } 16548 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 16549 if (constructor && maybeTrusted instanceof constructor) { 16550 return maybeTrusted.$$unwrapTrustedValue(); 16551 } 16552 // If we get here, then we may only take one of two actions. 16553 // 1. sanitize the value for the requested type, or 16554 // 2. throw an exception. 16555 if (type === SCE_CONTEXTS.RESOURCE_URL) { 16556 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 16557 return maybeTrusted; 16558 } else { 16559 throw $sceMinErr('insecurl', 16560 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 16561 maybeTrusted.toString()); 16562 } 16563 } else if (type === SCE_CONTEXTS.HTML) { 16564 return htmlSanitizer(maybeTrusted); 16565 } 16566 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 16567 } 16568 16569 return { trustAs: trustAs, 16570 getTrusted: getTrusted,
16571 valueOf: valueOf }; 16572 }]; 16573} 16574 16575 16576/** 16577 * @ngdoc provider 16578 * @name $sceProvider 16579 * @description 16580 * 16581 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 16582 * - enable/disable Strict Contextual Escaping (SCE) in a module 16583 * - override the default implementation with a custom delegate 16584 * 16585 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 16586 */ 16587 16588/* jshint maxlen: false*/ 16589 16590/** 16591 * @ngdoc service 16592 * @name $sce 16593 * @kind function 16594 * 16595 * @description 16596 * 16597 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 16598 * 16599 * # Strict Contextual Escaping 16600 * 16601 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 16602 * contexts to result in a value that is marked as safe to use for that context. One example of 16603 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 16604 * to these contexts as privileged or SCE contexts. 16605 * 16606 * As of version 1.2, Angular ships with SCE enabled by default. 16607 * 16608 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 16609 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 16610 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
16611 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 16612 * to the top of your HTML document. 16613 * 16614 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 16615 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 16616 * 16617 * Here's an example of a binding in a privileged context: 16618 * 16619 * ``` 16620 * <input ng-model="userHtml" aria-label="User input"> 16621 * <div ng-bind-html="userHtml"></div> 16622 * ``` 16623 * 16624 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 16625 * disabled, this application allows the user to render arbitrary HTML into the DIV. 16626 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 16627 * bindings. (HTML is just one example of a context where rendering user controlled input creates 16628 * security vulnerabilities.) 16629 * 16630 * For the case of HTML, you might use a library, either on the client side, or on the server side, 16631 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 16632 * 16633 * How would you ensure that every place that used these types of bindings was bound to a value that 16634 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 16635 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 16636 * properties/fields and forgot to update the binding to the sanitized value? 16637 * 16638 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 16639 * determine that something explicitly says it's safe to use a value for binding in that
16640 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 16641 * for those values that you can easily tell are safe - because they were received from your server, 16642 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 16643 * allowing only the files in a specific directory to do this. Ensuring that the internal API 16644 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 16645 * 16646 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 16647 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 16648 * obtain values that will be accepted by SCE / privileged contexts. 16649 * 16650 * 16651 * ## How does it work? 16652 * 16653 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 16654 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 16655 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 16656 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 16657 * 16658 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 16659 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 16660 * simplified): 16661 * 16662 * ``` 16663 * var ngBindHtmlDirective = ['$sce', function($sce) { 16664 * return function(scope, element, attr) { 16665 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 16666 * element.html(value || ''); 16667 * }); 16668 * }; 16669 * }]; 16670 * ``` 16671 * 16672 * ## Impact on loading templates 16673 * 16674 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 16675 * `templateUrl`'s specified by {@link guide/directive directives}. 16676 * 16677 * By default, Angular only loads templates from the same domain and protocol as the application 16678 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 16679 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 16680 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 16681 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 16682 * 16683 * *Please note*: 16684 * The browser's 16685 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 16686 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 16687 * policy apply in addition to this and may further restrict whether the template is successfully 16688 * loaded. This means that without the right CORS policy, loading templates from a different domain 16689 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 16690 * browsers. 16691 * 16692 * ## This feels like too much overhead 16693 * 16694 * It's important to remember that SCE only applies to interpolation expressions. 16695 * 16696 * If your expressions are constant literals, they're automatically trusted and you don't need to 16697 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 16698 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 16699 * 16700 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 16701 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 16702 * 16703 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 16704 * templates in `ng-include` from your application's domain without having to even know about SCE. 16705 * It blocks loading templates from other domains or loading templates over http from an https 16706 * served document. You can change these by setting your own custom {@link 16707 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 16708 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 16709 * 16710 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 16711 * application that's secure and can be audited to verify that with much more ease than bolting 16712 * security onto an application later. 16713 * 16714 * <a name="contexts"></a> 16715 * ## What trusted context types are supported? 16716 * 16717 * | Context | Notes | 16718 * |---------------------|----------------| 16719 * | `$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. | 16720 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 16721 * | `$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. | 16722 * | `$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. | 16723 * | `$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. | 16724 * 16725 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 16726 *
16727 * Each element in these arrays must be one of the following: 16728 * 16729 * - **'self'** 16730 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 16731 * domain** as the application document using the **same protocol**. 16732 * - **String** (except the special value `'self'`) 16733 * - The string is matched against the full *normalized / absolute URL* of the resource 16734 * being tested (substring matches are not good enough.) 16735 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 16736 * match themselves. 16737 * - `*`: matches zero or more occurrences of any character other than one of the following 6 16738 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use 16739 * in a whitelist. 16740 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 16741 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 16742 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 16743 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 16744 * http://foo.example.com/templates/**). 16745 * - **RegExp** (*see caveat below*) 16746 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 16747 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 16748 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 16749 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 16750 * small number of cases. A `.` character in the regex used when matching the scheme or a 16751 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 16752 * is highly recommended to use the string patterns and only fall back to regular expressions 16753 * if they as a last resort. 16754 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 16755 * matched against the **entire** *normalized / absolute URL* of the resource being tested 16756 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 16757 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 16758 * - If you are generating your JavaScript from some other templating engine (not 16759 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 16760 * remember to escape your regular expression (and be aware that you might need more than 16761 * one level of escaping depending on your templating engine and the way you interpolated 16762 * the value.) Do make use of your platform's escaping mechanism as it might be good 16763 * enough before coding your own. e.g. Ruby has 16764 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 16765 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 16766 * Javascript lacks a similar built in function for escaping. Take a look at Google 16767 * Closure library's [goog.string.regExpEscape(s)]( 16768 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 16769 * 16770 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 16771 * 16772 * ## Show me an example using SCE. 16773 * 16774 * <example module="mySceApp" deps="angular-sanitize.js"> 16775 * <file name="index.html"> 16776 * <div ng-controller="AppController as myCtrl"> 16777 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 16778 * <b>User comments</b><br> 16779 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 16780 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 16781 * exploit. 16782 * <div class="well"> 16783 * <div ng-repeat="userComment in myCtrl.userComments"> 16784 * <b>{{userComment.name}}</b>: 16785 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 16786 * <br> 16787 * </div>
16788 * </div> 16789 * </div> 16790 * </file> 16791 * 16792 * <file name="script.js"> 16793 * angular.module('mySceApp', ['ngSanitize']) 16794 * .controller('AppController', ['$http', '$templateCache', '$sce', 16795 * function($http, $templateCache, $sce) { 16796 * var self = this; 16797 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 16798 * self.userComments = userComments; 16799 * }); 16800 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 16801 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 16802 * 'sanitization."">Hover over this text.</span>'); 16803 * }]); 16804 * </file> 16805 * 16806 * <file name="test_data.json"> 16807 * [ 16808 * { "name": "Alice", 16809 * "htmlComment": 16810 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 16811 * }, 16812 * { "name": "Bob", 16813 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 16814 * } 16815 * ] 16816 * </file> 16817 * 16818 * <file name="protractor.js" type="protractor"> 16819 * describe('SCE doc demo', function() { 16820 * it('should sanitize untrusted values', function() { 16821 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 16822 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 16823 * }); 16824 * 16825 * it('should NOT sanitize explicitly trusted values', function() { 16826 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 16827 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 16828 * 'sanitization."">Hover over this text.</span>'); 16829 * }); 16830 * }); 16831 * </file> 16832 * </example> 16833 * 16834 * 16835 * 16836 * ## Can I disable SCE completely? 16837 * 16838 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 16839 * for little coding overhead. It will be much harder to take an SCE disabled application and 16840 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 16841 * for cases where you have a lot of existing code that was written before SCE was introduced and 16842 * you're migrating them a module at a time. 16843 * 16844 * That said, here's how you can completely disable SCE: 16845 * 16846 * ``` 16847 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 16848 * // Completely disable SCE. For demonstration purposes only! 16849 * // Do not use in new projects. 16850 * $sceProvider.enabled(false); 16851 * }); 16852 * ``` 16853 * 16854 */ 16855/* jshint maxlen: 100 */ 16856 16857function $SceProvider() { 16858 var enabled = true; 16859 16860 /** 16861 * @ngdoc method 16862 * @name $sceProvider#enabled 16863 * @kind function 16864 * 16865 * @param {boolean=} value If provided, then enables/disables SCE. 16866 * @return {boolean} true if SCE is enabled, false otherwise. 16867 * 16868 * @description 16869 * Enables/disables SCE and returns the current value. 16870 */ 16871 this.enabled = function(value) { 16872 if (arguments.length) { 16873 enabled = !!value; 16874 } 16875 return enabled; 16876 }; 16877 16878 16879 /* Design notes on the default implementation for SCE. 16880 * 16881 * The API contract for the SCE delegate 16882 * ------------------------------------- 16883 * The SCE delegate object must provide the following 3 methods: 16884 * 16885 * - trustAs(contextEnum, value) 16886 * This method is used to tell the SCE service that the provided value is OK to use in the 16887 * contexts specified by contextEnum. It must return an object that will be accepted by 16888 * getTrusted() for a compatible contextEnum and return this value. 16889 * 16890 * - valueOf(value) 16891 * For values that were not produced by trustAs(), return them as is. For values that were 16892 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 16893 * trustAs is wrapping the given values into some type, this operation unwraps it when given 16894 * such a value. 16895 * 16896 * - getTrusted(contextEnum, value) 16897 * This function should return the a value that is safe to use in the context specified by 16898 * contextEnum or throw and exception otherwise. 16899 * 16900 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 16901 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 16902 * instance, an implementation could maintain a registry of all trusted objects by context. In 16903 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 16904 * return the same object passed in if it was found in the registry under a compatible context or 16905 * throw an exception otherwise. An implementation might only wrap values some of the time based 16906 * on some criteria. getTrusted() might return a value and not throw an exception for special 16907 * constants or objects even if not wrapped. All such implementations fulfill this contract. 16908 * 16909 * 16910 * A note on the inheritance model for SCE contexts 16911 * ------------------------------------------------ 16912 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 16913 * is purely an implementation details. 16914 * 16915 * The contract is simply this: 16916 * 16917 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 16918 * will also succeed. 16919 * 16920 * Inheritance happens to capture this in a natural way. In some future, we
16921 * may not use inheritance anymore. That is OK because no code outside of 16922 * sce.js and sceSpecs.js would need to be aware of this detail. 16923 */ 16924 16925 this.$get = ['$parse', '$sceDelegate', function( 16926 $parse, $sceDelegate) { 16927 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 16928 // the "expression(javascript expression)" syntax which is insecure. 16929 if (enabled && msie < 8) { 16930 throw $sceMinErr('iequirks', 16931 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 16932 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 16933 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 16934 } 16935 16936 var sce = shallowCopy(SCE_CONTEXTS); 16937 16938 /** 16939 * @ngdoc method 16940 * @name $sce#isEnabled 16941 * @kind function 16942 * 16943 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 16944 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 16945 * 16946 * @description 16947 * Returns a boolean indicating if SCE is enabled. 16948 */ 16949 sce.isEnabled = function() { 16950 return enabled; 16951 }; 16952 sce.trustAs = $sceDelegate.trustAs; 16953 sce.getTrusted = $sceDelegate.getTrusted; 16954 sce.valueOf = $sceDelegate.valueOf; 16955 16956 if (!enabled) { 16957 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 16958 sce.valueOf = identity; 16959 } 16960 16961 /** 16962 * @ngdoc method 16963 * @name $sce#parseAs 16964 * 16965 * @description 16966 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 16967 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 16968 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 16969 * *result*)} 16970 * 16971 * @param {string} type The kind of SCE context in which this result will be used. 16972 * @param {string} expression String expression to compile. 16973 * @returns {function(context, locals)} a function which represents the compiled expression: 16974 * 16975 * * `context` â `{object}` â an object against which any expressions embedded in the strings 16976 * are evaluated against (typically a scope object). 16977 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 16978 * `context`. 16979 */ 16980 sce.parseAs = function sceParseAs(type, expr) { 16981 var parsed = $parse(expr); 16982 if (parsed.literal && parsed.constant) { 16983 return parsed; 16984 } else { 16985 return $parse(expr, function(value) { 16986 return sce.getTrusted(type, value); 16987 }); 16988 } 16989 }; 16990 16991 /** 16992 * @ngdoc method 16993 * @name $sce#trustAs 16994 * 16995 * @description 16996 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 16997 * returns an object that is trusted by angular for use in specified strict contextual 16998 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 16999 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 17000 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 17001 * escaping. 17002 * 17003 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 17004 * resourceUrl, html, js and css. 17005 * @param {*} value The value that that should be considered trusted/safe. 17006 * @returns {*} A value that can be used to stand in for the provided `value` in places 17007 * where Angular expects a $sce.trustAs() return value. 17008 */ 17009 17010 /** 17011 * @ngdoc method 17012 * @name $sce#trustAsHtml 17013 * 17014 * @description 17015 * Shorthand method. `$sce.trustAsHtml(value)` â 17016 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 17017 * 17018 * @param {*} value The value to trustAs. 17019 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 17020 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 17021 * only accept expressions that are either literal constants or are the 17022 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17023 */ 17024 17025 /** 17026 * @ngdoc method 17027 * @name $sce#trustAsUrl 17028 *
17029 * @description 17030 * Shorthand method. `$sce.trustAsUrl(value)` â 17031 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 17032 * 17033 * @param {*} value The value to trustAs. 17034 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 17035 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 17036 * only accept expressions that are either literal constants or are the 17037 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17038 */ 17039 17040 /** 17041 * @ngdoc method 17042 * @name $sce#trustAsResourceUrl 17043 * 17044 * @description 17045 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 17046 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 17047 * 17048 * @param {*} value The value to trustAs. 17049 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 17050 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 17051 * only accept expressions that are either literal constants or are the return 17052 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 17053 */ 17054 17055 /** 17056 * @ngdoc method 17057 * @name $sce#trustAsJs 17058 * 17059 * @description 17060 * Shorthand method. `$sce.trustAsJs(value)` â 17061 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 17062 * 17063 * @param {*} value The value to trustAs. 17064 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 17065 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 17066 * only accept expressions that are either literal constants or are the 17067 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17068 */ 17069 17070 /** 17071 * @ngdoc method 17072 * @name $sce#getTrusted 17073 * 17074 * @description 17075 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 17076 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 17077 * originally supplied value if the queried context type is a supertype of the created type. 17078 * If this condition isn't satisfied, throws an exception. 17079 * 17080 * @param {string} type The kind of context in which this value is to be used. 17081 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 17082 * call. 17083 * @returns {*} The value the was originally provided to 17084 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 17085 * Otherwise, throws an exception. 17086 */ 17087 17088 /** 17089 * @ngdoc method 17090 * @name $sce#getTrustedHtml 17091 * 17092 * @description 17093 * Shorthand method. `$sce.getTrustedHtml(value)` â 17094 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 17095 * 17096 * @param {*} value The value to pass to `$sce.getTrusted`. 17097 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 17098 */ 17099 17100 /** 17101 * @ngdoc method 17102 * @name $sce#getTrustedCss 17103 * 17104 * @description 17105 * Shorthand method. `$sce.getTrustedCss(value)` â 17106 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 17107 * 17108 * @param {*} value The value to pass to `$sce.getTrusted`. 17109 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 17110 */ 17111 17112 /** 17113 * @ngdoc method 17114 * @name $sce#getTrustedUrl 17115 * 17116 * @description 17117 * Shorthand method. `$sce.getTrustedUrl(value)` â 17118 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 17119 * 17120 * @param {*} value The value to pass to `$sce.getTrusted`. 17121 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 17122 */ 17123 17124 /** 17125 * @ngdoc method 17126 * @name $sce#getTrustedResourceUrl 17127 * 17128 * @description 17129 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 17130 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 17131 * 17132 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 17133 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 17134 */ 17135 17136 /** 17137 * @ngdoc method 17138 * @name $sce#getTrustedJs 17139 *
17140 * @description 17141 * Shorthand method. `$sce.getTrustedJs(value)` â 17142 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 17143 * 17144 * @param {*} value The value to pass to `$sce.getTrusted`. 17145 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 17146 */ 17147 17148 /** 17149 * @ngdoc method 17150 * @name $sce#parseAsHtml 17151 * 17152 * @description 17153 * Shorthand method. `$sce.parseAsHtml(expression string)` â 17154 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 17155 * 17156 * @param {string} expression String expression to compile. 17157 * @returns {function(context, locals)} a function which represents the compiled expression: 17158 * 17159 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17160 * are evaluated against (typically a scope object). 17161 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17162 * `context`. 17163 */ 17164 17165 /** 17166 * @ngdoc method 17167 * @name $sce#parseAsCss 17168 * 17169 * @description 17170 * Shorthand method. `$sce.parseAsCss(value)` â 17171 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 17172 * 17173 * @param {string} expression String expression to compile. 17174 * @returns {function(context, locals)} a function which represents the compiled expression: 17175 * 17176 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17177 * are evaluated against (typically a scope object). 17178 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17179 * `context`. 17180 */ 17181 17182 /** 17183 * @ngdoc method 17184 * @name $sce#parseAsUrl 17185 * 17186 * @description 17187 * Shorthand method. `$sce.parseAsUrl(value)` â 17188 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 17189 * 17190 * @param {string} expression String expression to compile. 17191 * @returns {function(context, locals)} a function which represents the compiled expression: 17192 * 17193 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17194 * are evaluated against (typically a scope object). 17195 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17196 * `context`. 17197 */ 17198 17199 /** 17200 * @ngdoc method 17201 * @name $sce#parseAsResourceUrl 17202 * 17203 * @description 17204 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 17205 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 17206 * 17207 * @param {string} expression String expression to compile. 17208 * @returns {function(context, locals)} a function which represents the compiled expression: 17209 * 17210 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17211 * are evaluated against (typically a scope object). 17212 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17213 * `context`. 17214 */ 17215 17216 /** 17217 * @ngdoc method 17218 * @name $sce#parseAsJs 17219 * 17220 * @description 17221 * Shorthand method. `$sce.parseAsJs(value)` â 17222 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 17223 * 17224 * @param {string} expression String expression to compile. 17225 * @returns {function(context, locals)} a function which represents the compiled expression: 17226 * 17227 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17228 * are evaluated against (typically a scope object). 17229 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17230 * `context`. 17231 */ 17232 17233 // Shorthand delegations. 17234 var parse = sce.parseAs, 17235 getTrusted = sce.getTrusted, 17236 trustAs = sce.trustAs; 17237 17238 forEach(SCE_CONTEXTS, function(enumValue, name) { 17239 var lName = lowercase(name); 17240 sce[camelCase("parse_as_" + lName)] = function(expr) { 17241 return parse(enumValue, expr); 17242 }; 17243 sce[camelCase("get_trusted_" + lName)] = function(value) { 17244 return getTrusted(enumValue, value); 17245 }; 17246 sce[camelCase("trust_as_" + lName)] = function(value) { 17247 return trustAs(enumValue, value); 17248 }; 17249 }); 17250 17251 return sce; 17252 }]; 17253} 17254 17255/** 17256 * !!! This is an undocumented "private" service !!! 17257 * 17258 * @name $sniffer 17259 * @requires $window 17260 * @requires $document 17261 * 17262 * @property {boolean} history Does the browser support html5 history api ? 17263 * @property {boolean} transitions Does the browser support CSS transition events ? 17264 * @property {boolean} animations Does the browser support CSS animation events ? 17265 * 17266 * @description 17267 * This is very simple implementation of testing browser's features. 17268 */ 17269function $SnifferProvider() { 17270 this.$get = ['$window', '$document', function($window, $document) { 17271 var eventSupport = {}, 17272 android = 17273 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 17274 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 17275 document = $document[0] || {}, 17276 vendorPrefix, 17277 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 17278 bodyStyle = document.body && document.body.style, 17279 transitions = false, 17280 animations = false, 17281 match; 17282 17283 if (bodyStyle) { 17284 for (var prop in bodyStyle) {
17285 if (match = vendorRegex.exec(prop)) { 17286 vendorPrefix = match[0]; 17287 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 17288 break; 17289 } 17290 } 17291 17292 if (!vendorPrefix) { 17293 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 17294 } 17295 17296 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 17297 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 17298 17299 if (android && (!transitions || !animations)) { 17300 transitions = isString(bodyStyle.webkitTransition); 17301 animations = isString(bodyStyle.webkitAnimation); 17302 } 17303 } 17304 17305 17306 return { 17307 // Android has history.pushState, but it does not update location correctly 17308 // so let's not use the history API at all. 17309 // http://code.google.com/p/android/issues/detail?id=17471 17310 // https://github.com/angular/angular.js/issues/904 17311 17312 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 17313 // so let's not use the history API also 17314 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 17315 // jshint -W018 17316 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 17317 // jshint +W018 17318 hasEvent: function(event) { 17319 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 17320 // it. In particular the event is not fired when backspace or delete key are pressed or 17321 // when cut operation is performed. 17322 // IE10+ implements 'input' event but it erroneously fires under various situations, 17323 // e.g. when placeholder changes, or a form is focused. 17324 if (event === 'input' && msie <= 11) return false; 17325 17326 if (isUndefined(eventSupport[event])) { 17327 var divElm = document.createElement('div'); 17328 eventSupport[event] = 'on' + event in divElm; 17329 } 17330 17331 return eventSupport[event]; 17332 }, 17333 csp: csp(), 17334 vendorPrefix: vendorPrefix, 17335 transitions: transitions, 17336 animations: animations, 17337 android: android 17338 }; 17339 }]; 17340} 17341 17342var $compileMinErr = minErr('$compile'); 17343 17344/** 17345 * @ngdoc service 17346 * @name $templateRequest 17347 * 17348 * @description 17349 * The `$templateRequest` service downloads the provided template using `$http` and, upon success, 17350 * stores the contents inside of `$templateCache`. If the HTTP request fails or the response data 17351 * of the HTTP request is empty, a `$compile` error will be thrown (the exception can be thwarted 17352 * by setting the 2nd parameter of the function to true). 17353 * 17354 * @param {string} tpl The HTTP request template URL 17355 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 17356 * 17357 * @return {Promise} a promise for the HTTP response data of the given URL. 17358 * 17359 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 17360 */ 17361function $TemplateRequestProvider() { 17362 this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) { 17363 function handleRequestFn(tpl, ignoreRequestError) { 17364 handleRequestFn.totalPendingRequests++; 17365 17366 var transformResponse = $http.defaults && $http.defaults.transformResponse; 17367 17368 if (isArray(transformResponse)) { 17369 transformResponse = transformResponse.filter(function(transformer) { 17370 return transformer !== defaultHttpResponseTransform; 17371 }); 17372 } else if (transformResponse === defaultHttpResponseTransform) { 17373 transformResponse = null; 17374 } 17375 17376 var httpOptions = { 17377 cache: $templateCache, 17378 transformResponse: transformResponse 17379 }; 17380 17381 return $http.get(tpl, httpOptions) 17382 ['finally'](function() { 17383 handleRequestFn.totalPendingRequests--; 17384 }) 17385 .then(function(response) { 17386 $templateCache.put(tpl, response.data); 17387 return response.data; 17388 }, handleError); 17389 17390 function handleError(resp) { 17391 if (!ignoreRequestError) { 17392 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 17393 tpl, resp.status, resp.statusText); 17394 } 17395 return $q.reject(resp); 17396 } 17397 } 17398 17399 handleRequestFn.totalPendingRequests = 0; 17400 17401 return handleRequestFn; 17402 }]; 17403} 17404
vendor: 5,440 bytes, lines 17405-17558
17405function $$TestabilityProvider() { 17406 this.$get = ['$rootScope', '$browser', '$location', 17407 function($rootScope, $browser, $location) { 17408 17409 /** 17410 * @name $testability 17411 * 17412 * @description 17413 * The private $$testability service provides a collection of methods for use when debugging 17414 * or by automated test and debugging tools. 17415 */ 17416 var testability = {}; 17417 17418 /** 17419 * @name $$testability#findBindings 17420 * 17421 * @description 17422 * Returns an array of elements that are bound (via ng-bind or {{}}) 17423 * to expressions matching the input. 17424 * 17425 * @param {Element} element The element root to search from. 17426 * @param {string} expression The binding expression to match. 17427 * @param {boolean} opt_exactMatch If true, only returns exact matches 17428 * for the expression. Filters and whitespace are ignored. 17429 */ 17430 testability.findBindings = function(element, expression, opt_exactMatch) { 17431 var bindings = element.getElementsByClassName('ng-binding'); 17432 var matches = []; 17433 forEach(bindings, function(binding) { 17434 var dataBinding = angular.element(binding).data('$binding'); 17435 if (dataBinding) { 17436 forEach(dataBinding, function(bindingName) { 17437 if (opt_exactMatch) { 17438 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 17439 if (matcher.test(bindingName)) { 17440 matches.push(binding); 17441 } 17442 } else { 17443 if (bindingName.indexOf(expression) != -1) { 17444 matches.push(binding); 17445 } 17446 } 17447 }); 17448 } 17449 }); 17450 return matches; 17451 }; 17452 17453 /** 17454 * @name $$testability#findModels 17455 * 17456 * @description 17457 * Returns an array of elements that are two-way found via ng-model to 17458 * expressions matching the input. 17459 * 17460 * @param {Element} element The element root to search from. 17461 * @param {string} expression The model expression to match. 17462 * @param {boolean} opt_exactMatch If true, only returns exact matches 17463 * for the expression. 17464 */ 17465 testability.findModels = function(element, expression, opt_exactMatch) { 17466 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 17467 for (var p = 0; p < prefixes.length; ++p) { 17468 var attributeEquals = opt_exactMatch ? '=' : '*='; 17469 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 17470 var elements = element.querySelectorAll(selector); 17471 if (elements.length) { 17472 return elements; 17473 } 17474 } 17475 }; 17476 17477 /** 17478 * @name $$testability#getLocation 17479 * 17480 * @description 17481 * Shortcut for getting the location in a browser agnostic way. Returns 17482 * the path, search, and hash. (e.g. /path?a=b#hash) 17483 */ 17484 testability.getLocation = function() { 17485 return $location.url(); 17486 }; 17487 17488 /** 17489 * @name $$testability#setLocation 17490 * 17491 * @description 17492 * Shortcut for navigating to a location without doing a full page reload. 17493 * 17494 * @param {string} url The location url (path, search and hash, 17495 * e.g. /path?a=b#hash) to go to. 17496 */ 17497 testability.setLocation = function(url) { 17498 if (url !== $location.url()) { 17499 $location.url(url); 17500 $rootScope.$digest(); 17501 } 17502 }; 17503 17504 /** 17505 * @name $$testability#whenStable 17506 * 17507 * @description 17508 * Calls the callback when $timeout and $http requests are completed. 17509 * 17510 * @param {function} callback 17511 */ 17512 testability.whenStable = function(callback) { 17513 $browser.notifyWhenNoOutstandingRequests(callback); 17514 }; 17515 17516 return testability; 17517 }]; 17518} 17519 17520function $TimeoutProvider() { 17521 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 17522 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 17523 17524 var deferreds = {}; 17525 17526 17527 /** 17528 * @ngdoc service 17529 * @name $timeout 17530 * 17531 * @description 17532 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 17533 * block and delegates any exceptions to 17534 * {@link ng.$exceptionHandler $exceptionHandler} service. 17535 * 17536 * The return value of calling `$timeout` is a promise, which will be resolved when 17537 * the delay has passed and the timeout function, if provided, is executed. 17538 * 17539 * To cancel a timeout request, call `$timeout.cancel(promise)`. 17540 * 17541 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 17542 * synchronously flush the queue of deferred functions. 17543 * 17544 * If you only want a promise that will be resolved after some specified delay 17545 * then you can call `$timeout` without the `fn` function. 17546 * 17547 * @param {function()=} fn A function, whose execution should be delayed. 17548 * @param {number=} [delay=0] Delay in milliseconds. 17549 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 17550 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 17551 * @param {...*=} Pass additional parameters to the executed function. 17552 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 17553 * promise will be resolved with is the return value of the `fn` function. 17554 * 17555 */ 17556 function timeout(fn, delay, invokeApply) { 17557 if (!isFunction(fn)) { 17558 invokeApply = delay;
17559 delay = fn; 17560 fn = noop; 17561 } 17562 17563 var args = sliceArgs(arguments, 3), 17564 skipApply = (isDefined(invokeApply) && !invokeApply), 17565 deferred = (skipApply ? $$q : $q).defer(), 17566 promise = deferred.promise, 17567 timeoutId; 17568 17569 timeoutId = $browser.defer(function() { 17570 try { 17571 deferred.resolve(fn.apply(null, args)); 17572 } catch (e) { 17573 deferred.reject(e); 17574 $exceptionHandler(e); 17575 } 17576 finally { 17577 delete deferreds[promise.$$timeoutId]; 17578 } 17579 17580 if (!skipApply) $rootScope.$apply(); 17581 }, delay); 17582 17583 promise.$$timeoutId = timeoutId; 17584 deferreds[timeoutId] = deferred; 17585 17586 return promise; 17587 } 17588 17589 17590 /** 17591 * @ngdoc method 17592 * @name $timeout#cancel 17593 * 17594 * @description 17595 * Cancels a task associated with the `promise`. As a result of this, the promise will be 17596 * resolved with a rejection. 17597 * 17598 * @param {Promise=} promise Promise returned by the `$timeout` function. 17599 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 17600 * canceled. 17601 */ 17602 timeout.cancel = function(promise) { 17603 if (promise && promise.$$timeoutId in deferreds) { 17604 deferreds[promise.$$timeoutId].reject('canceled'); 17605 delete deferreds[promise.$$timeoutId]; 17606 return $browser.defer.cancel(promise.$$timeoutId); 17607 } 17608 return false; 17609 }; 17610 17611 return timeout; 17612 }]; 17613} 17614 17615// NOTE: The usage of window and document instead of $window and $document here is 17616// deliberate. This service depends on the specific behavior of anchor nodes created by the 17617// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 17618// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 17619// doesn't know about mocked locations and resolves URLs to the real document - which is 17620// exactly the behavior needed here. There is little value is mocking these out for this 17621// service. 17622var urlParsingNode = document.createElement("a"); 17623var originUrl = urlResolve(window.location.href); 17624 17625 17626/** 17627 * 17628 * Implementation Notes for non-IE browsers 17629 * ---------------------------------------- 17630 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 17631 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 17632 * URL will be resolved into an absolute URL in the context of the application document. 17633 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 17634 * properties are all populated to reflect the normalized URL. This approach has wide 17635 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 17636 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 17637 * 17638 * Implementation Notes for IE 17639 * --------------------------- 17640 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 17641 * browsers. However, the parsed components will not be set if the URL assigned did not specify 17642 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 17643 * work around that by performing the parsing in a 2nd step by taking a previously normalized 17644 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 17645 * properties such as protocol, hostname, port, etc. 17646 * 17647 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 17648 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 17649 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 17650 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 17651 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 17652 * method and IE < 8 is unsupported. 17653 * 17654 * References: 17655 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 17656 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 17657 * http://url.spec.whatwg.org/#urlutils 17658 * https://github.com/angular/angular.js/pull/2902 17659 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 17660 * 17661 * @kind function 17662 * @param {string} url The URL to be parsed. 17663 * @description Normalizes and parses a URL. 17664 * @returns {object} Returns the normalized URL as a dictionary. 17665 * 17666 * | member name | Description | 17667 * |---------------|----------------| 17668 * | href | A normalized version of the provided URL if it was not an absolute URL | 17669 * | protocol | The protocol including the trailing colon | 17670 * | host | The host and port (if the port is non-default) of the normalizedUrl | 17671 * | search | The search params, minus the question mark | 17672 * | hash | The hash string, minus the hash symbol 17673 * | hostname | The hostname 17674 * | port | The port, without ":" 17675 * | pathname | The pathname, beginning with "/" 17676 * 17677 */ 17678function urlResolve(url) { 17679 var href = url; 17680 17681 if (msie) { 17682 // Normalize before parse. Refer Implementation Notes on why this is 17683 // done in two steps on IE. 17684 urlParsingNode.setAttribute("href", href); 17685 href = urlParsingNode.href; 17686 } 17687 17688 urlParsingNode.setAttribute('href', href); 17689 17690 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 17691 return { 17692 href: urlParsingNode.href, 17693 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 17694 host: urlParsingNode.host, 17695 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 17696 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 17697 hostname: urlParsingNode.hostname, 17698 port: urlParsingNode.port, 17699 pathname: (urlParsingNode.pathname.charAt(0) === '/') 17700 ? urlParsingNode.pathname 17701 : '/' + urlParsingNode.pathname 17702 }; 17703} 17704 17705/** 17706 * Parse a request URL and determine whether this is a same-origin request as the application document. 17707 * 17708 * @param {string|object} requestUrl The url of the request as a string that will be resolved 17709 * or a parsed URL object. 17710 * @returns {boolean} Whether the request is for the same origin as the application document. 17711 */ 17712function urlIsSameOrigin(requestUrl) { 17713 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 17714 return (parsed.protocol === originUrl.protocol && 17715 parsed.host === originUrl.host); 17716} 17717 17718/** 17719 * @ngdoc service 17720 * @name $window 17721 * 17722 * @description 17723 * A reference to the browser's `window` object. While `window` 17724 * is globally available in JavaScript, it causes testability problems, because 17725 * it is a global variable. In angular we always refer to it through the 17726 * `$window` service, so it may be overridden, removed or mocked for testing. 17727 * 17728 * Expressions, like the one defined for the `ngClick` directive in the example 17729 * below, are evaluated with respect to the current scope. Therefore, there is 17730 * no risk of inadvertently coding in a dependency on a global value in such an 17731 * expression. 17732 * 17733 * @example 17734 <example module="windowExample"> 17735 <file name="index.html"> 17736 <script> 17737 angular.module('windowExample', [])
17738 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 17739 $scope.greeting = 'Hello, World!'; 17740 $scope.doGreeting = function(greeting) { 17741 $window.alert(greeting); 17742 }; 17743 }]); 17744 </script> 17745 <div ng-controller="ExampleController"> 17746 <input type="text" ng-model="greeting" aria-label="greeting" /> 17747 <button ng-click="doGreeting(greeting)">ALERT</button> 17748 </div> 17749 </file> 17750 <file name="protractor.js" type="protractor"> 17751 it('should display the greeting in the input box', function() { 17752 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 17753 // If we click the button it will block the test runner 17754 // element(':button').click(); 17755 }); 17756 </file> 17757 </example> 17758 */ 17759function $WindowProvider() { 17760 this.$get = valueFn(window); 17761} 17762 17763/** 17764 * @name $$cookieReader 17765 * @requires $document 17766 * 17767 * @description 17768 * This is a private service for reading cookies used by $http and ngCookies 17769 * 17770 * @return {Object} a key/value map of the current cookies 17771 */ 17772function $$CookieReader($document) { 17773 var rawDocument = $document[0] || {}; 17774 var lastCookies = {}; 17775 var lastCookieString = ''; 17776 17777 function safeDecodeURIComponent(str) { 17778 try { 17779 return decodeURIComponent(str); 17780 } catch (e) { 17781 return str; 17782 } 17783 } 17784 17785 return function() { 17786 var cookieArray, cookie, i, index, name; 17787 var currentCookieString = rawDocument.cookie || ''; 17788 17789 if (currentCookieString !== lastCookieString) { 17790 lastCookieString = currentCookieString; 17791 cookieArray = lastCookieString.split('; '); 17792 lastCookies = {}; 17793 17794 for (i = 0; i < cookieArray.length; i++) { 17795 cookie = cookieArray[i]; 17796 index = cookie.indexOf('='); 17797 if (index > 0) { //ignore nameless cookies 17798 name = safeDecodeURIComponent(cookie.substring(0, index)); 17799 // the first value that is seen for a cookie is the most 17800 // specific one. values for the same cookie name that 17801 // follow are for less specific paths. 17802 if (lastCookies[name] === undefined) { 17803 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 17804 } 17805 } 17806 } 17807 } 17808 return lastCookies; 17809 }; 17810} 17811 17812$$CookieReader.$inject = ['$document']; 17813 17814function $$CookieReaderProvider() { 17815 this.$get = $$CookieReader; 17816} 17817 17818/* global currencyFilter: true, 17819 dateFilter: true, 17820 filterFilter: true, 17821 jsonFilter: true, 17822 limitToFilter: true, 17823 lowercaseFilter: true, 17824 numberFilter: true, 17825 orderByFilter: true, 17826 uppercaseFilter: true, 17827 */ 17828 17829/** 17830 * @ngdoc provider 17831 * @name $filterProvider 17832 * @description 17833 * 17834 * Filters are just functions which transform input to an output. However filters need to be 17835 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 17836 * annotated with dependencies and is responsible for creating a filter function. 17837 * 17838 * <div class="alert alert-warning"> 17839 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 17840 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 17841 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 17842 * (`myapp_subsection_filterx`). 17843 * </div> 17844 * 17845 * ```js 17846 * // Filter registration 17847 * function MyModule($provide, $filterProvider) { 17848 * // create a service to demonstrate injection (not always needed) 17849 * $provide.value('greet', function(name){ 17850 * return 'Hello ' + name + '!'; 17851 * }); 17852 * 17853 * // register a filter factory which uses the 17854 * // greet service to demonstrate DI. 17855 * $filterProvider.register('greet', function(greet){ 17856 * // return the filter function which uses the greet service 17857 * // to generate salutation 17858 * return function(text) { 17859 * // filters need to be forgiving so check input validity 17860 * return text && greet(text) || text; 17861 * }; 17862 * }); 17863 * } 17864 * ``` 17865 * 17866 * The filter function is registered with the `$injector` under the filter name suffix with 17867 * `Filter`. 17868 * 17869 * ```js 17870 * it('should be the same instance', inject( 17871 * function($filterProvider) { 17872 * $filterProvider.register('reverse', function(){ 17873 * return ...; 17874 * }); 17875 * }, 17876 * function($filter, reverseFilter) { 17877 * expect($filter('reverse')).toBe(reverseFilter); 17878 * }); 17879 * ``` 17880 * 17881 * 17882 * For more information about how angular filters work, and how to create your own filters, see 17883 * {@link guide/filter Filters} in the Angular Developer Guide. 17884 */ 17885 17886/** 17887 * @ngdoc service 17888 * @name $filter 17889 * @kind function 17890 * @description 17891 * Filters are used for formatting data displayed to the user. 17892 * 17893 * The general syntax in templates is as follows: 17894 * 17895 * {{ expression [| filter_name[:parameter_value] ... ] }} 17896 * 17897 * @param {String} name Name of the filter function to retrieve 17898 * @return {Function} the filter function 17899 * @example 17900 <example name="$filter" module="filterExample"> 17901 <file name="index.html"> 17902 <div ng-controller="MainCtrl"> 17903 <h3>{{ originalText }}</h3> 17904 <h3>{{ filteredText }}</h3> 17905 </div> 17906 </file> 17907 17908 <file name="script.js"> 17909 angular.module('filterExample', []) 17910 .controller('MainCtrl', function($scope, $filter) { 17911 $scope.originalText = 'hello'; 17912 $scope.filteredText = $filter('uppercase')($scope.originalText); 17913 }); 17914 </file> 17915 </example> 17916 */ 17917$FilterProvider.$inject = ['$provide']; 17918function $FilterProvider($provide) { 17919 var suffix = 'Filter'; 17920 17921 /** 17922 * @ngdoc method 17923 * @name $filterProvider#register 17924 * @param {string|Object} name Name of the filter function, or an object map of filters where 17925 * the keys are the filter names and the values are the filter factories. 17926 * 17927 * <div class="alert alert-warning"> 17928 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 17929 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 17930 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 17931 * (`myapp_subsection_filterx`). 17932 * </div> 17933 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 17934 * of the registered filter instances. 17935 */ 17936 function register(name, factory) { 17937 if (isObject(name)) { 17938 var filters = {};
17939 forEach(name, function(filter, key) { 17940 filters[key] = register(key, filter); 17941 }); 17942 return filters; 17943 } else { 17944 return $provide.factory(name + suffix, factory); 17945 } 17946 } 17947 this.register = register; 17948 17949 this.$get = ['$injector', function($injector) { 17950 return function(name) { 17951 return $injector.get(name + suffix); 17952 }; 17953 }]; 17954 17955 //////////////////////////////////////// 17956 17957 /* global 17958 currencyFilter: false, 17959 dateFilter: false, 17960 filterFilter: false, 17961 jsonFilter: false, 17962 limitToFilter: false, 17963 lowercaseFilter: false, 17964 numberFilter: false, 17965 orderByFilter: false, 17966 uppercaseFilter: false, 17967 */ 17968 17969 register('currency', currencyFilter); 17970 register('date', dateFilter); 17971 register('filter', filterFilter); 17972 register('json', jsonFilter); 17973 register('limitTo', limitToFilter); 17974 register('lowercase', lowercaseFilter); 17975 register('number', numberFilter); 17976 register('orderBy', orderByFilter); 17977 register('uppercase', uppercaseFilter); 17978} 17979 17980/** 17981 * @ngdoc filter 17982 * @name filter 17983 * @kind function 17984 * 17985 * @description 17986 * Selects a subset of items from `array` and returns it as a new array. 17987 * 17988 * @param {Array} array The source array. 17989 * @param {string|Object|function()} expression The predicate to be used for selecting items from 17990 * `array`. 17991 * 17992 * Can be one of: 17993 * 17994 * - `string`: The string is used for matching against the contents of the `array`. All strings or 17995 * objects with string properties in `array` that match this string will be returned. This also 17996 * applies to nested object properties. 17997 * The predicate can be negated by prefixing the string with `!`. 17998 * 17999 * - `Object`: A pattern object can be used to filter specific properties on objects contained 18000 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 18001 * which have property `name` containing "M" and property `phone` containing "1". A special 18002 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 18003 * property of the object or its nested object properties. That's equivalent to the simple 18004 * substring match with a `string` as described above. The predicate can be negated by prefixing 18005 * the string with `!`. 18006 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 18007 * not containing "M". 18008 * 18009 * Note that a named property will match properties on the same level only, while the special 18010 * `$` property will match properties on the same level or deeper. E.g. an array item like 18011 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 18012 * **will** be matched by `{$: 'John'}`. 18013 * 18014 * - `function(value, index)`: A predicate function can be used to write arbitrary filters. The 18015 * function is called for each element of `array`. The final result is an array of those 18016 * elements that the predicate returned true for. 18017 * 18018 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 18019 * determining if the expected value (from the filter expression) and actual value (from 18020 * the object in the array) should be considered a match. 18021 * 18022 * Can be one of: 18023 * 18024 * - `function(actual, expected)`: 18025 * The function will be given the object value and the predicate value to compare and 18026 * should return true if both values should be considered equal. 18027 * 18028 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 18029 * This is essentially strict comparison of expected and actual. 18030 * 18031 * - `false|undefined`: A short hand for a function which will look for a substring match in case 18032 * insensitive way. 18033 * 18034 * Primitive values are converted to strings. Objects are not compared against primitives, 18035 * unless they have a custom `toString` method (e.g. `Date` objects). 18036 * 18037 * @example 18038 <example> 18039 <file name="index.html"> 18040 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 18041 {name:'Mary', phone:'800-BIG-MARY'}, 18042 {name:'Mike', phone:'555-4321'}, 18043 {name:'Adam', phone:'555-5678'}, 18044 {name:'Julie', phone:'555-8765'}, 18045 {name:'Juliette', phone:'555-5678'}]"></div> 18046 18047 <label>Search: <input ng-model="searchText"></label> 18048 <table id="searchTextResults"> 18049 <tr><th>
18049Name</th><th>Phone</th></tr> 18050 <tr ng-repeat="friend in friends | filter:searchText"> 18051 <td>{{friend.name}}</td> 18052 <td>{{friend.phone}}</td> 18053 </tr> 18054 </table> 18055 <hr> 18056 <label>Any: <input ng-model="search.$"></label> <br> 18057 <label>Name only <input ng-model="search.name"></label><br> 18058 <label>Phone only <input ng-model="search.phone"></label><br> 18059 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 18060 <table id="searchObjResults"> 18061 <tr><th>Name</th><th>Phone</th></tr> 18062 <tr ng-repeat="friendObj in friends | filter:search:strict"> 18063 <td>{{friendObj.name}}</td> 18064 <td>{{friendObj.phone}}</td> 18065 </tr> 18066 </table> 18067 </file> 18068 <file name="protractor.js" type="protractor"> 18069 var expectFriendNames = function(expectedNames, key) { 18070 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 18071 arr.forEach(function(wd, i) { 18072 expect(wd.getText()).toMatch(expectedNames[i]); 18073 }); 18074 }); 18075 }; 18076 18077 it('should search across all fields when filtering with a string', function() { 18078 var searchText = element(by.model('searchText')); 18079 searchText.clear(); 18080 searchText.sendKeys('m'); 18081 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 18082 18083 searchText.clear(); 18084 searchText.sendKeys('76'); 18085 expectFriendNames(['John', 'Julie'], 'friend'); 18086 }); 18087 18088 it('should search in specific fields when filtering with a predicate object', function() { 18089 var searchAny = element(by.model('search.$')); 18090 searchAny.clear(); 18091 searchAny.sendKeys('i'); 18092 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 18093 }); 18094 it('should use a equal comparison when comparator is true', function() { 18095 var searchName = element(by.model('search.name')); 18096 var strict = element(by.model('strict')); 18097 searchName.clear(); 18098 searchName.sendKeys('Julie'); 18099 strict.click(); 18100 expectFriendNames(['Julie'], 'friendObj'); 18101 }); 18102 </file> 18103 </example> 18104 */ 18105function filterFilter() { 18106 return function(array, expression, comparator) { 18107 if (!isArrayLike(array)) { 18108 if (array == null) { 18109 return array; 18110 } else { 18111 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 18112 } 18113 } 18114 18115 var expressionType = getTypeForFilter(expression); 18116 var predicateFn; 18117 var matchAgainstAnyProp; 18118 18119 switch (expressionType) { 18120 case 'function': 18121 predicateFn = expression; 18122 break; 18123 case 'boolean': 18124 case 'null': 18125 case 'number': 18126 case 'string': 18127 matchAgainstAnyProp = true; 18128 //jshint -W086 18129 case 'object': 18130 //jshint +W086 18131 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 18132 break; 18133 default: 18134 return array; 18135 } 18136 18137 return Array.prototype.filter.call(array, predicateFn); 18138 }; 18139} 18140 18141function hasCustomToString(obj) { 18142 return isFunction(obj.toString) && obj.toString !== Object.prototype.toString; 18143} 18144 18145// Helper functions for `filterFilter` 18146function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 18147 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 18148 var predicateFn; 18149 18150 if (comparator === true) { 18151 comparator = equals; 18152 } else if (!isFunction(comparator)) { 18153 comparator = function(actual, expected) { 18154 if (isUndefined(actual)) { 18155 // No substring matching against `undefined` 18156 return false; 18157 } 18158 if ((actual === null) || (expected === null)) { 18159 // No substring matching against `null`; only match against `null` 18160 return actual === expected; 18161 } 18162 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 18163 // Should not compare primitives against objects, unless they have custom `toString` method 18164 return false; 18165 } 18166 18167 actual = lowercase('' + actual); 18168 expected = lowercase('' + expected); 18169 return actual.indexOf(expected) !== -1; 18170 }; 18171 } 18172 18173 predicateFn = function(item) { 18174 if (shouldMatchPrimitives && !isObject(item)) { 18175 return deepCompare(item, expression.$, comparator, false); 18176 } 18177 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 18178 }; 18179 18180 return predicateFn; 18181} 18182 18183function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 18184 var actualType = getTypeForFilter(actual); 18185 var expectedType = getTypeForFilter(expected); 18186 18187 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 18188 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 18189 } else if (isArray(actual)) { 18190 // In case `actual` is an array, consider it a match 18191 // if ANY of it's items matches `expected` 18192 return actual.some(function(item) { 18193 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 18194 }); 18195 } 18196 18197 switch (actualType) { 18198 case 'object': 18199 var key; 18200 if (matchAgainstAnyProp) { 18201 for (key in actual) { 18202 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 18203 return true; 18204 } 18205 } 18206 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 18207 } else if (expectedType === 'object') { 18208 for (key in expected) { 18209 var expectedVal = expected[key]; 18210 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 18211 continue; 18212 } 18213 18214 var matchAnyProperty = key === '$'; 18215 var actualVal = matchAnyProperty ? actual : actual[key]; 18216 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 18217 return false;
18218 } 18219 } 18220 return true; 18221 } else { 18222 return comparator(actual, expected); 18223 } 18224 break; 18225 case 'function': 18226 return false; 18227 default: 18228 return comparator(actual, expected); 18229 } 18230} 18231 18232// Used for easily differentiating between `null` and actual `object` 18233function getTypeForFilter(val) { 18234 return (val === null) ? 'null' : typeof val; 18235} 18236 18237/** 18238 * @ngdoc filter 18239 * @name currency 18240 * @kind function 18241 * 18242 * @description 18243 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 18244 * symbol for current locale is used. 18245 * 18246 * @param {number} amount Input to filter. 18247 * @param {string=} symbol Currency symbol or identifier to be displayed. 18248 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 18249 * @returns {string} Formatted number. 18250 * 18251 * 18252 * @example 18253 <example module="currencyExample"> 18254 <file name="index.html"> 18255 <script> 18256 angular.module('currencyExample', []) 18257 .controller('ExampleController', ['$scope', function($scope) { 18258 $scope.amount = 1234.56; 18259 }]); 18260 </script>
18261 <div ng-controller="ExampleController"> 18262 <input type="number" ng-model="amount" aria-label="amount"> <br> 18263 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 18264 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 18265 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 18266 </div> 18267 </file> 18268 <file name="protractor.js" type="protractor"> 18269 it('should init with 1234.56', function() { 18270 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 18271 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 18272 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 18273 }); 18274 it('should update', function() { 18275 if (browser.params.browser == 'safari') { 18276 // Safari does not understand the minus key. See 18277 // https://github.com/angular/protractor/issues/481 18278 return; 18279 } 18280 element(by.model('amount')).clear(); 18281 element(by.model('amount')).sendKeys('-1234'); 18282 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 18283 expect(element(by.id('currency-custom')).getText()).toBe('(USD$1,234.00)'); 18284 expect(element(by.id('currency-no-fractions')).getText()).toBe('(USD$1,234)'); 18285 }); 18286 </file> 18287 </example> 18288 */ 18289currencyFilter.$inject = ['$locale']; 18290function currencyFilter($locale) { 18291 var formats = $locale.NUMBER_FORMATS; 18292 return function(amount, currencySymbol, fractionSize) { 18293 if (isUndefined(currencySymbol)) { 18294 currencySymbol = formats.CURRENCY_SYM; 18295 } 18296 18297 if (isUndefined(fractionSize)) { 18298 fractionSize = formats.PATTERNS[1].maxFrac; 18299 } 18300 18301 // if null or undefined pass it through 18302 return (amount == null) 18303 ? amount 18304 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 18305 replace(/\u00A4/g, currencySymbol); 18306 }; 18307} 18308 18309/** 18310 * @ngdoc filter 18311 * @name number 18312 * @kind function 18313 * 18314 * @description 18315 * Formats a number as text. 18316 * 18317 * If the input is not a number an empty string is returned. 18318 * 18319 * If the input is an infinite (Infinity/-Infinity) the Infinity symbol 'â' is returned. 18320 * 18321 * @param {number|string} number Number to format. 18322 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 18323 * If this is not provided then the fraction size is computed from the current locale's number 18324 * formatting pattern. In the case of the default locale, it will be 3. 18325 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 18326 * 18327 * @example 18328 <example module="numberFilterExample"> 18329 <file name="index.html"> 18330 <script> 18331 angular.module('numberFilterExample', []) 18332 .controller('ExampleController', ['$scope', function($scope) { 18333 $scope.val = 1234.56789; 18334 }]); 18335 </script>
18336 <div ng-controller="ExampleController"> 18337 <label>Enter number: <input ng-model='val'></label><br> 18338 Default formatting: <span id='number-default'>{{val | number}}</span><br> 18339 No fractions: <span>{{val | number:0}}</span><br> 18340 Negative number: <span>{{-val | number:4}}</span> 18341 </div> 18342 </file> 18343 <file name="protractor.js" type="protractor"> 18344 it('should format numbers', function() { 18345 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 18346 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 18347 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 18348 }); 18349 18350 it('should update', function() { 18351 element(by.model('val')).clear(); 18352 element(by.model('val')).sendKeys('3374.333'); 18353 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 18354 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 18355 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 18356 }); 18357 </file> 18358 </example> 18359 */ 18360 18361 18362numberFilter.$inject = ['$locale']; 18363function numberFilter($locale) { 18364 var formats = $locale.NUMBER_FORMATS; 18365 return function(number, fractionSize) { 18366 18367 // if null or undefined pass it through 18368 return (number == null) 18369 ? number 18370 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 18371 fractionSize); 18372 }; 18373} 18374 18375var DECIMAL_SEP = '.'; 18376function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 18377 if (isObject(number)) return ''; 18378 18379 var isNegative = number < 0; 18380 number = Math.abs(number); 18381 18382 var isInfinity = number === Infinity; 18383 if (!isInfinity && !isFinite(number)) return ''; 18384 18385 var numStr = number + '', 18386 formatedText = '', 18387 hasExponent = false, 18388 parts = []; 18389 18390 if (isInfinity) formatedText = '\u221e'; 18391 18392 if (!isInfinity && numStr.indexOf('e') !== -1) { 18393 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 18394 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 18395 number = 0; 18396 } else { 18397 formatedText = numStr; 18398 hasExponent = true; 18399 } 18400 } 18401 18402 if (!isInfinity && !hasExponent) { 18403 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 18404 18405 // determine fractionSize if it is not specified 18406 if (isUndefined(fractionSize)) { 18407 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 18408 } 18409 18410 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics 18411 // inspired by: 18412 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 18413 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); 18414 18415 var fraction = ('' + number).split(DECIMAL_SEP); 18416 var whole = fraction[0]; 18417 fraction = fraction[1] || ''; 18418 18419 var i, pos = 0, 18420 lgroup = pattern.lgSize, 18421 group = pattern.gSize; 18422 18423 if (whole.length >= (lgroup + group)) { 18424 pos = whole.length - lgroup; 18425 for (i = 0; i < pos; i++) { 18426 if ((pos - i) % group === 0 && i !== 0) { 18427 formatedText += groupSep; 18428 } 18429 formatedText += whole.charAt(i); 18430 } 18431 } 18432 18433 for (i = pos; i < whole.length; i++) { 18434 if ((whole.length - i) % lgroup === 0 && i !== 0) { 18435 formatedText += groupSep; 18436 } 18437 formatedText += whole.charAt(i); 18438 } 18439 18440 // format fraction part. 18441 while (fraction.length < fractionSize) { 18442 fraction += '0'; 18443 } 18444 18445 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 18446 } else { 18447 if (fractionSize > 0 && number < 1) { 18448 formatedText = number.toFixed(fractionSize); 18449 number = parseFloat(formatedText); 18450 } 18451 } 18452 18453 if (number === 0) { 18454 isNegative = false; 18455 } 18456 18457 parts.push(isNegative ? pattern.negPre : pattern.posPre, 18458 formatedText, 18459 isNegative ? pattern.negSuf : pattern.posSuf); 18460 return parts.join(''); 18461} 18462 18463function padNumber(num, digits, trim) { 18464 var neg = ''; 18465 if (num < 0) { 18466 neg = '-'; 18467 num = -num; 18468 } 18469 num = '' + num; 18470 while (num.length < digits) num = '0' + num; 18471 if (trim) { 18472 num = num.substr(num.length - digits); 18473 } 18474 return neg + num; 18475} 18476 18477 18478function dateGetter(name, size, offset, trim) { 18479 offset = offset || 0; 18480 return function(date) { 18481 var value = date['get' + name](); 18482 if (offset > 0 || value > -offset) { 18483 value += offset; 18484 } 18485 if (value === 0 && offset == -12) value = 12; 18486 return padNumber(value, size, trim); 18487 }; 18488} 18489 18490function dateStrGetter(name, shortForm) { 18491 return function(date, formats) { 18492 var value = date['get' + name](); 18493 var get = uppercase(shortForm ? ('SHORT' + name) : name); 18494 18495 return formats[get][value]; 18496 }; 18497} 18498 18499function timeZoneGetter(date, formats, offset) { 18500 var zone = -1 * offset; 18501 var paddedZone = (zone >= 0) ? "+" : ""; 18502 18503 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 18504 padNumber(Math.abs(zone % 60), 2); 18505 18506 return paddedZone; 18507} 18508 18509function getFirstThursdayOfYear(year) { 18510 // 0 = index of January 18511 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 18512 // 4 = index of Thursday (+1 to account for 1st = 5) 18513 // 11 = index of *next* Thursday (+1 account for 1st = 12) 18514 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 18515} 18516 18517function getThursdayThisWeek(datetime) { 18518 return new Date(datetime.getFullYear(), datetime.getMonth(), 18519 // 4 = index of Thursday 18520 datetime.getDate() + (4 - datetime.getDay())); 18521} 18522 18523function weekGetter(size) { 18524 return function(date) { 18525 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 18526 thisThurs = getThursdayThisWeek(date); 18527 18528 var diff = +thisThurs - +firstThurs, 18529 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 18530 18531 return padNumber(result, size); 18532 }; 18533} 18534 18535function ampmGetter(date, formats) { 18536 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 18537} 18538 18539function eraGetter(date, formats) {
vendor: 5,157 bytes, lines 18540-18652
18540 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 18541} 18542 18543function longEraGetter(date, formats) { 18544 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 18545} 18546 18547var DATE_FORMATS = { 18548 yyyy: dateGetter('FullYear', 4), 18549 yy: dateGetter('FullYear', 2, 0, true), 18550 y: dateGetter('FullYear', 1), 18551 MMMM: dateStrGetter('Month'), 18552 MMM: dateStrGetter('Month', true), 18553 MM: dateGetter('Month', 2, 1), 18554 M: dateGetter('Month', 1, 1), 18555 dd: dateGetter('Date', 2), 18556 d: dateGetter('Date', 1), 18557 HH: dateGetter('Hours', 2), 18558 H: dateGetter('Hours', 1), 18559 hh: dateGetter('Hours', 2, -12), 18560 h: dateGetter('Hours', 1, -12), 18561 mm: dateGetter('Minutes', 2), 18562 m: dateGetter('Minutes', 1), 18563 ss: dateGetter('Seconds', 2), 18564 s: dateGetter('Seconds', 1), 18565 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 18566 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 18567 sss: dateGetter('Milliseconds', 3), 18568 EEEE: dateStrGetter('Day'), 18569 EEE: dateStrGetter('Day', true), 18570 a: ampmGetter, 18571 Z: timeZoneGetter, 18572 ww: weekGetter(2), 18573 w: weekGetter(1), 18574 G: eraGetter, 18575 GG: eraGetter, 18576 GGG: eraGetter, 18577 GGGG: longEraGetter 18578}; 18579 18580var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 18581 NUMBER_STRING = /^\-?\d+$/; 18582 18583/** 18584 * @ngdoc filter 18585 * @name date 18586 * @kind function 18587 * 18588 * @description 18589 * Formats `date` to a string based on the requested `format`. 18590 * 18591 * `format` string can be composed of the following elements: 18592 * 18593 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 18594 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 18595 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 18596 * * `'MMMM'`: Month in year (January-December) 18597 * * `'MMM'`: Month in year (Jan-Dec) 18598 * * `'MM'`: Month in year, padded (01-12) 18599 * * `'M'`: Month in year (1-12) 18600 * * `'dd'`: Day in month, padded (01-31) 18601 * * `'d'`: Day in month (1-31) 18602 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 18603 * * `'EEE'`: Day in Week, (Sun-Sat) 18604 * * `'HH'`: Hour in day, padded (00-23) 18605 * * `'H'`: Hour in day (0-23) 18606 * * `'hh'`: Hour in AM/PM, padded (01-12) 18607 * * `'h'`: Hour in AM/PM, (1-12) 18608 * * `'mm'`: Minute in hour, padded (00-59) 18609 * * `'m'`: Minute in hour (0-59) 18610 * * `'ss'`: Second in minute, padded (00-59) 18611 * * `'s'`: Second in minute (0-59) 18612 * * `'sss'`: Millisecond in second, padded (000-999) 18613 * * `'a'`: AM/PM marker 18614 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 18615 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 18616 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 18617 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 18618 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 18619 * 18620 * `format` string can also be one of the following predefined 18621 * {@link guide/i18n localizable formats}: 18622 * 18623 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 18624 * (e.g. Sep 3, 2010 12:05:08 PM) 18625 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 18626 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 18627 * (e.g. Friday, September 3, 2010) 18628 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 18629 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 18630 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 18631 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 18632 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 18633 * 18634 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 18635 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 18636 * (e.g. `"h 'o''clock'"`). 18637 * 18638 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 18639 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 18640 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 18641 * specified in the string input, the time is considered to be in the local timezone. 18642 * @param {string=} format Formatting rules (see Description). If not specified, 18643 * `mediumDate` is used. 18644 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 18645 * continental US time zone abbreviations, but for general use, use a time zone offset, for 18646 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 18647 * If not specified, the timezone of the browser will be used. 18648 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 18649 * 18650 * @example 18651 <example> 18652 <file name="index.html">
vendor: 4,078 bytes, lines 18653-18765
18653 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 18654 <span>{{1288323623006 | date:'medium'}}</span><br> 18655 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 18656 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 18657 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 18658 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 18659 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 18660 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 18661 </file> 18662 <file name="protractor.js" type="protractor"> 18663 it('should format date', function() { 18664 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 18665 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 18666 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 18667 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 18668 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 18669 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 18670 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 18671 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 18672 }); 18673 </file> 18674 </example> 18675 */ 18676dateFilter.$inject = ['$locale']; 18677function dateFilter($locale) { 18678 18679 18680 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 18681 // 1 2 3 4 5 6 7 8 9 10 11 18682 function jsonStringToDate(string) { 18683 var match; 18684 if (match = string.match(R_ISO8601_STR)) { 18685 var date = new Date(0), 18686 tzHour = 0, 18687 tzMin = 0, 18688 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 18689 timeSetter = match[8] ? date.setUTCHours : date.setHours; 18690 18691 if (match[9]) { 18692 tzHour = toInt(match[9] + match[10]); 18693 tzMin = toInt(match[9] + match[11]); 18694 } 18695 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 18696 var h = toInt(match[4] || 0) - tzHour; 18697 var m = toInt(match[5] || 0) - tzMin; 18698 var s = toInt(match[6] || 0); 18699 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 18700 timeSetter.call(date, h, m, s, ms); 18701 return date; 18702 } 18703 return string; 18704 } 18705 18706 18707 return function(date, format, timezone) { 18708 var text = '', 18709 parts = [], 18710 fn, match; 18711 18712 format = format || 'mediumDate'; 18713 format = $locale.DATETIME_FORMATS[format] || format; 18714 if (isString(date)) { 18715 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 18716 } 18717 18718 if (isNumber(date)) { 18719 date = new Date(date); 18720 } 18721 18722 if (!isDate(date) || !isFinite(date.getTime())) { 18723 return date; 18724 } 18725 18726 while (format) { 18727 match = DATE_FORMATS_SPLIT.exec(format); 18728 if (match) { 18729 parts = concat(parts, match, 1); 18730 format = parts.pop(); 18731 } else { 18732 parts.push(format); 18733 format = null; 18734 } 18735 } 18736 18737 var dateTimezoneOffset = date.getTimezoneOffset(); 18738 if (timezone) { 18739 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 18740 date = convertTimezoneToLocal(date, timezone, true); 18741 } 18742 forEach(parts, function(value) { 18743 fn = DATE_FORMATS[value]; 18744 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 18745 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 18746 }); 18747 18748 return text; 18749 }; 18750} 18751 18752 18753/** 18754 * @ngdoc filter 18755 * @name json 18756 * @kind function 18757 * 18758 * @description 18759 * Allows you to convert a JavaScript object into JSON string. 18760 * 18761 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 18762 * the binding is automatically converted to JSON. 18763 * 18764 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 18765 * @param {number=}
18765 spacing The number of spaces to use per indentation, defaults to 2. 18766 * @returns {string} JSON string. 18767 * 18768 * 18769 * @example 18770 <example> 18771 <file name="index.html"> 18772 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 18773 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 18774 </file> 18775 <file name="protractor.js" type="protractor"> 18776 it('should jsonify filtered objects', function() { 18777 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 18778 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 18779 }); 18780 </file> 18781 </example> 18782 * 18783 */ 18784function jsonFilter() { 18785 return function(object, spacing) { 18786 if (isUndefined(spacing)) { 18787 spacing = 2; 18788 } 18789 return toJson(object, spacing); 18790 }; 18791} 18792 18793 18794/** 18795 * @ngdoc filter 18796 * @name lowercase 18797 * @kind function 18798 * @description 18799 * Converts string to lowercase. 18800 * @see angular.lowercase 18801 */ 18802var lowercaseFilter = valueFn(lowercase); 18803 18804 18805/** 18806 * @ngdoc filter 18807 * @name uppercase 18808 * @kind function 18809 * @description 18810 * Converts string to uppercase. 18811 * @see angular.uppercase 18812 */ 18813var uppercaseFilter = valueFn(uppercase); 18814 18815/** 18816 * @ngdoc filter 18817 * @name limitTo 18818 * @kind function 18819 * 18820 * @description 18821 * Creates a new array or string containing only a specified number of elements. The elements 18822 * are taken from either the beginning or the end of the source array, string or number, as specified by 18823 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 18824 * converted to a string. 18825 * 18826 * @param {Array|string|number} input Source array, string or number to be limited. 18827 * @param {string|number} limit The length of the returned array or string. If the `limit` number 18828 * is positive, `limit` number of items from the beginning of the source array/string are copied. 18829 * If the number is negative, `limit` number of items from the end of the source array/string 18830 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 18831 * the input will be returned unchanged. 18832 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 18833 * indicates an offset from the end of `input`. Defaults to `0`. 18834 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 18835 * had less than `limit` elements. 18836 * 18837 * @example 18838 <example module="limitToExample"> 18839 <file name="index.html"> 18840 <script> 18841 angular.module('limitToExample', []) 18842 .controller('ExampleController', ['$scope', function($scope) { 18843 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 18844 $scope.letters = "abcdefghi"; 18845 $scope.longNumber = 2345432342; 18846 $scope.numLimit = 3; 18847 $scope.letterLimit = 3; 18848 $scope.longNumberLimit = 3; 18849 }]); 18850 </script>
18851 <div ng-controller="ExampleController"> 18852 <label> 18853 Limit {{numbers}} to: 18854 <input type="number" step="1" ng-model="numLimit"> 18855 </label> 18856 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 18857 <label> 18858 Limit {{letters}} to: 18859 <input type="number" step="1" ng-model="letterLimit"> 18860 </label> 18861 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 18862 <label> 18863 Limit {{longNumber}} to: 18864 <input type="number" step="1" ng-model="longNumberLimit"> 18865 </label> 18866 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 18867 </div> 18868 </file> 18869 <file name="protractor.js" type="protractor"> 18870 var numLimitInput = element(by.model('numLimit')); 18871 var letterLimitInput = element(by.model('letterLimit')); 18872 var longNumberLimitInput = element(by.model('longNumberLimit')); 18873 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 18874 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 18875 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 18876 18877 it('should limit the number array to first three items', function() { 18878 expect(numLimitInput.getAttribute('value')).toBe('3'); 18879 expect(letterLimitInput.getAttribute('value')).toBe('3'); 18880 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 18881 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 18882 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 18883 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 18884 }); 18885 18886 // There is a bug in safari and protractor that doesn't like the minus key 18887 // it('should update the output when -3 is entered', function() { 18888 // numLimitInput.clear(); 18889 // numLimitInput.sendKeys('-3'); 18890 // letterLimitInput.clear(); 18891 // letterLimitInput.sendKeys('-3'); 18892 // longNumberLimitInput.clear(); 18893 // longNumberLimitInput.sendKeys('-3'); 18894 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 18895 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 18896 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 18897 // }); 18898 18899 it('should not exceed the maximum size of input array', function() { 18900 numLimitInput.clear(); 18901 numLimitInput.sendKeys('100'); 18902 letterLimitInput.clear(); 18903 letterLimitInput.sendKeys('100'); 18904 longNumberLimitInput.clear(); 18905 longNumberLimitInput.sendKeys('100'); 18906 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 18907 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 18908 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 18909 }); 18910 </file> 18911 </example> 18912*/ 18913function limitToFilter() { 18914 return function(input, limit, begin) { 18915 if (Math.abs(Number(limit)) === Infinity) { 18916 limit = Number(limit); 18917 } else { 18918 limit = toInt(limit); 18919 } 18920 if (isNaN(limit)) return input; 18921 18922 if (isNumber(input)) input = input.toString(); 18923 if (!isArray(input) && !isString(input)) return input; 18924 18925 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 18926 begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin; 18927 18928 if (limit >= 0) { 18929 return input.slice(begin, begin + limit); 18930 } else { 18931 if (begin === 0) { 18932 return input.slice(limit, input.length); 18933 } else { 18934 return input.slice(Math.max(0, begin + limit), begin); 18935 } 18936 } 18937 }; 18938} 18939 18940/** 18941 * @ngdoc filter 18942 * @name orderBy 18943 * @kind function 18944 * 18945 * @description 18946 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 18947 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 18948 * correctly, make sure they are actually being saved as numbers and not strings. 18949 * 18950 * @param {Array} array The array to sort. 18951 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 18952 * used by the comparator to determine the order of elements. 18953 * 18954 * Can be one of: 18955 * 18956 * - `function`: Getter function. The result of this function will be sorted using the 18957 * `<`, `===`, `>` operator. 18958 * - `string`: An Angular expression. The result of this expression is used to compare elements 18959 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 18960 * 3 first characters of a property called `name`). The result of a constant expression 18961 * is interpreted as a property name to be used in comparisons (for example `"special name"` 18962 * to sort object by the value of their `special name` property). An expression can be 18963 * optionally prefixed with `+` or `-` to control ascending or descending sort order 18964 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 18965 * element itself is used to compare where sorting. 18966 * - `Array`: An array of function or string predicates. The first predicate in the array 18967 * is used for sorting, but when two items are equivalent, the next predicate is used. 18968 * 18969 * If the predicate is missing or empty then it defaults to `'+'`. 18970 * 18971 * @param {boolean=} reverse Reverse the order of the array. 18972 * @returns {Array} Sorted copy of the source array. 18973 * 18974 * 18975 * @example 18976 * The example below demonstrates a simple ngRepeat, where the data is sorted 18977 * by age in descending order (predicate is set to `'-age'`). 18978 * `reverse` is not set, which means it defaults to `false`. 18979 <example module="orderByExample"> 18980 <file name="index.html"> 18981 <script> 18982 angular.module('orderByExample', [])
18983 .controller('ExampleController', ['$scope', function($scope) { 18984 $scope.friends = 18985 [{name:'John', phone:'555-1212', age:10}, 18986 {name:'Mary', phone:'555-9876', age:19}, 18987 {name:'Mike', phone:'555-4321', age:21}, 18988 {name:'Adam', phone:'555-5678', age:35}, 18989 {name:'Julie', phone:'555-8765', age:29}]; 18990 }]); 18991 </script> 18992 <div ng-controller="ExampleController"> 18993 <table class="friend"> 18994 <tr> 18995 <th>Name</th> 18996 <th>Phone Number</th> 18997 <th>Age</th> 18998 </tr> 18999 <tr ng-repeat="friend in friends | orderBy:'-age'"> 19000 <td>{{friend.name}}</td> 19001 <td>{{friend.phone}}</td> 19002 <td>{{friend.age}}</td> 19003 </tr> 19004 </table> 19005 </div> 19006 </file> 19007 </example> 19008 * 19009 * The predicate and reverse parameters can be controlled dynamically through scope properties, 19010 * as shown in the next example. 19011 * @example 19012 <example module="orderByExample"> 19013 <file name="index.html"> 19014 <script> 19015 angular.module('orderByExample', []) 19016 .controller('ExampleController', ['$scope', function($scope) { 19017 $scope.friends = 19018 [{name:'John', phone:'555-1212', age:10}, 19019 {name:'Mary', phone:'555-9876', age:19}, 19020 {name:'Mike', phone:'555-4321', age:21}, 19021 {name:'Adam', phone:'555-5678', age:35}, 19022 {name:'Julie', phone:'555-8765', age:29}]; 19023 $scope.predicate = '-age'; 19024 }]); 19025 </script>
19026 <div ng-controller="ExampleController"> 19027 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 19028 <hr/> 19029 [ <a href="" ng-click="predicate=''">unsorted</a> ] 19030 <table class="friend"> 19031 <tr> 19032 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 19033 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 19034 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 19035 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 19036 </tr> 19037 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 19038 <td>{{friend.name}}</td> 19039 <td>{{friend.phone}}</td> 19040 <td>{{friend.age}}</td> 19041 </tr> 19042 </table> 19043 </div> 19044 </file> 19045 </example> 19046 * 19047 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 19048 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 19049 * desired parameters. 19050 * 19051 * Example: 19052 * 19053 * @example 19054 <example module="orderByExample"> 19055 <file name="index.html">
19056 <div ng-controller="ExampleController"> 19057 <table class="friend"> 19058 <tr> 19059 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> 19060 (<a href="" ng-click="order('-name',false)">^</a>)</th> 19061 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> 19062 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> 19063 </tr> 19064 <tr ng-repeat="friend in friends"> 19065 <td>{{friend.name}}</td> 19066 <td>{{friend.phone}}</td> 19067 <td>{{friend.age}}</td> 19068 </tr> 19069 </table> 19070 </div> 19071 </file> 19072 19073 <file name="script.js"> 19074 angular.module('orderByExample', []) 19075 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 19076 var orderBy = $filter('orderBy'); 19077 $scope.friends = [ 19078 { name: 'John', phone: '555-1212', age: 10 }, 19079 { name: 'Mary', phone: '555-9876', age: 19 }, 19080 { name: 'Mike', phone: '555-4321', age: 21 }, 19081 { name: 'Adam', phone: '555-5678', age: 35 }, 19082 { name: 'Julie', phone: '555-8765', age: 29 } 19083 ]; 19084 $scope.order = function(predicate, reverse) { 19085 $scope.friends = orderBy($scope.friends, predicate, reverse); 19086 }; 19087 $scope.order('-age',false); 19088 }]); 19089 </file> 19090</example> 19091 */ 19092orderByFilter.$inject = ['$parse']; 19093function orderByFilter($parse) { 19094 return function(array, sortPredicate, reverseOrder) { 19095 if (!(isArrayLike(array))) return array; 19096 sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; 19097 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 19098 sortPredicate = sortPredicate.map(function(predicate) { 19099 var descending = false, get = predicate || identity; 19100 if (isString(predicate)) { 19101 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 19102 descending = predicate.charAt(0) == '-'; 19103 predicate = predicate.substring(1); 19104 } 19105 if (predicate === '') { 19106 // Effectively no predicate was passed so we compare identity 19107 return reverseComparator(compare, descending); 19108 } 19109 get = $parse(predicate); 19110 if (get.constant) { 19111 var key = get(); 19112 return reverseComparator(function(a, b) { 19113 return compare(a[key], b[key]); 19114 }, descending); 19115 } 19116 } 19117 return reverseComparator(function(a, b) { 19118 return compare(get(a),get(b)); 19119 }, descending); 19120 }); 19121 return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); 19122 19123 function comparator(o1, o2) { 19124 for (var i = 0; i < sortPredicate.length; i++) { 19125 var comp = sortPredicate[i](o1, o2); 19126 if (comp !== 0) return comp; 19127 } 19128 return 0; 19129 } 19130 function reverseComparator(comp, descending) { 19131 return descending 19132 ? function(a, b) {return comp(b,a);} 19133 : comp; 19134 } 19135 19136 function isPrimitive(value) { 19137 switch (typeof value) { 19138 case 'number': /* falls through */ 19139 case 'boolean': /* falls through */ 19140 case 'string': 19141 return true; 19142 default: 19143 return false; 19144 } 19145 } 19146 19147 function objectToString(value) { 19148 if (value === null) return 'null'; 19149 if (typeof value.valueOf === 'function') { 19150 value = value.valueOf(); 19151 if (isPrimitive(value)) return value; 19152 } 19153 if (typeof value.toString === 'function') { 19154 value = value.toString(); 19155 if (isPrimitive(value)) return value; 19156 } 19157 return ''; 19158 } 19159 19160 function compare(v1, v2) { 19161 var t1 = typeof v1; 19162 var t2 = typeof v2; 19163 if (t1 === t2 && t1 === "object") { 19164 v1 = objectToString(v1); 19165 v2 = objectToString(v2); 19166 } 19167 if (t1 === t2) { 19168 if (t1 === "string") { 19169 v1 = v1.toLowerCase(); 19170 v2 = v2.toLowerCase(); 19171 } 19172 if (v1 === v2) return 0; 19173 return v1 < v2 ? -1 : 1; 19174 } else { 19175 return t1 < t2 ? -1 : 1; 19176 } 19177 } 19178 }; 19179} 19180 19181function ngDirective(directive) { 19182 if (isFunction(directive)) { 19183 directive = { 19184 link: directive 19185 }; 19186 } 19187 directive.restrict = directive.restrict || 'AC'; 19188 return valueFn(directive); 19189} 19190 19191/** 19192 * @ngdoc directive 19193 * @name a 19194 * @restrict E 19195 * 19196 * @description 19197 * Modifies the default behavior of the html A tag so that the default action is prevented when 19198 * the href attribute is empty. 19199 * 19200 * This change permits the easy creation of action links with the `ngClick` directive 19201 * without changing the location or causing page reloads, e.g.: 19202 * `<a href="" ng-click="list.addItem()">Add Item</a>` 19203 */ 19204var htmlAnchorDirective = valueFn({ 19205 restrict: 'E', 19206 compile: function(element, attr) { 19207 if (!attr.href && !attr.xlinkHref) { 19208 return function(scope, element) { 19209 // If the linked element is not an anchor tag anymore, do nothing 19210 if (element[0].nodeName.toLowerCase() !== 'a') return; 19211 19212 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 19213 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 19214 'xlink:href' : 'href'; 19215 element.on('click', function(event) { 19216 // if we have no href url, then don't navigate anywhere. 19217 if (!element.attr(href)) { 19218 event.preventDefault(); 19219 } 19220 }); 19221 }; 19222 } 19223 } 19224}); 19225 19226/** 19227 * @ngdoc directive 19228 * @name ngHref 19229 * @restrict A 19230 * @priority 99 19231 * 19232 * @description 19233 * Using Angular markup like `{{hash}}` in an href attribute will 19234 * make the link go to the wrong URL if the user clicks it before 19235 * Angular has a chance to replace the `{{hash}}
19235` markup with its 19236 * value. Until Angular replaces the markup the link will be broken 19237 * and will most likely return a 404 error. The `ngHref` directive 19238 * solves this problem. 19239 * 19240 * The wrong way to write it: 19241 * ```html 19242 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19243 * ``` 19244 * 19245 * The correct way to write it: 19246 * ```html 19247 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19248 * ``` 19249 * 19250 * @element A 19251 * @param {template} ngHref any string which can contain `{{}}` markup. 19252 * 19253 * @example 19254 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 19255 * in links and their different behaviors: 19256 <example> 19257 <file name="index.html"> 19258 <input ng-model="value" /><br /> 19259 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 19260 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 19261 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 19262 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 19263 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 19264 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 19265 </file> 19266 <file name="protractor.js" type="protractor"> 19267 it('should execute ng-click but not reload when href without value', function() { 19268 element(by.id('link-1')).click(); 19269 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 19270 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 19271 }); 19272 19273 it('should execute ng-click but not reload when href empty string', function() { 19274 element(by.id('link-2')).click(); 19275 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 19276 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 19277 }); 19278 19279 it('should execute ng-click and change url when ng-href specified', function() { 19280 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 19281 19282 element(by.id('link-3')).click(); 19283 19284 // At this point, we navigate away from an Angular page, so we need 19285 // to use browser.driver to get the base webdriver. 19286 19287 browser.wait(function() { 19288 return browser.driver.getCurrentUrl().then(function(url) { 19289 return url.match(/\/123$/); 19290 }); 19291 }, 5000, 'page should navigate to /123'); 19292 }); 19293 19294 it('should execute ng-click but not reload when href empty string and name specified', function() { 19295 element(by.id('link-4')).click(); 19296 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 19297 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 19298 }); 19299 19300 it('should execute ng-click but not reload when no href but name specified', function() { 19301 element(by.id('link-5')).click(); 19302 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 19303 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 19304 }); 19305 19306 it('should only change url when only ng-href', function() { 19307 element(by.model('value')).clear(); 19308 element(by.model('value')).sendKeys('6'); 19309 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 19310 19311 element(by.id('link-6')).click(); 19312 19313 // At this point, we navigate away from an Angular page, so we need 19314 // to use browser.driver to get the base webdriver. 19315 browser.wait(function() { 19316 return browser.driver.getCurrentUrl().then(function(url) { 19317 return url.match(/\/6$/); 19318 }); 19319 }, 5000, 'page should navigate to /6'); 19320 }); 19321 </file> 19322 </example> 19323 */ 19324 19325/** 19326 * @ngdoc directive 19327 * @name ngSrc 19328 * @restrict A 19329 * @priority 99 19330 * 19331 * @description 19332 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 19333 * work right: The browser will fetch from the URL with the literal 19334 * text `{{hash}}` until Angular replaces the expression inside 19335 * `{{hash}}`. The `ngSrc` directive solves this problem. 19336 * 19337 * The buggy way to write it: 19338 * ```html 19339 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 19340 * ``` 19341 * 19342 * The correct way to write it: 19343 * ```html 19344 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 19345 * ``` 19346 * 19347 * @element IMG 19348 * @param {template} ngSrc any string which can contain `{{}}` markup. 19349 */ 19350 19351/** 19352 * @ngdoc directive 19353 * @name ngSrcset 19354 * @restrict A 19355 * @priority 99 19356 * 19357 * @description 19358 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 19359 * work right: The browser will fetch from the URL with the literal 19360 * text `{{hash}}` until Angular replaces the expression inside 19361 * `{{hash}}`. The `ngSrcset` directive solves this problem. 19362 * 19363 * The buggy way to write it: 19364 * ```html 19365 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 19366 * ``` 19367 * 19368 * The correct way to write it: 19369 * ```html 19370 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 19371 * ``` 19372 * 19373 * @element IMG 19374 * @param {template} ngSrcset any string which can contain `{{}}` markup. 19375 */ 19376 19377/** 19378 * @ngdoc directive 19379 * @name ngDisabled 19380 * @restrict A 19381 * @priority 100 19382 * 19383 * @description 19384 * 19385 * This directive sets the `disabled` attribute on the element if the 19386 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 19387 * 19388 * A special directive is necessary because we cannot use interpolation inside the `disabled` 19389 * attribute. The following example would make the button enabled on Chrome/Firefox 19390 * but not on older IEs: 19391 * 19392 * ```html 19393 * <!-- See below for an example of ng-disabled being used correctly --> 19394 * <div ng-init="isDisabled = false"> 19395 * <button disabled="{{isDisabled}}">Disabled</button> 19396 * </div> 19397 * ``` 19398 * 19399 * This is because the HTML specification does not require browsers to preserve the values of 19400 * boolean attributes such as `disabled` (Their presence means true and their absence means false.) 19401 * If we put an Angular interpolation expression into such an attribute then the 19402 * binding information would be lost when the browser removes the attribute. 19403 * 19404 * @example 19405 <example> 19406 <file name="index.html"> 19407 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 19408 <button ng-model="button" ng-disabled="checked">Button</button> 19409 </file> 19410 <file name="protractor.js" type="protractor"> 19411 it('should toggle button', function() { 19412 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19413 element(by.model('checked')).click(); 19414 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 19415 }); 19416 </file> 19417 </example> 19418 * 19419 * @element INPUT 19420 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 19421 * then the `disabled` attribute will be set on the element 19422 */ 19423 19424 19425/** 19426 * @ngdoc directive 19427 * @name ngChecked 19428 * @restrict A 19429 * @priority 100 19430 * 19431 * @description 19432 * The HTML specification does not require browsers to preserve the values of boolean attributes 19433 * such as checked. (Their presence means true and their absence means false.) 19434 * If we put an Angular interpolation expression into such an attribute then the 19435 * binding information would be lost when the browser removes the attribute. 19436 * The `ngChecked` directive solves this problem for the `checked` attribute. 19437 * This complementary directive is not removed by the browser and so provides 19438 * a permanent reliable place to store the binding information. 19439 * @example 19440 <example> 19441 <file name="index.html"> 19442 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 19443 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 19444 </file> 19445 <file name="protractor.js" type="protractor"> 19446 it('should check both checkBoxes', function() { 19447 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 19448 element(by.model('master')).click(); 19449 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 19450 }); 19451 </file> 19452 </example> 19453 * 19454 * @element INPUT 19455 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 19456 * then special attribute "checked" will be set on the element 19457 */ 19458 19459 19460/** 19461 * @ngdoc directive 19462 * @name ngReadonly 19463 * @restrict A 19464 * @priority 100 19465 * 19466 * @description 19467 * The HTML specification does not require browsers to preserve the values of boolean attributes 19468 * such as readonly. (Their presence means true and their absence means false.) 19469 * If we put an Angular interpolation expression into such an attribute then the 19470 * binding information would be lost when the browser removes the attribute. 19471 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 19472 * This complementary directive is not removed by the browser and so provides 19473 * a permanent reliable place to store the binding information. 19474 * @example 19475 <example> 19476 <file name="index.html"> 19477 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 19478 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 19479 </file> 19480 <file name="protractor.js" type="protractor"> 19481 it('should toggle readonly attr', function() { 19482 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 19483 element(by.model('checked')).click(); 19484 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 19485 }); 19486 </file> 19487 </example> 19488 * 19489 * @element INPUT 19490 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 19491 * then special attribute "readonly" will be set on the element 19492 */ 19493 19494 19495/** 19496 * @ngdoc directive 19497 * @name ngSelected 19498 * @restrict A 19499 * @priority 100 19500 * 19501 * @description 19502 * The HTML specification does not require browsers to preserve the values of boolean attributes 19503 * such as selected. (Their presence means true and their absence means false.) 19504 * If we put an Angular interpolation expression into such an attribute then the 19505 * binding information would be lost when the browser removes the attribute. 19506 * The `ngSelected` directive solves this problem for the `selected` attribute. 19507 * This complementary directive is not removed by the browser and so provides 19508 * a permanent reliable place to store the binding information. 19509 * 19510 * @example 19511 <example> 19512 <file name="index.html"> 19513 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 19514 <select aria-label="ngSelected demo"> 19515 <option>Hello!</option> 19516 <option id="greet" ng-selected="selected">Greetings!</option> 19517 </select> 19518 </file> 19519 <file name="protractor.js" type="protractor"> 19520 it('should select Greetings!', function() { 19521 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
19522 element(by.model('selected')).click(); 19523 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 19524 }); 19525 </file> 19526 </example> 19527 * 19528 * @element OPTION 19529 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 19530 * then special attribute "selected" will be set on the element 19531 */ 19532 19533/** 19534 * @ngdoc directive 19535 * @name ngOpen 19536 * @restrict A 19537 * @priority 100 19538 * 19539 * @description 19540 * The HTML specification does not require browsers to preserve the values of boolean attributes 19541 * such as open. (Their presence means true and their absence means false.) 19542 * If we put an Angular interpolation expression into such an attribute then the 19543 * binding information would be lost when the browser removes the attribute. 19544 * The `ngOpen` directive solves this problem for the `open` attribute. 19545 * This complementary directive is not removed by the browser and so provides 19546 * a permanent reliable place to store the binding information. 19547 * @example 19548 <example> 19549 <file name="index.html"> 19550 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 19551 <details id="details" ng-open="open"> 19552 <summary>Show/Hide me</summary> 19553 </details> 19554 </file> 19555 <file name="protractor.js" type="protractor"> 19556 it('should toggle open', function() { 19557 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 19558 element(by.model('open')).click(); 19559 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 19560 }); 19561 </file> 19562 </example> 19563 * 19564 * @element DETAILS 19565 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 19566 * then special attribute "open" will be set on the element 19567 */ 19568 19569var ngAttributeAliasDirectives = {}; 19570 19571// boolean attrs are evaluated 19572forEach(BOOLEAN_ATTR, function(propName, attrName) { 19573 // binding to multiple is not supported 19574 if (propName == "multiple") return; 19575 19576 function defaultLinkFn(scope, element, attr) { 19577 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 19578 attr.$set(attrName, !!value); 19579 }); 19580 } 19581 19582 var normalized = directiveNormalize('ng-' + attrName); 19583 var linkFn = defaultLinkFn; 19584 19585 if (propName === 'checked') { 19586 linkFn = function(scope, element, attr) { 19587 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 19588 if (attr.ngModel !== attr[normalized]) { 19589 defaultLinkFn(scope, element, attr); 19590 } 19591 }; 19592 } 19593 19594 ngAttributeAliasDirectives[normalized] = function() { 19595 return { 19596 restrict: 'A', 19597 priority: 100, 19598 link: linkFn 19599 }; 19600 }; 19601}); 19602 19603// aliased input attrs are evaluated 19604forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 19605 ngAttributeAliasDirectives[ngAttr] = function() { 19606 return { 19607 priority: 100, 19608 link: function(scope, element, attr) { 19609 //special case ngPattern when a literal regular expression value 19610 //is used as the expression (this way we don't have to watch anything). 19611 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 19612 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 19613 if (match) { 19614 attr.$set("ngPattern", new RegExp(match[1], match[2])); 19615 return; 19616 } 19617 } 19618 19619 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 19620 attr.$set(ngAttr, value); 19621 }); 19622 } 19623 }; 19624 }; 19625}); 19626 19627// ng-src, ng-srcset, ng-href are interpolated 19628forEach(['src', 'srcset', 'href'], function(attrName) { 19629 var normalized = directiveNormalize('ng-' + attrName); 19630 ngAttributeAliasDirectives[normalized] = function() { 19631 return { 19632 priority: 99, // it needs to run after the attributes are interpolated 19633 link: function(scope, element, attr) { 19634 var propName = attrName, 19635 name = attrName; 19636 19637 if (attrName === 'href' && 19638 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 19639 name = 'xlinkHref'; 19640 attr.$attr[name] = 'xlink:href'; 19641 propName = null; 19642 } 19643 19644 attr.$observe(normalized, function(value) { 19645 if (!value) { 19646 if (attrName === 'href') { 19647 attr.$set(name, null); 19648 } 19649 return; 19650 } 19651 19652 attr.$set(name, value); 19653 19654 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 19655 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 19656 // to set the property as well to achieve the desired effect. 19657 // we use attr[attrName] value since $set can sanitize the url. 19658 if (msie && propName) element.prop(propName, attr[name]); 19659 }); 19660 } 19661 }; 19662 }; 19663}); 19664 19665/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 19666 */ 19667var nullFormCtrl = { 19668 $addControl: noop, 19669 $$renameControl: nullFormRenameControl, 19670 $removeControl: noop, 19671 $setValidity: noop, 19672 $setDirty: noop, 19673 $setPristine: noop, 19674 $setSubmitted: noop 19675}, 19676SUBMITTED_CLASS = 'ng-submitted'; 19677 19678function nullFormRenameControl(control, name) { 19679 control.$name = name; 19680} 19681
19682/** 19683 * @ngdoc type 19684 * @name form.FormController 19685 * 19686 * @property {boolean} $pristine True if user has not interacted with the form yet. 19687 * @property {boolean} $dirty True if user has already interacted with the form. 19688 * @property {boolean} $valid True if all of the containing forms and controls are valid. 19689 * @property {boolean} $invalid True if at least one containing control or form is invalid. 19690 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 19691 * 19692 * @property {Object} $error Is an object hash, containing references to controls or 19693 * forms with failing validators, where: 19694 * 19695 * - keys are validation tokens (error names), 19696 * - values are arrays of controls or forms that have a failing validator for given error name. 19697 * 19698 * Built-in validation tokens: 19699 * 19700 * - `email` 19701 * - `max` 19702 * - `maxlength` 19703 * - `min` 19704 * - `minlength` 19705 * - `number` 19706 * - `pattern` 19707 * - `required` 19708 * - `url` 19709 * - `date` 19710 * - `datetimelocal` 19711 * - `time` 19712 * - `week` 19713 * - `month` 19714 * 19715 * @description 19716 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 19717 * such as being valid/invalid or dirty/pristine. 19718 * 19719 * Each {@link ng.directive:form form} directive creates an instance 19720 * of `FormController`. 19721 * 19722 */ 19723//asks for $scope to fool the BC controller module 19724FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 19725function FormController(element, attrs, $scope, $animate, $interpolate) { 19726 var form = this, 19727 controls = []; 19728 19729 var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl; 19730 19731 // init state 19732 form.$error = {}; 19733 form.$$success = {}; 19734 form.$pending = undefined; 19735 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 19736 form.$dirty = false; 19737 form.$pristine = true; 19738 form.$valid = true; 19739 form.$invalid = false; 19740 form.$submitted = false; 19741 19742 parentForm.$addControl(form); 19743 19744 /** 19745 * @ngdoc method 19746 * @name form.FormController#$rollbackViewValue 19747 * 19748 * @description 19749 * Rollback all form controls pending updates to the `$modelValue`. 19750 * 19751 * Updates may be pending by a debounced event or because the input is waiting for a some future 19752 * event defined in `ng-model-options`. This method is typically needed by the reset button of 19753 * a form that uses `ng-model-options` to pend updates. 19754 */ 19755 form.$rollbackViewValue = function() { 19756 forEach(controls, function(control) { 19757 control.$rollbackViewValue(); 19758 }); 19759 }; 19760 19761 /** 19762 * @ngdoc method 19763 * @name form.FormController#$commitViewValue 19764 * 19765 * @description 19766 * Commit all form controls pending updates to the `$modelValue`. 19767 * 19768 * Updates may be pending by a debounced event or because the input is waiting for a some future 19769 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 19770 * usually handles calling this in response to input events. 19771 */ 19772 form.$commitViewValue = function() { 19773 forEach(controls, function(control) { 19774 control.$commitViewValue(); 19775 }); 19776 }; 19777 19778 /** 19779 * @ngdoc method 19780 * @name form.FormController#$addControl 19781 * 19782 * @description 19783 * Register a control with the form. 19784 * 19785 * Input elements using ngModelController do this automatically when they are linked. 19786 */ 19787 form.$addControl = function(control) { 19788 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 19789 // and not added to the scope. Now we throw an error. 19790 assertNotHasOwnProperty(control.$name, 'input'); 19791 controls.push(control); 19792 19793 if (control.$name) { 19794 form[control.$name] = control; 19795 } 19796 }; 19797 19798 // Private API: rename a form control 19799 form.$$renameControl = function(control, newName) { 19800 var oldName = control.$name; 19801 19802 if (form[oldName] === control) { 19803 delete form[oldName]; 19804 } 19805 form[newName] = control; 19806 control.$name = newName; 19807 }; 19808 19809 /** 19810 * @ngdoc method 19811 * @name form.FormController#$removeControl 19812 * 19813 * @description 19814 * Deregister a control from the form. 19815 * 19816 * Input elements using ngModelController do this automatically when they are destroyed. 19817 */ 19818 form.$removeControl = function(control) { 19819 if (control.$name && form[control.$name] === control) { 19820 delete form[control.$name]; 19821 }
19822 forEach(form.$pending, function(value, name) { 19823 form.$setValidity(name, null, control); 19824 }); 19825 forEach(form.$error, function(value, name) { 19826 form.$setValidity(name, null, control); 19827 }); 19828 forEach(form.$$success, function(value, name) { 19829 form.$setValidity(name, null, control); 19830 }); 19831 19832 arrayRemove(controls, control); 19833 }; 19834 19835 19836 /** 19837 * @ngdoc method 19838 * @name form.FormController#$setValidity 19839 * 19840 * @description 19841 * Sets the validity of a form control. 19842 * 19843 * This method will also propagate to parent forms. 19844 */ 19845 addSetValidityMethod({ 19846 ctrl: this, 19847 $element: element, 19848 set: function(object, property, controller) { 19849 var list = object[property]; 19850 if (!list) { 19851 object[property] = [controller]; 19852 } else { 19853 var index = list.indexOf(controller); 19854 if (index === -1) { 19855 list.push(controller); 19856 } 19857 } 19858 }, 19859 unset: function(object, property, controller) { 19860 var list = object[property]; 19861 if (!list) { 19862 return; 19863 } 19864 arrayRemove(list, controller); 19865 if (list.length === 0) { 19866 delete object[property]; 19867 } 19868 }, 19869 parentForm: parentForm, 19870 $animate: $animate 19871 }); 19872 19873 /** 19874 * @ngdoc method 19875 * @name form.FormController#$setDirty 19876 * 19877 * @description 19878 * Sets the form to a dirty state. 19879 * 19880 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 19881 * state (ng-dirty class). This method will also propagate to parent forms. 19882 */ 19883 form.$setDirty = function() { 19884 $animate.removeClass(element, PRISTINE_CLASS); 19885 $animate.addClass(element, DIRTY_CLASS); 19886 form.$dirty = true; 19887 form.$pristine = false; 19888 parentForm.$setDirty(); 19889 }; 19890 19891 /** 19892 * @ngdoc method 19893 * @name form.FormController#$setPristine 19894 * 19895 * @description 19896 * Sets the form to its pristine state. 19897 * 19898 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 19899 * state (ng-pristine class). This method will also propagate to all the controls contained 19900 * in this form. 19901 * 19902 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 19903 * saving or resetting it. 19904 */ 19905 form.$setPristine = function() { 19906 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 19907 form.$dirty = false; 19908 form.$pristine = true; 19909 form.$submitted = false; 19910 forEach(controls, function(control) { 19911 control.$setPristine(); 19912 }); 19913 }; 19914 19915 /** 19916 * @ngdoc method 19917 * @name form.FormController#$setUntouched 19918 * 19919 * @description 19920 * Sets the form to its untouched state. 19921 * 19922 * This method can be called to remove the 'ng-touched' class and set the form controls to their 19923 * untouched state (ng-untouched class). 19924 * 19925 * Setting a form controls back to their untouched state is often useful when setting the form 19926 * back to its pristine state. 19927 */ 19928 form.$setUntouched = function() { 19929 forEach(controls, function(control) { 19930 control.$setUntouched(); 19931 }); 19932 }; 19933 19934 /** 19935 * @ngdoc method 19936 * @name form.FormController#$setSubmitted 19937 * 19938 * @description 19939 * Sets the form to its submitted state. 19940 */ 19941 form.$setSubmitted = function() { 19942 $animate.addClass(element, SUBMITTED_CLASS); 19943 form.$submitted = true; 19944 parentForm.$setSubmitted(); 19945 }; 19946} 19947 19948/** 19949 * @ngdoc directive 19950 * @name ngForm 19951 * @restrict EAC 19952 * 19953 * @description 19954 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 19955 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 19956 * sub-group of controls needs to be determined. 19957 *
19958 * Note: the purpose of `ngForm` is to group controls, 19959 * but not to be a replacement for the `<form>` tag with all of its capabilities 19960 * (e.g. posting to the server, ...). 19961 * 19962 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 19963 * related scope, under this name. 19964 * 19965 */ 19966 19967 /** 19968 * @ngdoc directive 19969 * @name form 19970 * @restrict E 19971 * 19972 * @description 19973 * Directive that instantiates 19974 * {@link form.FormController FormController}. 19975 * 19976 * If the `name` attribute is specified, the form controller is published onto the current scope under 19977 * this name. 19978 * 19979 * # Alias: {@link ng.directive:ngForm `ngForm`} 19980 * 19981 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 19982 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 19983 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 19984 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 19985 * using Angular validation directives in forms that are dynamically generated using the 19986 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 19987 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 19988 * `ngForm` directive and nest these in an outer `form` element. 19989 * 19990 * 19991 * # CSS classes 19992 * - `ng-valid` is set if the form is valid. 19993 * - `ng-invalid` is set if the form is invalid. 19994 * - `ng-pristine` is set if the form is pristine. 19995 * - `ng-dirty` is set if the form is dirty. 19996 * - `ng-submitted` is set if the form was submitted. 19997 * 19998 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 19999 * 20000 * 20001 * # Submitting a form and preventing the default action 20002 * 20003 * Since the role of forms in client-side Angular applications is different than in classical 20004 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 20005 * page reload that sends the data to the server. Instead some javascript logic should be triggered 20006 * to handle the form submission in an application-specific way. 20007 * 20008 * For this reason, Angular prevents the default action (form submission to the server) unless the 20009 * `<form>` element has an `action` attribute specified. 20010 * 20011 * You can use one of the following two ways to specify what javascript method should be called when 20012 * a form is submitted: 20013 * 20014 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 20015 * - {@link ng.directive:ngClick ngClick} directive on the first 20016 * button or input field of type submit (input[type=submit]) 20017 * 20018 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 20019 * or {@link ng.directive:ngClick ngClick} directives. 20020 * This is because of the following form submission rules in the HTML specification: 20021 * 20022 * - If a form has only one input field then hitting enter in this field triggers form submit 20023 * (`ngSubmit`) 20024 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 20025 * doesn't trigger submit 20026 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 20027 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 20028 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 20029 * 20030 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 20031 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 20032 * to have access to the updated model. 20033 * 20034 * ## Animation Hooks 20035 * 20036 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 20037 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 20038 * other validations that are performed within the form. Animations in ngForm are similar to how 20039 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 20040 * as JS animations. 20041 * 20042 * The following example shows a simple way to utilize CSS transitions to style a form element
20043 * that has been rendered as invalid after it has been validated: 20044 * 20045 * <pre> 20046 * //be sure to include ngAnimate as a module to hook into more 20047 * //advanced animations 20048 * .my-form { 20049 * transition:0.5s linear all; 20050 * background: white; 20051 * } 20052 * .my-form.ng-invalid { 20053 * background: red; 20054 * color:white; 20055 * } 20056 * </pre> 20057 * 20058 * @example 20059 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 20060 <file name="index.html"> 20061 <script> 20062 angular.module('formExample', []) 20063 .controller('FormController', ['$scope', function($scope) { 20064 $scope.userType = 'guest'; 20065 }]); 20066 </script> 20067 <style> 20068 .my-form { 20069 -webkit-transition:all linear 0.5s; 20070 transition:all linear 0.5s; 20071 background: transparent; 20072 } 20073 .my-form.ng-invalid { 20074 background: red; 20075 } 20076 </style> 20077 <form name="myForm" ng-controller="FormController" class="my-form"> 20078 userType: <input name="input" ng-model="userType" required> 20079 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 20080 <code>userType = {{userType}}</code><br> 20081 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 20082 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 20083 <code>myForm.$valid = {{myForm.$valid}}</code><br> 20084 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 20085 </form> 20086 </file> 20087 <file name="protractor.js" type="protractor"> 20088 it('should initialize to model', function() { 20089 var userType = element(by.binding('userType')); 20090 var valid = element(by.binding('myForm.input.$valid')); 20091 20092 expect(userType.getText()).toContain('guest'); 20093 expect(valid.getText()).toContain('true'); 20094 }); 20095 20096 it('should be invalid if empty', function() { 20097 var userType = element(by.binding('userType')); 20098 var valid = element(by.binding('myForm.input.$valid')); 20099 var userInput = element(by.model('userType')); 20100 20101 userInput.clear(); 20102 userInput.sendKeys(''); 20103 20104 expect(userType.getText()).toEqual('userType ='); 20105 expect(valid.getText()).toContain('false'); 20106 }); 20107 </file> 20108 </example> 20109 * 20110 * @param {string=} name Name of the form. If specified, the form controller will be published into 20111 * related scope, under this name. 20112 */ 20113var formDirectiveFactory = function(isNgForm) { 20114 return ['$timeout', function($timeout) { 20115 var formDirective = { 20116 name: 'form', 20117 restrict: isNgForm ? 'EAC' : 'E', 20118 controller: FormController, 20119 compile: function ngFormCompile(formElement, attr) { 20120 // Setup initial state of the control 20121 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 20122 20123 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 20124 20125 return { 20126 pre: function ngFormPreLink(scope, formElement, attr, controller) { 20127 // if `action` attr is not present on the form, prevent the default action (submission) 20128 if (!('action' in attr)) { 20129 // we can't use jq events because if a form is destroyed during submission the default 20130 // action is not prevented. see #1238 20131 // 20132 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 20133 // page reload if the form was destroyed by submission of the form via a click handler 20134 // on a button in the form. Looks like an IE9 specific bug. 20135 var handleFormSubmission = function(event) { 20136 scope.$apply(function() { 20137 controller.$commitViewValue(); 20138 controller.$setSubmitted(); 20139 }); 20140 20141 event.preventDefault(); 20142 }; 20143 20144 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20145 20146 // unregister the preventDefault listener so that we don't not leak memory but in a 20147 // way that will achieve the prevention of the default action. 20148 formElement.on('$destroy', function() { 20149 $timeout(function() { 20150 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20151 }, 0, false); 20152 }); 20153 } 20154 20155 var parentFormCtrl = controller.$$parentForm; 20156 20157 if (nameAttr) { 20158 setter(scope, controller.$name, controller, controller.$name); 20159 attr.$observe(nameAttr, function(newValue) { 20160 if (controller.$name === newValue) return;
20161 setter(scope, controller.$name, undefined, controller.$name); 20162 parentFormCtrl.$$renameControl(controller, newValue); 20163 setter(scope, controller.$name, controller, controller.$name); 20164 }); 20165 } 20166 formElement.on('$destroy', function() { 20167 parentFormCtrl.$removeControl(controller); 20168 if (nameAttr) { 20169 setter(scope, attr[nameAttr], undefined, controller.$name); 20170 } 20171 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 20172 }); 20173 } 20174 }; 20175 } 20176 }; 20177 20178 return formDirective; 20179 }]; 20180}; 20181 20182var formDirective = formDirectiveFactory(); 20183var ngFormDirective = formDirectiveFactory(true); 20184 20185/* global VALID_CLASS: false, 20186 INVALID_CLASS: false, 20187 PRISTINE_CLASS: false, 20188 DIRTY_CLASS: false, 20189 UNTOUCHED_CLASS: false, 20190 TOUCHED_CLASS: false, 20191 $ngModelMinErr: false, 20192*/ 20193 20194// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 20195var 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)/; 20196var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 20197var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 20198var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 20199var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; 20200var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20201var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; 20202var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; 20203var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20204 20205var inputType = { 20206 20207 /** 20208 * @ngdoc input 20209 * @name input[text] 20210 * 20211 * @description 20212 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 20213 * 20214 * 20215 * @param {string} ngModel Assignable angular expression to data-bind to. 20216 * @param {string=} name Property name of the form under which the control is published. 20217 * @param {string=} required Adds `required` validation error key if the value is not entered. 20218 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20219 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20220 * `required` when you want to data-bind to the `required` attribute. 20221 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20222 * minlength. 20223 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20224 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 20225 * any length. 20226 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 20227 * that contains the regular expression body that will be converted to a regular expression 20228 * as in the ngPattern directive. 20229 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 20230 * a RegExp found by evaluating the Angular expression given in the attribute value. 20231 * If the expression evaluates to a RegExp object, then this is used directly. 20232 * If the expression evaluates to a string, then it will be converted to a RegExp 20233 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 20234 * `new RegExp('^abc$')`.<br /> 20235 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 20236 * start at the index of the last search's match, thus not taking the whole input value into 20237 * account. 20238 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20239 * interaction with the input element. 20240 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 20241 * This parameter is ignored for input[type=password] controls, which will never trim the 20242 * input. 20243 * 20244 * @example 20245 <example name="text-input-directive" module="textInputExample"> 20246 <file name="index.html"> 20247 <script> 20248 angular.module('textInputExample', [])
20249 .controller('ExampleController', ['$scope', function($scope) { 20250 $scope.example = { 20251 text: 'guest', 20252 word: /^\s*\w*\s*$/ 20253 }; 20254 }]); 20255 </script> 20256 <form name="myForm" ng-controller="ExampleController"> 20257 <label>Single word: 20258 <input type="text" name="input" ng-model="example.text" 20259 ng-pattern="example.word" required ng-trim="false"> 20260 </label> 20261 <div role="alert"> 20262 <span class="error" ng-show="myForm.input.$error.required"> 20263 Required!</span> 20264 <span class="error" ng-show="myForm.input.$error.pattern"> 20265 Single word only!</span> 20266 </div> 20267 <tt>text = {{example.text}}</tt><br/> 20268 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20269 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20270 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20271 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20272 </form> 20273 </file> 20274 <file name="protractor.js" type="protractor"> 20275 var text = element(by.binding('example.text')); 20276 var valid = element(by.binding('myForm.input.$valid')); 20277 var input = element(by.model('example.text')); 20278 20279 it('should initialize to model', function() { 20280 expect(text.getText()).toContain('guest'); 20281 expect(valid.getText()).toContain('true'); 20282 }); 20283 20284 it('should be invalid if empty', function() { 20285 input.clear(); 20286 input.sendKeys(''); 20287 20288 expect(text.getText()).toEqual('text ='); 20289 expect(valid.getText()).toContain('false'); 20290 }); 20291 20292 it('should be invalid if multi word', function() { 20293 input.clear(); 20294 input.sendKeys('hello world'); 20295 20296 expect(valid.getText()).toContain('false'); 20297 }); 20298 </file> 20299 </example> 20300 */ 20301 'text': textInputType, 20302 20303 /** 20304 * @ngdoc input 20305 * @name input[date] 20306 * 20307 * @description 20308 * Input with date validation and transformation. In browsers that do not yet support 20309 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 20310 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 20311 * modern browsers do not yet support this input type, it is important to provide cues to users on the 20312 * expected input format via a placeholder or label. 20313 * 20314 * The model must always be a Date object, otherwise Angular will throw an error. 20315 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20316 * 20317 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20318 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20319 * 20320 * @param {string} ngModel Assignable angular expression to data-bind to. 20321 * @param {string=} name Property name of the form under which the control is published. 20322 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20323 * valid ISO date string (yyyy-MM-dd). 20324 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 20325 * a valid ISO date string (yyyy-MM-dd). 20326 * @param {string=} required Sets `required` validation error key if the value is not entered. 20327 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20328 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20329 * `required` when you want to data-bind to the `required` attribute. 20330 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20331 * interaction with the input element. 20332 * 20333 * @example 20334 <example name="date-input-directive" module="dateInputExample"> 20335 <file name="index.html"> 20336 <script> 20337 angular.module('dateInputExample', []) 20338 .controller('DateController', ['$scope', function($scope) { 20339 $scope.example = { 20340 value: new Date(2013, 9, 22) 20341 }; 20342 }]); 20343 </script> 20344 <form name="myForm" ng-controller="DateController as dateCtrl"> 20345 <label for="exampleInput">Pick a date in 2013:</label> 20346 <input type="date" id="exampleInput" name="input" ng-model="example.value" 20347 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 20348 <div role="alert">
20349 <span class="error" ng-show="myForm.input.$error.required"> 20350 Required!</span> 20351 <span class="error" ng-show="myForm.input.$error.date"> 20352 Not a valid date!</span> 20353 </div> 20354 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 20355 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20356 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20357 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20358 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20359 </form> 20360 </file> 20361 <file name="protractor.js" type="protractor"> 20362 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 20363 var valid = element(by.binding('myForm.input.$valid')); 20364 var input = element(by.model('example.value')); 20365 20366 // currently protractor/webdriver does not support 20367 // sending keys to all known HTML5 input controls 20368 // for various browsers (see https://github.com/angular/protractor/issues/562). 20369 function setInput(val) { 20370 // set the value of the element and force validation. 20371 var scr = "var ipt = document.getElementById('exampleInput'); " + 20372 "ipt.value = '" + val + "';" + 20373 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20374 browser.executeScript(scr); 20375 } 20376 20377 it('should initialize to model', function() { 20378 expect(value.getText()).toContain('2013-10-22'); 20379 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20380 }); 20381 20382 it('should be invalid if empty', function() { 20383 setInput(''); 20384 expect(value.getText()).toEqual('value ='); 20385 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20386 }); 20387 20388 it('should be invalid if over max', function() { 20389 setInput('2015-01-01'); 20390 expect(value.getText()).toContain(''); 20391 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20392 }); 20393 </file> 20394 </example> 20395 */ 20396 'date': createDateInputType('date', DATE_REGEXP, 20397 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 20398 'yyyy-MM-dd'), 20399 20400 /** 20401 * @ngdoc input 20402 * @name input[datetime-local] 20403 * 20404 * @description 20405 * Input with datetime validation and transformation. In browsers that do not yet support 20406 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20407 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 20408 * 20409 * The model must always be a Date object, otherwise Angular will throw an error. 20410 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20411 * 20412 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20413 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20414 * 20415 * @param {string} ngModel Assignable angular expression to data-bind to. 20416 * @param {string=} name Property name of the form under which the control is published. 20417 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20418 * valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). 20419 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 20420 * a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). 20421 * @param {string=} required Sets `required` validation error key if the value is not entered. 20422 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20423 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20424 * `required` when you want to data-bind to the `required` attribute. 20425 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20426 * interaction with the input element. 20427 * 20428 * @example 20429 <example name="datetimelocal-input-directive" module="dateExample"> 20430 <file name="index.html"> 20431 <script> 20432 angular.module('dateExample', []) 20433 .controller('DateController', ['$scope', function($scope) { 20434 $scope.example = { 20435 value: new Date(2010, 11, 28, 14, 57) 20436 }; 20437 }]); 20438 </script> 20439 <form name="myForm" ng-controller="DateController as dateCtrl"> 20440 <label for="exampleInput">Pick a date between in 2013:</label> 20441 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 20442 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 20443 <div role="alert">
20444 <span class="error" ng-show="myForm.input.$error.required"> 20445 Required!</span> 20446 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 20447 Not a valid date!</span> 20448 </div> 20449 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 20450 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20451 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20452 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20453 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20454 </form> 20455 </file> 20456 <file name="protractor.js" type="protractor"> 20457 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 20458 var valid = element(by.binding('myForm.input.$valid')); 20459 var input = element(by.model('example.value')); 20460 20461 // currently protractor/webdriver does not support 20462 // sending keys to all known HTML5 input controls 20463 // for various browsers (https://github.com/angular/protractor/issues/562). 20464 function setInput(val) { 20465 // set the value of the element and force validation. 20466 var scr = "var ipt = document.getElementById('exampleInput'); " + 20467 "ipt.value = '" + val + "';" + 20468 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20469 browser.executeScript(scr); 20470 } 20471 20472 it('should initialize to model', function() { 20473 expect(value.getText()).toContain('2010-12-28T14:57:00'); 20474 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20475 }); 20476 20477 it('should be invalid if empty', function() { 20478 setInput(''); 20479 expect(value.getText()).toEqual('value ='); 20480 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20481 }); 20482 20483 it('should be invalid if over max', function() { 20484 setInput('2015-01-01T23:59:00'); 20485 expect(value.getText()).toContain(''); 20486 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20487 }); 20488 </file> 20489 </example> 20490 */ 20491 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 20492 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 20493 'yyyy-MM-ddTHH:mm:ss.sss'), 20494 20495 /** 20496 * @ngdoc input 20497 * @name input[time] 20498 * 20499 * @description 20500 * Input with time validation and transformation. In browsers that do not yet support 20501 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20502 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 20503 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 20504 * 20505 * The model must always be a Date object, otherwise Angular will throw an error. 20506 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20507 * 20508 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20509 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20510 * 20511 * @param {string} ngModel Assignable angular expression to data-bind to. 20512 * @param {string=} name Property name of the form under which the control is published. 20513 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20514 * valid ISO time format (HH:mm:ss). 20515 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a 20516 * valid ISO time format (HH:mm:ss). 20517 * @param {string=} required Sets `required` validation error key if the value is not entered. 20518 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20519 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20520 * `required` when you want to data-bind to the `required` attribute. 20521 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20522 * interaction with the input element. 20523 * 20524 * @example 20525 <example name="time-input-directive" module="timeExample"> 20526 <file name="index.html"> 20527 <script> 20528 angular.module('timeExample', []) 20529 .controller('DateController', ['$scope', function($scope) { 20530 $scope.example = { 20531 value: new Date(1970, 0, 1, 14, 57, 0) 20532 }; 20533 }]); 20534 </script> 20535 <form name="myForm" ng-controller="DateController as dateCtrl"> 20536 <label for="exampleInput">Pick a between 8am and 5pm:</label> 20537 <input type="time" id="exampleInput" name="input" ng-model="example.value" 20538 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 20539 <div role="alert">
20540 <span class="error" ng-show="myForm.input.$error.required"> 20541 Required!</span> 20542 <span class="error" ng-show="myForm.input.$error.time"> 20543 Not a valid date!</span> 20544 </div> 20545 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 20546 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20547 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20548 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20549 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20550 </form> 20551 </file> 20552 <file name="protractor.js" type="protractor"> 20553 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 20554 var valid = element(by.binding('myForm.input.$valid')); 20555 var input = element(by.model('example.value')); 20556 20557 // currently protractor/webdriver does not support 20558 // sending keys to all known HTML5 input controls 20559 // for various browsers (https://github.com/angular/protractor/issues/562). 20560 function setInput(val) { 20561 // set the value of the element and force validation. 20562 var scr = "var ipt = document.getElementById('exampleInput'); " + 20563 "ipt.value = '" + val + "';" + 20564 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20565 browser.executeScript(scr); 20566 } 20567 20568 it('should initialize to model', function() { 20569 expect(value.getText()).toContain('14:57:00'); 20570 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20571 }); 20572 20573 it('should be invalid if empty', function() { 20574 setInput(''); 20575 expect(value.getText()).toEqual('value ='); 20576 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20577 }); 20578 20579 it('should be invalid if over max', function() { 20580 setInput('23:59:00'); 20581 expect(value.getText()).toContain(''); 20582 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20583 }); 20584 </file> 20585 </example> 20586 */ 20587 'time': createDateInputType('time', TIME_REGEXP, 20588 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 20589 'HH:mm:ss.sss'), 20590 20591 /** 20592 * @ngdoc input 20593 * @name input[week] 20594 * 20595 * @description 20596 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 20597 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20598 * week format (yyyy-W##), for example: `2013-W02`. 20599 * 20600 * The model must always be a Date object, otherwise Angular will throw an error. 20601 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20602 * 20603 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20604 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20605 * 20606 * @param {string} ngModel Assignable angular expression to data-bind to. 20607 * @param {string=} name Property name of the form under which the control is published. 20608 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20609 * valid ISO week format (yyyy-W##). 20610 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 20611 * a valid ISO week format (yyyy-W##). 20612 * @param {string=} required Sets `required` validation error key if the value is not entered. 20613 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20614 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20615 * `required` when you want to data-bind to the `required` attribute. 20616 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20617 * interaction with the input element. 20618 * 20619 * @example 20620 <example name="week-input-directive" module="weekExample"> 20621 <file name="index.html"> 20622 <script> 20623 angular.module('weekExample', []) 20624 .controller('DateController', ['$scope', function($scope) { 20625 $scope.example = { 20626 value: new Date(2013, 0, 3) 20627 }; 20628 }]); 20629 </script> 20630 <form name="myForm" ng-controller="DateController as dateCtrl"> 20631 <label>Pick a date between in 2013: 20632 <input id="exampleInput" type="week" name="input" ng-model="example.value" 20633 placeholder="YYYY-W##" min="2012-W32" 20634 max="2013-W52" required /> 20635 </label> 20636 <div role="alert">
20637 <span class="error" ng-show="myForm.input.$error.required"> 20638 Required!</span> 20639 <span class="error" ng-show="myForm.input.$error.week"> 20640 Not a valid date!</span> 20641 </div> 20642 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 20643 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20644 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20645 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20646 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20647 </form> 20648 </file> 20649 <file name="protractor.js" type="protractor"> 20650 var value = element(by.binding('example.value | date: "yyyy-Www"')); 20651 var valid = element(by.binding('myForm.input.$valid')); 20652 var input = element(by.model('example.value')); 20653 20654 // currently protractor/webdriver does not support 20655 // sending keys to all known HTML5 input controls 20656 // for various browsers (https://github.com/angular/protractor/issues/562). 20657 function setInput(val) { 20658 // set the value of the element and force validation. 20659 var scr = "var ipt = document.getElementById('exampleInput'); " + 20660 "ipt.value = '" + val + "';" + 20661 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20662 browser.executeScript(scr); 20663 } 20664 20665 it('should initialize to model', function() { 20666 expect(value.getText()).toContain('2013-W01'); 20667 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20668 }); 20669 20670 it('should be invalid if empty', function() { 20671 setInput(''); 20672 expect(value.getText()).toEqual('value ='); 20673 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20674 }); 20675 20676 it('should be invalid if over max', function() { 20677 setInput('2015-W01'); 20678 expect(value.getText()).toContain(''); 20679 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20680 }); 20681 </file> 20682 </example> 20683 */ 20684 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 20685 20686 /** 20687 * @ngdoc input 20688 * @name input[month] 20689 * 20690 * @description 20691 * Input with month validation and transformation. In browsers that do not yet support 20692 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20693 * month format (yyyy-MM), for example: `2009-01`. 20694 * 20695 * The model must always be a Date object, otherwise Angular will throw an error. 20696 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20697 * If the model is not set to the first of the month, the next view to model update will set it 20698 * to the first of the month. 20699 * 20700 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20701 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20702 * 20703 * @param {string} ngModel Assignable angular expression to data-bind to. 20704 * @param {string=} name Property name of the form under which the control is published. 20705 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be 20706 * a valid ISO month format (yyyy-MM). 20707 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must 20708 * be a valid ISO month format (yyyy-MM). 20709 * @param {string=} required Sets `required` validation error key if the value is not entered. 20710 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20711 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20712 * `required` when you want to data-bind to the `required` attribute. 20713 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20714 * interaction with the input element. 20715 * 20716 * @example 20717 <example name="month-input-directive" module="monthExample"> 20718 <file name="index.html"> 20719 <script> 20720 angular.module('monthExample', []) 20721 .controller('DateController', ['$scope', function($scope) { 20722 $scope.example = { 20723 value: new Date(2013, 9, 1) 20724 }; 20725 }]); 20726 </script> 20727 <form name="myForm" ng-controller="DateController as dateCtrl"> 20728 <label for="exampleInput">Pick a month in 2013:</label> 20729 <input id="exampleInput" type="month" name="input" ng-model="example.value" 20730 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 20731 <div role="alert">
20732 <span class="error" ng-show="myForm.input.$error.required"> 20733 Required!</span> 20734 <span class="error" ng-show="myForm.input.$error.month"> 20735 Not a valid month!</span> 20736 </div> 20737 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 20738 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20739 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20740 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20741 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20742 </form> 20743 </file> 20744 <file name="protractor.js" type="protractor"> 20745 var value = element(by.binding('example.value | date: "yyyy-MM"')); 20746 var valid = element(by.binding('myForm.input.$valid')); 20747 var input = element(by.model('example.value')); 20748 20749 // currently protractor/webdriver does not support 20750 // sending keys to all known HTML5 input controls 20751 // for various browsers (https://github.com/angular/protractor/issues/562). 20752 function setInput(val) { 20753 // set the value of the element and force validation. 20754 var scr = "var ipt = document.getElementById('exampleInput'); " + 20755 "ipt.value = '" + val + "';" + 20756 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20757 browser.executeScript(scr); 20758 } 20759 20760 it('should initialize to model', function() { 20761 expect(value.getText()).toContain('2013-10'); 20762 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20763 }); 20764 20765 it('should be invalid if empty', function() { 20766 setInput(''); 20767 expect(value.getText()).toEqual('value ='); 20768 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20769 }); 20770 20771 it('should be invalid if over max', function() { 20772 setInput('2015-01'); 20773 expect(value.getText()).toContain(''); 20774 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20775 }); 20776 </file> 20777 </example> 20778 */ 20779 'month': createDateInputType('month', MONTH_REGEXP, 20780 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 20781 'yyyy-MM'), 20782 20783 /** 20784 * @ngdoc input 20785 * @name input[number] 20786 * 20787 * @description 20788 * Text input with number validation and transformation. Sets the `number` validation 20789 * error if not a valid number. 20790 * 20791 * <div class="alert alert-warning"> 20792 * The model must always be of type `number` otherwise Angular will throw an error. 20793 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 20794 * error docs for more information and an example of how to convert your model if necessary. 20795 * </div> 20796 * 20797 * @param {string} ngModel Assignable angular expression to data-bind to. 20798 * @param {string=} name Property name of the form under which the control is published. 20799 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 20800 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 20801 * @param {string=} required Sets `required` validation error key if the value is not entered. 20802 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20803 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20804 * `required` when you want to data-bind to the `required` attribute. 20805 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20806 * minlength. 20807 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20808 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 20809 * any length. 20810 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 20811 * that contains the regular expression body that will be converted to a regular expression 20812 * as in the ngPattern directive. 20813 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 20814 * a RegExp found by evaluating the Angular expression given in the attribute value. 20815 * If the expression evaluates to a RegExp object, then this is used directly. 20816 * If the expression evaluates to a string, then it will be converted to a RegExp 20817 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 20818 * `new RegExp('^abc$')`.<br /> 20819 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 20820 * start at the index of the last search's match, thus not taking the whole input value into 20821 * account. 20822 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20823 * interaction with the input element. 20824 * 20825 * @example 20826 <example name="number-input-directive" module="numberExample"> 20827 <file name="index.html"> 20828 <script> 20829 angular.module('numberExample', [])
20830 .controller('ExampleController', ['$scope', function($scope) { 20831 $scope.example = { 20832 value: 12 20833 }; 20834 }]); 20835 </script> 20836 <form name="myForm" ng-controller="ExampleController"> 20837 <label>Number: 20838 <input type="number" name="input" ng-model="example.value" 20839 min="0" max="99" required> 20840 </label> 20841 <div role="alert"> 20842 <span class="error" ng-show="myForm.input.$error.required"> 20843 Required!</span> 20844 <span class="error" ng-show="myForm.input.$error.number"> 20845 Not valid number!</span> 20846 </div> 20847 <tt>value = {{example.value}}</tt><br/> 20848 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20849 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20850 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20851 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20852 </form> 20853 </file> 20854 <file name="protractor.js" type="protractor"> 20855 var value = element(by.binding('example.value')); 20856 var valid = element(by.binding('myForm.input.$valid')); 20857 var input = element(by.model('example.value')); 20858 20859 it('should initialize to model', function() { 20860 expect(value.getText()).toContain('12'); 20861 expect(valid.getText()).toContain('true'); 20862 }); 20863 20864 it('should be invalid if empty', function() { 20865 input.clear(); 20866 input.sendKeys(''); 20867 expect(value.getText()).toEqual('value ='); 20868 expect(valid.getText()).toContain('false'); 20869 }); 20870 20871 it('should be invalid if over max', function() { 20872 input.clear(); 20873 input.sendKeys('123'); 20874 expect(value.getText()).toEqual('value ='); 20875 expect(valid.getText()).toContain('false'); 20876 }); 20877 </file> 20878 </example> 20879 */ 20880 'number': numberInputType, 20881 20882 20883 /** 20884 * @ngdoc input 20885 * @name input[url] 20886 * 20887 * @description 20888 * Text input with URL validation. Sets the `url` validation error key if the content is not a 20889 * valid URL. 20890 * 20891 * <div class="alert alert-warning"> 20892 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 20893 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 20894 * the built-in validators (see the {@link guide/forms Forms guide}) 20895 * </div> 20896 * 20897 * @param {string} ngModel Assignable angular expression to data-bind to. 20898 * @param {string=} name Property name of the form under which the control is published. 20899 * @param {string=} required Sets `required` validation error key if the value is not entered. 20900 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20901 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20902 * `required` when you want to data-bind to the `required` attribute. 20903 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20904 * minlength. 20905 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20906 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 20907 * any length. 20908 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 20909 * that contains the regular expression body that will be converted to a regular expression 20910 * as in the ngPattern directive. 20911 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 20912 * a RegExp found by evaluating the Angular expression given in the attribute value. 20913 * If the expression evaluates to a RegExp object, then this is used directly. 20914 * If the expression evaluates to a string, then it will be converted to a RegExp 20915 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 20916 * `new RegExp('^abc$')`.<br /> 20917 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 20918 * start at the index of the last search's match, thus not taking the whole input value into 20919 * account. 20920 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20921 * interaction with the input element. 20922 * 20923 * @example 20924 <example name="url-input-directive" module="urlExample"> 20925 <file name="index.html"> 20926 <script> 20927 angular.module('urlExample', [])
20928 .controller('ExampleController', ['$scope', function($scope) { 20929 $scope.url = { 20930 text: 'http://google.com' 20931 }; 20932 }]); 20933 </script> 20934 <form name="myForm" ng-controller="ExampleController"> 20935 <label>URL: 20936 <input type="url" name="input" ng-model="url.text" required> 20937 <label> 20938 <div role="alert"> 20939 <span class="error" ng-show="myForm.input.$error.required"> 20940 Required!</span> 20941 <span class="error" ng-show="myForm.input.$error.url"> 20942 Not valid url!</span> 20943 </div> 20944 <tt>text = {{url.text}}</tt><br/> 20945 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20946 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20947 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20948 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20949 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 20950 </form> 20951 </file> 20952 <file name="protractor.js" type="protractor"> 20953 var text = element(by.binding('url.text')); 20954 var valid = element(by.binding('myForm.input.$valid')); 20955 var input = element(by.model('url.text')); 20956 20957 it('should initialize to model', function() { 20958 expect(text.getText()).toContain('http://google.com'); 20959 expect(valid.getText()).toContain('true'); 20960 }); 20961 20962 it('should be invalid if empty', function() { 20963 input.clear(); 20964 input.sendKeys(''); 20965 20966 expect(text.getText()).toEqual('text ='); 20967 expect(valid.getText()).toContain('false'); 20968 }); 20969 20970 it('should be invalid if not url', function() { 20971 input.clear(); 20972 input.sendKeys('box'); 20973 20974 expect(valid.getText()).toContain('false'); 20975 }); 20976 </file> 20977 </example> 20978 */ 20979 'url': urlInputType, 20980 20981 20982 /** 20983 * @ngdoc input 20984 * @name input[email] 20985 * 20986 * @description 20987 * Text input with email validation. Sets the `email` validation error key if not a valid email 20988 * address. 20989 * 20990 * <div class="alert alert-warning"> 20991 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 20992 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 20993 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 20994 * </div> 20995 * 20996 * @param {string} ngModel Assignable angular expression to data-bind to. 20997 * @param {string=} name Property name of the form under which the control is published. 20998 * @param {string=} required Sets `required` validation error key if the value is not entered. 20999 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21000 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21001 * `required` when you want to data-bind to the `required` attribute. 21002 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21003 * minlength. 21004 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21005 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21006 * any length. 21007 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21008 * that contains the regular expression body that will be converted to a regular expression 21009 * as in the ngPattern directive. 21010 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21011 * a RegExp found by evaluating the Angular expression given in the attribute value. 21012 * If the expression evaluates to a RegExp object, then this is used directly. 21013 * If the expression evaluates to a string, then it will be converted to a RegExp 21014 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21015 * `new RegExp('^abc$')`.<br /> 21016 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21017 * start at the index of the last search's match, thus not taking the whole input value into 21018 * account. 21019 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21020 * interaction with the input element. 21021 * 21022 * @example 21023 <example name="email-input-directive" module="emailExample"> 21024 <file name="index.html"> 21025 <script> 21026 angular.module('emailExample', [])
21027 .controller('ExampleController', ['$scope', function($scope) { 21028 $scope.email = { 21029 text: '[email protected]' 21030 }; 21031 }]); 21032 </script> 21033 <form name="myForm" ng-controller="ExampleController"> 21034 <label>Email: 21035 <input type="email" name="input" ng-model="email.text" required> 21036 </label> 21037 <div role="alert"> 21038 <span class="error" ng-show="myForm.input.$error.required"> 21039 Required!</span> 21040 <span class="error" ng-show="myForm.input.$error.email"> 21041 Not valid email!</span> 21042 </div> 21043 <tt>text = {{email.text}}</tt><br/> 21044 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21045 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21046 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21047 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21048 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 21049 </form> 21050 </file> 21051 <file name="protractor.js" type="protractor"> 21052 var text = element(by.binding('email.text')); 21053 var valid = element(by.binding('myForm.input.$valid')); 21054 var input = element(by.model('email.text')); 21055 21056 it('should initialize to model', function() { 21057 expect(text.getText()).toContain('[email protected]'); 21058 expect(valid.getText()).toContain('true'); 21059 }); 21060 21061 it('should be invalid if empty', function() { 21062 input.clear(); 21063 input.sendKeys(''); 21064 expect(text.getText()).toEqual('text ='); 21065 expect(valid.getText()).toContain('false'); 21066 }); 21067 21068 it('should be invalid if not email', function() { 21069 input.clear(); 21070 input.sendKeys('xxx'); 21071 21072 expect(valid.getText()).toContain('false'); 21073 }); 21074 </file> 21075 </example> 21076 */ 21077 'email': emailInputType, 21078 21079 21080 /** 21081 * @ngdoc input 21082 * @name input[radio] 21083 * 21084 * @description 21085 * HTML radio button. 21086 * 21087 * @param {string} ngModel Assignable angular expression to data-bind to. 21088 * @param {string} value The value to which the expression should be set when selected. 21089 * @param {string=} name Property name of the form under which the control is published. 21090 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21091 * interaction with the input element. 21092 * @param {string} ngValue Angular expression which sets the value to which the expression should 21093 * be set when selected. 21094 * 21095 * @example 21096 <example name="radio-input-directive" module="radioExample"> 21097 <file name="index.html"> 21098 <script> 21099 angular.module('radioExample', []) 21100 .controller('ExampleController', ['$scope', function($scope) { 21101 $scope.color = { 21102 name: 'blue' 21103 }; 21104 $scope.specialValue = { 21105 "id": "12345", 21106 "value": "green" 21107 }; 21108 }]); 21109 </script>
21110 <form name="myForm" ng-controller="ExampleController"> 21111 <label> 21112 <input type="radio" ng-model="color.name" value="red"> 21113 Red 21114 </label><br/> 21115 <label> 21116 <input type="radio" ng-model="color.name" ng-value="specialValue"> 21117 Green 21118 </label><br/> 21119 <label> 21120 <input type="radio" ng-model="color.name" value="blue"> 21121 Blue 21122 </label><br/> 21123 <tt>color = {{color.name | json}}</tt><br/> 21124 </form> 21125 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 21126 </file> 21127 <file name="protractor.js" type="protractor"> 21128 it('should change state', function() { 21129 var color = element(by.binding('color.name')); 21130 21131 expect(color.getText()).toContain('blue'); 21132 21133 element.all(by.model('color.name')).get(0).click(); 21134 21135 expect(color.getText()).toContain('red'); 21136 }); 21137 </file> 21138 </example> 21139 */ 21140 'radio': radioInputType, 21141 21142 21143 /** 21144 * @ngdoc input 21145 * @name input[checkbox] 21146 * 21147 * @description 21148 * HTML checkbox. 21149 * 21150 * @param {string} ngModel Assignable angular expression to data-bind to. 21151 * @param {string=} name Property name of the form under which the control is published. 21152 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 21153 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 21154 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21155 * interaction with the input element. 21156 * 21157 * @example 21158 <example name="checkbox-input-directive" module="checkboxExample"> 21159 <file name="index.html"> 21160 <script> 21161 angular.module('checkboxExample', []) 21162 .controller('ExampleController', ['$scope', function($scope) {
21163 $scope.checkboxModel = { 21164 value1 : true, 21165 value2 : 'YES' 21166 }; 21167 }]); 21168 </script> 21169 <form name="myForm" ng-controller="ExampleController"> 21170 <label>Value1: 21171 <input type="checkbox" ng-model="checkboxModel.value1"> 21172 </label><br/> 21173 <label>Value2: 21174 <input type="checkbox" ng-model="checkboxModel.value2" 21175 ng-true-value="'YES'" ng-false-value="'NO'"> 21176 </label><br/> 21177 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 21178 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 21179 </form> 21180 </file> 21181 <file name="protractor.js" type="protractor"> 21182 it('should change state', function() { 21183 var value1 = element(by.binding('checkboxModel.value1')); 21184 var value2 = element(by.binding('checkboxModel.value2')); 21185 21186 expect(value1.getText()).toContain('true'); 21187 expect(value2.getText()).toContain('YES'); 21188 21189 element(by.model('checkboxModel.value1')).click(); 21190 element(by.model('checkboxModel.value2')).click(); 21191 21192 expect(value1.getText()).toContain('false'); 21193 expect(value2.getText()).toContain('NO'); 21194 }); 21195 </file> 21196 </example> 21197 */ 21198 'checkbox': checkboxInputType, 21199 21200 'hidden': noop, 21201 'button': noop, 21202 'submit': noop, 21203 'reset': noop, 21204 'file': noop 21205}; 21206 21207function stringBasedInputType(ctrl) { 21208 ctrl.$formatters.push(function(value) { 21209 return ctrl.$isEmpty(value) ? value : value.toString(); 21210 }); 21211} 21212 21213function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21214 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21215 stringBasedInputType(ctrl); 21216} 21217 21218function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21219 var type = lowercase(element[0].type); 21220 21221 // In composition mode, users are still inputing intermediate text buffer, 21222 // hold the listener until composition is done. 21223 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 21224 if (!$sniffer.android) { 21225 var composing = false; 21226 21227 element.on('compositionstart', function(data) { 21228 composing = true; 21229 }); 21230 21231 element.on('compositionend', function() { 21232 composing = false; 21233 listener(); 21234 }); 21235 } 21236 21237 var listener = function(ev) { 21238 if (timeout) { 21239 $browser.defer.cancel(timeout); 21240 timeout = null; 21241 } 21242 if (composing) return; 21243 var value = element.val(), 21244 event = ev && ev.type; 21245 21246 // By default we will trim the value 21247 // If the attribute ng-trim exists we will avoid trimming 21248 // If input type is 'password', the value is never trimmed 21249 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 21250 value = trim(value); 21251 } 21252 21253 // If a control is suffering from bad input (due to native validators), browsers discard its 21254 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 21255 // control's value is the same empty value twice in a row. 21256 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 21257 ctrl.$setViewValue(value, event); 21258 } 21259 }; 21260 21261 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 21262 // input event on backspace, delete or cut 21263 if ($sniffer.hasEvent('input')) { 21264 element.on('input', listener); 21265 } else { 21266 var timeout; 21267 21268 var deferListener = function(ev, input, origValue) { 21269 if (!timeout) { 21270 timeout = $browser.defer(function() { 21271 timeout = null; 21272 if (!input || input.value !== origValue) { 21273 listener(ev); 21274 } 21275 }); 21276 } 21277 }; 21278 21279 element.on('keydown', function(event) { 21280 var key = event.keyCode; 21281 21282 // ignore 21283 // command modifiers arrows 21284 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 21285 21286 deferListener(event, this, this.value); 21287 }); 21288 21289 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 21290 if ($sniffer.hasEvent('paste')) { 21291 element.on('paste cut', deferListener); 21292 } 21293 } 21294 21295 // if user paste into input using mouse on older browser 21296 // or form autocomplete on newer browser, we need "change" event to catch it 21297 element.on('change', listener); 21298 21299 ctrl.$render = function() { 21300 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 21301 }; 21302} 21303 21304function weekParser(isoWeek, existingDate) { 21305 if (isDate(isoWeek)) { 21306 return isoWeek; 21307 } 21308 21309 if (isString(isoWeek)) { 21310 WEEK_REGEXP.lastIndex = 0; 21311 var parts = WEEK_REGEXP.exec(isoWeek); 21312 if (parts) { 21313 var year = +parts[1], 21314 week = +parts[2], 21315 hours = 0, 21316 minutes = 0, 21317 seconds = 0, 21318 milliseconds = 0, 21319 firstThurs = getFirstThursdayOfYear(year), 21320 addDays = (week - 1) * 7; 21321 21322 if (existingDate) { 21323 hours = existingDate.getHours(); 21324 minutes = existingDate.getMinutes(); 21325 seconds = existingDate.getSeconds(); 21326 milliseconds = existingDate.getMilliseconds(); 21327 } 21328 21329 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 21330 } 21331 } 21332 21333 return NaN; 21334} 21335 21336function createDateParser(regexp, mapping) { 21337 return function(iso, date) { 21338 var parts, map; 21339 21340 if (isDate(iso)) { 21341 return iso; 21342 } 21343 21344 if (isString(iso)) { 21345 // When a date is JSON'ified to wraps itself inside of an extra 21346 // set of double quotes. This makes the date parsing code unable 21347 // to match the date string and parse it as a date. 21348 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 21349 iso = iso.substring(1, iso.length - 1); 21350 }
21351 if (ISO_DATE_REGEXP.test(iso)) { 21352 return new Date(iso); 21353 } 21354 regexp.lastIndex = 0; 21355 parts = regexp.exec(iso); 21356 21357 if (parts) { 21358 parts.shift(); 21359 if (date) { 21360 map = { 21361 yyyy: date.getFullYear(), 21362 MM: date.getMonth() + 1, 21363 dd: date.getDate(), 21364 HH: date.getHours(), 21365 mm: date.getMinutes(), 21366 ss: date.getSeconds(), 21367 sss: date.getMilliseconds() / 1000 21368 }; 21369 } else { 21370 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 21371 } 21372 21373 forEach(parts, function(part, index) { 21374 if (index < mapping.length) { 21375 map[mapping[index]] = +part; 21376 } 21377 }); 21378 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 21379 } 21380 } 21381 21382 return NaN; 21383 }; 21384} 21385 21386function createDateInputType(type, regexp, parseDate, format) { 21387 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 21388 badInputChecker(scope, element, attr, ctrl); 21389 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21390 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 21391 var previousDate; 21392 21393 ctrl.$$parserName = type; 21394 ctrl.$parsers.push(function(value) { 21395 if (ctrl.$isEmpty(value)) return null; 21396 if (regexp.test(value)) { 21397 // Note: We cannot read ctrl.$modelValue, as there might be a different 21398 // parser/formatter in the processing chain so that the model 21399 // contains some different data format! 21400 var parsedDate = parseDate(value, previousDate); 21401 if (timezone) { 21402 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 21403 } 21404 return parsedDate; 21405 } 21406 return undefined; 21407 }); 21408 21409 ctrl.$formatters.push(function(value) { 21410 if (value && !isDate(value)) { 21411 throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 21412 } 21413 if (isValidDate(value)) { 21414 previousDate = value; 21415 if (previousDate && timezone) { 21416 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 21417 } 21418 return $filter('date')(value, format, timezone); 21419 } else { 21420 previousDate = null; 21421 return ''; 21422 } 21423 }); 21424 21425 if (isDefined(attr.min) || attr.ngMin) { 21426 var minVal; 21427 ctrl.$validators.min = function(value) { 21428 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 21429 }; 21430 attr.$observe('min', function(val) { 21431 minVal = parseObservedDateValue(val); 21432 ctrl.$validate(); 21433 }); 21434 } 21435 21436 if (isDefined(attr.max) || attr.ngMax) { 21437 var maxVal; 21438 ctrl.$validators.max = function(value) { 21439 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 21440 }; 21441 attr.$observe('max', function(val) { 21442 maxVal = parseObservedDateValue(val); 21443 ctrl.$validate(); 21444 }); 21445 } 21446 21447 function isValidDate(value) { 21448 // Invalid Date: getTime() returns NaN 21449 return value && !(value.getTime && value.getTime() !== value.getTime()); 21450 } 21451 21452 function parseObservedDateValue(val) { 21453 return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; 21454 } 21455 }; 21456} 21457 21458function badInputChecker(scope, element, attr, ctrl) { 21459 var node = element[0]; 21460 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 21461 if (nativeValidation) { 21462 ctrl.$parsers.push(function(value) { 21463 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 21464 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): 21465 // - also sets validity.badInput (should only be validity.typeMismatch). 21466 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) 21467 // - can ignore this case as we can still read out the erroneous email... 21468 return validity.badInput && !validity.typeMismatch ? undefined : value; 21469 }); 21470 } 21471} 21472 21473function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21474 badInputChecker(scope, element, attr, ctrl); 21475 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21476 21477 ctrl.$$parserName = 'number'; 21478 ctrl.$parsers.push(function(value) { 21479 if (ctrl.$isEmpty(value)) return null; 21480 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 21481 return undefined; 21482 }); 21483 21484 ctrl.$formatters.push(function(value) { 21485 if (!ctrl.$isEmpty(value)) { 21486 if (!isNumber(value)) { 21487 throw $ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 21488 } 21489 value = value.toString(); 21490 } 21491 return value; 21492 }); 21493 21494 if (isDefined(attr.min) || attr.ngMin) { 21495 var minVal; 21496 ctrl.$validators.min = function(value) { 21497 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 21498 }; 21499 21500 attr.$observe('min', function(val) { 21501 if (isDefined(val) && !isNumber(val)) { 21502 val = parseFloat(val, 10); 21503 } 21504 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 21505 // TODO(matsko): implement validateLater to reduce number of validations 21506 ctrl.$validate(); 21507 }); 21508 } 21509 21510 if (isDefined(attr.max) || attr.ngMax) { 21511 var maxVal; 21512 ctrl.$validators.max = function(value) { 21513 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 21514 }; 21515 21516 attr.$observe('max', function(val) { 21517 if (isDefined(val) && !isNumber(val)) { 21518 val = parseFloat(val, 10); 21519 } 21520 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 21521 // TODO(matsko): implement validateLater to reduce number of validations 21522 ctrl.$validate(); 21523 }); 21524 } 21525} 21526 21527function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21528 // Note: no badInputChecker here by purpose as `url` is only a validation 21529 // in browsers, i.e. we can always read out input.value even if it is not val
21529id! 21530 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21531 stringBasedInputType(ctrl); 21532 21533 ctrl.$$parserName = 'url'; 21534 ctrl.$validators.url = function(modelValue, viewValue) { 21535 var value = modelValue || viewValue; 21536 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 21537 }; 21538} 21539 21540function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21541 // Note: no badInputChecker here by purpose as `url` is only a validation 21542 // in browsers, i.e. we can always read out input.value even if it is not valid! 21543 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21544 stringBasedInputType(ctrl); 21545 21546 ctrl.$$parserName = 'email'; 21547 ctrl.$validators.email = function(modelValue, viewValue) { 21548 var value = modelValue || viewValue; 21549 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 21550 }; 21551} 21552 21553function radioInputType(scope, element, attr, ctrl) { 21554 // make the name unique, if not defined 21555 if (isUndefined(attr.name)) { 21556 element.attr('name', nextUid()); 21557 } 21558 21559 var listener = function(ev) { 21560 if (element[0].checked) { 21561 ctrl.$setViewValue(attr.value, ev && ev.type); 21562 } 21563 }; 21564 21565 element.on('click', listener); 21566 21567 ctrl.$render = function() { 21568 var value = attr.value; 21569 element[0].checked = (value == ctrl.$viewValue); 21570 }; 21571 21572 attr.$observe('value', ctrl.$render); 21573} 21574 21575function parseConstantExpr($parse, context, name, expression, fallback) { 21576 var parseFn; 21577 if (isDefined(expression)) { 21578 parseFn = $parse(expression); 21579 if (!parseFn.constant) { 21580 throw minErr('ngModel')('constexpr', 'Expected constant expression for `{0}`, but saw ' + 21581 '`{1}`.', name, expression); 21582 } 21583 return parseFn(context); 21584 } 21585 return fallback; 21586} 21587 21588function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 21589 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 21590 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 21591 21592 var listener = function(ev) { 21593 ctrl.$setViewValue(element[0].checked, ev && ev.type); 21594 }; 21595 21596 element.on('click', listener); 21597 21598 ctrl.$render = function() { 21599 element[0].checked = ctrl.$viewValue; 21600 }; 21601 21602 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 21603 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 21604 // it to a boolean. 21605 ctrl.$isEmpty = function(value) { 21606 return value === false; 21607 }; 21608 21609 ctrl.$formatters.push(function(value) { 21610 return equals(value, trueValue); 21611 }); 21612 21613 ctrl.$parsers.push(function(value) { 21614 return value ? trueValue : falseValue; 21615 }); 21616} 21617 21618 21619/** 21620 * @ngdoc directive 21621 * @name textarea 21622 * @restrict E 21623 * 21624 * @description 21625 * HTML textarea element control with angular data-binding. The data-binding and validation 21626 * properties of this element are exactly the same as those of the 21627 * {@link ng.directive:input input element}. 21628 * 21629 * @param {string} ngModel Assignable angular expression to data-bind to. 21630 * @param {string=} name Property name of the form under which the control is published. 21631 * @param {string=} required Sets `required` validation error key if the value is not entered. 21632 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21633 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21634 * `required` when you want to data-bind to the `required` attribute. 21635 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21636 * minlength. 21637 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21638 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 21639 * length. 21640 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21641 * a RegExp found by evaluating the Angular expression given in the attribute value. 21642 * If the expression evaluates to a RegExp object, then this is used directly. 21643 * If the expression evaluates to a string, then it will be converted to a RegExp 21644 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21645 * `new RegExp('^abc$')`.<br /> 21646 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21647 * start at the index of the last search's match, thus not taking the whole input value into 21648 * account. 21649 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21650 * interaction with the input element. 21651 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 21652 */ 21653 21654 21655/** 21656 * @ngdoc directive 21657 * @name input 21658 * @restrict E 21659 * 21660 * @description 21661 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 21662 * input state control, and validation. 21663 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 21664 * 21665 * <div class="alert alert-warning"> 21666 * **Note:** Not every feature offered is available for all input types. 21667 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 21668 * </div> 21669 * 21670 * @param {string} ngModel Assignable angular expression to data-bind to. 21671 * @param {string=} name Property name of the form under which the control is published. 21672 * @param {string=} required Sets `required` validation error key if the value is not entered. 21673 * @param {boolean=} ngRequired Sets `required` attribute if set to true 21674 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21675 * minlength. 21676 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21677 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 21678 * length. 21679 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21680 * a RegExp found by evaluating the Angular expression given in the attribute value. 21681 * If the expression evaluates to a RegExp object, then this is used directly. 21682 * If the expression evaluates to a string, then it will be converted to a RegExp 21683 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21684 * `new RegExp('^abc$')`.<br /> 21685 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21686 * start at the index of the last search's match, thus not taking the whole input value into 21687 * account. 21688 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21689 * interaction with the input element. 21690 * @param {boolean=} [ngTrim=true] If set to false Angular
vendor: 4,842 bytes, lines 21690-21808
21690 will not automatically trim the input. 21691 * This parameter is ignored for input[type=password] controls, which will never trim the 21692 * input. 21693 * 21694 * @example 21695 <example name="input-directive" module="inputExample"> 21696 <file name="index.html"> 21697 <script> 21698 angular.module('inputExample', []) 21699 .controller('ExampleController', ['$scope', function($scope) { 21700 $scope.user = {name: 'guest', last: 'visitor'}; 21701 }]); 21702 </script> 21703 <div ng-controller="ExampleController"> 21704 <form name="myForm"> 21705 <label> 21706 User name: 21707 <input type="text" name="userName" ng-model="user.name" required> 21708 </label> 21709 <div role="alert"> 21710 <span class="error" ng-show="myForm.userName.$error.required"> 21711 Required!</span> 21712 </div> 21713 <label> 21714 Last name: 21715 <input type="text" name="lastName" ng-model="user.last" 21716 ng-minlength="3" ng-maxlength="10"> 21717 </label> 21718 <div role="alert"> 21719 <span class="error" ng-show="myForm.lastName.$error.minlength"> 21720 Too short!</span> 21721 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 21722 Too long!</span> 21723 </div> 21724 </form> 21725 <hr> 21726 <tt>user = {{user}}</tt><br/> 21727 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 21728 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 21729 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 21730 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 21731 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21732 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21733 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 21734 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 21735 </div> 21736 </file> 21737 <file name="protractor.js" type="protractor"> 21738 var user = element(by.exactBinding('user')); 21739 var userNameValid = element(by.binding('myForm.userName.$valid')); 21740 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 21741 var lastNameError = element(by.binding('myForm.lastName.$error')); 21742 var formValid = element(by.binding('myForm.$valid')); 21743 var userNameInput = element(by.model('user.name')); 21744 var userLastInput = element(by.model('user.last')); 21745 21746 it('should initialize to model', function() { 21747 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 21748 expect(userNameValid.getText()).toContain('true'); 21749 expect(formValid.getText()).toContain('true'); 21750 }); 21751 21752 it('should be invalid if empty when required', function() { 21753 userNameInput.clear(); 21754 userNameInput.sendKeys(''); 21755 21756 expect(user.getText()).toContain('{"last":"visitor"}'); 21757 expect(userNameValid.getText()).toContain('false'); 21758 expect(formValid.getText()).toContain('false'); 21759 }); 21760 21761 it('should be valid if empty when min length is set', function() { 21762 userLastInput.clear(); 21763 userLastInput.sendKeys(''); 21764 21765 expect(user.getText()).toContain('{"name":"guest","last":""}'); 21766 expect(lastNameValid.getText()).toContain('true'); 21767 expect(formValid.getText()).toContain('true'); 21768 }); 21769 21770 it('should be invalid if less than required min length', function() { 21771 userLastInput.clear(); 21772 userLastInput.sendKeys('xx'); 21773 21774 expect(user.getText()).toContain('{"name":"guest"}'); 21775 expect(lastNameValid.getText()).toContain('false'); 21776 expect(lastNameError.getText()).toContain('minlength'); 21777 expect(formValid.getText()).toContain('false'); 21778 }); 21779 21780 it('should be invalid if longer than max length', function() { 21781 userLastInput.clear(); 21782 userLastInput.sendKeys('some ridiculously long name'); 21783 21784 expect(user.getText()).toContain('{"name":"guest"}'); 21785 expect(lastNameValid.getText()).toContain('false'); 21786 expect(lastNameError.getText()).toContain('maxlength'); 21787 expect(formValid.getText()).toContain('false'); 21788 }); 21789 </file> 21790 </example> 21791 */ 21792var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 21793 function($browser, $sniffer, $filter, $parse) { 21794 return { 21795 restrict: 'E', 21796 require: ['?ngModel'], 21797 link: { 21798 pre: function(scope, element, attr, ctrls) { 21799 if (ctrls[0]) { 21800 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 21801 $browser, $filter, $parse); 21802 } 21803 } 21804 } 21805 }; 21806}]; 21807 21808
21809 21810var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 21811/** 21812 * @ngdoc directive 21813 * @name ngValue 21814 * 21815 * @description 21816 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 21817 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 21818 * the bound value. 21819 * 21820 * `ngValue` is useful when dynamically generating lists of radio buttons using 21821 * {@link ngRepeat `ngRepeat`}, as shown below. 21822 * 21823 * Likewise, `ngValue` can be used to generate `<option>` elements for 21824 * the {@link select `select`} element. In that case however, only strings are supported 21825 * for the `value `attribute, so the resulting `ngModel` will always be a string. 21826 * Support for `select` models with non-string values is available via `ngOptions`. 21827 * 21828 * @element input 21829 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 21830 * of the `input` element 21831 * 21832 * @example 21833 <example name="ngValue-directive" module="valueExample"> 21834 <file name="index.html"> 21835 <script> 21836 angular.module('valueExample', []) 21837 .controller('ExampleController', ['$scope', function($scope) { 21838 $scope.names = ['pizza', 'unicorns', 'robots']; 21839 $scope.my = { favorite: 'unicorns' }; 21840 }]); 21841 </script>
21842 <form ng-controller="ExampleController"> 21843 <h2>Which is your favorite?</h2> 21844 <label ng-repeat="name in names" for="{{name}}"> 21845 {{name}} 21846 <input type="radio" 21847 ng-model="my.favorite" 21848 ng-value="name" 21849 id="{{name}}" 21850 name="favorite"> 21851 </label> 21852 <div>You chose {{my.favorite}}</div> 21853 </form> 21854 </file> 21855 <file name="protractor.js" type="protractor"> 21856 var favorite = element(by.binding('my.favorite')); 21857 21858 it('should initialize to model', function() { 21859 expect(favorite.getText()).toContain('unicorns'); 21860 }); 21861 it('should bind the values to the inputs', function() { 21862 element.all(by.model('my.favorite')).get(0).click(); 21863 expect(favorite.getText()).toContain('pizza'); 21864 }); 21865 </file> 21866 </example> 21867 */ 21868var ngValueDirective = function() { 21869 return { 21870 restrict: 'A', 21871 priority: 100, 21872 compile: function(tpl, tplAttr) { 21873 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 21874 return function ngValueConstantLink(scope, elm, attr) { 21875 attr.$set('value', scope.$eval(attr.ngValue)); 21876 }; 21877 } else { 21878 return function ngValueLink(scope, elm, attr) { 21879 scope.$watch(attr.ngValue, function valueWatchAction(value) { 21880 attr.$set('value', value); 21881 }); 21882 }; 21883 } 21884 } 21885 }; 21886}; 21887 21888/** 21889 * @ngdoc directive 21890 * @name ngBind 21891 * @restrict AC 21892 * 21893 * @description 21894 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 21895 * with the value of a given expression, and to update the text content when the value of that 21896 * expression changes. 21897 * 21898 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 21899 * `{{ expression }}` which is similar but less verbose. 21900 * 21901 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 21902 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 21903 * element attribute, it makes the bindings invisible to the user while the page is loading. 21904 * 21905 * An alternative solution to this problem would be using the 21906 * {@link ng.directive:ngCloak ngCloak} directive. 21907 * 21908 * 21909 * @element ANY 21910 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 21911 * 21912 * @example 21913 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 21914 <example module="bindExample"> 21915 <file name="index.html"> 21916 <script> 21917 angular.module('bindExample', []) 21918 .controller('ExampleController', ['$scope', function($scope) { 21919 $scope.name = 'Whirled'; 21920 }]); 21921 </script>
21922 <div ng-controller="ExampleController"> 21923 <label>Enter name: <input type="text" ng-model="name"></label><br> 21924 Hello <span ng-bind="name"></span>! 21925 </div> 21926 </file> 21927 <file name="protractor.js" type="protractor"> 21928 it('should check ng-bind', function() { 21929 var nameInput = element(by.model('name')); 21930 21931 expect(element(by.binding('name')).getText()).toBe('Whirled'); 21932 nameInput.clear(); 21933 nameInput.sendKeys('world'); 21934 expect(element(by.binding('name')).getText()).toBe('world'); 21935 }); 21936 </file> 21937 </example> 21938 */ 21939var ngBindDirective = ['$compile', function($compile) { 21940 return { 21941 restrict: 'AC', 21942 compile: function ngBindCompile(templateElement) { 21943 $compile.$$addBindingClass(templateElement); 21944 return function ngBindLink(scope, element, attr) { 21945 $compile.$$addBindingInfo(element, attr.ngBind); 21946 element = element[0]; 21947 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 21948 element.textContent = value === undefined ? '' : value; 21949 }); 21950 }; 21951 } 21952 }; 21953}]; 21954 21955 21956/** 21957 * @ngdoc directive 21958 * @name ngBindTemplate 21959 * 21960 * @description 21961 * The `ngBindTemplate` directive specifies that the element 21962 * text content should be replaced with the interpolation of the template 21963 * in the `ngBindTemplate` attribute. 21964 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 21965 * expressions. This directive is needed since some HTML elements 21966 * (such as TITLE and OPTION) cannot contain SPAN elements. 21967 * 21968 * @element ANY 21969 * @param {string} ngBindTemplate template of form 21970 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 21971 * 21972 * @example 21973 * Try it here: enter text in text box and watch the greeting change. 21974 <example module="bindExample"> 21975 <file name="index.html"> 21976 <script> 21977 angular.module('bindExample', []) 21978 .controller('ExampleController', ['$scope', function($scope) { 21979 $scope.salutation = 'Hello'; 21980 $scope.name = 'World'; 21981 }]); 21982 </script>
21983 <div ng-controller="ExampleController"> 21984 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 21985 <label>Name: <input type="text" ng-model="name"></label><br> 21986 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 21987 </div> 21988 </file> 21989 <file name="protractor.js" type="protractor"> 21990 it('should check ng-bind', function() { 21991 var salutationElem = element(by.binding('salutation')); 21992 var salutationInput = element(by.model('salutation')); 21993 var nameInput = element(by.model('name')); 21994 21995 expect(salutationElem.getText()).toBe('Hello World!'); 21996 21997 salutationInput.clear(); 21998 salutationInput.sendKeys('Greetings'); 21999 nameInput.clear(); 22000 nameInput.sendKeys('user'); 22001 22002 expect(salutationElem.getText()).toBe('Greetings user!'); 22003 }); 22004 </file> 22005 </example> 22006 */ 22007var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 22008 return { 22009 compile: function ngBindTemplateCompile(templateElement) { 22010 $compile.$$addBindingClass(templateElement); 22011 return function ngBindTemplateLink(scope, element, attr) { 22012 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 22013 $compile.$$addBindingInfo(element, interpolateFn.expressions); 22014 element = element[0]; 22015 attr.$observe('ngBindTemplate', function(value) { 22016 element.textContent = value === undefined ? '' : value; 22017 }); 22018 }; 22019 } 22020 }; 22021}]; 22022 22023 22024/** 22025 * @ngdoc directive 22026 * @name ngBindHtml 22027 * 22028 * @description 22029 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 22030 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 22031 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 22032 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 22033 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 22034 * 22035 * You may also bypass sanitization for values you know are safe. To do so, bind to 22036 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 22037 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 22038 * 22039 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 22040 * will have an exception (instead of an exploit.) 22041 * 22042 * @element ANY 22043 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 22044 * 22045 * @example 22046 22047 <example module="bindHtmlExample" deps="angular-sanitize.js"> 22048 <file name="index.html">
22049 <div ng-controller="ExampleController"> 22050 <p ng-bind-html="myHTML"></p> 22051 </div> 22052 </file> 22053 22054 <file name="script.js"> 22055 angular.module('bindHtmlExample', ['ngSanitize']) 22056 .controller('ExampleController', ['$scope', function($scope) { 22057 $scope.myHTML = 22058 'I am an <code>HTML</code>string with ' + 22059 '<a href="#">links!</a> and other <em>stuff</em>'; 22060 }]); 22061 </file> 22062 22063 <file name="protractor.js" type="protractor"> 22064 it('should check ng-bind-html', function() { 22065 expect(element(by.binding('myHTML')).getText()).toBe( 22066 'I am an HTMLstring with links! and other stuff'); 22067 }); 22068 </file> 22069 </example> 22070 */ 22071var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 22072 return { 22073 restrict: 'A', 22074 compile: function ngBindHtmlCompile(tElement, tAttrs) { 22075 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 22076 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 22077 return (value || '').toString(); 22078 }); 22079 $compile.$$addBindingClass(tElement); 22080 22081 return function ngBindHtmlLink(scope, element, attr) { 22082 $compile.$$addBindingInfo(element, attr.ngBindHtml); 22083 22084 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 22085 // we re-evaluate the expr because we want a TrustedValueHolderType 22086 // for $sce, not a string 22087 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 22088 }); 22089 }; 22090 } 22091 }; 22092}]; 22093 22094/** 22095 * @ngdoc directive 22096 * @name ngChange 22097 * 22098 * @description 22099 * Evaluate the given expression when the user changes the input. 22100 * The expression is evaluated immediately, unlike the JavaScript onchange event 22101 * which only triggers at the end of a change (usually, when the user leaves the 22102 * form element or presses the return key). 22103 * 22104 * The `ngChange` expression is only evaluated when a change in the input value causes 22105 * a new value to be committed to the model. 22106 * 22107 * It will not be evaluated: 22108 * * if the value returned from the `$parsers` transformation pipeline has not changed 22109 * * if the input has continued to be invalid since the model will stay `null` 22110 * * if the model is changed programmatically and not by a change to the input value 22111 * 22112 * 22113 * Note, this directive requires `ngModel` to be present. 22114 * 22115 * @element input 22116 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 22117 * in input value. 22118 * 22119 * @example 22120 * <example name="ngChange-directive" module="changeExample"> 22121 * <file name="index.html"> 22122 * <script> 22123 * angular.module('changeExample', []) 22124 * .controller('ExampleController', ['$scope', function($scope) { 22125 * $scope.counter = 0; 22126 * $scope.change = function() { 22127 * $scope.counter++; 22128 * }; 22129 * }]); 22130 * </script>
22131 * <div ng-controller="ExampleController"> 22132 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 22133 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 22134 * <label for="ng-change-example2">Confirmed</label><br /> 22135 * <tt>debug = {{confirmed}}</tt><br/> 22136 * <tt>counter = {{counter}}</tt><br/> 22137 * </div> 22138 * </file> 22139 * <file name="protractor.js" type="protractor"> 22140 * var counter = element(by.binding('counter')); 22141 * var debug = element(by.binding('confirmed')); 22142 * 22143 * it('should evaluate the expression if changing from view', function() { 22144 * expect(counter.getText()).toContain('0'); 22145 * 22146 * element(by.id('ng-change-example1')).click(); 22147 * 22148 * expect(counter.getText()).toContain('1'); 22149 * expect(debug.getText()).toContain('true'); 22150 * }); 22151 * 22152 * it('should not evaluate the expression if changing from model', function() { 22153 * element(by.id('ng-change-example2')).click(); 22154 22155 * expect(counter.getText()).toContain('0'); 22156 * expect(debug.getText()).toContain('true'); 22157 * }); 22158 * </file> 22159 * </example> 22160 */ 22161var ngChangeDirective = valueFn({ 22162 restrict: 'A', 22163 require: 'ngModel', 22164 link: function(scope, element, attr, ctrl) { 22165 ctrl.$viewChangeListeners.push(function() { 22166 scope.$eval(attr.ngChange); 22167 }); 22168 } 22169}); 22170 22171function classDirective(name, selector) { 22172 name = 'ngClass' + name; 22173 return ['$animate', function($animate) { 22174 return { 22175 restrict: 'AC', 22176 link: function(scope, element, attr) { 22177 var oldVal; 22178 22179 scope.$watch(attr[name], ngClassWatchAction, true); 22180 22181 attr.$observe('class', function(value) { 22182 ngClassWatchAction(scope.$eval(attr[name])); 22183 }); 22184 22185 22186 if (name !== 'ngClass') { 22187 scope.$watch('$index', function($index, old$index) { 22188 // jshint bitwise: false 22189 var mod = $index & 1; 22190 if (mod !== (old$index & 1)) { 22191 var classes = arrayClasses(scope.$eval(attr[name])); 22192 mod === selector ? 22193 addClasses(classes) : 22194 removeClasses(classes); 22195 } 22196 }); 22197 } 22198 22199 function addClasses(classes) { 22200 var newClasses = digestClassCounts(classes, 1); 22201 attr.$addClass(newClasses); 22202 } 22203 22204 function removeClasses(classes) { 22205 var newClasses = digestClassCounts(classes, -1); 22206 attr.$removeClass(newClasses); 22207 } 22208 22209 function digestClassCounts(classes, count) { 22210 // Use createMap() to prevent class assumptions involving property 22211 // names in Object.prototype 22212 var classCounts = element.data('$classCounts') || createMap(); 22213 var classesToUpdate = []; 22214 forEach(classes, function(className) { 22215 if (count > 0 || classCounts[className]) { 22216 classCounts[className] = (classCounts[className] || 0) + count; 22217 if (classCounts[className] === +(count > 0)) { 22218 classesToUpdate.push(className); 22219 } 22220 } 22221 }); 22222 element.data('$classCounts', classCounts); 22223 return classesToUpdate.join(' '); 22224 } 22225 22226 function updateClasses(oldClasses, newClasses) { 22227 var toAdd = arrayDifference(newClasses, oldClasses); 22228 var toRemove = arrayDifference(oldClasses, newClasses); 22229 toAdd = digestClassCounts(toAdd, 1); 22230 toRemove = digestClassCounts(toRemove, -1); 22231 if (toAdd && toAdd.length) { 22232 $animate.addClass(element, toAdd); 22233 } 22234 if (toRemove && toRemove.length) { 22235 $animate.removeClass(element, toRemove); 22236 } 22237 } 22238 22239 function ngClassWatchAction(newVal) { 22240 if (selector === true || scope.$index % 2 === selector) { 22241 var newClasses = arrayClasses(newVal || []); 22242 if (!oldVal) { 22243 addClasses(newClasses); 22244 } else if (!equals(newVal,oldVal)) { 22245 var oldClasses = arrayClasses(oldVal);
22246 updateClasses(oldClasses, newClasses); 22247 } 22248 } 22249 oldVal = shallowCopy(newVal); 22250 } 22251 } 22252 }; 22253 22254 function arrayDifference(tokens1, tokens2) { 22255 var values = []; 22256 22257 outer: 22258 for (var i = 0; i < tokens1.length; i++) { 22259 var token = tokens1[i]; 22260 for (var j = 0; j < tokens2.length; j++) { 22261 if (token == tokens2[j]) continue outer; 22262 } 22263 values.push(token); 22264 } 22265 return values; 22266 } 22267 22268 function arrayClasses(classVal) { 22269 var classes = []; 22270 if (isArray(classVal)) { 22271 forEach(classVal, function(v) { 22272 classes = classes.concat(arrayClasses(v)); 22273 }); 22274 return classes; 22275 } else if (isString(classVal)) { 22276 return classVal.split(' '); 22277 } else if (isObject(classVal)) { 22278 forEach(classVal, function(v, k) { 22279 if (v) { 22280 classes = classes.concat(k.split(' ')); 22281 } 22282 }); 22283 return classes; 22284 } 22285 return classVal; 22286 } 22287 }]; 22288} 22289 22290/** 22291 * @ngdoc directive 22292 * @name ngClass 22293 * @restrict AC 22294 * 22295 * @description 22296 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 22297 * an expression that represents all classes to be added. 22298 * 22299 * The directive operates in three different ways, depending on which of three types the expression 22300 * evaluates to: 22301 * 22302 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 22303 * names. 22304 * 22305 * 2. If the expression evaluates to an object, then for each key-value pair of the 22306 * object with a truthy value the corresponding key is used as a class name. 22307 * 22308 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 22309 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 22310 * to give you more control over what CSS classes appear. See the code below for an example of this. 22311 * 22312 * 22313 * The directive won't add duplicate classes if a particular class was already set. 22314 * 22315 * When the expression changes, the previously added classes are removed and only then are the 22316 * new classes added. 22317 * 22318 * @animations 22319 * **add** - happens just before the class is applied to the elements 22320 * 22321 * **remove** - happens just before the class is removed from the element 22322 * 22323 * @element ANY 22324 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 22325 * of the evaluation can be a string representing space delimited class 22326 * names, an array, or a map of class names to boolean values. In the case of a map, the 22327 * names of the properties whose values are truthy will be added as css classes to the 22328 * element. 22329 * 22330 * @example Example that demonstrates basic bindings via ngClass directive. 22331 <example> 22332 <file name="index.html"> 22333 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 22334 <label> 22335 <input type="checkbox" ng-model="deleted"> 22336 deleted (apply "strike" class) 22337 </label><br> 22338 <label> 22339 <input type="checkbox" ng-model="important"> 22340 important (apply "bold" class) 22341 </label><br> 22342 <label> 22343 <input type="checkbox" ng-model="error"> 22344 error (apply "red" class) 22345 </label> 22346 <hr> 22347 <p ng-class="style">Using String Syntax</p> 22348 <input type="text" ng-model="style" 22349 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 22350 <hr> 22351 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 22352 <input ng-model="style1" 22353 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 22354 <input ng-model="style2" 22355 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 22356 <input ng-model="style3" 22357 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 22358 <hr> 22359 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 22360 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 22361 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 22362 </file> 22363 <file name="style.css"> 22364 .strike { 22365 text-decoration: line-through; 22366 } 22367 .bold { 22368 font-weight: bold; 22369 } 22370 .red { 22371 color: red; 22372 } 22373 .orange { 22374 color: orange; 22375 } 22376 </file> 22377 <file name="protractor.js" type="protractor"> 22378 var ps = element.all(by.css('p')); 22379 22380 it('should let you toggle the class', function() { 22381 22382 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 22383 expect(ps.first().getAttribute('class')).not.toMatch(/red/); 22384
22385 element(by.model('important')).click(); 22386 expect(ps.first().getAttribute('class')).toMatch(/bold/); 22387 22388 element(by.model('error')).click(); 22389 expect(ps.first().getAttribute('class')).toMatch(/red/); 22390 }); 22391 22392 it('should let you toggle string example', function() { 22393 expect(ps.get(1).getAttribute('class')).toBe(''); 22394 element(by.model('style')).clear(); 22395 element(by.model('style')).sendKeys('red'); 22396 expect(ps.get(1).getAttribute('class')).toBe('red'); 22397 }); 22398 22399 it('array example should have 3 classes', function() { 22400 expect(ps.get(2).getAttribute('class')).toBe(''); 22401 element(by.model('style1')).sendKeys('bold'); 22402 element(by.model('style2')).sendKeys('strike'); 22403 element(by.model('style3')).sendKeys('red'); 22404 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 22405 }); 22406 22407 it('array with map example should have 2 classes', function() { 22408 expect(ps.last().getAttribute('class')).toBe(''); 22409 element(by.model('style4')).sendKeys('bold'); 22410 element(by.model('warning')).click(); 22411 expect(ps.last().getAttribute('class')).toBe('bold orange'); 22412 }); 22413 </file> 22414 </example> 22415 22416 ## Animations 22417 22418 The example below demonstrates how to perform animations using ngClass. 22419 22420 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 22421 <file name="index.html"> 22422 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 22423 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 22424 <br> 22425 <span class="base-class" ng-class="myVar">Sample Text</span> 22426 </file> 22427 <file name="style.css"> 22428 .base-class { 22429 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 22430 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 22431 } 22432 22433 .base-class.my-class { 22434 color: red; 22435 font-size:3em; 22436 } 22437 </file> 22438 <file name="protractor.js" type="protractor"> 22439 it('should check ng-class', function() { 22440 expect(element(by.css('.base-class')).getAttribute('class')).not. 22441 toMatch(/my-class/); 22442 22443 element(by.id('setbtn')).click(); 22444 22445 expect(element(by.css('.base-class')).getAttribute('class')). 22446 toMatch(/my-class/); 22447 22448 element(by.id('clearbtn')).click(); 22449 22450 expect(element(by.css('.base-class')).getAttribute('class')).not. 22451 toMatch(/my-class/); 22452 }); 22453 </file> 22454 </example> 22455 22456 22457 ## ngClass and pre-existing CSS3 Transitions/Animations 22458 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 22459 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 22460 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 22461 to view the step by step details of {@link $animate#addClass $animate.addClass} and 22462 {@link $animate#removeClass $animate.removeClass}. 22463 */ 22464var ngClassDirective = classDirective('', true); 22465 22466/** 22467 * @ngdoc directive 22468 * @name ngClassOdd 22469 * @restrict AC 22470 * 22471 * @description 22472 * The `ngClassOdd` and `ngClassEven` directives work exactly as 22473 * {@link ng.directive:ngClass ngClass}, except they work in 22474 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 22475 * 22476 * This directive can be applied only within the scope of an 22477 * {@link ng.directive:ngRepeat ngRepeat}. 22478 * 22479 * @element ANY 22480 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 22481 * of the evaluation can be a string representing space delimited class names or an array. 22482 * 22483 * @example 22484 <example> 22485 <file name="index.html"> 22486 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 22487 <li ng-repeat="name in names"> 22488 <span ng-class-odd="'odd'" ng-class-even="'even'"> 22489 {{name}} 22490 </span> 22491 </li> 22492 </ol> 22493 </file> 22494 <file name="style.css"> 22495 .odd { 22496 color: red; 22497 } 22498 .even { 22499 color: blue; 22500 } 22501 </file> 22502 <file name="protractor.js" type="protractor"> 22503 it('should check ng-class-odd and ng-class-even', function() { 22504 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 22505 toMatch(/odd/); 22506 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 22507 toMatch(/even/); 22508 }); 22509 </file> 22510 </example> 22511 */ 22512var ngClassOddDirective = classDirective('Odd', 0); 22513 22514/** 22515 * @ngdoc directive 22516 * @name ngClassEven 22517 * @restrict AC 22518 * 22519 * @description 22520 * The `ngClassOdd` and `ngClassEven` directives work exactly as 22521 * {@link ng.directive:ngClass ngClass}, except they work in 22522 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 22523 * 22524 * This directive can be applied only within the scope of an 22525 * {@link ng.directive:ngRepeat ngRepeat}. 22526 * 22527 * @element ANY 22528 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 22529 * result of the evaluation can be a string representing space delimited class names or an array.
22530 * 22531 * @example 22532 <example> 22533 <file name="index.html"> 22534 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 22535 <li ng-repeat="name in names"> 22536 <span ng-class-odd="'odd'" ng-class-even="'even'"> 22537 {{name}} 22538 </span> 22539 </li> 22540 </ol> 22541 </file> 22542 <file name="style.css"> 22543 .odd { 22544 color: red; 22545 } 22546 .even { 22547 color: blue; 22548 } 22549 </file> 22550 <file name="protractor.js" type="protractor"> 22551 it('should check ng-class-odd and ng-class-even', function() { 22552 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 22553 toMatch(/odd/); 22554 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 22555 toMatch(/even/); 22556 }); 22557 </file> 22558 </example> 22559 */ 22560var ngClassEvenDirective = classDirective('Even', 1); 22561 22562/** 22563 * @ngdoc directive 22564 * @name ngCloak 22565 * @restrict AC 22566 * 22567 * @description 22568 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 22569 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 22570 * directive to avoid the undesirable flicker effect caused by the html template display. 22571 * 22572 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 22573 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 22574 * of the browser view. 22575 * 22576 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 22577 * `angular.min.js`. 22578 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 22579 * 22580 * ```css 22581 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 22582 * display: none !important; 22583 * } 22584 * ``` 22585 * 22586 * When this css rule is loaded by the browser, all html elements (including their children) that 22587 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 22588 * during the compilation of the template it deletes the `ngCloak` element attribute, making 22589 * the compiled element visible. 22590 * 22591 * For the best result, the `angular.js` script must be loaded in the head section of the html 22592 * document; alternatively, the css rule above must be included in the external stylesheet of the 22593 * application. 22594 * 22595 * @element ANY 22596 * 22597 * @example 22598 <example> 22599 <file name="index.html"> 22600 <div id="template1" ng-cloak>{{ 'hello' }}</div> 22601 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 22602 </file> 22603 <file name="protractor.js" type="protractor"> 22604 it('should remove the template directive and css class', function() { 22605 expect($('#template1').getAttribute('ng-cloak')). 22606 toBeNull(); 22607 expect($('#template2').getAttribute('ng-cloak')). 22608 toBeNull(); 22609 }); 22610 </file> 22611 </example> 22612 * 22613 */ 22614var ngCloakDirective = ngDirective({ 22615 compile: function(element, attr) { 22616 attr.$set('ngCloak', undefined); 22617 element.removeClass('ng-cloak'); 22618 } 22619}); 22620 22621/** 22622 * @ngdoc directive 22623 * @name ngController 22624 * 22625 * @description 22626 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 22627 * supports the principles behind the Model-View-Controller design pattern. 22628 * 22629 * MVC components in angular: 22630 * 22631 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 22632 * are accessed through bindings. 22633 * * View â The template (HTML with data bindings) that is rendered into the View. 22634 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 22635 * logic behind the application to decorate the scope with functions and values 22636 * 22637 * Note that you can also attach controllers to the DOM by declaring it in a route definition 22638 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 22639 * again using `ng-controller` in the template itself. This will cause the controller to be attached 22640 * and executed twice. 22641 * 22642 * @element ANY 22643 * @scope 22644 * @priority 500 22645 * @param {expression} ngController Name of a constructor function registered with the current 22646 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 22647 * that on the current scope evaluates to a constructor function. 22648 * 22649 * The controller instance can be published into a scope property by specifying 22650 * `ng-controller="as propertyName"`. 22651 * 22652 * If the current `$controllerProvider` is configured to use globals (via 22653 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 22654 * also be the name of a globally accessible constructor function (not recommended). 22655 * 22656 * @example 22657 * Here is a simple form for editing user contact information. Adding, remov
22657ing, clearing, and 22658 * greeting are methods declared on the controller (see source tab). These methods can 22659 * easily be called from the angular markup. Any changes to the data are automatically reflected 22660 * in the View without the need for a manual update. 22661 * 22662 * Two different declaration styles are included below: 22663 * 22664 * * one binds methods and properties directly onto the controller using `this`: 22665 * `ng-controller="SettingsController1 as settings"` 22666 * * one injects `$scope` into the controller: 22667 * `ng-controller="SettingsController2"` 22668 * 22669 * The second option is more common in the Angular community, and is generally used in boilerplates 22670 * and in this guide. However, there are advantages to binding properties directly to the controller 22671 * and avoiding scope. 22672 * 22673 * * Using `controller as` makes it obvious which controller you are accessing in the template when 22674 * multiple controllers apply to an element. 22675 * * If you are writing your controllers as classes you have easier access to the properties and 22676 * methods, which will appear on the scope, from inside the controller code. 22677 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 22678 * inheritance masking primitives. 22679 * 22680 * This example demonstrates the `controller as` syntax. 22681 * 22682 * <example name="ngControllerAs" module="controllerAsExample"> 22683 * <file name="index.html"> 22684 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 22685 * <label>Name: <input type="text" ng-model="settings.name"/></label> 22686 * <button ng-click="settings.greet()">greet</button><br/> 22687 * Contact: 22688 * <ul> 22689 * <li ng-repeat="contact in settings.contacts"> 22690 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 22691 * <option>phone</option> 22692 * <option>email</option> 22693 * </select> 22694 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 22695 * <button ng-click="settings.clearContact(contact)">clear</button> 22696 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 22697 * </li> 22698 * <li><button ng-click="settings.addContact()">add</button></li> 22699 * </ul> 22700 * </div> 22701 * </file> 22702 * <file name="app.js"> 22703 * angular.module('controllerAsExample', []) 22704 * .controller('SettingsController1', SettingsController1); 22705 * 22706 * function SettingsController1() { 22707 * this.name = "John Smith"; 22708 * this.contacts = [ 22709 * {type: 'phone', value: '408 555 1212'}, 22710 * {type: 'email', value: '[email protected]'} ]; 22711 * } 22712 * 22713 * SettingsController1.prototype.greet = function() { 22714 * alert(this.name); 22715 * }; 22716 * 22717 * SettingsController1.prototype.addContact = function() { 22718 * this.contacts.push({type: 'email', value: '[email protected]'}); 22719 * }; 22720 * 22721 * SettingsController1.prototype.removeContact = function(contactToRemove) { 22722 * var index = this.contacts.indexOf(contactToRemove); 22723 * this.contacts.splice(index, 1); 22724 * }; 22725 * 22726 * SettingsController1.prototype.clearContact = function(contact) { 22727 * contact.type = 'phone'; 22728 * contact.value = ''; 22729 * }; 22730 * </file> 22731 * <file name="protractor.js" type="protractor"> 22732 * it('should check controller as', function() { 22733 * var container = element(by.id('ctrl-as-exmpl')); 22734 * expect(container.element(by.model('settings.name')) 22735 * .getAttribute('value')).toBe('John Smith'); 22736 * 22737 * var firstRepeat = 22738 * container.element(by.repeater('contact in settings.contacts').row(0)); 22739 * var secondRepeat = 22740 * container.element(by.repeater('contact in settings.contacts').row(1)); 22741 * 22742 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 22743 * .toBe('408 555 1212'); 22744 * 22745 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 22746 * .toBe('[email protected]'); 22747 * 22748 * firstRepeat.element(by.buttonText('clear')).click(); 22749 * 22750 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 22751 * .toBe(''); 22752 * 22753 * container.element(by.buttonText('add')).click(); 22754 * 22755 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 22756 * .element(by.model('contact.value')) 22757 * .getAttribute('value')) 22758 * .toBe('[email protected]'); 22759 * }); 22760 * </file> 22761 * </example> 22762 * 22763 * This example demonstrates the "attach to `$scope`" style of controller. 22764 * 22765 * <example name="ngController" module="controllerExample"> 22766 * <file name="index.html"> 22767 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 22768 * <label>Name: <input type="text" ng-model="name"/></label> 22769 * <button ng-click="greet()">greet</button><br/> 22770 * Contact: 22771 * <ul> 22772 * <li ng-repeat="contact in contacts"> 22773 * <select ng-model="contact.type" id="select_{{$index}}"> 22774 * <option>phone</option> 22775 * <option>email</option> 22776 * </select> 22777 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 22778 * <button ng-click="clearContact(contact)">clear</button> 22779 * <button ng-click="removeContact(contact)">X</button> 22780 * </li> 22781 * <li>[ <button ng-click="addContact()">add</button> ]</li> 22782 * </ul> 22783 * </div> 22784 * </file> 22785 * <file name="app.js">
22786 * angular.module('controllerExample', []) 22787 * .controller('SettingsController2', ['$scope', SettingsController2]); 22788 * 22789 * function SettingsController2($scope) { 22790 * $scope.name = "John Smith"; 22791 * $scope.contacts = [ 22792 * {type:'phone', value:'408 555 1212'}, 22793 * {type:'email', value:'[email protected]'} ]; 22794 * 22795 * $scope.greet = function() { 22796 * alert($scope.name); 22797 * }; 22798 * 22799 * $scope.addContact = function() { 22800 * $scope.contacts.push({type:'email', value:'[email protected]'}); 22801 * }; 22802 * 22803 * $scope.removeContact = function(contactToRemove) { 22804 * var index = $scope.contacts.indexOf(contactToRemove); 22805 * $scope.contacts.splice(index, 1); 22806 * }; 22807 * 22808 * $scope.clearContact = function(contact) { 22809 * contact.type = 'phone'; 22810 * contact.value = ''; 22811 * }; 22812 * } 22813 * </file> 22814 * <file name="protractor.js" type="protractor"> 22815 * it('should check controller', function() { 22816 * var container = element(by.id('ctrl-exmpl')); 22817 * 22818 * expect(container.element(by.model('name')) 22819 * .getAttribute('value')).toBe('John Smith'); 22820 * 22821 * var firstRepeat = 22822 * container.element(by.repeater('contact in contacts').row(0)); 22823 * var secondRepeat = 22824 * container.element(by.repeater('contact in contacts').row(1)); 22825 * 22826 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 22827 * .toBe('408 555 1212'); 22828 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 22829 * .toBe('[email protected]'); 22830 * 22831 * firstRepeat.element(by.buttonText('clear')).click(); 22832 * 22833 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 22834 * .toBe(''); 22835 * 22836 * container.element(by.buttonText('add')).click(); 22837 * 22838 * expect(container.element(by.repeater('contact in contacts').row(2)) 22839 * .element(by.model('contact.value')) 22840 * .getAttribute('value')) 22841 * .toBe('[email protected]'); 22842 * }); 22843 * </file> 22844 *</example> 22845 22846 */ 22847var ngControllerDirective = [function() { 22848 return { 22849 restrict: 'A', 22850 scope: true, 22851 controller: '@', 22852 priority: 500 22853 }; 22854}]; 22855 22856/** 22857 * @ngdoc directive 22858 * @name ngCsp 22859 * 22860 * @element html 22861 * @description 22862 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 22863 * 22864 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 22865 * 22866 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 22867 * For Angular to be CSP compatible there are only two things that we need to do differently: 22868 * 22869 * - don't use `Function` constructor to generate optimized value getters 22870 * - don't inject custom stylesheet into the document 22871 * 22872 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 22873 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will 22874 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 22875 * be raised. 22876 * 22877 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 22878 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 22879 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 22880 * 22881 * Angular tries to autodetect if CSP is active and automatically turn on the CSP-safe mode. This 22882 * autodetection however triggers a CSP error to be logged in the console: 22883 * 22884 * ``` 22885 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 22886 * script in the following Content Security Policy directive: "default-src 'self'". Note that 22887 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 22888 * ``` 22889 * 22890 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 22891 * directive on the root element of the application or on the `angular.js` script tag, whichever 22892 * appears first in the html document. 22893 * 22894 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 22895 * 22896 * @example 22897 * This example shows how to apply the `ngCsp` directive to the `html` tag. 22898 ```html 22899 <!doctype html> 22900 <html ng-app ng-csp> 22901 ... 22902 ... 22903 </html> 22904 ``` 22905 * @example 22906 // Note: the suffix `.csp` in the example name triggers 22907 // csp mode in our http server! 22908 <example name="example.csp" module="cspExample" ng-csp="true"> 22909 <file name="index.html"> 22910 <div ng-controller="MainController as ctrl"> 22911 <div> 22912 <button ng-click="ctrl.inc()" id="inc">Increment</button>
22913 <span id="counter"> 22914 {{ctrl.counter}} 22915 </span> 22916 </div> 22917 22918 <div> 22919 <button ng-click="ctrl.evil()" id="evil">Evil</button> 22920 <span id="evilError"> 22921 {{ctrl.evilError}} 22922 </span> 22923 </div> 22924 </div> 22925 </file> 22926 <file name="script.js"> 22927 angular.module('cspExample', []) 22928 .controller('MainController', function() { 22929 this.counter = 0; 22930 this.inc = function() { 22931 this.counter++; 22932 }; 22933 this.evil = function() { 22934 // jshint evil:true 22935 try { 22936 eval('1+2'); 22937 } catch (e) { 22938 this.evilError = e.message; 22939 } 22940 }; 22941 }); 22942 </file> 22943 <file name="protractor.js" type="protractor"> 22944 var util, webdriver; 22945 22946 var incBtn = element(by.id('inc')); 22947 var counter = element(by.id('counter')); 22948 var evilBtn = element(by.id('evil')); 22949 var evilError = element(by.id('evilError')); 22950 22951 function getAndClearSevereErrors() { 22952 return browser.manage().logs().get('browser').then(function(browserLog) { 22953 return browserLog.filter(function(logEntry) { 22954 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 22955 }); 22956 }); 22957 } 22958 22959 function clearErrors() { 22960 getAndClearSevereErrors(); 22961 } 22962 22963 function expectNoErrors() { 22964 getAndClearSevereErrors().then(function(filteredLog) { 22965 expect(filteredLog.length).toEqual(0); 22966 if (filteredLog.length) { 22967 console.log('browser console errors: ' + util.inspect(filteredLog)); 22968 } 22969 }); 22970 } 22971 22972 function expectError(regex) { 22973 getAndClearSevereErrors().then(function(filteredLog) { 22974 var found = false; 22975 filteredLog.forEach(function(log) { 22976 if (log.message.match(regex)) { 22977 found = true; 22978 } 22979 }); 22980 if (!found) { 22981 throw new Error('expected an error that matches ' + regex); 22982 } 22983 }); 22984 } 22985 22986 beforeEach(function() { 22987 util = require('util'); 22988 webdriver = require('protractor/node_modules/selenium-webdriver'); 22989 }); 22990 22991 // For now, we only test on Chrome, 22992 // as Safari does not load the page with Protractor's injected scripts, 22993 // and Firefox webdriver always disables content security policy (#6358) 22994 if (browser.params.browser !== 'chrome') { 22995 return; 22996 } 22997 22998 it('should not report errors when the page is loaded', function() { 22999 // clear errors so we are not dependent on previous tests 23000 clearErrors(); 23001 // Need to reload the page as the page is already loaded when 23002 // we come here 23003 browser.driver.getCurrentUrl().then(function(url) { 23004 browser.get(url); 23005 }); 23006 expectNoErrors(); 23007 }); 23008 23009 it('should evaluate expressions', function() { 23010 expect(counter.getText()).toEqual('0'); 23011 incBtn.click(); 23012 expect(counter.getText()).toEqual('1'); 23013 expectNoErrors(); 23014 }); 23015 23016 it('should throw and report an error when using "eval"', function() { 23017 evilBtn.click(); 23018 expect(evilError.getText()).toMatch(/Content Security Policy/); 23019 expectError(/Content Security Policy/); 23020 }); 23021 </file> 23022 </example> 23023 */ 23024 23025// ngCsp is not implemented as a proper directive any more, because we need it be processed while we 23026// bootstrap the system (before $parse is instantiated), for this reason we just have 23027// the csp.isActive() fn that looks for ng-csp attribute anywhere in the current doc 23028 23029/** 23030 * @ngdoc directive 23031 * @name ngClick 23032 * 23033 * @description 23034 * The ngClick directive allows you to specify custom behavior when 23035 * an element is clicked. 23036 * 23037 * @element ANY 23038 * @priority 0 23039 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 23040 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 23041 * 23042 * @example 23043 <example> 23044 <file name="index.html"> 23045 <button ng-click="count = count + 1" ng-init="count=0"> 23046 Increment 23047 </button>
23048 <span> 23049 count: {{count}} 23050 </span> 23051 </file> 23052 <file name="protractor.js" type="protractor"> 23053 it('should check ng-click', function() { 23054 expect(element(by.binding('count')).getText()).toMatch('0'); 23055 element(by.css('button')).click(); 23056 expect(element(by.binding('count')).getText()).toMatch('1'); 23057 }); 23058 </file> 23059 </example> 23060 */ 23061/* 23062 * A collection of directives that allows creation of custom event handlers that are defined as 23063 * angular expressions and are compiled and executed within the current scope. 23064 */ 23065var ngEventDirectives = {}; 23066 23067// For events that might fire synchronously during DOM manipulation 23068// we need to execute their event handlers asynchronously using $evalAsync, 23069// so that they are not executed in an inconsistent state. 23070var forceAsyncEvents = { 23071 'blur': true, 23072 'focus': true 23073}; 23074forEach( 23075 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 23076 function(eventName) { 23077 var directiveName = directiveNormalize('ng-' + eventName); 23078 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 23079 return { 23080 restrict: 'A', 23081 compile: function($element, attr) { 23082 // We expose the powerful $event object on the scope that provides access to the Window, 23083 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 23084 // checks at the cost of speed since event handler expressions are not executed as 23085 // frequently as regular change detection. 23086 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 23087 return function ngEventHandler(scope, element) { 23088 element.on(eventName, function(event) { 23089 var callback = function() { 23090 fn(scope, {$event:event}); 23091 }; 23092 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 23093 scope.$evalAsync(callback); 23094 } else { 23095 scope.$apply(callback); 23096 } 23097 }); 23098 }; 23099 } 23100 }; 23101 }]; 23102 } 23103); 23104 23105/** 23106 * @ngdoc directive 23107 * @name ngDblclick 23108 * 23109 * @description 23110 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 23111 * 23112 * @element ANY 23113 * @priority 0 23114 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 23115 * a dblclick. (The Event object is available as `$event`) 23116 * 23117 * @example 23118 <example> 23119 <file name="index.html"> 23120 <button ng-dblclick="count = count + 1" ng-init="count=0"> 23121 Increment (on double click) 23122 </button> 23123 count: {{count}} 23124 </file> 23125 </example> 23126 */ 23127 23128 23129/** 23130 * @ngdoc directive 23131 * @name ngMousedown 23132 * 23133 * @description 23134 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 23135 * 23136 * @element ANY 23137 * @priority 0 23138 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 23139 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 23140 * 23141 * @example 23142 <example> 23143 <file name="index.html"> 23144 <button ng-mousedown="count = count + 1" ng-init="count=0"> 23145 Increment (on mouse down) 23146 </button> 23147 count: {{count}} 23148 </file> 23149 </example> 23150 */ 23151 23152 23153/** 23154 * @ngdoc directive 23155 * @name ngMouseup 23156 * 23157 * @description 23158 * Specify custom behavior on mouseup event. 23159 * 23160 * @element ANY 23161 * @priority 0 23162 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 23163 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 23164 * 23165 * @example 23166 <example> 23167 <file name="index.html"> 23168 <button ng-mouseup="count = count + 1" ng-init="count=0"> 23169 Increment (on mouse up) 23170 </button> 23171 count: {{count}} 23172 </file> 23173 </example> 23174 */ 23175 23176/** 23177 * @ngdoc directive 23178 * @name ngMouseover 23179 * 23180 * @description 23181 * Specify custom behavior on mouseover event. 23182 * 23183 * @element ANY 23184 * @priority 0 23185 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 23186 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 23187 * 23188 * @example 23189 <example> 23190 <file name="index.html"> 23191 <button ng-mouseover="count = count + 1" ng-init="count=0"> 23192 Increment (when mouse is over) 23193 </button> 23194 count: {{count}} 23195 </file> 23196 </example> 23197 */ 23198 23199 23200/** 23201 * @ngdoc directive 23202 * @name ngMouseenter 23203 * 23204 * @description 23205 * Specify custom behavior on mouseenter event. 23206 * 23207 * @element ANY 23208 * @priority 0 23209 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 23210 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 23211 * 23212 * @example 23213 <example> 23214 <file name="index.html"> 23215 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 23216 Increment (when mouse enters) 23217 </button> 23218 count: {{count}} 23219 </file> 23220 </example> 23221 */ 23222 23223 23224/** 23225 * @ngdoc directive 23226 * @name ngMouseleave 23227 * 23228 * @description 23229 * Specify custom behavior on mouseleave event. 23230 * 23231 * @element ANY 23232 * @priority 0 23233 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 23234 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 23235 * 23236 * @example 23237 <example> 23238 <file name="index.html"> 23239 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 23240 Increment (when mouse leaves) 23241 </button> 23242 count: {{count}} 23243 </file> 23244 </example> 23245 */ 23246 23247 23248/** 23249 * @ngdoc directive 23250 * @name ngMousemove 23251 * 23252 * @description 23253 * Specify custom behavior on mousemove event. 23254 * 23255 * @element ANY 23256 * @priority 0 23257 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 23258 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 23259 * 23260 * @example 23261 <example> 23262 <file name="index.html"> 23263 <button ng-mousemove="count = count + 1" ng-init="count=0"> 23264 Increment (when mouse moves) 23265 </button> 23266 count: {{count}} 23267 </file> 23268 </example> 23269 */ 23270 23271 23272/** 23273 * @ngdoc directive 23274 * @name ngKeydown 23275 * 23276 * @description 23277 * Specify custom behavior on keydown event. 23278 * 23279 * @element ANY 23280 * @priority 0 23281 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 23282 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 23283 * 23284 * @example 23285 <example> 23286 <file name="index.html"> 23287 <input ng-keydown="count = count + 1" ng-init="count=0"> 23288 key down count: {{count}} 23289 </file> 23290 </example> 23291 */ 23292 23293 23294/** 23295 * @ngdoc directive 23296 * @name ngKeyup 23297 * 23298 * @description 23299 * Specify custom behavior on keyup event. 23300 * 23301 * @element ANY 23302 * @priority 0 23303 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 23304 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 23305 * 23306 * @example 23307 <example> 23308 <file name="index.html"> 23309 <p>Typing in the input box below updates the key count</p> 23310 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 23311 23312 <p>Typing in the input box below updates the keycode</p> 23313 <input ng-keyup="event=$event"> 23314 <p>event keyCode: {{ event.keyCode }}</p> 23315 <p>event altKey: {{ event.altKey }}</p> 23316 </file> 23317 </example> 23318 */ 23319 23320 23321/** 23322 * @ngdoc directive 23323 * @name ngKeypress 23324 * 23325 * @description 23326 * Specify custom behavior on keypress event. 23327 * 23328 * @element ANY 23329 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 23330 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 23331 * and can be interrogated for keyCode, altKey, etc.) 23332 * 23333 * @example 23334 <example> 23335 <file name="index.html"> 23336 <input ng-keypress="count = count + 1" ng-init="count=0"> 23337 key press count: {{count}} 23338 </file> 23339 </example> 23340 */ 23341 23342 23343/** 23344 * @ngdoc directive 23345 * @name ngSubmit 23346 * 23347 * @description 23348 * Enables binding angular expressions to onsubmit events. 23349 * 23350 * Additionally it prevents the default action (which for form means
23350sending the request to the 23351 * server and reloading the current page), but only if the form does not contain `action`, 23352 * `data-action`, or `x-action` attributes. 23353 * 23354 * <div class="alert alert-warning"> 23355 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 23356 * `ngSubmit` handlers together. See the 23357 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 23358 * for a detailed discussion of when `ngSubmit` may be triggered. 23359 * </div> 23360 * 23361 * @element form 23362 * @priority 0 23363 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 23364 * ({@link guide/expression#-event- Event object is available as `$event`}) 23365 * 23366 * @example 23367 <example module="submitExample"> 23368 <file name="index.html"> 23369 <script> 23370 angular.module('submitExample', []) 23371 .controller('ExampleController', ['$scope', function($scope) { 23372 $scope.list = []; 23373 $scope.text = 'hello'; 23374 $scope.submit = function() { 23375 if ($scope.text) { 23376 $scope.list.push(this.text); 23377 $scope.text = ''; 23378 } 23379 }; 23380 }]); 23381 </script>
23382 <form ng-submit="submit()" ng-controller="ExampleController"> 23383 Enter text and hit enter: 23384 <input type="text" ng-model="text" name="text" /> 23385 <input type="submit" id="submit" value="Submit" /> 23386 <pre>list={{list}}</pre> 23387 </form> 23388 </file> 23389 <file name="protractor.js" type="protractor"> 23390 it('should check ng-submit', function() { 23391 expect(element(by.binding('list')).getText()).toBe('list=[]'); 23392 element(by.css('#submit')).click(); 23393 expect(element(by.binding('list')).getText()).toContain('hello'); 23394 expect(element(by.model('text')).getAttribute('value')).toBe(''); 23395 }); 23396 it('should ignore empty strings', function() { 23397 expect(element(by.binding('list')).getText()).toBe('list=[]'); 23398 element(by.css('#submit')).click(); 23399 element(by.css('#submit')).click(); 23400 expect(element(by.binding('list')).getText()).toContain('hello'); 23401 }); 23402 </file> 23403 </example> 23404 */ 23405 23406/** 23407 * @ngdoc directive 23408 * @name ngFocus 23409 * 23410 * @description 23411 * Specify custom behavior on focus event. 23412 * 23413 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 23414 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 23415 * during an `$apply` to ensure a consistent state. 23416 * 23417 * @element window, input, select, textarea, a 23418 * @priority 0 23419 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 23420 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 23421 * 23422 * @example 23423 * See {@link ng.directive:ngClick ngClick} 23424 */ 23425 23426/** 23427 * @ngdoc directive 23428 * @name ngBlur 23429 * 23430 * @description 23431 * Specify custom behavior on blur event. 23432 * 23433 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 23434 * an element has lost focus. 23435 * 23436 * Note: As the `blur` event is executed synchronously also during DOM manipulations 23437 * (e.g. removing a focussed input), 23438 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 23439 * during an `$apply` to ensure a consistent state. 23440 * 23441 * @element window, input, select, textarea, a 23442 * @priority 0 23443 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 23444 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 23445 * 23446 * @example 23447 * See {@link ng.directive:ngClick ngClick} 23448 */ 23449 23450/** 23451 * @ngdoc directive 23452 * @name ngCopy 23453 * 23454 * @description 23455 * Specify custom behavior on copy event. 23456 * 23457 * @element window, input, select, textarea, a 23458 * @priority 0 23459 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 23460 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 23461 * 23462 * @example 23463 <example> 23464 <file name="index.html"> 23465 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 23466 copied: {{copied}} 23467 </file> 23468 </example> 23469 */ 23470 23471/** 23472 * @ngdoc directive 23473 * @name ngCut 23474 * 23475 * @description 23476 * Specify custom behavior on cut event. 23477 * 23478 * @element window, input, select, textarea, a 23479 * @priority 0 23480 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 23481 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 23482 * 23483 * @example 23484 <example> 23485 <file name="index.html"> 23486 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 23487 cut: {{cut}} 23488 </file> 23489 </example> 23490 */ 23491 23492/** 23493 * @ngdoc directive 23494 * @name ngPaste 23495 * 23496 * @description 23497 * Specify custom behavior on paste event. 23498 * 23499 * @element window, input, select, textarea, a 23500 * @priority 0 23501 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 23502 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 23503 * 23504 * @example 23505 <example> 23506 <file name="index.html"> 23507 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 23508 pasted: {{paste}} 23509 </file> 23510 </example> 23511 */ 23512 23513/** 23514 * @ngdoc directive 23515 * @name ngIf 23516 * @restrict A 23517 * 23518 * @description 23519 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 23520 * {expression}. If the expression assigned to `ngIf` evaluates to a false 23521 * value then the element is removed from the DOM, otherwise a clone of the 23522 * element is reinserted into the DOM. 23523 * 23524 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 23525 * element in the DOM rather than changing its visibility via the `display` css property. A common 23526 * case when this difference is significant is when using css selectors that rely on an element's 23527 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
23527s. 23528 * 23529 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 23530 * is created when the element is restored. The scope created within `ngIf` inherits from 23531 * its parent scope using 23532 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 23533 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 23534 * a javascript primitive defined in the parent scope. In this case any modifications made to the 23535 * variable within the child scope will override (hide) the value in the parent scope. 23536 * 23537 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 23538 * is if an element's class attribute is directly modified after it's compiled, using something like 23539 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 23540 * the added class will be lost because the original compiled state is used to regenerate the element. 23541 * 23542 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 23543 * and `leave` effects. 23544 * 23545 * @animations 23546 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container 23547 * leave - happens just before the `ngIf` contents are removed from the DOM 23548 * 23549 * @element ANY 23550 * @scope 23551 * @priority 600 23552 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 23553 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 23554 * element is added to the DOM tree. 23555 * 23556 * @example 23557 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 23558 <file name="index.html"> 23559 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 23560 Show when checked: 23561 <span ng-if="checked" class="animate-if"> 23562 This is removed when the checkbox is unchecked. 23563 </span> 23564 </file> 23565 <file name="animations.css"> 23566 .animate-if { 23567 background:white; 23568 border:1px solid black; 23569 padding:10px; 23570 } 23571 23572 .animate-if.ng-enter, .animate-if.ng-leave { 23573 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23574 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23575 } 23576 23577 .animate-if.ng-enter, 23578 .animate-if.ng-leave.ng-leave-active { 23579 opacity:0; 23580 } 23581 23582 .animate-if.ng-leave, 23583 .animate-if.ng-enter.ng-enter-active { 23584 opacity:1; 23585 } 23586 </file> 23587 </example> 23588 */ 23589var ngIfDirective = ['$animate', function($animate) { 23590 return { 23591 multiElement: true, 23592 transclude: 'element', 23593 priority: 600, 23594 terminal: true, 23595 restrict: 'A', 23596 $$tlb: true, 23597 link: function($scope, $element, $attr, ctrl, $transclude) { 23598 var block, childScope, previousElements; 23599 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 23600 23601 if (value) { 23602 if (!childScope) { 23603 $transclude(function(clone, newScope) { 23604 childScope = newScope; 23605 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 23606 // Note: We only need the first/last node of the cloned nodes. 23607 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 23608 // by a directive with templateUrl when its template arrives. 23609 block = { 23610 clone: clone 23611 }; 23612 $animate.enter(clone, $element.parent(), $element); 23613 }); 23614 } 23615 } else { 23616 if (previousElements) { 23617 previousElements.remove(); 23618 previousElements = null; 23619 } 23620 if (childScope) { 23621 childScope.$destroy(); 23622 childScope = null; 23623 } 23624 if (block) { 23625 previousElements = getBlockNodes(block.clone); 23626 $animate.leave(previousElements).then(function() { 23627 previousElements = null; 23628 }); 23629 block = null; 23630 } 23631 } 23632 }); 23633 } 23634 }; 23635}]; 23636 23637/** 23638 * @ngdoc directive 23639 * @name ngInclude 23640 * @restrict ECA 23641 * 23642 * @description 23643 * Fetches, compiles and includes an external HTML fragment. 23644 * 23645 * By default, the template URL is restricted to the same domain and protocol as the 23646 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 23647 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 23648 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 23649 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 23650 * ng.$sce Strict Contextual Escaping}. 23651 * 23652 * In addition, the browser's 23653 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 23654 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 23655 * policy may further restrict whether the template is successfully loaded. 23656 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 23657 * access on some browsers. 23658 * 23659 * @animations 23660 * enter - animation is used to bring new content into the browser. 23661 * leave - animation is used to animate existing content away. 23662 * 23663 * The enter and leave animation occur concurrently. 23664 * 23665 * @scope 23666 * @priority 400 23667 * 23668 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 23669 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 23670 * @param {string=} onload Expression to evaluate when a new partial is loaded. 23671 * 23672 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 23673 * $anchorScroll} to scroll the viewport after the content is loaded. 23674 * 23675 * - If the attribute is not set, disable scrolling. 23676 * - If the attribute is set without value, enable scrolling. 23677 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 23678 * 23679 * @example 23680 <example module="includeExample" deps="angular-animate.js" animations="true"> 23681 <file name="index.html">
23682 <div ng-controller="ExampleController"> 23683 <select ng-model="template" ng-options="t.name for t in templates"> 23684 <option value="">(blank)</option> 23685 </select> 23686 url of the template: <code>{{template.url}}</code> 23687 <hr/> 23688 <div class="slide-animate-container"> 23689 <div class="slide-animate" ng-include="template.url"></div> 23690 </div> 23691 </div> 23692 </file> 23693 <file name="script.js"> 23694 angular.module('includeExample', ['ngAnimate']) 23695 .controller('ExampleController', ['$scope', function($scope) { 23696 $scope.templates = 23697 [ { name: 'template1.html', url: 'template1.html'}, 23698 { name: 'template2.html', url: 'template2.html'} ]; 23699 $scope.template = $scope.templates[0]; 23700 }]); 23701 </file> 23702 <file name="template1.html"> 23703 Content of template1.html 23704 </file> 23705 <file name="template2.html"> 23706 Content of template2.html 23707 </file> 23708 <file name="animations.css"> 23709 .slide-animate-container { 23710 position:relative; 23711 background:white; 23712 border:1px solid black; 23713 height:40px; 23714 overflow:hidden; 23715 } 23716 23717 .slide-animate { 23718 padding:10px; 23719 } 23720 23721 .slide-animate.ng-enter, .slide-animate.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 position:absolute; 23726 top:0; 23727 left:0; 23728 right:0; 23729 bottom:0; 23730 display:block; 23731 padding:10px; 23732 } 23733 23734 .slide-animate.ng-enter { 23735 top:-50px; 23736 } 23737 .slide-animate.ng-enter.ng-enter-active { 23738 top:0; 23739 } 23740 23741 .slide-animate.ng-leave { 23742 top:0; 23743 } 23744 .slide-animate.ng-leave.ng-leave-active { 23745 top:50px; 23746 } 23747 </file> 23748 <file name="protractor.js" type="protractor"> 23749 var templateSelect = element(by.model('template')); 23750 var includeElem = element(by.css('[ng-include]')); 23751 23752 it('should load template1.html', function() { 23753 expect(includeElem.getText()).toMatch(/Content of template1.html/); 23754 }); 23755 23756 it('should load template2.html', function() { 23757 if (browser.params.browser == 'firefox') { 23758 // Firefox can't handle using selects 23759 // See https://github.com/angular/protractor/issues/480 23760 return; 23761 } 23762 templateSelect.click(); 23763 templateSelect.all(by.css('option')).get(2).click(); 23764 expect(includeElem.getText()).toMatch(/Content of template2.html/); 23765 }); 23766 23767 it('should change to blank', function() { 23768 if (browser.params.browser == 'firefox') { 23769 // Firefox can't handle using selects 23770 return; 23771 } 23772 templateSelect.click(); 23773 templateSelect.all(by.css('option')).get(0).click(); 23774 expect(includeElem.isPresent()).toBe(false); 23775 }); 23776 </file> 23777 </example> 23778 */ 23779 23780 23781/** 23782 * @ngdoc event 23783 * @name ngInclude#$includeContentRequested 23784 * @eventType emit on the scope ngInclude was declared in 23785 * @description 23786 * Emitted every time the ngInclude content is requested. 23787 * 23788 * @param {Object} angularEvent Synthetic event object. 23789 * @param {String} src URL of content to load. 23790 */ 23791 23792 23793/** 23794 * @ngdoc event 23795 * @name ngInclude#$includeContentLoaded 23796 * @eventType emit on the current ngInclude scope 23797 * @description 23798 * Emitted every time the ngInclude content is reloaded. 23799 * 23800 * @param {Object} angularEvent Synthetic event object. 23801 * @param {String} src URL of content to load. 23802 */ 23803 23804 23805/** 23806 * @ngdoc event 23807 * @name ngInclude#$includeContentError 23808 * @eventType emit on the scope ngInclude was declared in 23809 * @description 23810 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 23811 * 23812 * @param {Object} angularEvent Synthetic event object. 23813 * @param {String} src URL of content to load. 23814 */ 23815var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce', 23816 function($templateRequest, $anchorScroll, $animate, $sce) { 23817 return { 23818 restrict: 'ECA', 23819 priority: 400, 23820 terminal: true, 23821 transclude: 'element', 23822 controller: angular.noop, 23823 compile: function(element, attr) { 23824 var srcExp = attr.ngInclude || attr.src, 23825 onloadExp = attr.onload || '', 23826 autoScrollExp = attr.autoscroll; 23827 23828 return function(scope, $element, $attr, ctrl, $transclude) { 23829 var changeCounter = 0, 23830 currentScope, 23831 previousElement, 23832 currentElement; 23833 23834 var cleanupLastIncludeContent = function() { 23835 if (previousElement) { 23836 previousElement.remove(); 23837 previousElement = null; 23838 } 23839 if (currentScope) { 23840 currentScope.$destroy(); 23841 currentScope = null; 23842 } 23843 if (currentElement) { 23844 $animate.leave(currentElement).then(function() { 23845 previousElement = null; 23846 }); 23847 previousElement = currentElement; 23848 currentElement = null; 23849 } 23850 }; 23851 23852 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 23853 var afterAnimation = function() { 23854 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 23855 $anchorScroll(); 23856 } 23857 }; 23858 var thisChangeId = ++changeCounter; 23859 23860 if (src) { 23861 //set the 2nd param to true to ignore the template request error so that the inner 23862 //contents and scope can be cleaned up. 23863 $templateRequest(src, true).then(function(response) { 23864 if (thisChangeId !== changeCounter) return; 23865 var newScope = scope.$new(); 23866 ctrl.template = response; 23867 23868 // Note: This will also link all children of ng-include that were contained in the original 23869 // html. If that content contains controllers, ... they could pollute/change the scope. 23870 // However, using ng-include on an element with additional content does not make sense... 23871 // Note: We can't remove them in the cloneAttchFn of $transclude as that 23872 // function is called before linking the content, which would apply child 23873 // directives to non existing elements. 23874 var clone = $transclude(newScope, function(clone) { 23875 cleanupLastIncludeContent(); 23876 $animate.enter(clone, null, $element).then(afterAnimation); 23877 }); 23878 23879 currentScope = newScope; 23880 currentElement = clone; 23881 23882 currentScope.$emit('$includeContentLoaded', src); 23883 scope.$eval(onloadExp); 23884 }, function() { 23885 if (thisChangeId === changeCounter) { 23886 cleanupLastIncludeContent();
vendor: 2,651 bytes, lines 23887-23967
23887 scope.$emit('$includeContentError', src); 23888 } 23889 }); 23890 scope.$emit('$includeContentRequested', src); 23891 } else { 23892 cleanupLastIncludeContent(); 23893 ctrl.template = null; 23894 } 23895 }); 23896 }; 23897 } 23898 }; 23899}]; 23900 23901// This directive is called during the $transclude call of the first `ngInclude` directive. 23902// It will replace and compile the content of the element with the loaded template. 23903// We need this directive so that the element content is already filled when 23904// the link function of another directive on the same element as ngInclude 23905// is called. 23906var ngIncludeFillContentDirective = ['$compile', 23907 function($compile) { 23908 return { 23909 restrict: 'ECA', 23910 priority: -400, 23911 require: 'ngInclude', 23912 link: function(scope, $element, $attr, ctrl) { 23913 if (/SVG/.test($element[0].toString())) { 23914 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 23915 // support innerHTML, so detect this here and try to generate the contents 23916 // specially. 23917 $element.empty(); 23918 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, 23919 function namespaceAdaptedClone(clone) { 23920 $element.append(clone); 23921 }, {futureParentElement: $element}); 23922 return; 23923 } 23924 23925 $element.html(ctrl.template); 23926 $compile($element.contents())(scope); 23927 } 23928 }; 23929 }]; 23930 23931/** 23932 * @ngdoc directive 23933 * @name ngInit 23934 * @restrict AC 23935 * 23936 * @description 23937 * The `ngInit` directive allows you to evaluate an expression in the 23938 * current scope. 23939 * 23940 * <div class="alert alert-danger"> 23941 * The only appropriate use of `ngInit` is for aliasing special properties of 23942 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 23943 * should use {@link guide/controller controllers} rather than `ngInit` 23944 * to initialize values on a scope. 23945 * </div> 23946 * <div class="alert alert-warning"> 23947 * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make 23948 * sure you have parenthesis for correct precedence: 23949 * <pre class="prettyprint"> 23950 * `<div ng-init="test1 = (data | orderBy:'name')"></div>` 23951 * </pre> 23952 * </div> 23953 * 23954 * @priority 450 23955 * 23956 * @element ANY 23957 * @param {expression} ngInit {@link guide/expression Expression} to eval. 23958 * 23959 * @example 23960 <example module="initExample"> 23961 <file name="index.html"> 23962 <script> 23963 angular.module('initExample', []) 23964 .controller('ExampleController', ['$scope', function($scope) { 23965 $scope.list = [['a', 'b'], ['c', 'd']]; 23966 }]); 23967 </script>
23968 <div ng-controller="ExampleController"> 23969 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 23970 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 23971 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 23972 </div> 23973 </div> 23974 </div> 23975 </file> 23976 <file name="protractor.js" type="protractor"> 23977 it('should alias index positions', function() { 23978 var elements = element.all(by.css('.example-init')); 23979 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 23980 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 23981 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 23982 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 23983 }); 23984 </file> 23985 </example> 23986 */ 23987var ngInitDirective = ngDirective({ 23988 priority: 450, 23989 compile: function() { 23990 return { 23991 pre: function(scope, element, attrs) { 23992 scope.$eval(attrs.ngInit); 23993 } 23994 }; 23995 } 23996}); 23997 23998/** 23999 * @ngdoc directive 24000 * @name ngList 24001 * 24002 * @description 24003 * Text input that converts between a delimited string and an array of strings. The default 24004 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 24005 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 24006 * 24007 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 24008 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 24009 * list item is respected. This implies that the user of the directive is responsible for 24010 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 24011 * tab or newline character. 24012 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 24013 * when joining the list items back together) and whitespace around each list item is stripped 24014 * before it is added to the model. 24015 * 24016 * ### Example with Validation 24017 * 24018 * <example name="ngList-directive" module="listExample"> 24019 * <file name="app.js"> 24020 * angular.module('listExample', []) 24021 * .controller('ExampleController', ['$scope', function($scope) { 24022 * $scope.names = ['morpheus', 'neo', 'trinity']; 24023 * }]); 24024 * </file> 24025 * <file name="index.html">
24026 * <form name="myForm" ng-controller="ExampleController"> 24027 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 24028 * <span role="alert"> 24029 * <span class="error" ng-show="myForm.namesInput.$error.required"> 24030 * Required!</span> 24031 * </span> 24032 * <br> 24033 * <tt>names = {{names}}</tt><br/> 24034 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 24035 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 24036 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24037 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24038 * </form> 24039 * </file> 24040 * <file name="protractor.js" type="protractor"> 24041 * var listInput = element(by.model('names')); 24042 * var names = element(by.exactBinding('names')); 24043 * var valid = element(by.binding('myForm.namesInput.$valid')); 24044 * var error = element(by.css('span.error')); 24045 * 24046 * it('should initialize to model', function() { 24047 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 24048 * expect(valid.getText()).toContain('true'); 24049 * expect(error.getCssValue('display')).toBe('none'); 24050 * }); 24051 * 24052 * it('should be invalid if empty', function() { 24053 * listInput.clear(); 24054 * listInput.sendKeys(''); 24055 * 24056 * expect(names.getText()).toContain(''); 24057 * expect(valid.getText()).toContain('false'); 24058 * expect(error.getCssValue('display')).not.toBe('none'); 24059 * }); 24060 * </file> 24061 * </example> 24062 * 24063 * ### Example - splitting on whitespace 24064 * <example name="ngList-directive-newlines"> 24065 * <file name="index.html"> 24066 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 24067 * <pre>{{ list | json }}</pre> 24068 * </file> 24069 * <file name="protractor.js" type="protractor"> 24070 * it("should split the text by newlines", function() { 24071 * var listInput = element(by.model('list')); 24072 * var output = element(by.binding('list | json')); 24073 * listInput.sendKeys('abc\ndef\nghi'); 24074 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 24075 * }); 24076 * </file> 24077 * </example> 24078 * 24079 * @element input 24080 * @param {string=} ngList optional delimiter that should be used to split the value. 24081 */ 24082var ngListDirective = function() { 24083 return { 24084 restrict: 'A', 24085 priority: 100, 24086 require: 'ngModel', 24087 link: function(scope, element, attr, ctrl) { 24088 // We want to control whitespace trimming so we use this convoluted approach 24089 // to access the ngList attribute, which doesn't pre-trim the attribute 24090 var ngList = element.attr(attr.$attr.ngList) || ', '; 24091 var trimValues = attr.ngTrim !== 'false'; 24092 var separator = trimValues ? trim(ngList) : ngList; 24093 24094 var parse = function(viewValue) { 24095 // If the viewValue is invalid (say required but empty) it will be `undefined` 24096 if (isUndefined(viewValue)) return; 24097 24098 var list = []; 24099 24100 if (viewValue) { 24101 forEach(viewValue.split(separator), function(value) { 24102 if (value) list.push(trimValues ? trim(value) : value); 24103 }); 24104 } 24105 24106 return list; 24107 }; 24108 24109 ctrl.$parsers.push(parse); 24110 ctrl.$formatters.push(function(value) { 24111 if (isArray(value)) { 24112 return value.join(ngList); 24113 } 24114 24115 return undefined; 24116 }); 24117 24118 // Override the standard $isEmpty because an empty array means the input is empty. 24119 ctrl.$isEmpty = function(value) { 24120 return !value || !value.length; 24121 }; 24122 } 24123 }; 24124}; 24125 24126/* global VALID_CLASS: true, 24127 INVALID_CLASS: true, 24128 PRISTINE_CLASS: true, 24129 DIRTY_CLASS: true, 24130 UNTOUCHED_CLASS: true, 24131 TOUCHED_CLASS: true, 24132*/ 24133 24134var VALID_CLASS = 'ng-valid', 24135 INVALID_CLASS = 'ng-invalid', 24136 PRISTINE_CLASS = 'ng-pristine', 24137 DIRTY_CLASS = 'ng-dirty', 24138 UNTOUCHED_CLASS = 'ng-untouched', 24139 TOUCHED_CLASS = 'ng-touched', 24140 PENDING_CLASS = 'ng-pending'; 24141 24142 24143var $ngModelMinErr = new minErr('ngModel'); 24144 24145/** 24146 * @ngdoc type 24147 * @name ngModel.NgModelController 24148 * 24149 * @property {string} $viewValue Actual string value in the view. 24150 * @property {*} $modelValue The value in the model that the control is bound to. 24151 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 24152 the control reads value from the DOM. The functions are called in array order, each passing 24153 its return value through to the next. The last return value is forwarded to the 24154 {@link ngModel.NgModelController#$validators `$validators`} collection. 24155 24156Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 24157`$viewValue`}. 24158 24159Returning `undefined` from a parser means a parse error occurred. In that case, 24160no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 24161will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 24162is set to `true`. The parse error is stored in `ngModel.$error.parse`. 24163 24164 * 24165 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 24166 the model value changes. The functions are called in reverse array order, each passing the value through to the 24167 next. The last return value is used as the actual DOM value. 24168 Used to format / convert values for display in the control. 24169 * ```js 24170 * function formatter(value) { 24171 * if (value) { 24172 * return value.toUpperCase(); 24173 * } 24174 * } 24175 * ngModel.$formatters.push(formatter); 24176 * ``` 24177 * 24178 * @property {Object.<string, function>} $validators A collection of validators that are applied 24179 * whenever the model value changes. The key value within the object refers to the name of the 24180 * validator while the function refers to the validation operation. The validation operation is 24181 * provided with the model value as an argument and must return a true or false value depending 24182 * on the response of that validation. 24183 * 24184 * ```js 24185 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 24186 * var value = modelValue || viewValue; 24187 * return /[0-9]+/.test(value) && 24188 * /[a-z]+/.test(value) && 24189 * /[A-Z]+/.test(value) && 24190 * /\W+/.test(value); 24191 * }; 24192 * ``` 24193 * 24194 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 24195 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 24196 * is expected to return a promise when it is run during the model validation process. Once the promise 24197 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 24198 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 24199 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 24200 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 24201 * will only run once all synchronous validators have passed. 24202 * 24203 * Please note that if $http is used then it is important that the server returns a success HTTP response code 24204 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 24205 * 24206 * ```js 24207 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 24208 * var value = modelValue || viewValue; 24209 * 24210 * // Lookup user by username 24211 * return $http.get('/api/users/' + value). 24212 * then(function resolved() { 24213 * //username exists, this means validation fails 24214 * return $q.reject('exists'); 24215 * }, function rejected() { 24216 * //username does not exist, therefore this validation passes 24217 * return true; 24218 * }); 24219 * }; 24220 * ``` 24221 * 24222 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 24223 * view value has changed. It is called with no arguments, and its return value is ignored. 24224 * This can be used in place of additional $watches against the model value. 24225 * 24226 * @property {Object} $error An object hash with all failing validator ids as keys. 24227 * @property {Object} $pending An object hash with all pending validator ids as keys. 24228 * 24229 * @property {boolean} $untouched True if control has not lost focus yet. 24230 * @property {boolean} $touched True if control has lost focus. 24231 * @property {boolean} $pristine True if user has not interacted with the control yet. 24232 * @property {boolean} $dirty True if user has already interacted with the control. 24233 * @property {boolean} $valid True if there is no error. 24234 * @property {boolean}
24234 $invalid True if at least one error on the control. 24235 * @property {string} $name The name attribute of the control. 24236 * 24237 * @description 24238 * 24239 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 24240 * The controller contains services for data-binding, validation, CSS updates, and value formatting 24241 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 24242 * listening to DOM events. 24243 * Such DOM related logic should be provided by other directives which make use of 24244 * `NgModelController` for data-binding to control elements. 24245 * Angular provides this DOM logic for most {@link input `input`} elements. 24246 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 24247 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 24248 * 24249 * @example 24250 * ### Custom Control Example 24251 * This example shows how to use `NgModelController` with a custom control to achieve 24252 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 24253 * collaborate together to achieve the desired result. 24254 * 24255 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 24256 * contents be edited in place by the user. 24257 * 24258 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 24259 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 24260 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 24261 * that content using the `$sce` service. 24262 * 24263 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 24264 <file name="style.css"> 24265 [contenteditable] { 24266 border: 1px solid black; 24267 background-color: white; 24268 min-height: 20px; 24269 } 24270 24271 .ng-invalid { 24272 border: 1px solid red; 24273 } 24274 24275 </file> 24276 <file name="script.js"> 24277 angular.module('customControl', ['ngSanitize']). 24278 directive('contenteditable', ['$sce', function($sce) { 24279 return { 24280 restrict: 'A', // only activate on element attribute 24281 require: '?ngModel', // get a hold of NgModelController 24282 link: function(scope, element, attrs, ngModel) { 24283 if (!ngModel) return; // do nothing if no ng-model 24284 24285 // Specify how UI should be updated 24286 ngModel.$render = function() { 24287 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 24288 }; 24289 24290 // Listen for change events to enable binding 24291 element.on('blur keyup change', function() { 24292 scope.$evalAsync(read); 24293 }); 24294 read(); // initialize 24295 24296 // Write data to the model 24297 function read() { 24298 var html = element.html(); 24299 // When we clear the content editable the browser leaves a <br> behind 24300 // If strip-br attribute is provided then we strip this out 24301 if ( attrs.stripBr && html == '<br>' ) { 24302 html = ''; 24303 } 24304 ngModel.$setViewValue(html); 24305 } 24306 } 24307 }; 24308 }]); 24309 </file> 24310 <file name="index.html"> 24311 <form name="myForm"> 24312 <div contenteditable 24313 name="myWidget" ng-model="userContent" 24314 strip-br="true" 24315 required>Change me!</div> 24316 <span ng-show="myForm.myWidget.$error.required">Required!</span> 24317 <hr> 24318 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 24319 </form> 24320 </file> 24321 <file name="protractor.js" type="protractor"> 24322 it('should data-bind and become invalid', function() { 24323 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 24324 // SafariDriver can't handle contenteditable 24325 // and Firefox driver can't clear contenteditables very well 24326 return; 24327 } 24328 var contentEditable = element(by.css('[contenteditable]')); 24329 var content = 'Change me!'; 24330 24331 expect(contentEditable.getText()).toEqual(content); 24332 24333 contentEditable.clear(); 24334 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 24335 expect(contentEditable.getText()).toEqual(''); 24336 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 24337 }); 24338 </file> 24339 * </example> 24340 * 24341 * 24342 */ 24343var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 24344 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 24345 this.$viewValue = Number.NaN; 24346 this.$modelValue = Number.NaN; 24347 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 24348 this.$validators = {}; 24349 this.$asyncValidators = {}; 24350 this.$parsers = []; 24351 this.$formatters = []; 24352 this.$viewChangeListeners = []; 24353 this.$untouched = true; 24354 this.$touched = false;
24355 this.$pristine = true; 24356 this.$dirty = false; 24357 this.$valid = true; 24358 this.$invalid = false; 24359 this.$error = {}; // keep invalid keys here 24360 this.$$success = {}; // keep valid keys here 24361 this.$pending = undefined; // keep pending keys here 24362 this.$name = $interpolate($attr.name || '', false)($scope); 24363 24364 24365 var parsedNgModel = $parse($attr.ngModel), 24366 parsedNgModelAssign = parsedNgModel.assign, 24367 ngModelGet = parsedNgModel, 24368 ngModelSet = parsedNgModelAssign, 24369 pendingDebounce = null, 24370 parserValid, 24371 ctrl = this; 24372 24373 this.$$setOptions = function(options) { 24374 ctrl.$options = options; 24375 if (options && options.getterSetter) { 24376 var invokeModelGetter = $parse($attr.ngModel + '()'), 24377 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 24378 24379 ngModelGet = function($scope) { 24380 var modelValue = parsedNgModel($scope); 24381 if (isFunction(modelValue)) { 24382 modelValue = invokeModelGetter($scope); 24383 } 24384 return modelValue; 24385 }; 24386 ngModelSet = function($scope, newValue) { 24387 if (isFunction(parsedNgModel($scope))) { 24388 invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); 24389 } else { 24390 parsedNgModelAssign($scope, ctrl.$modelValue); 24391 } 24392 }; 24393 } else if (!parsedNgModel.assign) { 24394 throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 24395 $attr.ngModel, startingTag($element)); 24396 } 24397 }; 24398 24399 /** 24400 * @ngdoc method 24401 * @name ngModel.NgModelController#$render 24402 * 24403 * @description 24404 * Called when the view needs to be updated. It is expected that the user of the ng-model 24405 * directive will implement this method. 24406 * 24407 * The `$render()` method is invoked in the following situations: 24408 * 24409 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 24410 * committed value then `$render()` is called to update the input control. 24411 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 24412 * the `$viewValue` are different from last time. 24413 * 24414 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 24415 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue` 24416 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 24417 * invoked if you only change a property on the objects. 24418 */ 24419 this.$render = noop; 24420 24421 /** 24422 * @ngdoc method 24423 * @name ngModel.NgModelController#$isEmpty 24424 * 24425 * @description 24426 * This is called when we need to determine if the value of an input is empty. 24427 * 24428 * For instance, the required directive does this to work out if the input has data or not. 24429 * 24430 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 24431 * 24432 * You can override this for input directives whose concept of being empty is different from the 24433 * default. The `checkboxInputType` directive does this because in its case a value of `false` 24434 * implies empty. 24435 * 24436 * @param {*} value The value of the input to check for emptiness. 24437 * @returns {boolean} True if `value` is "empty". 24438 */ 24439 this.$isEmpty = function(value) { 24440 return isUndefined(value) || value === '' || value === null || value !== value; 24441 }; 24442 24443 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 24444 currentValidationRunId = 0; 24445 24446 /** 24447 * @ngdoc method 24448 * @name ngModel.NgModelController#$setValidity 24449 * 24450 * @description 24451 * Change the validity state, and notify the form. 24452 * 24453 * This method can be called within $parsers/$formatters or a custom validation implementation. 24454 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 24455 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 24456 * 24457 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 24458 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 24459 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
24460 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 24461 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 24462 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 24463 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 24464 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 24465 * Skipped is used by Angular when validators do not run because of parse errors and 24466 * when `$asyncValidators` do not run because any of the `$validators` failed. 24467 */ 24468 addSetValidityMethod({ 24469 ctrl: this, 24470 $element: $element, 24471 set: function(object, property) { 24472 object[property] = true; 24473 }, 24474 unset: function(object, property) { 24475 delete object[property]; 24476 }, 24477 parentForm: parentForm, 24478 $animate: $animate 24479 }); 24480 24481 /** 24482 * @ngdoc method 24483 * @name ngModel.NgModelController#$setPristine 24484 * 24485 * @description 24486 * Sets the control to its pristine state. 24487 * 24488 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 24489 * state (`ng-pristine` class). A model is considered to be pristine when the control 24490 * has not been changed from when first compiled. 24491 */ 24492 this.$setPristine = function() { 24493 ctrl.$dirty = false; 24494 ctrl.$pristine = true; 24495 $animate.removeClass($element, DIRTY_CLASS); 24496 $animate.addClass($element, PRISTINE_CLASS); 24497 }; 24498 24499 /** 24500 * @ngdoc method 24501 * @name ngModel.NgModelController#$setDirty 24502 * 24503 * @description 24504 * Sets the control to its dirty state. 24505 * 24506 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 24507 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 24508 * from when first compiled. 24509 */ 24510 this.$setDirty = function() { 24511 ctrl.$dirty = true; 24512 ctrl.$pristine = false; 24513 $animate.removeClass($element, PRISTINE_CLASS); 24514 $animate.addClass($element, DIRTY_CLASS); 24515 parentForm.$setDirty(); 24516 }; 24517 24518 /** 24519 * @ngdoc method 24520 * @name ngModel.NgModelController#$setUntouched 24521 * 24522 * @description 24523 * Sets the control to its untouched state. 24524 * 24525 * This method can be called to remove the `ng-touched` class and set the control to its 24526 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 24527 * by default, however this function can be used to restore that state if the model has 24528 * already been touched by the user. 24529 */ 24530 this.$setUntouched = function() { 24531 ctrl.$touched = false; 24532 ctrl.$untouched = true; 24533 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 24534 }; 24535 24536 /** 24537 * @ngdoc method 24538 * @name ngModel.NgModelController#$setTouched 24539 * 24540 * @description 24541 * Sets the control to its touched state. 24542 * 24543 * This method can be called to remove the `ng-untouched` class and set the control to its 24544 * touched state (`ng-touched` class). A model is considered to be touched when the user has 24545 * first focused the control element and then shifted focus away from the control (blur event). 24546 */ 24547 this.$setTouched = function() { 24548 ctrl.$touched = true; 24549 ctrl.$untouched = false; 24550 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 24551 }; 24552 24553 /** 24554 * @ngdoc method 24555 * @name ngModel.NgModelController#$rollbackViewValue 24556 * 24557 * @description 24558 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 24559 * which may be caused by a pending debounced event or because the input is waiting for a some 24560 * future event. 24561 * 24562 * If you have an input that uses `ng-model-options` to set up debounced events or events such 24563 * as blur you can have a situation where there is a period when the `$viewValue` 24564 * is out of synch with the ngModel's `$modelValue`. 24565 * 24566 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` 24567 * programmatically before these debounced/future events have resolved/occurred, because Angular's 24568 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 24569 * 24570 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 24571 * input which may have such events pending. This is important in order to make sure that the 24572 * input field will be updated with the new model value and any pending operations are cancelled. 24573 * 24574 * <example name="ng-model-cancel-update" module="cancel-update-example"> 24575 * <file name="app.js">
24576 * angular.module('cancel-update-example', []) 24577 * 24578 * .controller('CancelUpdateController', ['$scope', function($scope) { 24579 * $scope.resetWithCancel = function(e) { 24580 * if (e.keyCode == 27) { 24581 * $scope.myForm.myInput1.$rollbackViewValue(); 24582 * $scope.myValue = ''; 24583 * } 24584 * }; 24585 * $scope.resetWithoutCancel = function(e) { 24586 * if (e.keyCode == 27) { 24587 * $scope.myValue = ''; 24588 * } 24589 * }; 24590 * }]); 24591 * </file> 24592 * <file name="index.html"> 24593 * <div ng-controller="CancelUpdateController"> 24594 * <p>Try typing something in each input. See that the model only updates when you 24595 * blur off the input. 24596 * </p> 24597 * <p>Now see what happens if you start typing then press the Escape key</p> 24598 * 24599 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 24600 * <p id="inputDescription1">With $rollbackViewValue()</p> 24601 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue" 24602 * ng-keydown="resetWithCancel($event)"><br/> 24603 * myValue: "{{ myValue }}" 24604 * 24605 * <p id="inputDescription2">Without $rollbackViewValue()</p> 24606 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue" 24607 * ng-keydown="resetWithoutCancel($event)"><br/> 24608 * myValue: "{{ myValue }}" 24609 * </form> 24610 * </div> 24611 * </file> 24612 * </example> 24613 */ 24614 this.$rollbackViewValue = function() { 24615 $timeout.cancel(pendingDebounce); 24616 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 24617 ctrl.$render(); 24618 }; 24619 24620 /** 24621 * @ngdoc method 24622 * @name ngModel.NgModelController#$validate 24623 * 24624 * @description 24625 * Runs each of the registered validators (first synchronous validators and then 24626 * asynchronous validators). 24627 * If the validity changes to invalid, the model will be set to `undefined`, 24628 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 24629 * If the validity changes to valid, it will set the model to the last available valid 24630 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 24631 */ 24632 this.$validate = function() { 24633 // ignore $validate before model is initialized 24634 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 24635 return; 24636 } 24637 24638 var viewValue = ctrl.$$lastCommittedViewValue; 24639 // Note: we use the $$rawModelValue as $modelValue might have been 24640 // set to undefined during a view -> model update that found validation 24641 // errors. We can't parse the view here, since that could change 24642 // the model although neither viewValue nor the model on the scope changed 24643 var modelValue = ctrl.$$rawModelValue; 24644 24645 var prevValid = ctrl.$valid; 24646 var prevModelValue = ctrl.$modelValue; 24647 24648 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 24649 24650 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 24651 // If there was no change in validity, don't update the model 24652 // This prevents changing an invalid modelValue to undefined 24653 if (!allowInvalid && prevValid !== allValid) { 24654 // Note: Don't check ctrl.$valid here, as we could have 24655 // external validators (e.g. calculated on the server), 24656 // that just call $setValidity and need the model value 24657 // to calculate their validity. 24658 ctrl.$modelValue = allValid ? modelValue : undefined; 24659 24660 if (ctrl.$modelValue !== prevModelValue) { 24661 ctrl.$$writeModelToScope(); 24662 } 24663 } 24664 }); 24665 24666 }; 24667 24668 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 24669 currentValidationRunId++; 24670 var localValidationRunId = currentValidationRunId; 24671 24672 // check parser error 24673 if (!processParseErrors()) { 24674 validationDone(false); 24675 return; 24676 } 24677 if (!processSyncValidators()) { 24678 validationDone(false); 24679 return; 24680 } 24681 processAsyncValidators(); 24682 24683 function processParseErrors() { 24684 var errorKey = ctrl.$$parserName || 'parse'; 24685 if (parserValid === undefined) {
24686 setValidity(errorKey, null); 24687 } else { 24688 if (!parserValid) { 24689 forEach(ctrl.$validators, function(v, name) { 24690 setValidity(name, null); 24691 }); 24692 forEach(ctrl.$asyncValidators, function(v, name) { 24693 setValidity(name, null); 24694 }); 24695 } 24696 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 24697 setValidity(errorKey, parserValid); 24698 return parserValid; 24699 } 24700 return true; 24701 } 24702 24703 function processSyncValidators() { 24704 var syncValidatorsValid = true; 24705 forEach(ctrl.$validators, function(validator, name) { 24706 var result = validator(modelValue, viewValue); 24707 syncValidatorsValid = syncValidatorsValid && result; 24708 setValidity(name, result); 24709 }); 24710 if (!syncValidatorsValid) { 24711 forEach(ctrl.$asyncValidators, function(v, name) { 24712 setValidity(name, null); 24713 }); 24714 return false; 24715 } 24716 return true; 24717 } 24718 24719 function processAsyncValidators() { 24720 var validatorPromises = []; 24721 var allValid = true; 24722 forEach(ctrl.$asyncValidators, function(validator, name) { 24723 var promise = validator(modelValue, viewValue); 24724 if (!isPromiseLike(promise)) { 24725 throw $ngModelMinErr("$asyncValidators", 24726 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 24727 } 24728 setValidity(name, undefined); 24729 validatorPromises.push(promise.then(function() { 24730 setValidity(name, true); 24731 }, function(error) { 24732 allValid = false; 24733 setValidity(name, false); 24734 })); 24735 }); 24736 if (!validatorPromises.length) { 24737 validationDone(true); 24738 } else { 24739 $q.all(validatorPromises).then(function() { 24740 validationDone(allValid); 24741 }, noop); 24742 } 24743 } 24744 24745 function setValidity(name, isValid) { 24746 if (localValidationRunId === currentValidationRunId) { 24747 ctrl.$setValidity(name, isValid); 24748 } 24749 } 24750 24751 function validationDone(allValid) { 24752 if (localValidationRunId === currentValidationRunId) { 24753 24754 doneCallback(allValid); 24755 } 24756 } 24757 }; 24758 24759 /** 24760 * @ngdoc method 24761 * @name ngModel.NgModelController#$commitViewValue 24762 * 24763 * @description 24764 * Commit a pending update to the `$modelValue`. 24765 * 24766 * Updates may be pending by a debounced event or because the input is waiting for a some future 24767 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 24768 * usually handles calling this in response to input events. 24769 */ 24770 this.$commitViewValue = function() { 24771 var viewValue = ctrl.$viewValue; 24772 24773 $timeout.cancel(pendingDebounce); 24774 24775 // If the view value has not changed then we should just exit, except in the case where there is 24776 // a native validator on the element. In this case the validation state may have changed even though 24777 // the viewValue has stayed empty. 24778 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 24779 return; 24780 } 24781 ctrl.$$lastCommittedViewValue = viewValue; 24782 24783 // change to dirty 24784 if (ctrl.$pristine) { 24785 this.$setDirty(); 24786 } 24787 this.$$parseAndValidate(); 24788 }; 24789 24790 this.$$parseAndValidate = function() { 24791 var viewValue = ctrl.$$lastCommittedViewValue; 24792 var modelValue = viewValue; 24793 parserValid = isUndefined(modelValue) ? undefined : true; 24794 24795 if (parserValid) { 24796 for (var i = 0; i < ctrl.$parsers.length; i++) { 24797 modelValue = ctrl.$parsers[i](modelValue); 24798 if (isUndefined(modelValue)) { 24799 parserValid = false; 24800 break; 24801 } 24802 } 24803 } 24804 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 24805 // ctrl.$modelValue has not been touched yet... 24806 ctrl.$modelValue = ngModelGet($scope); 24807 } 24808 var prevModelValue = ctrl.$modelValue; 24809 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 24810 ctrl.$$rawModelValue = modelValue; 24811 24812 if (allowInvalid) { 24813 ctrl.$modelValue = modelValue; 24814 writeToModelIfNeeded(); 24815 } 24816 24817 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 24818 // This can happen if e.g. $setViewValue is called from inside a parser 24819 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 24820 if (!allowInvalid) { 24821 // Note: Don't check ctrl.$valid here, as we could have 24822 // external validators (e.g. calculated on the server), 24823 // that just call $setValidity and need the model value 24824 // to calculate their validity. 24825 ctrl.$modelValue = allValid ? modelValue : undefined; 24826 writeToModelIfNeeded(); 24827 } 24828 }); 24829 24830 function writeToModelIfNeeded() { 24831 if (ctrl.$modelValue !== prevModelValue) { 24832 ctrl.$$writeModelToScope(); 24833 } 24834 } 24835 }; 24836 24837 this.$$writeModelToScope = function() { 24838 ngModelSet($scope, ctrl.$modelValue);
24839 forEach(ctrl.$viewChangeListeners, function(listener) { 24840 try { 24841 listener(); 24842 } catch (e) { 24843 $exceptionHandler(e); 24844 } 24845 }); 24846 }; 24847 24848 /** 24849 * @ngdoc method 24850 * @name ngModel.NgModelController#$setViewValue 24851 * 24852 * @description 24853 * Update the view value. 24854 * 24855 * This method should be called when an input directive want to change the view value; typically, 24856 * this is done from within a DOM event handler. 24857 * 24858 * For example {@link ng.directive:input input} calls it when the value of the input changes and 24859 * {@link ng.directive:select select} calls it when an option is selected. 24860 * 24861 * If the new `value` is an object (rather than a string or a number), we should make a copy of the 24862 * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep 24863 * watch of objects, it only looks for a change of identity. If you only change the property of 24864 * the object then ngModel will not realise that the object has changed and will not invoke the 24865 * `$parsers` and `$validators` pipelines. 24866 * 24867 * For this reason, you should not change properties of the copy once it has been passed to 24868 * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly. 24869 * 24870 * When this method is called, the new `value` will be staged for committing through the `$parsers` 24871 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 24872 * value sent directly for processing, finally to be applied to `$modelValue` and then the 24873 * **expression** specified in the `ng-model` attribute. 24874 * 24875 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 24876 * 24877 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 24878 * and the `default` trigger is not listed, all those actions will remain pending until one of the 24879 * `updateOn` events is triggered on the DOM element. 24880 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 24881 * directive is used with a custom debounce for this particular event. 24882 * 24883 * Note that calling this function does not trigger a `$digest`. 24884 * 24885 * @param {string} value Value from the view. 24886 * @param {string} trigger Event that triggered the update. 24887 */ 24888 this.$setViewValue = function(value, trigger) { 24889 ctrl.$viewValue = value; 24890 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 24891 ctrl.$$debounceViewValueCommit(trigger); 24892 } 24893 }; 24894 24895 this.$$debounceViewValueCommit = function(trigger) { 24896 var debounceDelay = 0, 24897 options = ctrl.$options, 24898 debounce; 24899 24900 if (options && isDefined(options.debounce)) { 24901 debounce = options.debounce; 24902 if (isNumber(debounce)) { 24903 debounceDelay = debounce; 24904 } else if (isNumber(debounce[trigger])) { 24905 debounceDelay = debounce[trigger]; 24906 } else if (isNumber(debounce['default'])) { 24907 debounceDelay = debounce['default']; 24908 } 24909 } 24910 24911 $timeout.cancel(pendingDebounce); 24912 if (debounceDelay) { 24913 pendingDebounce = $timeout(function() { 24914 ctrl.$commitViewValue(); 24915 }, debounceDelay); 24916 } else if ($rootScope.$$phase) { 24917 ctrl.$commitViewValue(); 24918 } else { 24919 $scope.$apply(function() { 24920 ctrl.$commitViewValue(); 24921 }); 24922 } 24923 }; 24924 24925 // model -> value 24926 // Note: we cannot use a normal scope.$watch as we want to detect the following: 24927 // 1. scope value is 'a' 24928 // 2. user enters 'b' 24929 // 3. ng-change kicks in and reverts scope value to 'a' 24930 // -> scope value did not change since the last digest as 24931 // ng-change executes in apply phase 24932 // 4. view should be changed back to 'a' 24933 $scope.$watch(function ngModelWatch() { 24934 var modelValue = ngModelGet($scope); 24935 24936 // if scope model value and ngModel value are out of sync 24937 // TODO(perf): why not move this to the action fn? 24938 if (modelValue !== ctrl.$modelValue && 24939 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 24940 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 24941 ) { 24942 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 24943 parserValid = undefined; 24944 24945 var formatters = ctrl.$formatters, 24946 idx = formatters.length; 24947 24948 var viewValue = modelValue; 24949 while (idx--) { 24950 viewValue = formatters[idx](viewValue); 24951 } 24952 if (ctrl.$viewValue !== viewValue) { 24953 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 24954 ctrl.$render(); 24955 24956 ctrl.$$runValidators(modelValue, viewValue, noop); 24957 } 24958 } 24959 24960 return modelValue; 24961 }); 24962}]; 24963 24964 24965/** 24966 * @ngdoc directive 24967 * @name ngModel 24968 * 24969 * @element input 24970 * @priority 1 24971 * 24972 * @description 24973 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 24974 * property on the scope using {@link ngModel.NgModelController NgModelController}, 24975 * which is created and exposed by this directive. 24976 * 24977 * `ngModel` is responsible for: 24978 * 24979 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 24980 * require. 24981 * - Providing validation behavior (i.e. required, number, email, url). 24982 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 24983 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. 24984 * - Registering the control with its parent {@link ng.directive:form form}. 24985 *
24986 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 24987 * current scope. If the property doesn't already exist on this scope, it will be created 24988 * implicitly and added to the scope. 24989 * 24990 * For best practices on using `ngModel`, see: 24991 * 24992 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 24993 * 24994 * For basic examples, how to use `ngModel`, see: 24995 * 24996 * - {@link ng.directive:input input} 24997 * - {@link input[text] text} 24998 * - {@link input[checkbox] checkbox} 24999 * - {@link input[radio] radio} 25000 * - {@link input[number] number} 25001 * - {@link input[email] email} 25002 * - {@link input[url] url} 25003 * - {@link input[date] date} 25004 * - {@link input[datetime-local] datetime-local} 25005 * - {@link input[time] time} 25006 * - {@link input[month] month} 25007 * - {@link input[week] week} 25008 * - {@link ng.directive:select select} 25009 * - {@link ng.directive:textarea textarea} 25010 * 25011 * # CSS classes 25012 * The following CSS classes are added and removed on the associated input/select/textarea element 25013 * depending on the validity of the model. 25014 * 25015 * - `ng-valid`: the model is valid 25016 * - `ng-invalid`: the model is invalid 25017 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 25018 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 25019 * - `ng-pristine`: the control hasn't been interacted with yet 25020 * - `ng-dirty`: the control has been interacted with 25021 * - `ng-touched`: the control has been blurred 25022 * - `ng-untouched`: the control hasn't been blurred 25023 * - `ng-pending`: any `$asyncValidators` are unfulfilled 25024 * 25025 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 25026 * 25027 * ## Animation Hooks 25028 * 25029 * Animations within models are triggered when any of the associated CSS classes are added and removed 25030 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 25031 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 25032 * The animations that are triggered within ngModel are similar to how they work in ngClass and 25033 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 25034 * 25035 * The following example shows a simple way to utilize CSS transitions to style an input element 25036 * that has been rendered as invalid after it has been validated: 25037 * 25038 * <pre> 25039 * //be sure to include ngAnimate as a module to hook into more 25040 * //advanced animations 25041 * .my-input { 25042 * transition:0.5s linear all; 25043 * background: white; 25044 * } 25045 * .my-input.ng-invalid { 25046 * background: red; 25047 * color:white; 25048 * } 25049 * </pre> 25050 * 25051 * @example 25052 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 25053 <file name="index.html"> 25054 <script> 25055 angular.module('inputExample', []) 25056 .controller('ExampleController', ['$scope', function($scope) { 25057 $scope.val = '1'; 25058 }]); 25059 </script> 25060 <style> 25061 .my-input { 25062 -webkit-transition:all linear 0.5s; 25063 transition:all linear 0.5s; 25064 background: transparent; 25065 } 25066 .my-input.ng-invalid { 25067 color:white; 25068 background: red; 25069 } 25070 </style> 25071 <p id="inputDescription"> 25072 Update input to see transitions when valid/invalid. 25073 Integer is a valid value. 25074 </p>
25075 <form name="testForm" ng-controller="ExampleController"> 25076 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 25077 aria-describedby="inputDescription" /> 25078 </form> 25079 </file> 25080 * </example> 25081 * 25082 * ## Binding to a getter/setter 25083 * 25084 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 25085 * function that returns a representation of the model when called with zero arguments, and sets 25086 * the internal state of a model when called with an argument. It's sometimes useful to use this 25087 * for models that have an internal representation that's different from what the model exposes 25088 * to the view. 25089 * 25090 * <div class="alert alert-success"> 25091 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 25092 * frequently than other parts of your code. 25093 * </div> 25094 * 25095 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 25096 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 25097 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 25098 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 25099 * 25100 * The following example shows how to use `ngModel` with a getter/setter: 25101 * 25102 * @example 25103 * <example name="ngModel-getter-setter" module="getterSetterExample"> 25104 <file name="index.html">
25105 <div ng-controller="ExampleController"> 25106 <form name="userForm"> 25107 <label>Name: 25108 <input type="text" name="userName" 25109 ng-model="user.name" 25110 ng-model-options="{ getterSetter: true }" /> 25111 </label> 25112 </form> 25113 <pre>user.name = <span ng-bind="user.name()"></span></pre> 25114 </div> 25115 </file> 25116 <file name="app.js"> 25117 angular.module('getterSetterExample', []) 25118 .controller('ExampleController', ['$scope', function($scope) { 25119 var _name = 'Brian'; 25120 $scope.user = { 25121 name: function(newName) { 25122 // Note that newName can be undefined for two reasons: 25123 // 1. Because it is called as a getter and thus called with no arguments 25124 // 2. Because the property should actually be set to undefined. This happens e.g. if the 25125 // input is invalid 25126 return arguments.length ? (_name = newName) : _name; 25127 } 25128 }; 25129 }]); 25130 </file> 25131 * </example> 25132 */ 25133var ngModelDirective = ['$rootScope', function($rootScope) { 25134 return { 25135 restrict: 'A', 25136 require: ['ngModel', '^?form', '^?ngModelOptions'], 25137 controller: NgModelController, 25138 // Prelink needs to run before any input directive 25139 // so that we can set the NgModelOptions in NgModelController 25140 // before anyone else uses it. 25141 priority: 1, 25142 compile: function ngModelCompile(element) { 25143 // Setup initial state of the control 25144 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 25145 25146 return { 25147 pre: function ngModelPreLink(scope, element, attr, ctrls) { 25148 var modelCtrl = ctrls[0], 25149 formCtrl = ctrls[1] || nullFormCtrl; 25150 25151 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 25152 25153 // notify others, especially parent forms 25154 formCtrl.$addControl(modelCtrl); 25155 25156 attr.$observe('name', function(newValue) { 25157 if (modelCtrl.$name !== newValue) { 25158 formCtrl.$$renameControl(modelCtrl, newValue); 25159 } 25160 }); 25161 25162 scope.$on('$destroy', function() { 25163 formCtrl.$removeControl(modelCtrl); 25164 }); 25165 }, 25166 post: function ngModelPostLink(scope, element, attr, ctrls) { 25167 var modelCtrl = ctrls[0]; 25168 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 25169 element.on(modelCtrl.$options.updateOn, function(ev) { 25170 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 25171 }); 25172 } 25173 25174 element.on('blur', function(ev) { 25175 if (modelCtrl.$touched) return; 25176 25177 if ($rootScope.$$phase) { 25178 scope.$evalAsync(modelCtrl.$setTouched); 25179 } else { 25180 scope.$apply(modelCtrl.$setTouched); 25181 } 25182 }); 25183 } 25184 }; 25185 } 25186 }; 25187}]; 25188 25189var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 25190 25191/** 25192 * @ngdoc directive 25193 * @name ngModelOptions 25194 * 25195 * @description 25196 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 25197 * events that will trigger a model update and/or a debouncing delay so that the actual update only 25198 * takes place when a timer expires; this timer will be reset after another change takes place. 25199 * 25200 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 25201 * be different from the value in the actual model. This means that if you update the model you 25202 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 25203 * order to make sure it is synchronized with the model and that any debounced action is canceled. 25204 * 25205 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 25206 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 25207 * important because `form` controllers are published to the related scope under the name in their 25208 * `name` attribute. 25209 * 25210 * Any pending changes will take place immediately when an enclosing form is submitted via the 25211 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 25212 * to have access to the updated model. 25213 * 25214 * `ngModelOptions` has an effect on the element it's declared on and its desce
25214ndants. 25215 * 25216 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 25217 * - `updateOn`: string specifying which event should the input be bound to. You can set several 25218 * events using an space delimited list. There is a special event called `default` that 25219 * matches the default events belonging of the control. 25220 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 25221 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 25222 * custom value for each event. For example: 25223 * `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"` 25224 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 25225 * not validate correctly instead of the default behavior of setting the model to undefined. 25226 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 25227 `ngModel` as getters/setters. 25228 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 25229 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 25230 * continental US time zone abbreviations, but for general use, use a time zone offset, for 25231 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 25232 * If not specified, the timezone of the browser will be used. 25233 * 25234 * @example 25235 25236 The following example shows how to override immediate updates. Changes on the inputs within the 25237 form will update the model only when the control loses focus (blur event). If `escape` key is 25238 pressed while the input field is focused, the value is reset to the value in the current model. 25239 25240 <example name="ngModelOptions-directive-blur" module="optionsExample"> 25241 <file name="index.html">
25242 <div ng-controller="ExampleController"> 25243 <form name="userForm"> 25244 <label>Name: 25245 <input type="text" name="userName" 25246 ng-model="user.name" 25247 ng-model-options="{ updateOn: 'blur' }" 25248 ng-keyup="cancel($event)" /> 25249 </label><br /> 25250 <label>Other data: 25251 <input type="text" ng-model="user.data" /> 25252 </label><br /> 25253 </form> 25254 <pre>user.name = <span ng-bind="user.name"></span></pre> 25255 </div> 25256 </file> 25257 <file name="app.js"> 25258 angular.module('optionsExample', []) 25259 .controller('ExampleController', ['$scope', function($scope) { 25260 $scope.user = { name: 'say', data: '' }; 25261 25262 $scope.cancel = function(e) { 25263 if (e.keyCode == 27) { 25264 $scope.userForm.userName.$rollbackViewValue(); 25265 } 25266 }; 25267 }]); 25268 </file> 25269 <file name="protractor.js" type="protractor"> 25270 var model = element(by.binding('user.name')); 25271 var input = element(by.model('user.name')); 25272 var other = element(by.model('user.data')); 25273 25274 it('should allow custom events', function() { 25275 input.sendKeys(' hello'); 25276 input.click(); 25277 expect(model.getText()).toEqual('say'); 25278 other.click(); 25279 expect(model.getText()).toEqual('say hello'); 25280 }); 25281 25282 it('should $rollbackViewValue when model changes', function() { 25283 input.sendKeys(' hello'); 25284 expect(input.getAttribute('value')).toEqual('say hello'); 25285 input.sendKeys(protractor.Key.ESCAPE); 25286 expect(input.getAttribute('value')).toEqual('say'); 25287 other.click(); 25288 expect(model.getText()).toEqual('say'); 25289 }); 25290 </file> 25291 </example> 25292 25293 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 25294 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 25295 25296 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 25297 <file name="index.html">
25298 <div ng-controller="ExampleController"> 25299 <form name="userForm"> 25300 <label>Name: 25301 <input type="text" name="userName" 25302 ng-model="user.name" 25303 ng-model-options="{ debounce: 1000 }" /> 25304 </label> 25305 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 25306 <br /> 25307 </form> 25308 <pre>user.name = <span ng-bind="user.name"></span></pre> 25309 </div> 25310 </file> 25311 <file name="app.js"> 25312 angular.module('optionsExample', []) 25313 .controller('ExampleController', ['$scope', function($scope) { 25314 $scope.user = { name: 'say' }; 25315 }]); 25316 </file> 25317 </example> 25318 25319 This one shows how to bind to getter/setters: 25320 25321 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 25322 <file name="index.html"> 25323 <div ng-controller="ExampleController"> 25324 <form name="userForm"> 25325 <label>Name: 25326 <input type="text" name="userName" 25327 ng-model="user.name" 25328 ng-model-options="{ getterSetter: true }" /> 25329 </label> 25330 </form> 25331 <pre>user.name = <span ng-bind="user.name()"></span></pre> 25332 </div> 25333 </file> 25334 <file name="app.js"> 25335 angular.module('getterSetterExample', []) 25336 .controller('ExampleController', ['$scope', function($scope) { 25337 var _name = 'Brian'; 25338 $scope.user = { 25339 name: function(newName) { 25340 // Note that newName can be undefined for two reasons: 25341 // 1. Because it is called as a getter and thus called with no arguments 25342 // 2. Because the property should actually be set to undefined. This happens e.g. if the 25343 // input is invalid 25344 return arguments.length ? (_name = newName) : _name; 25345 } 25346 }; 25347 }]); 25348 </file> 25349 </example> 25350 */ 25351var ngModelOptionsDirective = function() { 25352 return { 25353 restrict: 'A', 25354 controller: ['$scope', '$attrs', function($scope, $attrs) { 25355 var that = this; 25356 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 25357 // Allow adding/overriding bound events 25358 if (this.$options.updateOn !== undefined) { 25359 this.$options.updateOnDefault = false;
25360 // extract "default" pseudo-event from list of events that can trigger a model update 25361 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 25362 that.$options.updateOnDefault = true; 25363 return ' '; 25364 })); 25365 } else { 25366 this.$options.updateOnDefault = true; 25367 } 25368 }] 25369 }; 25370}; 25371 25372 25373 25374// helper methods 25375function addSetValidityMethod(context) { 25376 var ctrl = context.ctrl, 25377 $element = context.$element, 25378 classCache = {}, 25379 set = context.set, 25380 unset = context.unset, 25381 parentForm = context.parentForm, 25382 $animate = context.$animate; 25383 25384 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 25385 25386 ctrl.$setValidity = setValidity; 25387 25388 function setValidity(validationErrorKey, state, controller) { 25389 if (state === undefined) { 25390 createAndSet('$pending', validationErrorKey, controller); 25391 } else { 25392 unsetAndCleanup('$pending', validationErrorKey, controller); 25393 } 25394 if (!isBoolean(state)) { 25395 unset(ctrl.$error, validationErrorKey, controller); 25396 unset(ctrl.$$success, validationErrorKey, controller); 25397 } else { 25398 if (state) { 25399 unset(ctrl.$error, validationErrorKey, controller); 25400 set(ctrl.$$success, validationErrorKey, controller); 25401 } else { 25402 set(ctrl.$error, validationErrorKey, controller); 25403 unset(ctrl.$$success, validationErrorKey, controller); 25404 } 25405 } 25406 if (ctrl.$pending) { 25407 cachedToggleClass(PENDING_CLASS, true); 25408 ctrl.$valid = ctrl.$invalid = undefined; 25409 toggleValidationCss('', null); 25410 } else { 25411 cachedToggleClass(PENDING_CLASS, false); 25412 ctrl.$valid = isObjectEmpty(ctrl.$error); 25413 ctrl.$invalid = !ctrl.$valid; 25414 toggleValidationCss('', ctrl.$valid); 25415 } 25416 25417 // re-read the state as the set/unset methods could have 25418 // combined state in ctrl.$error[validationError] (used for forms), 25419 // where setting/unsetting only increments/decrements the value, 25420 // and does not replace it. 25421 var combinedState; 25422 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 25423 combinedState = undefined; 25424 } else if (ctrl.$error[validationErrorKey]) { 25425 combinedState = false; 25426 } else if (ctrl.$$success[validationErrorKey]) { 25427 combinedState = true; 25428 } else { 25429 combinedState = null; 25430 } 25431
25432 toggleValidationCss(validationErrorKey, combinedState); 25433 parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 25434 } 25435 25436 function createAndSet(name, value, controller) { 25437 if (!ctrl[name]) { 25438 ctrl[name] = {}; 25439 } 25440 set(ctrl[name], value, controller); 25441 } 25442 25443 function unsetAndCleanup(name, value, controller) { 25444 if (ctrl[name]) { 25445 unset(ctrl[name], value, controller); 25446 } 25447 if (isObjectEmpty(ctrl[name])) { 25448 ctrl[name] = undefined; 25449 } 25450 } 25451 25452 function cachedToggleClass(className, switchValue) { 25453 if (switchValue && !classCache[className]) { 25454 $animate.addClass($element, className); 25455 classCache[className] = true; 25456 } else if (!switchValue && classCache[className]) { 25457 $animate.removeClass($element, className); 25458 classCache[className] = false; 25459 } 25460 } 25461 25462 function toggleValidationCss(validationErrorKey, isValid) { 25463 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 25464 25465 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 25466 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 25467 } 25468} 25469 25470function isObjectEmpty(obj) { 25471 if (obj) { 25472 for (var prop in obj) { 25473 return false; 25474 } 25475 } 25476 return true; 25477} 25478 25479/** 25480 * @ngdoc directive 25481 * @name ngNonBindable 25482 * @restrict AC 25483 * @priority 1000 25484 * 25485 * @description 25486 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 25487 * DOM element. This is useful if the element contains what appears to be Angular directives and 25488 * bindings but which should be ignored by Angular. This could be the case if you have a site that 25489 * displays snippets of code, for instance. 25490 * 25491 * @element ANY 25492 * 25493 * @example 25494 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 25495 * but the one wrapped in `ngNonBindable` is left alone. 25496 * 25497 * @example 25498 <example> 25499 <file name="index.html"> 25500 <div>Normal: {{1 + 2}}</div> 25501 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 25502 </file> 25503 <file name="protractor.js" type="protractor"> 25504 it('should check ng-non-bindable', function() { 25505 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 25506 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 25507 }); 25508 </file> 25509 </example> 25510 */ 25511var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 25512 25513/* global jqLiteRemove */ 25514 25515var ngOptionsMinErr = minErr('ngOptions'); 25516 25517/** 25518 * @ngdoc directive 25519 * @name ngOptions 25520 * @restrict A 25521 * 25522 * @description 25523 * 25524 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 25525 * elements for the `<select>` element using the array or object obtained by evaluating the 25526 * `ngOptions` comprehension expression. 25527 * 25528 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 25529 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 25530 * increasing speed by not creating a new scope for each repeated instance, as well as providing 25531 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 25532 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 25533 * to a non-string value. This is because an option element can only be bound to string values at 25534 * present. 25535 * 25536 * When an item in the `<select>` menu is selected, the array element or object property 25537 * represented by the selected option will be bound to the model identified by the `ngModel` 25538 * directive. 25539 * 25540 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 25541 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 25542 * option. See example below for demonstration. 25543 * 25544 * ## Complex Models (objects or collections) 25545 * 25546 * **Note:** By default, `ngModel` watches the model by reference, not value. This is important when 25547 * binding any input directive to a model that is an object or a collection. 25548 * 25549 * Since this is a common situation for `ngOptions` the directive additionally watches the model using 25550 * `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in 25551 * the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual 25552 * object/collection has not changed identity but only a property on the object or an item in the collection 25553 * changes. 25554 *
25555 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 25556 * if the model is an array). This means that changing a property deeper inside the object/collection that the 25557 * first level will not trigger a re-rendering. 25558 * 25559 * 25560 * ## `select` **`as`** 25561 * 25562 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 25563 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 25564 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 25565 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 25566 * 25567 * 25568 * ### `select` **`as`** and **`track by`** 25569 * 25570 * <div class="alert alert-warning"> 25571 * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together. 25572 * </div> 25573 * 25574 * Consider the following example: 25575 * 25576 * ```html 25577 * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"> 25578 * ``` 25579 * 25580 * ```js 25581 * $scope.values = [{ 25582 * id: 1, 25583 * label: 'aLabel', 25584 * subItem: { name: 'aSubItem' } 25585 * }, { 25586 * id: 2, 25587 * label: 'bLabel', 25588 * subItem: { name: 'bSubItem' } 25589 * }]; 25590 * 25591 * $scope.selected = { name: 'aSubItem' }; 25592 * ``` 25593 * 25594 * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element 25595 * of the data source (to `item` in this example). To calculate whether an element is selected, we do the 25596 * following: 25597 * 25598 * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]` 25599 * 2. Apply **`track by`** to the already selected value in `ngModel`. 25600 * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected 25601 * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to 25602 * a wrong object, the selected element can't be found, `<select>` is always reset to the "not 25603 * selected" option. 25604 * 25605 * 25606 * @param {string} ngModel Assignable angular expression to data-bind to. 25607 * @param {string=} name Property name of the form under which the control is published. 25608 * @param {string=} required The control is considered valid only if value is entered. 25609 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25610 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25611 * `required` when you want to data-bind to the `required` attribute. 25612 * @param {comprehension_expression=} ngOptions in one of the following forms: 25613 * 25614 * * for array data sources: 25615 * * `label` **`for`** `value` **`in`** `array` 25616 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 25617 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 25618 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 25619 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 25620 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 25621 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 25622 * (for including a filter with `track by`) 25623 * * for object data sources: 25624 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 25625 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 25626 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 25627 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 25628 * * `select` **`as`** `label` **`group by`** `group` 25629 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 25630 * * `select` **`as`** `label` **`disable when`** `disable` 25631 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 25632 * 25633 * Where: 25634 * 25635 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 25636 * * `value`: local variable which will refer to each item in the `array` or each property value 25637 * of `object` during iteration. 25638 * * `key`: local variable which will refer to a property name in `object` during iteration. 25639 * * `label`: The result of this expression will be the label for `<option>` element. The 25640 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 25641 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 25642 * element. If not specified, `select` expression will default to `value`. 25643 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 25644 * DOM element. 25645 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 25646 * element. Return `true` to disable. 25647 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 25648 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 25649 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 25650 * even when the options are recreated (e.g. reloaded from the server). 25651 * 25652 * @example 25653 <example module="selectExample"> 25654 <file name="index.html"> 25655 <script> 25656 angular.module('selectExample', [])
25657 .controller('ExampleController', ['$scope', function($scope) { 25658 $scope.colors = [ 25659 {name:'black', shade:'dark'}, 25660 {name:'white', shade:'light', notAnOption: true}, 25661 {name:'red', shade:'dark'}, 25662 {name:'blue', shade:'dark', notAnOption: true}, 25663 {name:'yellow', shade:'light', notAnOption: false} 25664 ]; 25665 $scope.myColor = $scope.colors[2]; // red 25666 }]); 25667 </script> 25668 <div ng-controller="ExampleController"> 25669 <ul> 25670 <li ng-repeat="color in colors"> 25671 <label>Name: <input ng-model="color.name"></label> 25672 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 25673 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 25674 </li> 25675 <li> 25676 <button ng-click="colors.push({})">add</button> 25677 </li> 25678 </ul> 25679 <hr/> 25680 <label>Color (null not allowed): 25681 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 25682 </label><br/> 25683 <label>Color (null allowed): 25684 <span class="nullable"> 25685 <select ng-model="myColor" ng-options="color.name for color in colors"> 25686 <option value="">-- choose color --</option> 25687 </select> 25688 </span></label><br/> 25689 25690 <label>Color grouped by shade: 25691 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 25692 </select> 25693 </label><br/> 25694 25695 <label>Color grouped by shade, with some disabled: 25696 <select ng-model="myColor" 25697 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 25698 </select> 25699 </label><br/> 25700 25701 25702 25703 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 25704 <br/> 25705 <hr/> 25706 Currently selected: {{ {selected_color:myColor} }} 25707 <div style="border:solid 1px black; height:20px" 25708 ng-style="{'background-color':myColor.name}"> 25709 </div> 25710 </div> 25711 </file> 25712 <file name="protractor.js" type="protractor"> 25713 it('should check ng-options', function() { 25714 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 25715 element.all(by.model('myColor')).first().click(); 25716 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 25717 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 25718 element(by.css('.nullable select[ng-model="myColor"]')).click(); 25719 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 25720 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 25721 }); 25722 </file> 25723 </example> 25724 */ 25725 25726// jshint maxlen: false 25727// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 25728var 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]+?))?$/; 25729 // 1: value expression (valueFn) 25730 // 2: label expression (displayFn) 25731 // 3: group by expression (groupByFn) 25732 // 4: disable when expression (disableWhenFn) 25733 // 5: array item variable name 25734 // 6: object item key variable name 25735 // 7: object item value variable name 25736 // 8: collection expression 25737 // 9: track by expression 25738// jshint maxlen: 100 25739 25740 25741var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { 25742 25743 function parseOptionsExpression(optionsExp, selectElement, scope) { 25744 25745 var match = optionsExp.match(NG_OPTIONS_REGEXP); 25746 if (!(match)) { 25747 throw ngOptionsMinErr('iexp', 25748 "Expected expression in form of " + 25749 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 25750 " but got '{0}'. Element: {1}", 25751 optionsExp, startingTag(selectElement)); 25752 } 25753 25754 // Extract the parts from the ngOptions expression 25755 25756 // The variable name for the value of the item in the collection 25757 var valueName = match[5] || match[7]; 25758 // The variable name for the key of the item in the collection 25759 var keyName = match[6]; 25760 25761 // An expression that generates the viewValue for an option if there is a label expression 25762 var selectAs = / as /.test(match[0]) && match[1]; 25763 // An expression that is used to track the id of each object in the options collection 25764 var trackBy = match[9]; 25765 // An expression that generates the viewValue for an option if there is no label expression 25766 var valueFn = $parse(match[2] ? match[1] : valueName); 25767 var selectAsFn = selectAs && $parse(selectAs); 25768 var viewValueFn = selectAsFn || valueFn; 25769 var trackByFn = trackBy && $parse(trackBy); 25770 25771 // Get the value by which we are going to track the option 25772 // if we have a trackFn then use that (passing scope and locals) 25773 // otherwise just hash the given viewValue 25774 var getTrackByValueFn = trackBy ? 25775 function(value, locals) { return trackByFn(scope, locals); } : 25776 function getHashOfValue(value) { return hashKey(value); }; 25777 var getTrackByValue = function(value, key) {
25778 return getTrackByValueFn(value, getLocals(value, key)); 25779 }; 25780 25781 var displayFn = $parse(match[2] || match[1]); 25782 var groupByFn = $parse(match[3] || ''); 25783 var disableWhenFn = $parse(match[4] || ''); 25784 var valuesFn = $parse(match[8]); 25785 25786 var locals = {}; 25787 var getLocals = keyName ? function(value, key) { 25788 locals[keyName] = key; 25789 locals[valueName] = value; 25790 return locals; 25791 } : function(value) { 25792 locals[valueName] = value; 25793 return locals; 25794 }; 25795 25796 25797 function Option(selectValue, viewValue, label, group, disabled) { 25798 this.selectValue = selectValue; 25799 this.viewValue = viewValue; 25800 this.label = label; 25801 this.group = group; 25802 this.disabled = disabled; 25803 } 25804 25805 return { 25806 trackBy: trackBy, 25807 getTrackByValue: getTrackByValue, 25808 getWatchables: $parse(valuesFn, function(values) { 25809 // Create a collection of things that we would like to watch (watchedArray) 25810 // so that they can all be watched using a single $watchCollection 25811 // that only runs the handler once if anything changes 25812 var watchedArray = []; 25813 values = values || []; 25814 25815 Object.keys(values).forEach(function getWatchable(key) { 25816 var locals = getLocals(values[key], key); 25817 var selectValue = getTrackByValueFn(values[key], locals); 25818 watchedArray.push(selectValue); 25819 25820 // Only need to watch the displayFn if there is a specific label expression 25821 if (match[2] || match[1]) { 25822 var label = displayFn(scope, locals); 25823 watchedArray.push(label); 25824 } 25825 25826 // Only need to watch the disableWhenFn if there is a specific disable expression 25827 if (match[4]) { 25828 var disableWhen = disableWhenFn(scope, locals); 25829 watchedArray.push(disableWhen); 25830 } 25831 }); 25832 return watchedArray; 25833 }), 25834 25835 getOptions: function() { 25836 25837 var optionItems = []; 25838 var selectValueMap = {}; 25839 25840 // The option values were already computed in the `getWatchables` fn, 25841 // which must have been called to trigger `getOptions` 25842 var optionValues = valuesFn(scope) || []; 25843 var optionValuesKeys; 25844 25845 25846 if (!keyName && isArrayLike(optionValues)) { 25847 optionValuesKeys = optionValues; 25848 } else { 25849 // if object, extract keys, in enumeration order, unsorted 25850 optionValuesKeys = []; 25851 for (var itemKey in optionValues) { 25852 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 25853 optionValuesKeys.push(itemKey); 25854 } 25855 } 25856 } 25857 25858 var optionValuesLength = optionValuesKeys.length; 25859 25860 for (var index = 0; index < optionValuesLength; index++) { 25861 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 25862 var value = optionValues[key]; 25863 var locals = getLocals(value, key); 25864 var viewValue = viewValueFn(scope, locals); 25865 var selectValue = getTrackByValueFn(viewValue, locals); 25866 var label = displayFn(scope, locals); 25867 var group = groupByFn(scope, locals); 25868 var disabled = disableWhenFn(scope, locals); 25869 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 25870 25871 optionItems.push(optionItem); 25872 selectValueMap[selectValue] = optionItem; 25873 } 25874 25875 return { 25876 items: optionItems, 25877 selectValueMap: selectValueMap, 25878 getOptionFromViewValue: function(value) { 25879 return selectValueMap[getTrackByValue(value)]; 25880 }, 25881 getViewValueFromOption: function(option) { 25882 // If the viewValue could be an object that may be mutated by the application, 25883 // we need to make a copy and not return the reference to the value on the option. 25884 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 25885 } 25886 }; 25887 } 25888 }; 25889 } 25890 25891 25892 // we can't just jqLite('<option>') since jqLite is not smart enough 25893 // to create it in <select> and IE barfs otherwise. 25894 var optionTemplate = document.createElement('option'), 25895 optGroupTemplate = document.createElement('optgroup'); 25896 25897 return { 25898 restrict: 'A', 25899 terminal: true, 25900 require: ['select', '?ngModel'], 25901 link: function(scope, selectElement, attr, ctrls) { 25902 25903 // if ngModel is not defined, we don't need to do anything 25904 var ngModelCtrl = ctrls[1]; 25905 if (!ngModelCtrl) return; 25906 25907 var selectCtrl = ctrls[0]; 25908 var multiple = attr.multiple; 25909 25910 // The emptyOption allows the application developer to provide their own custom "empty" 25911 // option when the viewValue does not match any of the option values. 25912 var emptyOption; 25913 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 25914 if (children[i].value === '') { 25915 emptyOption = children.eq(i); 25916 break; 25917 } 25918 } 25919 25920 var providedEmptyOption = !!emptyOption; 25921 25922 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 25923 unknownOption.val('?'); 25924 25925 var options; 25926 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 25927 25928 25929 var renderEmptyOption = function() { 25930 if (!providedEmptyOption) {
25931 selectElement.prepend(emptyOption); 25932 } 25933 selectElement.val(''); 25934 emptyOption.prop('selected', true); // needed for IE 25935 emptyOption.attr('selected', true); 25936 }; 25937 25938 var removeEmptyOption = function() { 25939 if (!providedEmptyOption) { 25940 emptyOption.remove(); 25941 } 25942 }; 25943 25944 25945 var renderUnknownOption = function() { 25946 selectElement.prepend(unknownOption); 25947 selectElement.val('?'); 25948 unknownOption.prop('selected', true); // needed for IE 25949 unknownOption.attr('selected', true); 25950 }; 25951 25952 var removeUnknownOption = function() { 25953 unknownOption.remove(); 25954 }; 25955 25956 25957 // Update the controller methods for multiple selectable options 25958 if (!multiple) { 25959 25960 selectCtrl.writeValue = function writeNgOptionsValue(value) { 25961 var option = options.getOptionFromViewValue(value); 25962 25963 if (option && !option.disabled) { 25964 if (selectElement[0].value !== option.selectValue) { 25965 removeUnknownOption(); 25966 removeEmptyOption(); 25967 25968 selectElement[0].value = option.selectValue; 25969 option.element.selected = true; 25970 option.element.setAttribute('selected', 'selected'); 25971 } 25972 } else { 25973 if (value === null || providedEmptyOption) { 25974 removeUnknownOption(); 25975 renderEmptyOption(); 25976 } else { 25977 removeEmptyOption(); 25978 renderUnknownOption(); 25979 } 25980 } 25981 }; 25982 25983 selectCtrl.readValue = function readNgOptionsValue() { 25984 25985 var selectedOption = options.selectValueMap[selectElement.val()]; 25986 25987 if (selectedOption && !selectedOption.disabled) { 25988 removeEmptyOption(); 25989 removeUnknownOption(); 25990 return options.getViewValueFromOption(selectedOption); 25991 } 25992 return null; 25993 }; 25994 25995 // If we are using `track by` then we must watch the tracked value on the model 25996 // since ngModel only watches for object identity change 25997 if (ngOptions.trackBy) { 25998 scope.$watch( 25999 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 26000 function() { ngModelCtrl.$render(); } 26001 ); 26002 } 26003 26004 } else { 26005 26006 ngModelCtrl.$isEmpty = function(value) { 26007 return !value || value.length === 0; 26008 }; 26009 26010 26011 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 26012 options.items.forEach(function(option) { 26013 option.element.selected = false; 26014 }); 26015 26016 if (value) { 26017 value.forEach(function(item) { 26018 var option = options.getOptionFromViewValue(item); 26019 if (option && !option.disabled) option.element.selected = true; 26020 }); 26021 } 26022 }; 26023 26024 26025 selectCtrl.readValue = function readNgOptionsMultiple() { 26026 var selectedValues = selectElement.val() || [], 26027 selections = []; 26028 26029 forEach(selectedValues, function(value) { 26030 var option = options.selectValueMap[value]; 26031 if (!option.disabled) selections.push(options.getViewValueFromOption(option)); 26032 }); 26033 26034 return selections; 26035 }; 26036 26037 // If we are using `track by` then we must watch these tracked values on the model 26038 // since ngModel only watches for object identity change 26039 if (ngOptions.trackBy) { 26040 26041 scope.$watchCollection(function() { 26042 if (isArray(ngModelCtrl.$viewValue)) { 26043 return ngModelCtrl.$viewValue.map(function(value) { 26044 return ngOptions.getTrackByValue(value); 26045 }); 26046 } 26047 }, function() { 26048 ngModelCtrl.$render(); 26049 }); 26050 26051 } 26052 } 26053 26054 26055 if (providedEmptyOption) { 26056 26057 // we need to remove it before calling selectElement.empty() because otherwise IE will 26058 // remove the label from the element. wtf? 26059 emptyOption.remove(); 26060 26061 // compile the element since there might be bindings in it 26062 $compile(emptyOption)(scope); 26063 26064 // remove the class, which is added automatically because we recompile the element and it 26065 // becomes the compilation root 26066 emptyOption.removeClass('ng-scope'); 26067 } else { 26068 emptyOption = jqLite(optionTemplate.cloneNode(false)); 26069 } 26070 26071 // We need to do this here to ensure that the options object is defined 26072 // when we first hit it in writeNgOptionsValue 26073 updateOptions(); 26074 26075 // We will re-render the option elements if the option values or labels change 26076 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 26077 26078 // ------------------------------------------------------------------ // 26079 26080 26081 function updateOptionElement(option, element) { 26082 option.element = element; 26083 element.disabled = option.disabled; 26084 if (option.value !== element.value) element.value = option.selectValue; 26085 if (option.label !== element.label) { 26086 element.label = option.label; 26087 element.textContent = option.label; 26088 } 26089 } 26090 26091 function addOrReuseElement(parent, current, type, templateElement) { 26092 var element; 26093 // Check whether we can reuse the next element 26094 if (current && lowercase(current.nodeName) === type) { 26095 // The next element is the right type so reuse it 26096 element = current; 26097 } else { 26098 // The next element is not the right type so create a new one 26099 element = templateElement.cloneNode(false); 26100 if (!current) { 26101 // There are no more elements so just append it to the select 26102 parent.appendChild(element); 26103 } else { 26104 // The next element is not a group so insert the new one 26105 parent.insertBefore(element, current); 26106 } 26107 } 26108 return element; 26109 } 26110 26111 26112 function removeExcessElements(current) { 26113 var next; 26114 while (current) { 26115 next = current.nextSibling; 26116 jqLiteRemove(current); 26117 current = next; 26118 } 26119 } 26120 26121 26122 function skipEmptyAndUnknownOptions(current) { 26123 var emptyOption_ = emptyOption && emptyOption[0]; 26124 var unknownOption_ = unknownOption && unknownOption[0]; 26125 26126 if (emptyOption_ || unknownOption_) { 26127 while (current && 26128 (current === emptyOption_ || 26129 current === unknownOption_)) { 26130 current = current.nextSibling; 26131 } 26132 } 26133 return current; 26134 } 26135 26136 26137 function updateOptions() { 26138 26139 var previousValue = options && selectCtrl.readValue(); 26140 26141 options = ngOptions.getOptions(); 26142 26143 var groupMap = {}; 26144 var currentElement = selectElement[0].firstChild; 26145 26146 // Ensure that the empty option is always there if it was explicitly provided 26147 if (providedEmptyOption) {
26148 selectElement.prepend(emptyOption); 26149 } 26150 26151 currentElement = skipEmptyAndUnknownOptions(currentElement); 26152 26153 options.items.forEach(function updateOption(option) { 26154 var group; 26155 var groupElement; 26156 var optionElement; 26157 26158 if (option.group) { 26159 26160 // This option is to live in a group 26161 // See if we have already created this group 26162 group = groupMap[option.group]; 26163 26164 if (!group) { 26165 26166 // We have not already created this group 26167 groupElement = addOrReuseElement(selectElement[0], 26168 currentElement, 26169 'optgroup', 26170 optGroupTemplate); 26171 // Move to the next element 26172 currentElement = groupElement.nextSibling; 26173 26174 // Update the label on the group element 26175 groupElement.label = option.group; 26176 26177 // Store it for use later 26178 group = groupMap[option.group] = { 26179 groupElement: groupElement, 26180 currentOptionElement: groupElement.firstChild 26181 }; 26182 26183 } 26184 26185 // So now we have a group for this option we add the option to the group 26186 optionElement = addOrReuseElement(group.groupElement, 26187 group.currentOptionElement, 26188 'option', 26189 optionTemplate); 26190 updateOptionElement(option, optionElement); 26191 // Move to the next element 26192 group.currentOptionElement = optionElement.nextSibling; 26193 26194 } else { 26195 26196 // This option is not in a group 26197 optionElement = addOrReuseElement(selectElement[0], 26198 currentElement, 26199 'option', 26200 optionTemplate); 26201 updateOptionElement(option, optionElement); 26202 // Move to the next element 26203 currentElement = optionElement.nextSibling; 26204 } 26205 }); 26206 26207 26208 // Now remove all excess options and group 26209 Object.keys(groupMap).forEach(function(key) { 26210 removeExcessElements(groupMap[key].currentOptionElement); 26211 }); 26212 removeExcessElements(currentElement); 26213 26214 ngModelCtrl.$render(); 26215 26216 // Check to see if the value has changed due to the update to the options 26217 if (!ngModelCtrl.$isEmpty(previousValue)) { 26218 var nextValue = selectCtrl.readValue(); 26219 if (ngOptions.trackBy && !equals(previousValue, nextValue) || 26220 previousValue !== nextValue) { 26221 ngModelCtrl.$setViewValue(nextValue); 26222 ngModelCtrl.$render(); 26223 } 26224 } 26225 26226 } 26227 26228 } 26229 }; 26230}]; 26231 26232/** 26233 * @ngdoc directive 26234 * @name ngPluralize 26235 * @restrict EA 26236 * 26237 * @description 26238 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 26239 * These rules are bundled with angular.js, but can be overridden 26240 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 26241 * by specifying the mappings between 26242 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 26243 * and the strings to be displayed. 26244 * 26245 * # Plural categories and explicit number rules 26246 * There are two 26247 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 26248 * in Angular's default en-US locale: "one" and "other". 26249 * 26250 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 26251 * any number that is not 1), an explicit number rule can only match one number. For example, the 26252 * explicit number rule for "3" matches the number 3. There are examples of plural categories 26253 * and explicit number rules throughout the rest of this documentation. 26254 * 26255 * # Configuring ngPluralize 26256 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 26257 * You can also provide an optional attribute, `offset`. 26258 * 26259 * The value of the `count` attribute can be either a string or an {@link guide/expression 26260 * Angular expression}; these are evaluated on the current scope for its bound value. 26261 * 26262 * The `when` attribute specifies the mappings between plural categories and the actual 26263 * string to be displayed. The value of the attribute should be a JSON object. 26264 * 26265 * The following example shows how to configure ngPluralize: 26266 * 26267 * ```html 26268 * <ng-pluralize count="personCount"
26269 when="{'0': 'Nobody is viewing.', 26270 * 'one': '1 person is viewing.', 26271 * 'other': '{} people are viewing.'}"> 26272 * </ng-pluralize> 26273 *``` 26274 * 26275 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 26276 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 26277 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 26278 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 26279 * show "a dozen people are viewing". 26280 * 26281 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 26282 * into pluralized strings. In the previous example, Angular will replace `{}` with 26283 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 26284 * for <span ng-non-bindable>{{numberExpression}}</span>. 26285 * 26286 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 26287 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 26288 * 26289 * # Configuring ngPluralize with offset 26290 * The `offset` attribute allows further customization of pluralized text, which can result in 26291 * a better user experience. For example, instead of the message "4 people are viewing this document", 26292 * you might display "John, Kate and 2 others are viewing this document". 26293 * The offset attribute allows you to offset a number by any desired value. 26294 * Let's take a look at an example: 26295 * 26296 * ```html 26297 * <ng-pluralize count="personCount" offset=2 26298 * when="{'0': 'Nobody is viewing.', 26299 * '1': '{{person1}} is viewing.', 26300 * '2': '{{person1}} and {{person2}} are viewing.', 26301 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 26302 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 26303 * </ng-pluralize> 26304 * ``` 26305 * 26306 * Notice that we are still using two plural categories(one, other), but we added 26307 * three explicit number rules 0, 1 and 2. 26308 * When one person, perhaps John, views the document, "John is viewing" will be shown. 26309 * When three people view the document, no explicit number rule is found, so 26310 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 26311 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 26312 * is shown. 26313 * 26314 * Note that when you specify offsets, you must provide explicit number rules for 26315 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 26316 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 26317 * plural categories "one" and "other". 26318 * 26319 * @param {string|expression} count The variable to be bound to. 26320 * @param {string} when The mapping between plural category to its corresponding strings. 26321 * @param {number=} offset Offset to deduct from the total number. 26322 * 26323 * @example 26324 <example module="pluralizeExampl
vendor: 4,913 bytes, lines 26324-26438
26324e"> 26325 <file name="index.html"> 26326 <script> 26327 angular.module('pluralizeExample', []) 26328 .controller('ExampleController', ['$scope', function($scope) { 26329 $scope.person1 = 'Igor'; 26330 $scope.person2 = 'Misko'; 26331 $scope.personCount = 1; 26332 }]); 26333 </script> 26334 <div ng-controller="ExampleController"> 26335 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 26336 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 26337 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 26338 26339 <!--- Example with simple pluralization rules for en locale ---> 26340 Without Offset: 26341 <ng-pluralize count="personCount" 26342 when="{'0': 'Nobody is viewing.', 26343 'one': '1 person is viewing.', 26344 'other': '{} people are viewing.'}"> 26345 </ng-pluralize><br> 26346 26347 <!--- Example with offset ---> 26348 With Offset(2): 26349 <ng-pluralize count="personCount" offset=2 26350 when="{'0': 'Nobody is viewing.', 26351 '1': '{{person1}} is viewing.', 26352 '2': '{{person1}} and {{person2}} are viewing.', 26353 'one': '{{person1}}, {{person2}} and one other person are viewing.', 26354 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 26355 </ng-pluralize> 26356 </div> 26357 </file> 26358 <file name="protractor.js" type="protractor"> 26359 it('should show correct pluralized string', function() { 26360 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 26361 var withOffset = element.all(by.css('ng-pluralize')).get(1); 26362 var countInput = element(by.model('personCount')); 26363 26364 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 26365 expect(withOffset.getText()).toEqual('Igor is viewing.'); 26366 26367 countInput.clear(); 26368 countInput.sendKeys('0'); 26369 26370 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 26371 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 26372 26373 countInput.clear(); 26374 countInput.sendKeys('2'); 26375 26376 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 26377 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 26378 26379 countInput.clear(); 26380 countInput.sendKeys('3'); 26381 26382 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 26383 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 26384 26385 countInput.clear(); 26386 countInput.sendKeys('4'); 26387 26388 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 26389 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 26390 }); 26391 it('should show data-bound names', function() { 26392 var withOffset = element.all(by.css('ng-pluralize')).get(1); 26393 var personCount = element(by.model('personCount')); 26394 var person1 = element(by.model('person1')); 26395 var person2 = element(by.model('person2')); 26396 personCount.clear(); 26397 personCount.sendKeys('4'); 26398 person1.clear(); 26399 person1.sendKeys('Di'); 26400 person2.clear(); 26401 person2.sendKeys('Vojta'); 26402 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 26403 }); 26404 </file> 26405 </example> 26406 */ 26407var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 26408 var BRACE = /{}/g, 26409 IS_WHEN = /^when(Minus)?(.+)$/; 26410 26411 return { 26412 link: function(scope, element, attr) { 26413 var numberExp = attr.count, 26414 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 26415 offset = attr.offset || 0, 26416 whens = scope.$eval(whenExp) || {}, 26417 whensExpFns = {}, 26418 startSymbol = $interpolate.startSymbol(), 26419 endSymbol = $interpolate.endSymbol(), 26420 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 26421 watchRemover = angular.noop, 26422 lastCount; 26423 26424 forEach(attr, function(expression, attributeName) { 26425 var tmpMatch = IS_WHEN.exec(attributeName); 26426 if (tmpMatch) { 26427 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 26428 whens[whenKey] = element.attr(attr.$attr[attributeName]); 26429 } 26430 }); 26431 forEach(whens, function(expression, key) { 26432 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 26433 26434 }); 26435 26436 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 26437 var count = parseFloat(newVal); 26438 var countIsNaN = isN
vendor: 10,458 bytes, lines 26438-26664
26438aN(count); 26439 26440 if (!countIsNaN && !(count in whens)) { 26441 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 26442 // Otherwise, check it against pluralization rules in $locale service. 26443 count = $locale.pluralCat(count - offset); 26444 } 26445 26446 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 26447 // In JS `NaN !== NaN`, so we have to exlicitly check. 26448 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 26449 watchRemover(); 26450 var whenExpFn = whensExpFns[count]; 26451 if (isUndefined(whenExpFn)) { 26452 if (newVal != null) { 26453 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 26454 } 26455 watchRemover = noop; 26456 updateElementText(); 26457 } else { 26458 watchRemover = scope.$watch(whenExpFn, updateElementText); 26459 } 26460 lastCount = count; 26461 } 26462 }); 26463 26464 function updateElementText(newText) { 26465 element.text(newText || ''); 26466 } 26467 } 26468 }; 26469}]; 26470 26471/** 26472 * @ngdoc directive 26473 * @name ngRepeat 26474 * 26475 * @description 26476 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 26477 * instance gets its own scope, where the given loop variable is set to the current collection item, 26478 * and `$index` is set to the item index or key. 26479 * 26480 * Special properties are exposed on the local scope of each template instance, including: 26481 * 26482 * | Variable | Type | Details | 26483 * |-----------|-----------------|-----------------------------------------------------------------------------| 26484 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 26485 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 26486 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 26487 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 26488 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 26489 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 26490 * 26491 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 26492 * This may be useful when, for instance, nesting ngRepeats. 26493 * 26494 * 26495 * # Iterating over object properties 26496 * 26497 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 26498 * syntax: 26499 * 26500 * ```js 26501 * <div ng-repeat="(key, value) in myObj"> ... </div> 26502 * ``` 26503 * 26504 * You need to be aware that the JavaScript specification does not define the order of keys 26505 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive 26506 * used to sort the keys alphabetically.) 26507 * 26508 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser 26509 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing 26510 * keys in the order in which they were defined, although there are exceptions when keys are deleted 26511 * and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues 26512 * 26513 * If this is not desired, the recommended workaround is to convert your object into an array 26514 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 26515 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 26516 * or implement a `$watch` on the object yourself. 26517 * 26518 * 26519 * # Tracking and Duplicates 26520 * 26521 * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM: 26522 * 26523 * * When an item is added, a new instance of the template is added to the DOM. 26524 * * When an item is removed, its template instance is removed from the DOM. 26525 * * When items are reordered, their respective templates are reordered in the DOM. 26526 * 26527 * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when 26528 * there are duplicates, it is not possible to maintain a one-to-one mapping between collection 26529 * items and DOM elements. 26530 * 26531 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 26532 * with your own using the `track by` expression. 26533 * 26534 * For example, you may track items by the index of each item in the collection, using the 26535 * special scope property `$index`: 26536 * ```html 26537 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 26538 * {{n}} 26539 * </div> 26540 * ``` 26541 * 26542 * You may use arbitrary expressions in `track by`, including references to custom functions 26543 * on the scope: 26544 * ```html 26545 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 26546 * {{n}} 26547 * </div> 26548 * ``` 26549 * 26550 * If you are working with objects that have an identifier property, you can track 26551 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 26552 * will not have to rebuild the DOM elements for items it has already rendered, even if the 26553 * JavaScript objects in the collection have been substituted for new ones: 26554 * ```html 26555 * <div ng-repeat="model in collection track by model.id"> 26556 * {{model.name}} 26557 * </div> 26558 * ``` 26559 * 26560 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 26561 * `$id` function, which tracks items by their identity: 26562 * ```html 26563 * <div ng-repeat="obj in collection track by $id(obj)"> 26564 * {{obj.prop}} 26565 * </div> 26566 * ``` 26567 * 26568 * # Special repeat start and end points 26569 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 26570 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 26571 * 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) 26572 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 26573 * 26574 * The example below makes use of this feature: 26575 * ```html 26576 * <header ng-repeat-start="item in items"> 26577 * Header {{ item }} 26578 * </header> 26579 * <div class="body"> 26580 * Body {{ item }} 26581 * </div> 26582 * <footer ng-repeat-end> 26583 * Footer {{ item }} 26584 * </footer> 26585 * ``` 26586 * 26587 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 26588 * ```html 26589 * <header> 26590 * Header A 26591 * </header> 26592 * <div class="body"> 26593 * Body A 26594 * </div> 26595 * <footer> 26596 * Footer A 26597 * </footer> 26598 * <header> 26599 * Header B 26600 * </header> 26601 * <div class="body"> 26602 * Body B 26603 * </div> 26604 * <footer> 26605 * Footer B 26606 * </footer> 26607 * ``` 26608 * 26609 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 26610 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 26611 * 26612 * @animations 26613 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 26614 * 26615 * **.leave** - when an item is removed from the list or when an item is filtered out 26616 * 26617 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 26618 * 26619 * @element ANY 26620 * @scope 26621 * @priority 1000 26622 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 26623 * formats are currently supported: 26624 * 26625 * * `variable in expression` â where variable is the user defined loop variable and `expression` 26626 * is a scope expression giving the collection to enumerate. 26627 * 26628 * For example: `album in artist.albums`. 26629 * 26630 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 26631 * and `expression` is the scope expression giving the collection to enumerate. 26632 * 26633 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 26634 * 26635 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 26636 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 26637 * is specified, ng-repeat associates elements by identity. It is an error to have 26638 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 26639 * mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be 26640 * applied before the tracking expression. 26641 * 26642 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 26643 * will be associated by item identity in the array. 26644 * 26645 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 26646 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 26647 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 26648 * element in the same way in the DOM. 26649 * 26650 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 26651 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 26652 * property is same. 26653 * 26654 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 26655 * to items in conjunction with a tracking expression. 26656 * 26657 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 26658 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 26659 * when a filter is active on the repeater, but the filtered result set is empty. 26660 * 26661 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 26662 * the items have been processed through the filter. 26663 * 26664 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax s
26664o it can be used only at the end 26665 * (and not as operator, inside an expression). 26666 * 26667 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 26668 * 26669 * @example 26670 * This example initializes the scope to a list of names and 26671 * then uses `ngRepeat` to display every person: 26672 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 26673 <file name="index.html"> 26674 <div ng-init="friends = [ 26675 {name:'John', age:25, gender:'boy'}, 26676 {name:'Jessie', age:30, gender:'girl'}, 26677 {name:'Johanna', age:28, gender:'girl'}, 26678 {name:'Joy', age:15, gender:'girl'}, 26679 {name:'Mary', age:28, gender:'girl'}, 26680 {name:'Peter', age:95, gender:'boy'}, 26681 {name:'Sebastian', age:50, gender:'boy'}, 26682 {name:'Erika', age:27, gender:'girl'}, 26683 {name:'Patrick', age:40, gender:'boy'}, 26684 {name:'Samantha', age:60, gender:'girl'} 26685 ]"> 26686 I have {{friends.length}} friends. They are: 26687 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 26688 <ul class="example-animate-container"> 26689 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 26690 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 26691 </li> 26692 <li class="animate-repeat" ng-if="results.length == 0"> 26693 <strong>No results found...</strong> 26694 </li> 26695 </ul> 26696 </div> 26697 </file> 26698 <file name="animations.css"> 26699 .example-animate-container { 26700 background:white; 26701 border:1px solid black; 26702 list-style:none; 26703 margin:0; 26704 padding:0 10px; 26705 } 26706 26707 .animate-repeat { 26708 line-height:40px; 26709 list-style:none; 26710 box-sizing:border-box; 26711 } 26712 26713 .animate-repeat.ng-move, 26714 .animate-repeat.ng-enter, 26715 .animate-repeat.ng-leave { 26716 -webkit-transition:all linear 0.5s; 26717 transition:all linear 0.5s; 26718 } 26719 26720 .animate-repeat.ng-leave.ng-leave-active, 26721 .animate-repeat.ng-move, 26722 .animate-repeat.ng-enter { 26723 opacity:0; 26724 max-height:0; 26725 } 26726 26727 .animate-repeat.ng-leave, 26728 .animate-repeat.ng-move.ng-move-active, 26729 .animate-repeat.ng-enter.ng-enter-active { 26730 opacity:1; 26731 max-height:40px; 26732 } 26733 </file> 26734 <file name="protractor.js" type="protractor"> 26735 var friends = element.all(by.repeater('friend in friends')); 26736 26737 it('should render initial data set', function() { 26738 expect(friends.count()).toBe(10); 26739 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 26740 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 26741 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 26742 expect(element(by.binding('friends.length')).getText()) 26743 .toMatch("I have 10 friends. They are:"); 26744 }); 26745 26746 it('should update repeater when filter predicate changes', function() { 26747 expect(friends.count()).toBe(10); 26748 26749 element(by.model('q')).sendKeys('ma'); 26750 26751 expect(friends.count()).toBe(2); 26752 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 26753 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 26754 }); 26755 </file> 26756 </example> 26757 */ 26758var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 26759 var NG_REMOVED = '$$NG_REMOVED'; 26760 var ngRepeatMinErr = minErr('ngRepeat'); 26761 26762 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 26763 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 26764 scope[valueIdentifier] = value; 26765 if (keyIdentifier) scope[keyIdentifier] = key; 26766 scope.$index = index; 26767 scope.$first = (index === 0); 26768 scope.$last = (index === (arrayLength - 1)); 26769 scope.$middle = !(scope.$first || scope.$last); 26770 // jshint bitwise: false 26771 scope.$odd = !(scope.$even = (index&1) === 0); 26772 // jshint bitwise: true 26773 }; 26774 26775 var getBlockStart = function(block) { 26776 return block.clone[0]; 26777 }; 26778 26779 var getBlockEnd = function(block) { 26780 return block.clone[block.clone.length - 1]; 26781 }; 26782 26783 26784 return { 26785 restrict: 'A', 26786 multiElement: true, 26787 transclude: 'element', 26788 priority: 1000, 26789 terminal: true, 26790 $$tlb: true, 26791 compile: function ngRepeatCompile($element, $attr) { 26792 var expression = $attr.ngRepeat; 26793 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); 26794 26795 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*$/); 26796 26797 if (!match) { 26798 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 26799 expression); 26800 } 26801 26802 var lhs = match[1]; 26803 var rhs = match[2]; 26804 var aliasAs = match[3]; 26805 var trackByExp = match[4]; 26806 26807 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 26808 26809 if (!match) { 26810 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 26811 lhs); 26812 } 26813 var valueIdentifier = match[3] || match[1]; 26814 var keyIdentifier = match[2]; 26815 26816 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 26817 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 26818 throw ngRepeatMinErr('badident', "alias '{0}
vendor: 11,910 bytes, lines 26818-27089
26818' is invalid --- must be a valid JS identifier which is not a reserved name.", 26819 aliasAs); 26820 } 26821 26822 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 26823 var hashFnLocals = {$id: hashKey}; 26824 26825 if (trackByExp) { 26826 trackByExpGetter = $parse(trackByExp); 26827 } else { 26828 trackByIdArrayFn = function(key, value) { 26829 return hashKey(value); 26830 }; 26831 trackByIdObjFn = function(key) { 26832 return key; 26833 }; 26834 } 26835 26836 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 26837 26838 if (trackByExpGetter) { 26839 trackByIdExpFn = function(key, value, index) { 26840 // assign key, value, and $index to the locals so that they can be used in hash functions 26841 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 26842 hashFnLocals[valueIdentifier] = value; 26843 hashFnLocals.$index = index; 26844 return trackByExpGetter($scope, hashFnLocals); 26845 }; 26846 } 26847 26848 // Store a list of elements from previous run. This is a hash where key is the item from the 26849 // iterator, and the value is objects with following properties. 26850 // - scope: bound scope 26851 // - element: previous element. 26852 // - index: position 26853 // 26854 // We are using no-proto object so that we don't need to guard against inherited props via 26855 // hasOwnProperty. 26856 var lastBlockMap = createMap(); 26857 26858 //watch props 26859 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 26860 var index, length, 26861 previousNode = $element[0], // node that cloned nodes should be inserted after 26862 // initialized to the comment node anchor 26863 nextNode, 26864 // Same as lastBlockMap but it has the current state. It will become the 26865 // lastBlockMap on the next iteration. 26866 nextBlockMap = createMap(), 26867 collectionLength, 26868 key, value, // key/value of iteration 26869 trackById, 26870 trackByIdFn, 26871 collectionKeys, 26872 block, // last object information {scope, element, id} 26873 nextBlockOrder, 26874 elementsToRemove; 26875 26876 if (aliasAs) { 26877 $scope[aliasAs] = collection; 26878 } 26879 26880 if (isArrayLike(collection)) { 26881 collectionKeys = collection; 26882 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 26883 } else { 26884 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 26885 // if object, extract keys, in enumeration order, unsorted 26886 collectionKeys = []; 26887 for (var itemKey in collection) { 26888 if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 26889 collectionKeys.push(itemKey); 26890 } 26891 } 26892 } 26893 26894 collectionLength = collectionKeys.length; 26895 nextBlockOrder = new Array(collectionLength); 26896 26897 // locate existing items 26898 for (index = 0; index < collectionLength; index++) { 26899 key = (collection === collectionKeys) ? index : collectionKeys[index]; 26900 value = collection[key]; 26901 trackById = trackByIdFn(key, value, index); 26902 if (lastBlockMap[trackById]) { 26903 // found previously seen block 26904 block = lastBlockMap[trackById]; 26905 delete lastBlockMap[trackById]; 26906 nextBlockMap[trackById] = block; 26907 nextBlockOrder[index] = block; 26908 } else if (nextBlockMap[trackById]) { 26909 // if collision detected. restore lastBlockMap and throw an error 26910 forEach(nextBlockOrder, function(block) { 26911 if (block && block.scope) lastBlockMap[block.id] = block; 26912 }); 26913 throw ngRepeatMinErr('dupes', 26914 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 26915 expression, trackById, value); 26916 } else { 26917 // new never before seen block 26918 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 26919 nextBlockMap[trackById] = true; 26920 } 26921 } 26922 26923 // remove leftover items 26924 for (var blockKey in lastBlockMap) { 26925 block = lastBlockMap[blockKey]; 26926 elementsToRemove = getBlockNodes(block.clone); 26927 $animate.leave(elementsToRemove); 26928 if (elementsToRemove[0].parentNode) { 26929 // if the element was not removed yet because of pending animation, mark it as deleted 26930 // so that we can ignore it later 26931 for (index = 0, length = elementsToRemove.length; index < length; index++) { 26932 elementsToRemove[index][NG_REMOVED] = true; 26933 } 26934 } 26935 block.scope.$destroy(); 26936 } 26937 26938 // we are not using forEach for perf reasons (trying to avoid #call) 26939 for (index = 0; index < collectionLength; index++) { 26940 key = (collection === collectionKeys) ? index : collectionKeys[index]; 26941 value = collection[key]; 26942 block = nextBlockOrder[index]; 26943 26944 if (block.scope) { 26945 // if we have already seen this object, then we need to reuse the 26946 // associated scope/element 26947 26948 nextNode = previousNode; 26949 26950 // skip nodes that are already pending removal via leave animation 26951 do { 26952 nextNode = nextNode.nextSibling; 26953 } while (nextNode && nextNode[NG_REMOVED]); 26954 26955 if (getBlockStart(block) != nextNode) { 26956 // existing item which got moved 26957 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); 26958 } 26959 previousNode = getBlockEnd(block); 26960 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 26961 } else { 26962 // new item which we don't know about 26963 $transclude(function ngRepeatTransclude(clone, scope) { 26964 block.scope = scope; 26965 // http://jsperf.com/clone-vs-createcomment 26966 var endNode = ngRepeatEndComment.cloneNode(false); 26967 clone[clone.length++] = endNode; 26968 26969 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? 26970 $animate.enter(clone, null, jqLite(previousNode)); 26971 previousNode = endNode; 26972 // Note: We only need the first/last node of the cloned nodes. 26973 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 26974 // by a directive with templateUrl when its template arrives. 26975 block.clone = clone; 26976 nextBlockMap[block.id] = block; 26977 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 26978 }); 26979 } 26980 } 26981 lastBlockMap = nextBlockMap; 26982 }); 26983 }; 26984 } 26985 }; 26986}]; 26987 26988var NG_HIDE_CLASS = 'ng-hide'; 26989var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 26990/** 26991 * @ngdoc directive 26992 * @name ngShow 26993 * 26994 * @description 26995 * The `ngShow` directive shows or hides the given HTML element based on the expression 26996 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 26997 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 26998 * in AngularJS and sets the display style to none (using an !important flag). 26999 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27000 * 27001 * ```html 27002 * <!-- when $scope.myValue is truthy (element is visible) --> 27003 * <div ng-show="myValue"></div> 27004 * 27005 * <!-- when $scope.myValue is falsy (element is hidden) --> 27006 * <div ng-show="myValue" class="ng-hide"></div> 27007 * ``` 27008 * 27009 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 27010 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 27011 * from the element causing the element not to appear hidden. 27012 * 27013 * ## Why is !important used? 27014 * 27015 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27016 * can be easily overridden by heavier selectors. For example, something as simple 27017 * as changing the display style on a HTML list item would make hidden elements appear visible. 27018 * This also becomes a bigger issue when dealing with CSS frameworks. 27019 * 27020 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27021 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27022 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27023 * 27024 * ### Overriding `.ng-hide` 27025 * 27026 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27027 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27028 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 27029 * with extra animation classes that can be added. 27030 * 27031 * ```css 27032 * .ng-hide:not(.ng-hide-animate) { 27033 * /* this is just another form of hiding an element */ 27034 * display: block!important; 27035 * position: absolute; 27036 * top: -9999px; 27037 * left: -9999px; 27038 * } 27039 * ``` 27040 * 27041 * By default you don't need to override in CSS anything and the animations will work around the display style. 27042 * 27043 * ## A note about animations with `ngShow` 27044 * 27045 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 27046 * is true and false. This system works like the animation system present with ngClass except that 27047 * you must also include the !important flag to override the display property 27048 * so that you can perform an animation when the element is hidden during the time of the animation. 27049 * 27050 * ```css 27051 * // 27052 * //a working example can be found at the bottom of this page 27053 * // 27054 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 27055 * /* this is required as of 1.3x to properly 27056 * apply all styling in a show/hide animation */ 27057 * transition: 0s linear all; 27058 * } 27059 * 27060 * .my-element.ng-hide-add-active, 27061 * .my-element.ng-hide-remove-active { 27062 * /* the transition is defined in the active class */ 27063 * transition: 1s linear all; 27064 * } 27065 * 27066 * .my-element.ng-hide-add { ... } 27067 * .my-element.ng-hide-add.ng-hide-add-active { ... } 27068 * .my-element.ng-hide-remove { ... } 27069 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 27070 * ``` 27071 * 27072 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 27073 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 27074 * 27075 * @animations 27076 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible 27077 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden 27078 * 27079 * @element ANY 27080 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 27081 * then the element is shown or hidden respectively. 27082 * 27083 * @example 27084 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27085 <file name="index.html"> 27086 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 27087 <div> 27088 Show: 27089 <div class="check-element animate-show" ng-show="checked">
27090 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 27091 </div> 27092 </div> 27093 <div> 27094 Hide: 27095 <div class="check-element animate-show" ng-hide="checked"> 27096 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 27097 </div> 27098 </div> 27099 </file> 27100 <file name="glyphicons.css"> 27101 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 27102 </file> 27103 <file name="animations.css"> 27104 .animate-show { 27105 line-height: 20px; 27106 opacity: 1; 27107 padding: 10px; 27108 border: 1px solid black; 27109 background: white; 27110 } 27111 27112 .animate-show.ng-hide-add.ng-hide-add-active, 27113 .animate-show.ng-hide-remove.ng-hide-remove-active { 27114 -webkit-transition: all linear 0.5s; 27115 transition: all linear 0.5s; 27116 } 27117 27118 .animate-show.ng-hide { 27119 line-height: 0; 27120 opacity: 0; 27121 padding: 0 10px; 27122 } 27123 27124 .check-element { 27125 padding: 10px; 27126 border: 1px solid black; 27127 background: white; 27128 } 27129 </file> 27130 <file name="protractor.js" type="protractor"> 27131 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 27132 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 27133 27134 it('should check ng-show / ng-hide', function() { 27135 expect(thumbsUp.isDisplayed()).toBeFalsy(); 27136 expect(thumbsDown.isDisplayed()).toBeTruthy(); 27137 27138 element(by.model('checked')).click(); 27139 27140 expect(thumbsUp.isDisplayed()).toBeTruthy(); 27141 expect(thumbsDown.isDisplayed()).toBeFalsy(); 27142 }); 27143 </file> 27144 </example> 27145 */ 27146var ngShowDirective = ['$animate', function($animate) { 27147 return { 27148 restrict: 'A', 27149 multiElement: true, 27150 link: function(scope, element, attr) { 27151 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 27152 // we're adding a temporary, animation-specific class for ng-hide since this way 27153 // we can control when the element is actually displayed on screen without having 27154 // to have a global/greedy CSS selector that breaks when other animations are run. 27155 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 27156 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 27157 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 27158 }); 27159 }); 27160 } 27161 }; 27162}]; 27163 27164 27165/** 27166 * @ngdoc directive 27167 * @name ngHide 27168 * 27169 * @description 27170 * The `ngHide` directive shows or hides the given HTML element based on the expression 27171 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 27172 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 27173 * in AngularJS and sets the display style to none (using an !important flag). 27174 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27175 * 27176 * ```html 27177 * <!-- when $scope.myValue is truthy (element is hidden) --> 27178 * <div ng-hide="myValue" class="ng-hide"></div> 27179 * 27180 * <!-- when $scope.myValue is falsy (element is visible) --> 27181 * <div ng-hide="myValue"></div> 27182 * ``` 27183 * 27184 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 27185 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 27186 * from the element causing the element not to appear hidden. 27187 * 27188 * ## Why is !important used? 27189 * 27190 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27191 * can be easily overridden by heavier selectors. For example, something as simple 27192 * as changing the display style on a HTML list item would make hidden elements appear visible. 27193 * This also becomes a bigger issue when dealing with CSS frameworks. 27194 * 27195 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27196 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27197 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27198 * 27199 * ### Overriding `.ng-hide` 27200 * 27201 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27202 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27203 * class in CSS: 27204 * 27205 * ```css 27206 * .ng-hide { 27207 * /* this is just another form of hiding an element */ 27208 * display: block!important; 27209 * position: absolute; 27210 * top: -9999px; 27211 * left: -9999px; 27212 * } 27213 * ``` 27214 * 27215 * By default you don't need to override in CSS anything and the animations will work around the display style. 27216 * 27217 * ## A note about animations with `ngHide` 27218 * 27219 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 27220 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 27221 * CSS class is added and removed for you instead of your own CSS class. 27222 * 27223 * ```css 27224 * // 27225 * //a working example can be found at the bottom of this page 27226 * // 27227 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 27228 * transition: 0.5s linear all; 27229 * } 27230 * 27231 * .my-element.ng-hide-add { ... } 27232 * .my-element.ng-hide-add.ng-hide-add-active { ... } 27233 * .my-element.ng-hide-remove { ... } 27234 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 27235 * ``` 27236 * 27237 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 27238 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 27239 * 27240 * @animations 27241 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden 27242 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible 27243 * 27244 * @element ANY 27245 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 27246 * the element is shown or hidden respectively. 27247 * 27248 * @example 27249 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27250 <file name="index.html"> 27251 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 27252 <div> 27253 Show: 27254 <div class="check-element animate-hide" ng-show="checked">
27255 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 27256 </div> 27257 </div> 27258 <div> 27259 Hide: 27260 <div class="check-element animate-hide" ng-hide="checked"> 27261 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 27262 </div> 27263 </div> 27264 </file> 27265 <file name="glyphicons.css"> 27266 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 27267 </file> 27268 <file name="animations.css"> 27269 .animate-hide { 27270 -webkit-transition: all linear 0.5s; 27271 transition: all linear 0.5s; 27272 line-height: 20px; 27273 opacity: 1; 27274 padding: 10px; 27275 border: 1px solid black; 27276 background: white; 27277 } 27278 27279 .animate-hide.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 ngHideDirective = ['$animate', function($animate) { 27308 return { 27309 restrict: 'A', 27310 multiElement: true, 27311 link: function(scope, element, attr) { 27312 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 27313 // The comment inside of the ngShowDirective explains why we add and 27314 // remove a temporary class for the show/hide animation 27315 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 27316 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 27317 }); 27318 }); 27319 } 27320 }; 27321}]; 27322 27323/** 27324 * @ngdoc directive 27325 * @name ngStyle 27326 * @restrict AC 27327 * 27328 * @description 27329 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 27330 * 27331 * @element ANY 27332 * @param {expression} ngStyle 27333 * 27334 * {@link guide/expression Expression} which evals to an 27335 * object whose keys are CSS style names and values are corresponding values for those CSS 27336 * keys. 27337 * 27338 * Since some CSS style names are not valid keys for an object, they must be quoted. 27339 * See the 'background-color' style in the example below. 27340 * 27341 * @example 27342 <example> 27343 <file name="index.html"> 27344 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 27345 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 27346 <input type="button" value="clear" ng-click="myStyle={}"> 27347 <br/> 27348 <span ng-style="myStyle">Sample Text</span> 27349 <pre>myStyle={{myStyle}}</pre> 27350 </file> 27351 <file name="style.css"> 27352 span { 27353 color: black; 27354 } 27355 </file> 27356 <file name="protractor.js" type="protractor"> 27357 var colorSpan = element(by.css('span')); 27358 27359 it('should check ng-style', function() { 27360 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 27361 element(by.css('input[value=\'set color\']')).click(); 27362 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 27363 element(by.css('input[value=clear]')).click(); 27364 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 27365 }); 27366 </file> 27367 </example> 27368 */ 27369var ngStyleDirective = ngDirective(function(scope, element, attr) { 27370 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 27371 if (oldStyles && (newStyles !== oldStyles)) { 27372 forEach(oldStyles, function(val, style) { element.css(style, '');}); 27373 } 27374 if (newStyles) element.css(newStyles); 27375 }, true); 27376}); 27377 27378/** 27379 * @ngdoc directive 27380 * @name ngSwitch 27381 * @restrict EA 27382 * 27383 * @description 27384 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 27385 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 27386 * as specified in the template. 27387 * 27388 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 27389 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 27390 * matches the value obtained from the evaluated expression. In other words, you define a container element 27391 * (where you place the directive), place an expression on the **`on="..."` attribute** 27392 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 27393 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 27394 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 27395 * attribute is displayed. 27396 * 27397 * <div class="alert alert-info"> 27398 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 27399 * as literal string values to match against. 27400 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
27400omeVal"` not against the 27401 * value of the expression `$scope.someVal`. 27402 * </div> 27403 27404 * @animations 27405 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 27406 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 27407 * 27408 * @usage 27409 * 27410 * ``` 27411 * <ANY ng-switch="expression"> 27412 * <ANY ng-switch-when="matchValue1">...</ANY> 27413 * <ANY ng-switch-when="matchValue2">...</ANY> 27414 * <ANY ng-switch-default>...</ANY> 27415 * </ANY> 27416 * ``` 27417 * 27418 * 27419 * @scope 27420 * @priority 1200 27421 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 27422 * On child elements add: 27423 * 27424 * * `ngSwitchWhen`: the case statement to match against. If match then this 27425 * case will be displayed. If the same match appears multiple times, all the 27426 * elements will be displayed. 27427 * * `ngSwitchDefault`: the default case when no other case match. If there 27428 * are multiple default cases, all of them will be displayed when no other 27429 * case match. 27430 * 27431 * 27432 * @example 27433 <example module="switchExample" deps="angular-animate.js" animations="true"> 27434 <file name="index.html">
27435 <div ng-controller="ExampleController"> 27436 <select ng-model="selection" ng-options="item for item in items"> 27437 </select> 27438 <code>selection={{selection}}</code> 27439 <hr/> 27440 <div class="animate-switch-container" 27441 ng-switch on="selection"> 27442 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 27443 <div class="animate-switch" ng-switch-when="home">Home Span</div> 27444 <div class="animate-switch" ng-switch-default>default</div> 27445 </div> 27446 </div> 27447 </file> 27448 <file name="script.js"> 27449 angular.module('switchExample', ['ngAnimate']) 27450 .controller('ExampleController', ['$scope', function($scope) { 27451 $scope.items = ['settings', 'home', 'other']; 27452 $scope.selection = $scope.items[0]; 27453 }]); 27454 </file> 27455 <file name="animations.css"> 27456 .animate-switch-container { 27457 position:relative; 27458 background:white; 27459 border:1px solid black; 27460 height:40px; 27461 overflow:hidden; 27462 } 27463 27464 .animate-switch { 27465 padding:10px; 27466 } 27467 27468 .animate-switch.ng-animate { 27469 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 27470 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 27471 27472 position:absolute; 27473 top:0; 27474 left:0; 27475 right:0; 27476 bottom:0; 27477 } 27478 27479 .animate-switch.ng-leave.ng-leave-active, 27480 .animate-switch.ng-enter { 27481 top:-50px; 27482 } 27483 .animate-switch.ng-leave, 27484 .animate-switch.ng-enter.ng-enter-active { 27485 top:0; 27486 } 27487 </file> 27488 <file name="protractor.js" type="protractor"> 27489 var switchElem = element(by.css('[ng-switch]')); 27490 var select = element(by.model('selection')); 27491 27492 it('should start in settings', function() { 27493 expect(switchElem.getText()).toMatch(/Settings Div/); 27494 }); 27495 it('should change to home', function() { 27496 select.all(by.css('option')).get(1).click(); 27497 expect(switchElem.getText()).toMatch(/Home Span/); 27498 }); 27499 it('should select default', function() { 27500 select.all(by.css('option')).get(2).click(); 27501 expect(switchElem.getText()).toMatch(/default/); 27502 }); 27503 </file> 27504 </example> 27505 */ 27506var ngSwitchDirective = ['$animate', function($animate) { 27507 return { 27508 require: 'ngSwitch', 27509 27510 // asks for $scope to fool the BC controller module 27511 controller: ['$scope', function ngSwitchController() { 27512 this.cases = {}; 27513 }], 27514 link: function(scope, element, attr, ngSwitchController) { 27515 var watchExpr = attr.ngSwitch || attr.on, 27516 selectedTranscludes = [], 27517 selectedElements = [], 27518 previousLeaveAnimations = [], 27519 selectedScopes = []; 27520 27521 var spliceFactory = function(array, index) { 27522 return function() { array.splice(index, 1); }; 27523 }; 27524 27525 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 27526 var i, ii; 27527 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 27528 $animate.cancel(previousLeaveAnimations[i]); 27529 } 27530 previousLeaveAnimations.length = 0; 27531 27532 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 27533 var selected = getBlockNodes(selectedElements[i].clone); 27534 selectedScopes[i].$destroy(); 27535 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 27536 promise.then(spliceFactory(previousLeaveAnimations, i)); 27537 } 27538 27539 selectedElements.length = 0; 27540 selectedScopes.length = 0; 27541 27542 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 27543 forEach(selectedTranscludes, function(selectedTransclude) { 27544 selectedTransclude.transclude(function(caseElement, selectedScope) { 27545 selectedScopes.push(selectedScope); 27546 var anchor = selectedTransclude.element; 27547 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); 27548 var block = { clone: caseElement }; 27549 27550 selectedElements.push(block);
27551 $animate.enter(caseElement, anchor.parent(), anchor); 27552 }); 27553 }); 27554 } 27555 }); 27556 } 27557 }; 27558}]; 27559 27560var ngSwitchWhenDirective = ngDirective({ 27561 transclude: 'element', 27562 priority: 1200, 27563 require: '^ngSwitch', 27564 multiElement: true, 27565 link: function(scope, element, attrs, ctrl, $transclude) { 27566 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 27567 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 27568 } 27569}); 27570 27571var ngSwitchDefaultDirective = ngDirective({ 27572 transclude: 'element', 27573 priority: 1200, 27574 require: '^ngSwitch', 27575 multiElement: true, 27576 link: function(scope, element, attr, ctrl, $transclude) { 27577 ctrl.cases['?'] = (ctrl.cases['?'] || []); 27578 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 27579 } 27580}); 27581 27582/** 27583 * @ngdoc directive 27584 * @name ngTransclude 27585 * @restrict EAC 27586 * 27587 * @description 27588 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 27589 * 27590 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 27591 * 27592 * @element ANY 27593 * 27594 * @example 27595 <example module="transcludeExample"> 27596 <file name="index.html"> 27597 <script> 27598 angular.module('transcludeExample', []) 27599 .directive('pane', function(){ 27600 return { 27601 restrict: 'E', 27602 transclude: true, 27603 scope: { title:'@' }, 27604 template: '<div style="border: 1px solid black;">' + 27605 '<div style="background-color: gray">{{title}}</div>' + 27606 '<ng-transclude></ng-transclude>' + 27607 '</div>' 27608 }; 27609 }) 27610 .controller('ExampleController', ['$scope', function($scope) { 27611 $scope.title = 'Lorem Ipsum'; 27612 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 27613 }]); 27614 </script>
27615 <div ng-controller="ExampleController"> 27616 <input ng-model="title" aria-label="title"> <br/> 27617 <textarea ng-model="text" aria-label="text"></textarea> <br/> 27618 <pane title="{{title}}">{{text}}</pane> 27619 </div> 27620 </file> 27621 <file name="protractor.js" type="protractor"> 27622 it('should have transcluded', function() { 27623 var titleElement = element(by.model('title')); 27624 titleElement.clear(); 27625 titleElement.sendKeys('TITLE'); 27626 var textElement = element(by.model('text')); 27627 textElement.clear(); 27628 textElement.sendKeys('TEXT'); 27629 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 27630 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 27631 }); 27632 </file> 27633 </example> 27634 * 27635 */ 27636var ngTranscludeDirective = ngDirective({ 27637 restrict: 'EAC', 27638 link: function($scope, $element, $attrs, controller, $transclude) { 27639 if (!$transclude) { 27640 throw minErr('ngTransclude')('orphan', 27641 'Illegal use of ngTransclude directive in the template! ' + 27642 'No parent directive that requires a transclusion found. ' + 27643 'Element: {0}', 27644 startingTag($element)); 27645 } 27646 27647 $transclude(function(clone) { 27648 $element.empty(); 27649 $element.append(clone); 27650 }); 27651 } 27652}); 27653 27654/** 27655 * @ngdoc directive 27656 * @name script 27657 * @restrict E 27658 * 27659 * @description 27660 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 27661 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 27662 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 27663 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 27664 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 27665 * 27666 * @param {string} type Must be set to `'text/ng-template'`. 27667 * @param {string} id Cache name of the template. 27668 * 27669 * @example 27670 <example> 27671 <file name="index.html"> 27672 <script type="text/ng-template" id="/tpl.html"> 27673 Content of the template. 27674 </script> 27675 27676 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 27677 <div id="tpl-content" ng-include src="currentTpl"></div> 27678 </file> 27679 <file name="protractor.js" type="protractor"> 27680 it('should load template defined inside script tag', function() { 27681 element(by.css('#tpl-link')).click(); 27682 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 27683 }); 27684 </file> 27685 </example> 27686 */ 27687var scriptDirective = ['$templateCache', function($templateCache) { 27688 return { 27689 restrict: 'E', 27690 terminal: true, 27691 compile: function(element, attr) { 27692 if (attr.type == 'text/ng-template') { 27693 var templateUrl = attr.id, 27694 text = element[0].text; 27695 27696 $templateCache.put(templateUrl, text); 27697 } 27698 } 27699 }; 27700}]; 27701 27702var noopNgModelController = { $setViewValue: noop, $render: noop }; 27703 27704/** 27705 * @ngdoc type 27706 * @name select.SelectController 27707 * @description 27708 * The controller for the `<select>` directive. This provides support for reading 27709 * and writing the selected value(s) of the control and also coordinates dynamically 27710 * added `<option>` elements, perhaps by an `ngRepeat` directive. 27711 */ 27712var SelectController = 27713 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 27714 27715 var self = this, 27716 optionsMap = new HashMap(); 27717 27718 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 27719 self.ngModelCtrl = noopNgModelController; 27720 27721 // The "unknown" option is one that is prepended to the list if the viewValue 27722 // does not match any of the options. When it is rendered the value of the unknown 27723 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 27724 // 27725 // We can't just jqLite('<option>') since jqLite is not smart enough 27726 // to create it in <select> and IE barfs otherwise. 27727 self.unknownOption = jqLite(document.createElement('option')); 27728 self.renderUnknownOption = function(val) { 27729 var unknownVal = '? ' + hashKey(val) + ' ?'; 27730 self.unknownOption.val(unknownVal);
27731 $element.prepend(self.unknownOption); 27732 $element.val(unknownVal); 27733 }; 27734 27735 $scope.$on('$destroy', function() { 27736 // disable unknown option so that we don't do work when the whole select is being destroyed 27737 self.renderUnknownOption = noop; 27738 }); 27739 27740 self.removeUnknownOption = function() { 27741 if (self.unknownOption.parent()) self.unknownOption.remove(); 27742 }; 27743 27744 27745 // Read the value of the select control, the implementation of this changes depending 27746 // upon whether the select can have multiple values and whether ngOptions is at work. 27747 self.readValue = function readSingleValue() { 27748 self.removeUnknownOption(); 27749 return $element.val(); 27750 }; 27751 27752 27753 // Write the value to the select control, the implementation of this changes depending 27754 // upon whether the select can have multiple values and whether ngOptions is at work. 27755 self.writeValue = function writeSingleValue(value) { 27756 if (self.hasOption(value)) { 27757 self.removeUnknownOption(); 27758 $element.val(value); 27759 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 27760 } else { 27761 if (value == null && self.emptyOption) { 27762 self.removeUnknownOption(); 27763 $element.val(''); 27764 } else { 27765 self.renderUnknownOption(value); 27766 } 27767 } 27768 }; 27769 27770 27771 // Tell the select control that an option, with the given value, has been added 27772 self.addOption = function(value, element) { 27773 assertNotHasOwnProperty(value, '"option value"'); 27774 if (value === '') { 27775 self.emptyOption = element; 27776 } 27777 var count = optionsMap.get(value) || 0; 27778 optionsMap.put(value, count + 1); 27779 }; 27780 27781 // Tell the select control that an option, with the given value, has been removed 27782 self.removeOption = function(value) { 27783 var count = optionsMap.get(value); 27784 if (count) { 27785 if (count === 1) { 27786 optionsMap.remove(value); 27787 if (value === '') { 27788 self.emptyOption = undefined; 27789 } 27790 } else { 27791 optionsMap.put(value, count - 1); 27792 } 27793 } 27794 }; 27795 27796 // Check whether the select control has an option matching the given value 27797 self.hasOption = function(value) { 27798 return !!optionsMap.get(value); 27799 }; 27800}]; 27801 27802/** 27803 * @ngdoc directive 27804 * @name select 27805 * @restrict E 27806 * 27807 * @description 27808 * HTML `SELECT` element with angular data-binding. 27809 * 27810 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 27811 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits such as reducing 27812 * memory and increasing speed by not creating a new scope for each repeated instance, as well as providing 27813 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 27814 * comprehension expression. 27815 * 27816 * When an item in the `<select>` menu is selected, the array element or object property 27817 * represented by the selected option will be bound to the model identified by the `ngModel` 27818 * directive. 27819 * 27820 * If the viewValue contains a value that doesn't match any of the options then the control 27821 * will automatically add an "unknown" option, which it then removes when this is resolved. 27822 * 27823 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 27824 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 27825 * option. See example below for demonstration. 27826 * 27827 * <div class="alert alert-info"> 27828 * The value of a `select` directive used without `ngOptions` is always a string. 27829 * When the model needs to be bound to a non-string value, you must either explictly convert it 27830 * using a directive (see example below) or use `ngOptions` to specify the set of options. 27831 * This is because an option element can only be bound to string values at present. 27832 * </div> 27833 * 27834 * ### Example (binding `select` to a non-string value) 27835 * 27836 * <example name="select-with-non-string-options" module="nonStringSelect"> 27837 * <file name="index.html"> 27838 * <select ng-model="model.id" convert-to-number> 27839 * <option value="0">Zero</option> 27840 * <option value="1">One</option> 27841 * <option value="2">Two</option> 27842 * </select> 27843 * {{ model }} 27844 * </file> 27845 * <file name="app.js"> 27846 * angular.module('nonStringSelect', []) 27847 * .run(function($rootScope) { 27848 * $rootScope.model = { id: 2 }; 27849 * }) 27850 * .directive('convertToNumber', function() { 27851 * return { 27852 * require: 'ngModel', 27853 * link: function(scope, element, attrs, ngModel) { 27854 * ngModel.$parsers.push(function(val) { 27855 * return parseInt(val, 10); 27856 * }); 27857 * ngModel.$formatters.push(function(val) { 27858 * return '' + val; 27859 * }); 27860 * } 27861 * }; 27862 * }); 27863 * </file> 27864 * <file name="protractor.js" type="protractor"> 27865 * it('should initialize to model', function() { 27866 * var select = element(by.css('select')); 27867 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 27868 * }); 27869 * </file> 27870 * </example> 27871 * 27872 */ 27873var selectDirective = function() { 27874 27875 return { 27876 restrict: 'E', 27877 require: ['select', '?ngModel'], 27878 controller: SelectController, 27879 link: function(scope, element, attr, ctrls) { 27880 27881 // if ngModel is not defined, we don't need to do anything 27882 var ngModelCtrl = ctrls[1]; 27883 if (!ngModelCtrl) return; 27884 27885 var selectCtrl = ctrls[0]; 27886 27887 selectCtrl.ngModelCtrl = ngModelCtrl; 27888 27889 // We delegate rendering to the `writeValue` method, which can be changed 27890 // if the select can have multiple selected values or if the options are being 27891 // generated by `ngOptions` 27892 ngModelCtrl.$render = function() { 27893 selectCtrl.writeValue(ngModelCtrl.$viewValue); 27894 }; 27895 27896 // When the selected item(s) changes we delegate getting the value of the select control 27897 // to the `readValue` method, which can be changed if the select can have multiple 27898 // selected values or if the options are being generated by `ngOptions` 27899 element.on('change', function() { 27900 scope.$apply(function() { 27901 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 27902 }); 27903 }); 27904 27905 // If the select allows multiple values then we need to modify how we read and write 27906 // values from and to the control; also what it means for the value to be empty and
27907 // we have to add an extra watch since ngModel doesn't work well with arrays - it 27908 // doesn't trigger rendering if only an item in the array changes. 27909 if (attr.multiple) { 27910 27911 // Read value now needs to check each option to see if it is selected 27912 selectCtrl.readValue = function readMultipleValue() { 27913 var array = []; 27914 forEach(element.find('option'), function(option) { 27915 if (option.selected) { 27916 array.push(option.value); 27917 } 27918 }); 27919 return array; 27920 }; 27921 27922 // Write value now needs to set the selected property of each matching option 27923 selectCtrl.writeValue = function writeMultipleValue(value) { 27924 var items = new HashMap(value); 27925 forEach(element.find('option'), function(option) { 27926 option.selected = isDefined(items.get(option.value)); 27927 }); 27928 }; 27929 27930 // we have to do it on each watch since ngModel watches reference, but 27931 // we need to work of an array, so we need to see if anything was inserted/removed 27932 var lastView, lastViewRef = NaN; 27933 scope.$watch(function selectMultipleWatch() { 27934 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 27935 lastView = shallowCopy(ngModelCtrl.$viewValue); 27936 ngModelCtrl.$render(); 27937 } 27938 lastViewRef = ngModelCtrl.$viewValue; 27939 }); 27940 27941 // If we are a multiple select then value is now a collection 27942 // so the meaning of $isEmpty changes 27943 ngModelCtrl.$isEmpty = function(value) { 27944 return !value || value.length === 0; 27945 }; 27946 27947 } 27948 } 27949 }; 27950}; 27951 27952 27953// The option directive is purely designed to communicate the existence (or lack of) 27954// of dynamically created (and destroyed) option elements to their containing select 27955// directive via its controller. 27956var optionDirective = ['$interpolate', function($interpolate) { 27957 27958 function chromeHack(optionElement) { 27959 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 27960 // Adding an <option selected="selected"> element to a <select required="required"> should 27961 // automatically select the new element 27962 if (optionElement[0].hasAttribute('selected')) { 27963 optionElement[0].selected = true; 27964 } 27965 } 27966 27967 return { 27968 restrict: 'E', 27969 priority: 100, 27970 compile: function(element, attr) { 27971 27972 // If the value attribute is not defined then we fall back to the 27973 // text content of the option element, which may be interpolated 27974 if (isUndefined(attr.value)) { 27975 var interpolateFn = $interpolate(element.text(), true); 27976 if (!interpolateFn) { 27977 attr.$set('value', element.text()); 27978 } 27979 } 27980 27981 return function(scope, element, attr) { 27982 27983 // This is an optimization over using ^^ since we don't want to have to search 27984 // all the way to the root of the DOM for every single option element 27985 var selectCtrlName = '$selectController', 27986 parent = element.parent(), 27987 selectCtrl = parent.data(selectCtrlName) || 27988 parent.parent().data(selectCtrlName); // in case we are in optgroup 27989 27990 // Only update trigger option updates if this is an option within a `select` 27991 // that also has `ngModel` attached 27992 if (selectCtrl && selectCtrl.ngModelCtrl) { 27993 27994 if (interpolateFn) { 27995 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 27996 attr.$set('value', newVal); 27997 if (oldVal !== newVal) { 27998 selectCtrl.removeOption(oldVal); 27999 } 28000 selectCtrl.addOption(newVal, element); 28001 selectCtrl.ngModelCtrl.$render(); 28002 chromeHack(element); 28003 }); 28004 } else { 28005 selectCtrl.addOption(attr.value, element); 28006 selectCtrl.ngModelCtrl.$render(); 28007 chromeHack(element); 28008 } 28009 28010 element.on('$destroy', function() { 28011 selectCtrl.removeOption(attr.value); 28012 selectCtrl.ngModelCtrl.$render(); 28013 }); 28014 } 28015 }; 28016 } 28017 }; 28018}]; 28019 28020var styleDirective = valueFn({ 28021 restrict: 'E', 28022 terminal: false 28023}); 28024 28025var requiredDirective = function() { 28026 return { 28027 restrict: 'A', 28028 require: '?ngModel', 28029 link: function(scope, elm, attr, ctrl) { 28030 if (!ctrl) return; 28031 attr.required = true; // force truthy in case we are on non input element 28032 28033 ctrl.$validators.required = function(modelValue, viewValue) { 28034 return !attr.required || !ctrl.$isEmpty(viewValue); 28035 }; 28036 28037 attr.$observe('required', function() { 28038 ctrl.$validate(); 28039 }); 28040 } 28041 }; 28042}; 28043 28044 28045var patternDirective = function() { 28046 return { 28047 restrict: 'A', 28048 require: '?ngModel', 28049 link: function(scope, elm, attr, ctrl) { 28050 if (!ctrl) return; 28051 28052 var regexp, patternExp = attr.ngPattern || attr.pattern; 28053 attr.$observe('pattern', function(regex) { 28054 if (isString(regex) && regex.length > 0) { 28055 regex = new RegExp('^' + regex + '$'); 28056 } 28057 28058 if (regex && !regex.test) { 28059 throw minErr('ngPattern')('noregexp', 28060 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 28061 regex, startingTag(elm)); 28062 } 28063 28064 regexp = regex || undefined; 28065 ctrl.$validate(); 28066 }); 28067 28068 ctrl.$validators.pattern = function(value) { 28069 return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value); 28070 }; 28071 } 28072 }; 28073}; 28074 28075 28076var maxlengthDirective = function() { 28077 return { 28078 restrict: 'A', 28079 require: '?ngModel', 28080 link: function(scope, elm, attr, ctrl) { 28081 if (!ctrl) return; 28082 28083 var maxlength = -1; 28084 attr.$observe('maxlength', function(value) { 28085 var intVal = toInt(value); 28086 maxlength = isNaN(intVal) ? -1 : intVal; 28087 ctrl.$validate(); 28088 }); 28089 ctrl.$validators.maxlength = function(modelValue, viewValue) { 28090 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 28091 }; 28092 } 28093 }; 28094}; 28095 28096var minlengthDirective = function() { 28097 return { 28098 restrict: 'A', 28099 require: '?ngModel', 28100 link: function(scope, elm, attr, ctrl) { 28101 if (!ctrl) return; 28102 28103 var minlength = 0; 28104 attr.$observe('minlength', function(value) { 28105 minlength = toInt(value) || 0; 28106 ctrl.$validate(); 28107 }); 28108 ctrl.$validators.minlength = function(modelValue, viewValue) { 28109 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 28110 }; 28111 } 28112 }; 28113}; 28114 28115 if (window.angular.bootstrap) { 28116 //AngularJS is already loaded, so we can return here... 28117 console.log('WARNING: Tried to load angular more than once.'); 28118 return; 28119 } 28120 28121 //try to bind to jquery now so that one can write jqLite(document).ready() 28122 //but we will rebind on bootstrap again. 28123 bindJQuery(); 28124 28125 publishExternalAPI(angular); 28126 28127 jqLite(document).ready(function() { 28128 angularInit(document, bootstrap); 28129 }); 28130 28131})(window, document); 28132 28133!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.