1/** 2 * @license AngularJS v1.4.9 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.9/' + 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 271 // `null`, `undefined` and `window` are not array-like 272 if (obj == null || isWindow(obj)) return false; 273 274 // arrays, strings and jQuery/jqLite objects are array like 275 // * jqLite is either the jQuery or jqLite constructor function 276 // * we have to check the existance of jqLite first as this method is called 277 // via the forEach method when constructing the jqLite object in the first place 278 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; 279 280 // Support: iOS 8.2 (not reproducible in simulator) 281 // "length" in obj used to prevent JIT error (gh-11508) 282 var length = "length" in Object(obj) && obj.length; 283 284 // NodeList objects (with `item` method) and 285 // other objects with suitable length characteristics are array-like 286 return isNumber(length) && 287 (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item == 'function'); 288 289} 290 291/** 292 * @ngdoc function 293 * @name angular.forEach 294 * @module ng 295 * @kind function 296 * 297 * @description 298 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 299 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 300 * is the value of an object property or an array element, `key` is the object property key or 301 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 302 * 303 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 304 * using the `hasOwnProperty` method. 305 * 306 * Unlike ES262's 307 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 308 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 309 * return the value provided. 310 * 311 ```js 312 var values = {name: 'misko', gender: 'male'}; 313 var log = []; 314 angular.forEach(values, function(value, key) { 315 this.push(key + ': ' + value); 316 }, log); 317 expect(log).toEqual(['name: misko', 'gender: male']); 318 ``` 319 * 320 * @param {Object|Array} obj Object to iterate over. 321 * @param {Function} iterator Iterator function. 322 * @param {Object=} context Object to become context (`this`) for the iterator function. 323 * @returns {Object|Array} Reference to `obj`. 324 */ 325 326function forEach(obj, iterator, context) { 327 var key, length; 328 if (obj) { 329 if (isFunction(obj)) { 330 for (key in obj) { 331 // Need to check if hasOwnProperty exists, 332 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 333 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 334 iterator.call(context, obj[key], key, obj); 335 } 336 } 337 } else if (isArray(obj) || isArrayLike(obj)) { 338 var isPrimitive = typeof obj !== 'object'; 339 for (key = 0, length = obj.length; key < length; key++) { 340 if (isPrimitive || key in obj) { 341 iterator.call(context, obj[key], key, obj); 342 } 343 } 344 }
vendor: 11,524 bytes, lines 344-824
344 else if (obj.forEach && obj.forEach !== forEach) { 345 obj.forEach(iterator, context, obj); 346 } else if (isBlankObject(obj)) { 347 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 348 for (key in obj) { 349 iterator.call(context, obj[key], key, obj); 350 } 351 } else if (typeof obj.hasOwnProperty === 'function') { 352 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 353 for (key in obj) { 354 if (obj.hasOwnProperty(key)) { 355 iterator.call(context, obj[key], key, obj); 356 } 357 } 358 } else { 359 // Slow path for objects which do not have a method `hasOwnProperty` 360 for (key in obj) { 361 if (hasOwnProperty.call(obj, key)) { 362 iterator.call(context, obj[key], key, obj); 363 } 364 } 365 } 366 } 367 return obj; 368} 369 370function forEachSorted(obj, iterator, context) { 371 var keys = Object.keys(obj).sort(); 372 for (var i = 0; i < keys.length; i++) { 373 iterator.call(context, obj[keys[i]], keys[i]); 374 } 375 return keys; 376} 377 378 379/** 380 * when using forEach the params are value, key, but it is often useful to have key, value. 381 * @param {function(string, *)} iteratorFn 382 * @returns {function(*, string)} 383 */ 384function reverseParams(iteratorFn) { 385 return function(value, key) { iteratorFn(key, value); }; 386} 387 388/** 389 * A consistent way of creating unique IDs in angular. 390 * 391 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 392 * we hit number precision issues in JavaScript. 393 * 394 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 395 * 396 * @returns {number} an unique alpha-numeric string 397 */ 398function nextUid() { 399 return ++uid; 400} 401 402 403/** 404 * Set or clear the hashkey for an object. 405 * @param obj object 406 * @param h the hashkey (!truthy to delete the hashkey) 407 */ 408function setHashKey(obj, h) { 409 if (h) { 410 obj.$$hashKey = h; 411 } else { 412 delete obj.$$hashKey; 413 } 414} 415 416 417function baseExtend(dst, objs, deep) { 418 var h = dst.$$hashKey; 419 420 for (var i = 0, ii = objs.length; i < ii; ++i) { 421 var obj = objs[i]; 422 if (!isObject(obj) && !isFunction(obj)) continue; 423 var keys = Object.keys(obj); 424 for (var j = 0, jj = keys.length; j < jj; j++) { 425 var key = keys[j]; 426 var src = obj[key]; 427 428 if (deep && isObject(src)) { 429 if (isDate(src)) { 430 dst[key] = new Date(src.valueOf()); 431 } else if (isRegExp(src)) { 432 dst[key] = new RegExp(src); 433 } else if (src.nodeName) { 434 dst[key] = src.cloneNode(true); 435 } else if (isElement(src)) { 436 dst[key] = src.clone(); 437 } else { 438 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 439 baseExtend(dst[key], [src], true); 440 } 441 } else { 442 dst[key] = src; 443 } 444 } 445 } 446 447 setHashKey(dst, h); 448 return dst; 449} 450 451/** 452 * @ngdoc function 453 * @name angular.extend 454 * @module ng 455 * @kind function 456 * 457 * @description 458 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 459 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 460 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 461 * 462 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 463 * {@link angular.merge} for this. 464 * 465 * @param {Object} dst Destination object. 466 * @param {...Object} src Source object(s). 467 * @returns {Object} Reference to `dst`. 468 */ 469function extend(dst) { 470 return baseExtend(dst, slice.call(arguments, 1), false); 471} 472 473 474/** 475* @ngdoc function 476* @name angular.merge 477* @module ng 478* @kind function 479* 480* @description 481* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 482* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 483* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 484* 485* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 486* objects, performing a deep copy. 487* 488* @param {Object} dst Destination object. 489* @param {...Object} src Source object(s). 490* @returns {Object} Reference to `dst`. 491*/ 492function merge(dst) { 493 return baseExtend(dst, slice.call(arguments, 1), true); 494} 495 496 497 498function toInt(str) { 499 return parseInt(str, 10); 500} 501 502 503function inherit(parent, extra) { 504 return extend(Object.create(parent), extra); 505} 506 507/** 508 * @ngdoc function 509 * @name angular.noop 510 * @module ng 511 * @kind function 512 * 513 * @description 514 * A function that performs no operations. This function can be useful when writing code in the 515 * functional style. 516 ```js 517 function foo(callback) { 518 var result = calculateResult(); 519 (callback || angular.noop)(result); 520 } 521 ``` 522 */ 523function noop() {} 524noop.$inject = []; 525 526 527/** 528 * @ngdoc function 529 * @name angular.identity 530 * @module ng 531 * @kind function 532 * 533 * @description 534 * A function that returns its first argument. This function is useful when writing code in the 535 * functional style. 536 * 537 ```js 538 function transformer(transformationFn, value) { 539 return (transformationFn || angular.identity)(value); 540 }; 541 ``` 542 * @param {*} value to be returned. 543 * @returns {*} the value passed in. 544 */ 545function identity($) {return $;} 546identity.$inject = []; 547 548 549function valueFn(value) {return function() {return value;};} 550 551function hasCustomToString(obj) { 552 return isFunction(obj.toString) && obj.toString !== toString; 553} 554 555 556/** 557 * @ngdoc function 558 * @name angular.isUndefined 559 * @module ng 560 * @kind function 561 * 562 * @description 563 * Determines if a reference is undefined. 564 * 565 * @param {*} value Reference to check. 566 * @returns {boolean} True if `value` is undefined. 567 */ 568function isUndefined(value) {return typeof value === 'undefined';} 569 570 571/** 572 * @ngdoc function 573 * @name angular.isDefined 574 * @module ng 575 * @kind function 576 * 577 * @description 578 * Determines if a reference is defined. 579 * 580 * @param {*} value Reference to check. 581 * @returns {boolean} True if `value` is defined. 582 */ 583function isDefined(value) {return typeof value !== 'undefined';} 584 585 586/** 587 * @ngdoc function 588 * @name angular.isObject 589 * @module ng 590 * @kind function 591 * 592 * @description 593 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 594 * considered to be objects. Note that JavaScript arrays are objects. 595 * 596 * @param {*} value Reference to check. 597 * @returns {boolean} True if `value` is an `Object` but not `null`. 598 */ 599function isObject(value) { 600 // http://jsperf.com/isobject4 601 return value !== null && typeof value === 'object'; 602} 603 604 605/** 606 * Determine if a value is an object with a null prototype 607 * 608 * @returns {boolean} True if `value` is an `Object` with a null prototype 609 */ 610function isBlankObject(value) { 611 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 612} 613 614 615/** 616 * @ngdoc function 617 * @name angular.isString 618 * @module ng 619 * @kind function 620 * 621 * @description 622 * Determines if a reference is a `String`. 623 * 624 * @param {*} value Reference to check. 625 * @returns {boolean} True if `value` is a `String`. 626 */ 627function isString(value) {return typeof value === 'string';} 628 629 630/** 631 * @ngdoc function 632 * @name angular.isNumber 633 * @module ng 634 * @kind function 635 * 636 * @description 637 * Determines if a reference is a `Number`. 638 * 639 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 640 * 641 * If you wish to exclude these then you can use the native 642 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 643 * method. 644 * 645 * @param {*} value Reference to check. 646 * @returns {boolean} True if `value` is a `Number`. 647 */ 648function isNumber(value) {return typeof value === 'number';} 649 650 651/** 652 * @ngdoc function 653 * @name angular.isDate 654 * @module ng 655 * @kind function 656 * 657 * @description 658 * Determines if a value is a date. 659 * 660 * @param {*} value Reference to check. 661 * @returns {boolean} True if `value` is a `Date`. 662 */ 663function isDate(value) { 664 return toString.call(value) === '[object Date]'; 665} 666 667 668/** 669 * @ngdoc function 670 * @name angular.isArray 671 * @module ng 672 * @kind function 673 * 674 * @description 675 * Determines if a reference is an `Array`. 676 * 677 * @param {*} value Reference to check. 678 * @returns {boolean} True if `value` is an `Array`. 679 */ 680var isArray = Array.isArray; 681 682/** 683 * @ngdoc function 684 * @name angular.isFunction 685 * @module ng 686 * @kind function 687 * 688 * @description 689 * Determines if a reference is a `Function`. 690 * 691 * @param {*} value Reference to check. 692 * @returns {boolean} True if `value` is a `Function`. 693 */ 694function isFunction(value) {return typeof value === 'function';} 695 696 697/** 698 * Determines if a value is a regular expression object. 699 * 700 * @private 701 * @param {*} value Reference to check. 702 * @returns {boolean} True if `value` is a `RegExp`. 703 */ 704function isRegExp(value) { 705 return toString.call(value) === '[object RegExp]'; 706} 707 708 709/** 710 * Checks if `obj` is a window object. 711 * 712 * @private 713 * @param {*} obj Object to check 714 * @returns {boolean} True if `obj` is a window obj. 715 */ 716function isWindow(obj) { 717 return obj && obj.window === obj; 718} 719 720 721function isScope(obj) { 722 return obj && obj.$evalAsync && obj.$watch; 723} 724 725 726function isFile(obj) { 727 return toString.call(obj) === '[object File]'; 728} 729 730 731function isFormData(obj) { 732 return toString.call(obj) === '[object FormData]'; 733} 734 735 736function isBlob(obj) { 737 return toString.call(obj) === '[object Blob]'; 738} 739 740 741function isBoolean(value) { 742 return typeof value === 'boolean'; 743} 744 745 746function isPromiseLike(obj) { 747 return obj && isFunction(obj.then); 748} 749 750 751var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/; 752function isTypedArray(value) { 753 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); 754} 755 756 757var trim = function(value) { 758 return isString(value) ? value.trim() : value; 759}; 760 761// Copied from: 762// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 763// Prereq: s is a string. 764var escapeForRegexp = function(s) { 765 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 766 replace(/\x08/g, '\\x08'); 767}; 768 769 770/** 771 * @ngdoc function 772 * @name angular.isElement 773 * @module ng 774 * @kind function 775 * 776 * @description 777 * Determines if a reference is a DOM element (or wrapped jQuery element). 778 * 779 * @param {*} value Reference to check. 780 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 781 */ 782function isElement(node) { 783 return !!(node && 784 (node.nodeName // we are a direct element 785 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 786} 787 788/** 789 * @param str 'key1,key2,...' 790 * @returns {object} in the form of {key1:true, key2:true, ...} 791 */ 792function makeMap(str) { 793 var obj = {}, items = str.split(","), i; 794 for (i = 0; i < items.length; i++) { 795 obj[items[i]] = true; 796 } 797 return obj; 798} 799 800 801function nodeName_(element) { 802 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 803} 804 805function includes(array, obj) { 806 return Array.prototype.indexOf.call(array, obj) != -1; 807} 808 809function arrayRemove(array, value) { 810 var index = array.indexOf(value); 811 if (index >= 0) { 812 array.splice(index, 1); 813 } 814 return index; 815} 816 817/** 818 * @ngdoc function 819 * @name angular.copy 820 * @module ng 821 * @kind function 822 * 823 * @description 824 * Creates a deep copy of `source`, which should be an object or an array.
825 * 826 * * If no destination is supplied, a copy of the object or array is created. 827 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 828 * are deleted and then all elements/properties from the source are copied to it. 829 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 830 * * If `source` is identical to 'destination' an exception will be thrown. 831 * 832 * @param {*} source The source that will be used to make a copy. 833 * Can be any type, including primitives, `null`, and `undefined`. 834 * @param {(Object|Array)=} destination Destination into which the source is copied. If 835 * provided, must be of the same type as `source`. 836 * @returns {*} The copy or updated `destination`, if `destination` was specified. 837 * 838 * @example 839 <example module="copyExample"> 840 <file name="index.html"> 841 <div ng-controller="ExampleController"> 842 <form novalidate class="simple-form"> 843 Name: <input type="text" ng-model="user.name" /><br /> 844 E-mail: <input type="email" ng-model="user.email" /><br /> 845 Gender: <input type="radio" ng-model="user.gender" value="male" />male 846 <input type="radio" ng-model="user.gender" value="female" />female<br /> 847 <button ng-click="reset()">RESET</button> 848 <button ng-click="update(user)">SAVE</button> 849 </form> 850 <pre>form = {{user | json}}</pre> 851 <pre>master = {{master | json}}</pre> 852 </div> 853 854 <script> 855 angular.module('copyExample', []) 856 .controller('ExampleController', ['$scope', function($scope) { 857 $scope.master= {}; 858 859 $scope.update = function(user) { 860 // Example with 1 argument 861 $scope.master= angular.copy(user); 862 }; 863 864 $scope.reset = function() { 865 // Example with 2 arguments 866 angular.copy($scope.master, $scope.user); 867 }; 868 869 $scope.reset(); 870 }]); 871 </script> 872 </file> 873 </example> 874 */ 875function copy(source, destination) { 876 var stackSource = []; 877 var stackDest = []; 878 879 if (destination) { 880 if (isTypedArray(destination)) { 881 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated."); 882 } 883 if (source === destination) { 884 throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); 885 } 886 887 // Empty the destination object 888 if (isArray(destination)) { 889 destination.length = 0; 890 } else { 891 forEach(destination, function(value, key) { 892 if (key !== '$$hashKey') { 893 delete destination[key]; 894 } 895 }); 896 } 897 898 stackSource.push(source); 899 stackDest.push(destination); 900 return copyRecurse(source, destination); 901 } 902 903 return copyElement(source); 904 905 function copyRecurse(source, destination) { 906 var h = destination.$$hashKey; 907 var result, key; 908 if (isArray(source)) { 909 for (var i = 0, ii = source.length; i < ii; i++) { 910 destination.push(copyElement(source[i])); 911 } 912 } else if (isBlankObject(source)) { 913 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 914 for (key in source) { 915 destination[key] = copyElement(source[key]); 916 } 917 } else if (source && typeof source.hasOwnProperty === 'function') { 918 // Slow path, which must rely on hasOwnProperty 919 for (key in source) { 920 if (source.hasOwnProperty(key)) { 921 destination[key] = copyElement(source[key]); 922 } 923 } 924 } else { 925 // Slowest path --- hasOwnProperty can't be called as a method 926 for (key in source) { 927 if (hasOwnProperty.call(source, key)) { 928 destination[key] = copyElement(source[key]); 929 } 930 } 931 } 932 setHashKey(destination, h); 933 return destination; 934 } 935 936 function copyElement(source) { 937 // Simple values 938 if (!isObject(source)) { 939 return source; 940 } 941 942 // Already copied values 943 var index = stackSource.indexOf(source); 944 if (index !== -1) { 945 return stackDest[index]; 946 } 947 948 if (isWindow(source) || isScope(source)) { 949 throw ngMinErr('cpws', 950 "Can't copy! Making copies of Window or Scope instances is not supported."); 951 } 952 953 var needsRecurse = false;
954 var destination; 955 956 if (isArray(source)) { 957 destination = []; 958 needsRecurse = true; 959 } else if (isTypedArray(source)) { 960 destination = new source.constructor(source); 961 } else if (isDate(source)) { 962 destination = new Date(source.getTime()); 963 } else if (isRegExp(source)) { 964 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 965 destination.lastIndex = source.lastIndex; 966 } else if (isFunction(source.cloneNode)) { 967 destination = source.cloneNode(true); 968 } else { 969 destination = Object.create(getPrototypeOf(source)); 970 needsRecurse = true; 971 } 972 973 stackSource.push(source); 974 stackDest.push(destination); 975 976 return needsRecurse 977 ? copyRecurse(source, destination) 978 : destination; 979 } 980} 981 982/** 983 * Creates a shallow copy of an object, an array or a primitive. 984 * 985 * Assumes that there are no proto properties for objects. 986 */ 987function shallowCopy(src, dst) { 988 if (isArray(src)) { 989 dst = dst || []; 990 991 for (var i = 0, ii = src.length; i < ii; i++) { 992 dst[i] = src[i]; 993 } 994 } else if (isObject(src)) { 995 dst = dst || {}; 996 997 for (var key in src) { 998 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 999 dst[key] = src[key]; 1000 } 1001 } 1002 } 1003 1004 return dst || src; 1005} 1006 1007 1008/** 1009 * @ngdoc function 1010 * @name angular.equals 1011 * @module ng 1012 * @kind function 1013 * 1014 * @description 1015 * Determines if two objects or two values are equivalent. Supports value types, regular 1016 * expressions, arrays and objects. 1017 * 1018 * Two objects or values are considered equivalent if at least one of the following is true: 1019 * 1020 * * Both objects or values pass `===` comparison. 1021 * * Both objects or values are of the same type and all of their properties are equal by 1022 * comparing them with `angular.equals`. 1023 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 1024 * * Both values represent the same regular expression (In JavaScript, 1025 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 1026 * representation matches). 1027 * 1028 * During a property comparison, properties of `function` type and properties with names 1029 * that begin with `$` are ignored. 1030 * 1031 * Scope and DOMWindow objects are being compared only by identify (`===`). 1032 * 1033 * @param {*} o1 Object or value to compare. 1034 * @param {*} o2 Object or value to compare. 1035 * @returns {boolean} True if arguments are equal. 1036 */ 1037function equals(o1, o2) { 1038 if (o1 === o2) return true; 1039 if (o1 === null || o2 === null) return false; 1040 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1041 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1042 if (t1 == t2) { 1043 if (t1 == 'object') { 1044 if (isArray(o1)) { 1045 if (!isArray(o2)) return false; 1046 if ((length = o1.length) == o2.length) { 1047 for (key = 0; key < length; key++) { 1048 if (!equals(o1[key], o2[key])) return false; 1049 } 1050 return true; 1051 } 1052 } else if (isDate(o1)) { 1053 if (!isDate(o2)) return false; 1054 return equals(o1.getTime(), o2.getTime()); 1055 } else if (isRegExp(o1)) { 1056 return isRegExp(o2) ? o1.toString() == o2.toString() : false; 1057 } else { 1058 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1059 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1060 keySet = createMap(); 1061 for (key in o1) { 1062 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1063 if (!equals(o1[key], o2[key])) return false; 1064 keySet[key] = true; 1065 } 1066 for (key in o2) { 1067 if (!(key in keySet) && 1068 key.charAt(0) !== '$' && 1069 isDefined(o2[key]) && 1070 !isFunction(o2[key])) return false; 1071 } 1072 return true; 1073 } 1074 } 1075 } 1076 return false; 1077} 1078 1079var csp = function() { 1080 if (!isDefined(csp.rules)) { 1081 1082 1083 var ngCspElement = (document.querySelector('[ng-csp]') || 1084 document.querySelector('[data-ng-csp]')); 1085 1086 if (ngCspElement) { 1087 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 1088 ngCspElement.getAttribute('data-ng-csp'); 1089 csp.rules = { 1090 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 1091 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 1092 }; 1093 } else { 1094 csp.rules = { 1095 noUnsafeEval: noUnsafeEval(), 1096 noInlineStyle: false 1097 }; 1098 } 1099 } 1100 1101 return csp.rules; 1102 1103 function noUnsafeEval() { 1104 try { 1105 /* jshint -W031, -W054 */ 1106 new Function(''); 1107 /* jshint +W031, +W054 */ 1108 return false;
vendor: 10,174 bytes, lines 1109-1450
1109 } catch (e) { 1110 return true; 1111 } 1112 } 1113}; 1114 1115/** 1116 * @ngdoc directive 1117 * @module ng 1118 * @name ngJq 1119 * 1120 * @element ANY 1121 * @param {string=} ngJq the name of the library available under `window` 1122 * to be used for angular.element 1123 * @description 1124 * Use this directive to force the angular.element library. This should be 1125 * used to force either jqLite by leaving ng-jq blank or setting the name of 1126 * the jquery variable under window (eg. jQuery). 1127 * 1128 * Since angular looks for this directive when it is loaded (doesn't wait for the 1129 * DOMContentLoaded event), it must be placed on an element that comes before the script 1130 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1131 * others ignored. 1132 * 1133 * @example 1134 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1135 ```html 1136 <!doctype html> 1137 <html ng-app ng-jq> 1138 ... 1139 ... 1140 </html> 1141 ``` 1142 * @example 1143 * This example shows how to use a jQuery based library of a different name. 1144 * The library name must be available at the top most 'window'. 1145 ```html 1146 <!doctype html> 1147 <html ng-app ng-jq="jQueryLib"> 1148 ... 1149 ... 1150 </html> 1151 ``` 1152 */ 1153var jq = function() { 1154 if (isDefined(jq.name_)) return jq.name_; 1155 var el; 1156 var i, ii = ngAttrPrefixes.length, prefix, name; 1157 for (i = 0; i < ii; ++i) { 1158 prefix = ngAttrPrefixes[i]; 1159 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 1160 name = el.getAttribute(prefix + 'jq'); 1161 break; 1162 } 1163 } 1164 1165 return (jq.name_ = name); 1166}; 1167 1168function concat(array1, array2, index) { 1169 return array1.concat(slice.call(array2, index)); 1170} 1171 1172function sliceArgs(args, startIndex) { 1173 return slice.call(args, startIndex || 0); 1174} 1175 1176 1177/* jshint -W101 */ 1178/** 1179 * @ngdoc function 1180 * @name angular.bind 1181 * @module ng 1182 * @kind function 1183 * 1184 * @description 1185 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1186 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1187 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1188 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1189 * 1190 * @param {Object} self Context which `fn` should be evaluated in. 1191 * @param {function()} fn Function to be bound. 1192 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1193 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1194 */ 1195/* jshint +W101 */ 1196function bind(self, fn) { 1197 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1198 if (isFunction(fn) && !(fn instanceof RegExp)) { 1199 return curryArgs.length 1200 ? function() { 1201 return arguments.length 1202 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1203 : fn.apply(self, curryArgs); 1204 } 1205 : function() { 1206 return arguments.length 1207 ? fn.apply(self, arguments) 1208 : fn.call(self); 1209 }; 1210 } else { 1211 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1212 return fn; 1213 } 1214} 1215 1216 1217function toJsonReplacer(key, value) { 1218 var val = value; 1219 1220 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1221 val = undefined; 1222 } else if (isWindow(value)) { 1223 val = '$WINDOW'; 1224 } else if (value && document === value) { 1225 val = '$DOCUMENT'; 1226 } else if (isScope(value)) { 1227 val = '$SCOPE'; 1228 } 1229 1230 return val; 1231} 1232 1233 1234/** 1235 * @ngdoc function 1236 * @name angular.toJson 1237 * @module ng 1238 * @kind function 1239 * 1240 * @description 1241 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1242 * stripped since angular uses this notation internally. 1243 * 1244 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1245 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1246 * If set to an integer, the JSON output will contain that many spaces per indentation. 1247 * @returns {string|undefined} JSON-ified string representing `obj`. 1248 */ 1249function toJson(obj, pretty) { 1250 if (typeof obj === 'undefined') return undefined; 1251 if (!isNumber(pretty)) { 1252 pretty = pretty ? 2 : null; 1253 } 1254 return JSON.stringify(obj, toJsonReplacer, pretty); 1255} 1256 1257 1258/** 1259 * @ngdoc function 1260 * @name angular.fromJson 1261 * @module ng 1262 * @kind function 1263 * 1264 * @description 1265 * Deserializes a JSON string. 1266 * 1267 * @param {string} json JSON string to deserialize. 1268 * @returns {Object|Array|string|number} Deserialized JSON string. 1269 */ 1270function fromJson(json) { 1271 return isString(json) 1272 ? JSON.parse(json) 1273 : json; 1274} 1275 1276 1277function timezoneToOffset(timezone, fallback) { 1278 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1279 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1280} 1281 1282 1283function addDateMinutes(date, minutes) { 1284 date = new Date(date.getTime()); 1285 date.setMinutes(date.getMinutes() + minutes); 1286 return date; 1287} 1288 1289 1290function convertTimezoneToLocal(date, timezone, reverse) { 1291 reverse = reverse ? -1 : 1; 1292 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 1293 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); 1294} 1295 1296 1297/** 1298 * @returns {string} Returns the string representation of the element. 1299 */ 1300function startingTag(element) { 1301 element = jqLite(element).clone(); 1302 try { 1303 // turns out IE does not let you set .html() on elements which 1304 // are not allowed to have children. So we just ignore it. 1305 element.empty(); 1306 } catch (e) {} 1307 var elemHtml = jqLite('<div>').append(element).html(); 1308 try { 1309 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1310 elemHtml. 1311 match(/^(<[^>]+>)/)[1]. 1312 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1313 } catch (e) { 1314 return lowercase(elemHtml); 1315 } 1316 1317} 1318 1319 1320///////////////////////////////////////////////// 1321 1322/** 1323 * Tries to decode the URI component without throwing an exception. 1324 * 1325 * @private 1326 * @param str value potential URI component to check. 1327 * @returns {boolean} True if `value` can be decoded 1328 * with the decodeURIComponent function. 1329 */ 1330function tryDecodeURIComponent(value) { 1331 try { 1332 return decodeURIComponent(value); 1333 } catch (e) { 1334 // Ignore any invalid uri component 1335 } 1336} 1337 1338 1339/** 1340 * Parses an escaped url query string into key-value pairs. 1341 * @returns {Object.<string,boolean|Array>} 1342 */ 1343function parseKeyValue(/**string*/keyValue) { 1344 var obj = {}; 1345 forEach((keyValue || "").split('&'), function(keyValue) { 1346 var splitPoint, key, val; 1347 if (keyValue) { 1348 key = keyValue = keyValue.replace(/\+/g,'%20'); 1349 splitPoint = keyValue.indexOf('='); 1350 if (splitPoint !== -1) { 1351 key = keyValue.substring(0, splitPoint); 1352 val = keyValue.substring(splitPoint + 1); 1353 } 1354 key = tryDecodeURIComponent(key); 1355 if (isDefined(key)) { 1356 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 1357 if (!hasOwnProperty.call(obj, key)) { 1358 obj[key] = val; 1359 } else if (isArray(obj[key])) { 1360 obj[key].push(val); 1361 } else { 1362 obj[key] = [obj[key],val]; 1363 } 1364 } 1365 } 1366 }); 1367 return obj; 1368} 1369 1370function toKeyValue(obj) { 1371 var parts = []; 1372 forEach(obj, function(value, key) { 1373 if (isArray(value)) { 1374 forEach(value, function(arrayValue) { 1375 parts.push(encodeUriQuery(key, true) + 1376 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1377 }); 1378 } else { 1379 parts.push(encodeUriQuery(key, true) + 1380 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1381 } 1382 }); 1383 return parts.length ? parts.join('&') : ''; 1384} 1385 1386 1387/** 1388 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1389 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1390 * segments: 1391 * segment = *pchar 1392 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1393 * pct-encoded = "%" HEXDIG HEXDIG 1394 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1395 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1396 * / "*" / "+" / "," / ";" / "=" 1397 */ 1398function encodeUriSegment(val) { 1399 return encodeUriQuery(val, true). 1400 replace(/%26/gi, '&'). 1401 replace(/%3D/gi, '='). 1402 replace(/%2B/gi, '+'); 1403} 1404 1405 1406/** 1407 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1408 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1409 * encoded per http://tools.ietf.org/html/rfc3986: 1410 * query = *( pchar / "/" / "?" ) 1411 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1412 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1413 * pct-encoded = "%" HEXDIG HEXDIG 1414 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1415 * / "*" / "+" / "," / ";" / "=" 1416 */ 1417function encodeUriQuery(val, pctEncodeSpaces) { 1418 return encodeURIComponent(val). 1419 replace(/%40/gi, '@'). 1420 replace(/%3A/gi, ':'). 1421 replace(/%24/g, '$'). 1422 replace(/%2C/gi, ','). 1423 replace(/%3B/gi, ';'). 1424 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1425} 1426 1427var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1428 1429function getNgAttribute(element, ngAttr) { 1430 var attr, i, ii = ngAttrPrefixes.length; 1431 for (i = 0; i < ii; ++i) { 1432 attr = ngAttrPrefixes[i] + ngAttr; 1433 if (isString(attr = element.getAttribute(attr))) { 1434 return attr; 1435 } 1436 } 1437 return null; 1438} 1439 1440/** 1441 * @ngdoc directive 1442 * @name ngApp 1443 * @module ng 1444 * 1445 * @element ANY 1446 * @param {angular.Module} ngApp an optional application 1447 * {@link angular.module module} name to load. 1448 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1449 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1450 * do not use explicit function annotation (and are thus unsuitable for minification), as de
1450scribed 1451 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1452 * tracking down the root of these bugs. 1453 * 1454 * @description 1455 * 1456 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1457 * designates the **root element** of the application and is typically placed near the root element 1458 * of the page - e.g. on the `<body>` or `<html>` tags. 1459 * 1460 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1461 * found in the document will be used to define the root element to auto-bootstrap as an 1462 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1463 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1464 * 1465 * You can specify an **AngularJS module** to be used as the root module for the application. This 1466 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1467 * should contain the application code needed or have dependencies on other modules that will 1468 * contain the code. See {@link angular.module} for more information. 1469 * 1470 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1471 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1472 * would not be resolved to `3`. 1473 * 1474 * `ngApp` is the easiest, and most common way to bootstrap an application. 1475 * 1476 <example module="ngAppDemo"> 1477 <file name="index.html"> 1478 <div ng-controller="ngAppDemoController"> 1479 I can add: {{a}} + {{b}} = {{ a+b }} 1480 </div> 1481 </file> 1482 <file name="script.js"> 1483 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1484 $scope.a = 1; 1485 $scope.b = 2; 1486 }); 1487 </file> 1488 </example> 1489 * 1490 * Using `ngStrictDi`, you would see something like this: 1491 * 1492 <example ng-app-included="true"> 1493 <file name="index.html"> 1494 <div ng-app="ngAppStrictDemo" ng-strict-di> 1495 <div ng-controller="GoodController1"> 1496 I can add: {{a}} + {{b}} = {{ a+b }} 1497 1498 <p>This renders because the controller does not fail to 1499 instantiate, by using explicit annotation style (see 1500 script.js for details) 1501 </p> 1502 </div> 1503 1504 <div ng-controller="GoodController2"> 1505 Name: <input ng-model="name"><br /> 1506 Hello, {{name}}! 1507 1508 <p>This renders because the controller does not fail to 1509 instantiate, by using explicit annotation style 1510 (see script.js for details) 1511 </p> 1512 </div> 1513 1514 <div ng-controller="BadController"> 1515 I can add: {{a}} + {{b}} = {{ a+b }} 1516 1517 <p>The controller could not be instantiated, due to relying 1518 on automatic function annotations (which are disabled in 1519 strict mode). As such, the content of this section is not 1520 interpolated, and there should be an error in your web console. 1521 </p> 1522 </div> 1523 </div> 1524 </file> 1525 <file name="script.js"> 1526 angular.module('ngAppStrictDemo', []) 1527 // BadController will fail to instantiate, due to relying on automatic function annotation, 1528 // rather than an explicit annotation 1529 .controller('BadController', function($scope) { 1530 $scope.a = 1; 1531 $scope.b = 2; 1532 }) 1533 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1534 // due to using explicit annotations using the array style and $inject property, respectively. 1535 .controller('GoodController1', ['$scope', function($scope) { 1536 $scope.a = 1; 1537 $scope.b = 2; 1538 }]) 1539 .controller('GoodController2', GoodController2); 1540 function GoodController2($scope) { 1541 $scope.name = "World"; 1542 } 1543 GoodController2.$inject = ['$scope']; 1544 </file> 1545 <file name="style.css"> 1546 div[ng-controller] { 1547 margin-bottom: 1em; 1548 -webkit-border-radius: 4px; 1549 border-radius: 4px; 1550 border: 1px solid; 1551 padding: .5em; 1552 } 1553 div[ng-controller^=Good] { 1554 border-color: #d6e9c6; 1555 background-color: #dff0d8; 1556 color: #3c763d; 1557 } 1558 div[ng-controller^=Bad] { 1559 border-color: #ebccd1; 1560 background-color: #f2dede; 1561 color: #a94442; 1562 margin-bottom: 0; 1563 } 1564 </file> 1565 </example> 1566 */ 1567function angularInit(element, bootstrap) { 1568 var appElement, 1569 module, 1570 config = {}; 1571 1572 // The element `element` has priority over any other element 1573 forEach(ngAttrPrefixes, function(prefix) { 1574 var name = prefix + 'app'; 1575 1576 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1577 appElement = element; 1578 module = element.getAttribute(name); 1579 } 1580 }); 1581 forEach(ngAttrPrefixes, function(prefix) { 1582 var name = prefix + 'app'; 1583 var candidate; 1584 1585 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1586 appElement = candidate; 1587 module = candidate.getAttribute(name); 1588 } 1589 }); 1590 if (appElement) { 1591 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 1592 bootstrap(appElement, module ? [module] : [], config); 1593 } 1594} 1595 1596/** 1597 * @ngdoc function 1598 * @name angular.bootstrap 1599 * @module ng 1600 * @description 1601 * Use this function to manually start up angular application. 1602 * 1603 * See: {@link guide/bootstrap Bootstrap} 1604 *
1605 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. 1606 * They must use {@link ng.directive:ngApp ngApp}. 1607 * 1608 * Angular will detect if it has been loaded into the browser more than once and only allow the 1609 * first loaded script to be bootstrapped and will report a warning to the browser console for 1610 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1611 * multiple instances of Angular try to work on the DOM. 1612 * 1613 * ```html 1614 * <!doctype html> 1615 * <html> 1616 * <body> 1617 * <div ng-controller="WelcomeController"> 1618 * {{greeting}} 1619 * </div> 1620 * 1621 * <script src="angular.js"></script> 1622 * <script> 1623 * var app = angular.module('demo', []) 1624 * .controller('WelcomeController', function($scope) { 1625 * $scope.greeting = 'Welcome!'; 1626 * }); 1627 * angular.bootstrap(document, ['demo']); 1628 * </script> 1629 * </body> 1630 * </html> 1631 * ``` 1632 * 1633 * @param {DOMElement} element DOM element which is the root of angular application. 1634 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1635 * Each item in the array should be the name of a predefined module or a (DI annotated) 1636 * function that will be invoked by the injector as a `config` block. 1637 * See: {@link angular.module modules} 1638 * @param {Object=} config an object for defining configuration options for the application. The 1639 * following keys are supported: 1640 * 1641 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1642 * assist in finding bugs which break minified code. Defaults to `false`. 1643 * 1644 * @returns {auto.$injector} Returns the newly created injector for this app. 1645 */ 1646function bootstrap(element, modules, config) { 1647 if (!isObject(config)) config = {}; 1648 var defaultConfig = { 1649 strictDi: false 1650 }; 1651 config = extend(defaultConfig, config); 1652 var doBootstrap = function() { 1653 element = jqLite(element); 1654 1655 if (element.injector()) { 1656 var tag = (element[0] === document) ? 'document' : startingTag(element); 1657 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1658 throw ngMinErr( 1659 'btstrpd', 1660 "App Already Bootstrapped with this Element '{0}'", 1661 tag.replace(/</,'<').replace(/>/,'>')); 1662 } 1663 1664 modules = modules || []; 1665 modules.unshift(['$provide', function($provide) { 1666 $provide.value('$rootElement', element); 1667 }]); 1668 1669 if (config.debugInfoEnabled) { 1670 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1671 modules.push(['$compileProvider', function($compileProvider) { 1672 $compileProvider.debugInfoEnabled(true); 1673 }]); 1674 } 1675 1676 modules.unshift('ng'); 1677 var injector = createInjector(modules, config.strictDi); 1678 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1679 function bootstrapApply(scope, element, compile, injector) { 1680 scope.$apply(function() { 1681 element.data('$injector', injector); 1682 compile(element)(scope); 1683 }); 1684 }] 1685 ); 1686 return injector; 1687 }; 1688 1689 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1690 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1691 1692 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1693 config.debugInfoEnabled = true; 1694 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1695 } 1696 1697 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1698 return doBootstrap(); 1699 } 1700 1701 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1702 angular.resumeBootstrap = function(extraModules) { 1703 forEach(extraModules, function(module) { 1704 modules.push(module); 1705 }); 1706 return doBootstrap(); 1707 }; 1708 1709 if (isFunction(angular.resumeDeferredBootstrap)) { 1710 angular.resumeDeferredBootstrap(); 1711 } 1712} 1713 1714/** 1715 * @ngdoc function 1716 * @name angular.reloadWithDebugInfo 1717 * @module ng 1718 * @description 1719 * Use this function to reload the current application with debug information turned on. 1720 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 1721 * 1722 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 1723 */ 1724function reloadWithDebugInfo() { 1725 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 1726 window.location.reload(); 1727} 1728 1729/** 1730 * @name angular.getTestability 1731 * @module ng 1732 * @description 1733 * Get the testability service for the instance of Angular on the given 1734 * element. 1735 * @param {DOMElement} element DOM element which is the root of angular application. 1736 */ 1737function getTestability(rootElement) { 1738 var injector = angular.element(rootElement).injector(); 1739 if (!injector) { 1740 throw ngMinErr('test', 1741 'no injector found for element argument to getTestability'); 1742 } 1743 return injector.get('$$testability'); 1744} 1745 1746var SNAKE_CASE_REGEXP = /[A-Z]/g; 1747function snake_case(name, separator) { 1748 separator = separator || '_'; 1749 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1750 return (pos ? separator : '') + letter.toLowerCase(); 1751 }); 1752} 1753 1754var bindJQueryFired = false;
vendor: 9,107 bytes, lines 1755-2044
1755var skipDestroyOnNextJQueryCleanData; 1756function bindJQuery() { 1757 var originalCleanData; 1758 1759 if (bindJQueryFired) { 1760 return; 1761 } 1762 1763 // bind to jQuery if present; 1764 var jqName = jq(); 1765 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 1766 !jqName ? undefined : // use jqLite 1767 window[jqName]; // use jQuery specified by `ngJq` 1768 1769 // Use jQuery if it exists with proper functionality, otherwise default to us. 1770 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 1771 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 1772 // versions. It will not work for sure with jQuery <1.7, though. 1773 if (jQuery && jQuery.fn.on) { 1774 jqLite = jQuery; 1775 extend(jQuery.fn, { 1776 scope: JQLitePrototype.scope, 1777 isolateScope: JQLitePrototype.isolateScope, 1778 controller: JQLitePrototype.controller, 1779 injector: JQLitePrototype.injector, 1780 inheritedData: JQLitePrototype.inheritedData 1781 }); 1782 1783 // All nodes removed from the DOM via various jQuery APIs like .remove() 1784 // are passed through jQuery.cleanData. Monkey-patch this method to fire 1785 // the $destroy event on all removed nodes. 1786 originalCleanData = jQuery.cleanData; 1787 jQuery.cleanData = function(elems) { 1788 var events; 1789 if (!skipDestroyOnNextJQueryCleanData) { 1790 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 1791 events = jQuery._data(elem, "events"); 1792 if (events && events.$destroy) { 1793 jQuery(elem).triggerHandler('$destroy'); 1794 } 1795 } 1796 } else { 1797 skipDestroyOnNextJQueryCleanData = false; 1798 } 1799 originalCleanData(elems); 1800 }; 1801 } else { 1802 jqLite = JQLite; 1803 } 1804 1805 angular.element = jqLite; 1806 1807 // Prevent double-proxying. 1808 bindJQueryFired = true; 1809} 1810 1811/** 1812 * throw error if the argument is falsy. 1813 */ 1814function assertArg(arg, name, reason) { 1815 if (!arg) { 1816 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1817 } 1818 return arg; 1819} 1820 1821function assertArgFn(arg, name, acceptArrayAnnotation) { 1822 if (acceptArrayAnnotation && isArray(arg)) { 1823 arg = arg[arg.length - 1]; 1824 } 1825 1826 assertArg(isFunction(arg), name, 'not a function, got ' + 1827 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1828 return arg; 1829} 1830 1831/** 1832 * throw error if the name given is hasOwnProperty 1833 * @param {String} name the name to test 1834 * @param {String} context the context in which the name is used, such as module or directive 1835 */ 1836function assertNotHasOwnProperty(name, context) { 1837 if (name === 'hasOwnProperty') { 1838 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1839 } 1840} 1841 1842/** 1843 * Return the value accessible from the object by path. Any undefined traversals are ignored 1844 * @param {Object} obj starting object 1845 * @param {String} path path to traverse 1846 * @param {boolean} [bindFnToScope=true] 1847 * @returns {Object} value as accessible by path 1848 */ 1849//TODO(misko): this function needs to be removed 1850function getter(obj, path, bindFnToScope) { 1851 if (!path) return obj; 1852 var keys = path.split('.'); 1853 var key; 1854 var lastInstance = obj; 1855 var len = keys.length; 1856 1857 for (var i = 0; i < len; i++) { 1858 key = keys[i]; 1859 if (obj) { 1860 obj = (lastInstance = obj)[key]; 1861 } 1862 } 1863 if (!bindFnToScope && isFunction(obj)) { 1864 return bind(lastInstance, obj); 1865 } 1866 return obj; 1867} 1868 1869/** 1870 * Return the DOM siblings between the first and last node in the given array. 1871 * @param {Array} array like object 1872 * @returns {Array} the inputted object or a jqLite collection containing the nodes 1873 */ 1874function getBlockNodes(nodes) { 1875 // TODO(perf): update `nodes` instead of creating a new object? 1876 var node = nodes[0]; 1877 var endNode = nodes[nodes.length - 1]; 1878 var blockNodes; 1879 1880 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 1881 if (blockNodes || nodes[i] !== node) { 1882 if (!blockNodes) { 1883 blockNodes = jqLite(slice.call(nodes, 0, i)); 1884 } 1885 blockNodes.push(node); 1886 } 1887 } 1888 1889 return blockNodes || nodes; 1890} 1891 1892 1893/** 1894 * Creates a new object without a prototype. This object is useful for lookup without having to 1895 * guard against prototypically inherited properties via hasOwnProperty. 1896 * 1897 * Related micro-benchmarks: 1898 * - http://jsperf.com/object-create2 1899 * - http://jsperf.com/proto-map-lookup/2 1900 * - http://jsperf.com/for-in-vs-object-keys2 1901 * 1902 * @returns {Object} 1903 */ 1904function createMap() { 1905 return Object.create(null); 1906} 1907 1908var NODE_TYPE_ELEMENT = 1; 1909var NODE_TYPE_ATTRIBUTE = 2; 1910var NODE_TYPE_TEXT = 3; 1911var NODE_TYPE_COMMENT = 8; 1912var NODE_TYPE_DOCUMENT = 9; 1913var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 1914 1915/** 1916 * @ngdoc type 1917 * @name angular.Module 1918 * @module ng 1919 * @description 1920 * 1921 * Interface for configuring angular {@link angular.module modules}. 1922 */ 1923 1924function setupModuleLoader(window) { 1925 1926 var $injectorMinErr = minErr('$injector'); 1927 var ngMinErr = minErr('ng'); 1928 1929 function ensure(obj, name, factory) { 1930 return obj[name] || (obj[name] = factory()); 1931 } 1932 1933 var angular = ensure(window, 'angular', Object); 1934 1935 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1936 angular.$$minErr = angular.$$minErr || minErr; 1937 1938 return ensure(angular, 'module', function() { 1939 /** @type {Object.<string, angular.Module>} */ 1940 var modules = {}; 1941 1942 /** 1943 * @ngdoc function 1944 * @name angular.module 1945 * @module ng 1946 * @description 1947 * 1948 * The `angular.module` is a global place for creating, registering and retrieving Angular 1949 * modules. 1950 * All modules (angular core or 3rd party) that should be available to an application must be 1951 * registered using this mechanism. 1952 * 1953 * Passing one argument retrieves an existing {@link angular.Module}, 1954 * whereas passing more than one argument creates a new {@link angular.Module} 1955 * 1956 * 1957 * # Module 1958 * 1959 * A module is a collection of services, directives, controllers, filters, and configuration information. 1960 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1961 * 1962 * ```js 1963 * // Create a new module 1964 * var myModule = angular.module('myModule', []); 1965 * 1966 * // register a new service 1967 * myModule.value('appName', 'MyCoolApp'); 1968 * 1969 * // configure existing services inside initialization blocks. 1970 * myModule.config(['$locationProvider', function($locationProvider) { 1971 * // Configure existing providers 1972 * $locationProvider.hashPrefix('!'); 1973 * }]); 1974 * ``` 1975 * 1976 * Then you can create an injector and load your modules like this: 1977 * 1978 * ```js 1979 * var injector = angular.injector(['ng', 'myModule']) 1980 * ``` 1981 * 1982 * However it's more likely that you'll just use 1983 * {@link ng.directive:ngApp ngApp} or 1984 * {@link angular.bootstrap} to simplify this process for you. 1985 * 1986 * @param {!string} name The name of the module to create or retrieve. 1987 * @param {!Array.<string>=} requires If specified then new module is being created. If 1988 * unspecified then the module is being retrieved for further configuration. 1989 * @param {Function=} configFn Optional configuration function for the module. Same as 1990 * {@link angular.Module#config Module#config()}. 1991 * @returns {angular.Module} new module with the {@link angular.Module} api. 1992 */ 1993 return function module(name, requires, configFn) { 1994 var assertNotHasOwnProperty = function(name, context) { 1995 if (name === 'hasOwnProperty') { 1996 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1997 } 1998 }; 1999 2000 assertNotHasOwnProperty(name, 'module'); 2001 if (requires && modules.hasOwnProperty(name)) { 2002 modules[name] = null; 2003 } 2004 return ensure(modules, name, function() { 2005 if (!requires) { 2006 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 2007 "the module name or forgot to load it. If registering a module ensure that you " + 2008 "specify the dependencies as the second argument.", name); 2009 } 2010 2011 /** @type {!Array.<Array.<*>>} */ 2012 var invokeQueue = []; 2013 2014 /** @type {!Array.<Function>} */ 2015 var configBlocks = []; 2016 2017 /** @type {!Array.<Function>} */ 2018 var runBlocks = []; 2019 2020 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 2021 2022 /** @type {angular.Module} */ 2023 var moduleInstance = { 2024 // Private state 2025 _invokeQueue: invokeQueue, 2026 _configBlocks: configBlocks, 2027 _runBlocks: runBlocks, 2028 2029 /** 2030 * @ngdoc property 2031 * @name angular.Module#requires 2032 * @module ng 2033 * 2034 * @description 2035 * Holds the list of modules which the injector will load before the current module is 2036 * loaded. 2037 */ 2038 requires: requires, 2039 2040 /** 2041 * @ngdoc property 2042 * @name angular.Module#name 2043 * @module ng 2044 *
2045 * @description 2046 * Name of the module. 2047 */ 2048 name: name, 2049 2050 2051 /** 2052 * @ngdoc method 2053 * @name angular.Module#provider 2054 * @module ng 2055 * @param {string} name service name 2056 * @param {Function} providerType Construction function for creating new instance of the 2057 * service. 2058 * @description 2059 * See {@link auto.$provide#provider $provide.provider()}. 2060 */ 2061 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 2062 2063 /** 2064 * @ngdoc method 2065 * @name angular.Module#factory 2066 * @module ng 2067 * @param {string} name service name 2068 * @param {Function} providerFunction Function for creating new instance of the service. 2069 * @description 2070 * See {@link auto.$provide#factory $provide.factory()}. 2071 */ 2072 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 2073 2074 /** 2075 * @ngdoc method 2076 * @name angular.Module#service 2077 * @module ng 2078 * @param {string} name service name 2079 * @param {Function} constructor A constructor function that will be instantiated. 2080 * @description 2081 * See {@link auto.$provide#service $provide.service()}. 2082 */ 2083 service: invokeLaterAndSetModuleName('$provide', 'service'), 2084 2085 /** 2086 * @ngdoc method 2087 * @name angular.Module#value 2088 * @module ng 2089 * @param {string} name service name 2090 * @param {*} object Service instance object. 2091 * @description 2092 * See {@link auto.$provide#value $provide.value()}. 2093 */ 2094 value: invokeLater('$provide', 'value'), 2095 2096 /** 2097 * @ngdoc method 2098 * @name angular.Module#constant 2099 * @module ng 2100 * @param {string} name constant name 2101 * @param {*} object Constant value. 2102 * @description 2103 * Because the constants are fixed, they get applied before other provide methods. 2104 * See {@link auto.$provide#constant $provide.constant()}. 2105 */ 2106 constant: invokeLater('$provide', 'constant', 'unshift'), 2107 2108 /** 2109 * @ngdoc method 2110 * @name angular.Module#decorator 2111 * @module ng 2112 * @param {string} The name of the service to decorate. 2113 * @param {Function} This function will be invoked when the service needs to be 2114 * instantiated and should return the decorated service instance. 2115 * @description 2116 * See {@link auto.$provide#decorator $provide.decorator()}. 2117 */ 2118 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 2119 2120 /** 2121 * @ngdoc method 2122 * @name angular.Module#animation 2123 * @module ng 2124 * @param {string} name animation name 2125 * @param {Function} animationFactory Factory function for creating new instance of an 2126 * animation. 2127 * @description 2128 * 2129 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2130 * 2131 * 2132 * Defines an animation hook that can be later used with 2133 * {@link $animate $animate} service and directives that use this service. 2134 * 2135 * ```js 2136 * module.animation('.animation-name', function($inject1, $inject2) { 2137 * return { 2138 * eventName : function(element, done) { 2139 * //code to run the animation 2140 * //once complete, then run done() 2141 * return function cancellationFunction(element) { 2142 * //code to cancel the animation 2143 * } 2144 * } 2145 * } 2146 * }) 2147 * ``` 2148 * 2149 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2150 * {@link ngAnimate ngAnimate module} for more information. 2151 */ 2152 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 2153 2154 /** 2155 * @ngdoc method 2156 * @name angular.Module#filter 2157 * @module ng 2158 * @param {string} name Filter name - this must be a valid angular expression identifier 2159 * @param {Function} filterFactory Factory function for creating new instance of filter.
2160 * @description 2161 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2162 * 2163 * <div class="alert alert-warning"> 2164 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 2165 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2166 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2167 * (`myapp_subsection_filterx`). 2168 * </div> 2169 */ 2170 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 2171 2172 /** 2173 * @ngdoc method 2174 * @name angular.Module#controller 2175 * @module ng 2176 * @param {string|Object} name Controller name, or an object map of controllers where the 2177 * keys are the names and the values are the constructors. 2178 * @param {Function} constructor Controller constructor function. 2179 * @description 2180 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2181 */ 2182 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 2183 2184 /** 2185 * @ngdoc method 2186 * @name angular.Module#directive 2187 * @module ng 2188 * @param {string|Object} name Directive name, or an object map of directives where the 2189 * keys are the names and the values are the factories. 2190 * @param {Function} directiveFactory Factory function for creating new instance of 2191 * directives. 2192 * @description 2193 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2194 */ 2195 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 2196 2197 /** 2198 * @ngdoc method 2199 * @name angular.Module#config 2200 * @module ng 2201 * @param {Function} configFn Execute this function on module load. Useful for service 2202 * configuration. 2203 * @description 2204 * Use this method to register work which needs to be performed on module loading. 2205 * For more about how to configure services, see 2206 * {@link providers#provider-recipe Provider Recipe}. 2207 */ 2208 config: config, 2209 2210 /** 2211 * @ngdoc method 2212 * @name angular.Module#run 2213 * @module ng 2214 * @param {Function} initializationFn Execute this function after injector creation. 2215 * Useful for application initialization. 2216 * @description 2217 * Use this method to register work which should be performed when the injector is done 2218 * loading all modules. 2219 */ 2220 run: function(block) { 2221 runBlocks.push(block); 2222 return this; 2223 } 2224 }; 2225 2226 if (configFn) { 2227 config(configFn); 2228 } 2229 2230 return moduleInstance; 2231 2232 /** 2233 * @param {string} provider 2234 * @param {string} method 2235 * @param {String=} insertMethod 2236 * @returns {angular.Module} 2237 */ 2238 function invokeLater(provider, method, insertMethod, queue) { 2239 if (!queue) queue = invokeQueue; 2240 return function() { 2241 queue[insertMethod || 'push']([provider, method, arguments]); 2242 return moduleInstance; 2243 }; 2244 } 2245 2246 /** 2247 * @param {string} provider 2248 * @param {string} method 2249 * @returns {angular.Module} 2250 */ 2251 function invokeLaterAndSetModuleName(provider, method) { 2252 return function(recipeName, factoryFunction) { 2253 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 2254 invokeQueue.push([provider, method, arguments]); 2255 return moduleInstance; 2256 }; 2257 } 2258 }); 2259 }; 2260 }); 2261 2262} 2263 2264/* global: toDebugString: true */ 2265 2266function serializeObject(obj) { 2267 var seen = []; 2268 2269 return JSON.stringify(obj, function(key, val) { 2270 val = toJsonReplacer(key, val); 2271 if (isObject(val)) { 2272 2273 if (seen.indexOf(val) >= 0) return '...'; 2274 2275 seen.push(val); 2276 } 2277 return val; 2278 }); 2279} 2280 2281function toDebugString(obj) { 2282 if (typeof obj === 'function') { 2283 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2284 } else if (isUndefined(obj)) { 2285 return 'undefined'; 2286 } else if (typeof obj !== 'string') { 2287 return serializeObject(obj); 2288 } 2289 return obj; 2290} 2291 2292/* global angularModule: true, 2293 version: true, 2294 2295 $CompileProvider, 2296 2297 htmlAnchorDirective, 2298 inputDirective, 2299 inputDirective, 2300 formDirective, 2301 scriptDirective, 2302 selectDirective, 2303 styleDirective, 2304 optionDirective, 2305 ngBindDirective, 2306 ngBindHtmlDirective, 2307 ngBindTemplateDirective, 2308 ngClassDirective, 2309 ngClassEvenDirective, 2310 ngClassOddDirective, 2311 ngCloakDirective, 2312 ngControllerDirective, 2313 ngFormDirective, 2314 ngHideDirective, 2315 ngIfDirective, 2316 ngIncludeDirective, 2317 ngIncludeFillContentDirective, 2318 ngInitDirective, 2319 ngNonBindableDirective, 2320 ngPluralizeDirective, 2321 ngRepeatDirective, 2322 ngShowDirective, 2323 ngStyleDirective, 2324 ngSwitchDirective, 2325 ngSwitchWhenDirective, 2326 ngSwitchDefaultDirective, 2327 ngOptionsDirective, 2328 ngTranscludeDirective, 2329 ngModelDirective, 2330 ngListDirective, 2331 ngChangeDirective, 2332 patternDirective, 2333 patternDirective, 2334 requiredDirective, 2335 requiredDirective, 2336 minlengthDirective, 2337 minlengthDirective, 2338 maxlengthDirective, 2339 maxlengthDirective, 2340 ngValueDirective, 2341 ngModelOptionsDirective, 2342 ngAttributeAliasDirectives, 2343 ngEventDirectives, 2344 2345 $AnchorScrollProvider, 2346 $AnimateProvider, 2347 $CoreAnimateCssProvider,
2348 $$CoreAnimateJsProvider, 2349 $$CoreAnimateQueueProvider, 2350 $$AnimateRunnerFactoryProvider, 2351 $$AnimateAsyncRunFactoryProvider, 2352 $BrowserProvider, 2353 $CacheFactoryProvider, 2354 $ControllerProvider, 2355 $DocumentProvider, 2356 $ExceptionHandlerProvider, 2357 $FilterProvider, 2358 $$ForceReflowProvider, 2359 $InterpolateProvider, 2360 $IntervalProvider, 2361 $$HashMapProvider, 2362 $HttpProvider, 2363 $HttpParamSerializerProvider, 2364 $HttpParamSerializerJQLikeProvider, 2365 $HttpBackendProvider, 2366 $xhrFactoryProvider, 2367 $LocationProvider, 2368 $LogProvider, 2369 $ParseProvider, 2370 $RootScopeProvider, 2371 $QProvider, 2372 $$QProvider, 2373 $$SanitizeUriProvider, 2374 $SceProvider, 2375 $SceDelegateProvider, 2376 $SnifferProvider, 2377 $TemplateCacheProvider, 2378 $TemplateRequestProvider, 2379 $$TestabilityProvider, 2380 $TimeoutProvider, 2381 $$RAFProvider, 2382 $WindowProvider, 2383 $$jqLiteProvider, 2384 $$CookieReaderProvider 2385*/ 2386 2387 2388/** 2389 * @ngdoc object 2390 * @name angular.version 2391 * @module ng 2392 * @description 2393 * An object that contains information about the current AngularJS version. 2394 * 2395 * This object has the following properties: 2396 * 2397 * - `full` â `{string}` â Full version string, such as "0.9.18". 2398 * - `major` â `{number}` â Major version number, such as "0". 2399 * - `minor` â `{number}` â Minor version number, such as "9". 2400 * - `dot` â `{number}` â Dot version number, such as "18". 2401 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 2402 */ 2403var version = { 2404 full: '1.4.9', // all of these placeholder strings will be replaced by grunt's 2405 major: 1, // package task 2406 minor: 4, 2407 dot: 9, 2408 codeName: 'implicit-superannuation' 2409}; 2410 2411 2412function publishExternalAPI(angular) { 2413 extend(angular, { 2414 'bootstrap': bootstrap, 2415 'copy': copy, 2416 'extend': extend, 2417 'merge': merge, 2418 'equals': equals, 2419 'element': jqLite, 2420 'forEach': forEach, 2421 'injector': createInjector, 2422 'noop': noop, 2423 'bind': bind, 2424 'toJson': toJson, 2425 'fromJson': fromJson, 2426 'identity': identity, 2427 'isUndefined': isUndefined, 2428 'isDefined': isDefined, 2429 'isString': isString, 2430 'isFunction': isFunction, 2431 'isObject': isObject, 2432 'isNumber': isNumber, 2433 'isElement': isElement, 2434 'isArray': isArray, 2435 'version': version, 2436 'isDate': isDate, 2437 'lowercase': lowercase, 2438 'uppercase': uppercase, 2439 'callbacks': {counter: 0}, 2440 'getTestability': getTestability, 2441 '$$minErr': minErr, 2442 '$$csp': csp, 2443 'reloadWithDebugInfo': reloadWithDebugInfo 2444 }); 2445 2446 angularModule = setupModuleLoader(window); 2447 2448 angularModule('ng', ['ngLocale'], ['$provide', 2449 function ngModule($provide) { 2450 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2451 $provide.provider({ 2452 $$sanitizeUri: $$SanitizeUriProvider 2453 }); 2454 $provide.provider('$compile', $CompileProvider). 2455 directive({ 2456 a: htmlAnchorDirective, 2457 input: inputDirective, 2458 textarea: inputDirective, 2459 form: formDirective, 2460 script: scriptDirective, 2461 select: selectDirective, 2462 style: styleDirective, 2463 option: optionDirective, 2464 ngBind: ngBindDirective, 2465 ngBindHtml: ngBindHtmlDirective, 2466 ngBindTemplate: ngBindTemplateDirective, 2467 ngClass: ngClassDirective, 2468 ngClassEven: ngClassEvenDirective, 2469 ngClassOdd: ngClassOddDirective, 2470 ngCloak: ngCloakDirective, 2471 ngController: ngControllerDirective, 2472 ngForm: ngFormDirective, 2473 ngHide: ngHideDirective, 2474 ngIf: ngIfDirective, 2475 ngInclude: ngIncludeDirective, 2476 ngInit: ngInitDirective, 2477 ngNonBindable: ngNonBindableDirective, 2478 ngPluralize: ngPluralizeDirective, 2479 ngRepeat: ngRepeatDirective, 2480 ngShow: ngShowDirective, 2481 ngStyle: ngStyleDirective, 2482 ngSwitch: ngSwitchDirective, 2483 ngSwitchWhen: ngSwitchWhenDirective, 2484 ngSwitchDefault: ngSwitchDefaultDirective, 2485 ngOptions: ngOptionsDirective, 2486 ngTransclude: ngTranscludeDirective, 2487 ngModel: ngModelDirective, 2488 ngList: ngListDirective, 2489 ngChange: ngChangeDirective, 2490 pattern: patternDirective, 2491 ngPattern: patternDirective, 2492 required: requiredDirective, 2493 ngRequired: requiredDirective, 2494 minlength: minlengthDirective, 2495 ngMinlength: minlengthDirective, 2496 maxlength: maxlengthDirective, 2497 ngMaxlength: maxlengthDirective, 2498 ngValue: ngValueDirective, 2499 ngModelOptions: ngModelOptionsDirective 2500 }). 2501 directive({ 2502 ngInclude: ngIncludeFillContentDirective 2503 }). 2504 directive(ngAttributeAliasDirectives). 2505 directive(ngEventDirectives); 2506 $provide.provider({ 2507 $anchorScroll: $AnchorScrollProvider, 2508 $animate: $AnimateProvider, 2509 $animateCss: $CoreAnimateCssProvider,
2510 $$animateJs: $$CoreAnimateJsProvider, 2511 $$animateQueue: $$CoreAnimateQueueProvider, 2512 $$AnimateRunner: $$AnimateRunnerFactoryProvider, 2513 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, 2514 $browser: $BrowserProvider, 2515 $cacheFactory: $CacheFactoryProvider, 2516 $controller: $ControllerProvider, 2517 $document: $DocumentProvider, 2518 $exceptionHandler: $ExceptionHandlerProvider, 2519 $filter: $FilterProvider, 2520 $$forceReflow: $$ForceReflowProvider, 2521 $interpolate: $InterpolateProvider, 2522 $interval: $IntervalProvider, 2523 $http: $HttpProvider, 2524 $httpParamSerializer: $HttpParamSerializerProvider, 2525 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2526 $httpBackend: $HttpBackendProvider, 2527 $xhrFactory: $xhrFactoryProvider, 2528 $location: $LocationProvider, 2529 $log: $LogProvider, 2530 $parse: $ParseProvider, 2531 $rootScope: $RootScopeProvider, 2532 $q: $QProvider, 2533 $$q: $$QProvider, 2534 $sce: $SceProvider, 2535 $sceDelegate: $SceDelegateProvider, 2536 $sniffer: $SnifferProvider, 2537 $templateCache: $TemplateCacheProvider, 2538 $templateRequest: $TemplateRequestProvider, 2539 $$testability: $$TestabilityProvider, 2540 $timeout: $TimeoutProvider, 2541 $window: $WindowProvider, 2542 $$rAF: $$RAFProvider, 2543 $$jqLite: $$jqLiteProvider, 2544 $$HashMap: $$HashMapProvider, 2545 $$cookieReader: $$CookieReaderProvider 2546 }); 2547 } 2548 ]); 2549} 2550 2551/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2552 * Any commits to this file should be reviewed with security in mind. * 2553 * Changes to this file can potentially create security vulnerabilities. * 2554 * An approval from 2 Core members with history of modifying * 2555 * this file is required. * 2556 * * 2557 * Does the change somehow allow for arbitrary javascript to be executed? * 2558 * Or allows for someone to change the prototype of built-in objects? * 2559 * Or gives undesired access to variables likes document or window? * 2560 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2561 2562/* global JQLitePrototype: true, 2563 addEventListenerFn: true, 2564 removeEventListenerFn: true, 2565 BOOLEAN_ATTR: true, 2566 ALIASED_ATTR: true, 2567*/ 2568 2569////////////////////////////////// 2570//JQLite 2571////////////////////////////////// 2572 2573/** 2574 * @ngdoc function 2575 * @name angular.element 2576 * @module ng 2577 * @kind function 2578 * 2579 * @description 2580 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2581 * 2582 * If jQuery is available, `angular.element` is an alias for the 2583 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2584 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**. 2585 * 2586 * jqLite is a tiny, API-compatible subset of jQuery that allows 2587 * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most 2588 * commonly needed functionality with the goal of having a very small footprint. 2589 * 2590 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the 2591 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a 2592 * specific version of jQuery if multiple versions exist on the page. 2593 * 2594 * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or 2595 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div> 2596 * 2597 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements 2598 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` 2599 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div> 2600 * 2601 * ## Angular's jqLite 2602 * jqLite provides only the following jQuery methods: 2603 * 2604 * - [`addClass()`](http://api.jquery.com/addClass/) 2605 * - [`after()`](http://api.jquery.com/after/) 2606 * - [`append()`](http://api.jquery.com/append/) 2607 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 2608 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2609 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2610 * - [`clone()`](http://api.jquery.com/clone/) 2611 * - [`contents()`](http://api.jquery.com/contents/) 2612 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
2613 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. 2614 * - [`data()`](http://api.jquery.com/data/) 2615 * - [`detach()`](http://api.jquery.com/detach/) 2616 * - [`empty()`](http://api.jquery.com/empty/) 2617 * - [`eq()`](http://api.jquery.com/eq/) 2618 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2619 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2620 * - [`html()`](http://api.jquery.com/html/) 2621 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2622 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2623 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 2624 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2625 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2626 * - [`prepend()`](http://api.jquery.com/prepend/) 2627 * - [`prop()`](http://api.jquery.com/prop/) 2628 * - [`ready()`](http://api.jquery.com/ready/) 2629 * - [`remove()`](http://api.jquery.com/remove/) 2630 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2631 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2632 * - [`removeData()`](http://api.jquery.com/removeData/) 2633 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2634 * - [`text()`](http://api.jquery.com/text/) 2635 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2636 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2637 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter 2638 * - [`val()`](http://api.jquery.com/val/) 2639 * - [`wrap()`](http://api.jquery.com/wrap/) 2640 * 2641 * ## jQuery/jqLite Extras 2642 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2643 * 2644 * ### Events 2645 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event 2646 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2647 * element before it is removed. 2648 * 2649 * ### Methods 2650 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2651 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2652 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2653 * `'ngModel'`). 2654 * - `injector()` - retrieves the injector of the current element or its parent. 2655 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2656 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 2657 * be enabled. 2658 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2659 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2660 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2661 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 2662 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2663 * parent element is reached. 2664 * 2665 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2666 * @returns {Object} jQuery object. 2667 */ 2668 2669JQLite.expando = 'ng339'; 2670 2671var jqCache = JQLite.cache = {}, 2672 jqId = 1, 2673 addEventListenerFn = function(element, type, fn) { 2674 element.addEventListener(type, fn, false); 2675 }, 2676 removeEventListenerFn = function(element, type, fn) { 2677 element.removeEventListener(type, fn, false); 2678 }; 2679 2680/* 2681 * !!! This is an undocumented "private" function !!! 2682 */ 2683JQLite._data = function(node) { 2684 //jQuery always returns an object on cache miss 2685 return this.cache[node[this.expando]] || {}; 2686}; 2687 2688function jqNextId() { return ++jqId; } 2689 2690 2691var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2692var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2693var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 2694var jqLiteMinErr = minErr('jqLite'); 2695 2696/** 2697 * Converts snake_case to camelCase. 2698 * Also there is special case for Moz prefix starting with upper case letter. 2699 * @param name Name to normalize 2700 */ 2701function camelCase(name) { 2702 return name. 2703 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2704 return offset ? letter.toUpperCase() : letter; 2705 }). 2706 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2707} 2708 2709var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2710var HTML_REGEXP = /<|&#?\w+;/; 2711var TAG_NAME_REGEXP = /<([\w:-]+)/; 2712var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 2713 2714var wrapMap = { 2715 'option': [1, '<select multiple="multiple">', '</select>'], 2716 2717 'thead': [1, '<table>', '</table>'], 2718 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2719 'tr': [2, '<table><tbody>', '</tbody></table>'], 2720 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2721 '_default': [0, "", ""] 2722}; 2723 2724wrapMap.optgroup = wrapMap.option; 2725wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2726wrapMap.th = wrapMap.td; 2727 2728 2729function jqLiteIsTextNode(html) { 2730 return !HTML_REGEXP.test(html); 2731} 2732 2733function jqLiteAcceptsData(node) { 2734 // The window object can accept data but has no nodeType 2735 // Otherwise we are only interested in elements (1) and documents (9) 2736 var nodeType = node.nodeType; 2737 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 2738} 2739 2740function jqLiteHasData(node) { 2741 for (var key in jqCache[node.ng339]) { 2742 return true; 2743 } 2744 return false; 2745} 2746 2747function jqLiteBuildFragment(html, context) { 2748 var tmp, tag, wrap, 2749 fragment = context.createDocumentFragment(), 2750 nodes = [], i; 2751 2752 if (jqLiteIsTextNode(html)) { 2753 // Convert non-html into a text node 2754 nodes.push(context.createTextNode(html)); 2755 } else { 2756 // Convert html into DOM nodes 2757 tmp = tmp || fragment.appendChild(context.createElement("div")); 2758 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2759 wrap = wrapMap[tag] || wrapMap._default; 2760 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2761 2762 // Descend through wrappers to the right content 2763 i = wrap[0]; 2764 while (i--) { 2765 tmp = tmp.lastChild; 2766 } 2767 2768 nodes = concat(nodes, tmp.childNodes); 2769 2770 tmp = fragment.firstChild; 2771 tmp.textContent = ""; 2772 } 2773 2774 // Remove wrapper from fragment 2775 fragment.textContent = ""; 2776 fragment.innerHTML = ""; // Clear inner HTML 2777 forEach(nodes, function(node) { 2778 fragment.appendChild(node); 2779 }); 2780 2781 return fragment; 2782} 2783 2784function jqLiteParseHTML(html, context) { 2785 context = context || document; 2786 var parsed; 2787 2788 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2789 return [context.createElement(parsed[1])]; 2790 } 2791 2792 if ((parsed = jqLiteBuildFragment(html, context))) { 2793 return parsed.childNodes; 2794 } 2795 2796 return []; 2797} 2798 2799 2800// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 2801var jqLiteContains = Node.prototype.contains || function(arg) { 2802 // jshint bitwise: false 2803 return !!(this.compareDocumentPosition(arg) & 16); 2804 // jshint bitwise: true 2805}; 2806 2807///////////////////////////////////////////// 2808function JQLite(element) { 2809 if (element instanceof JQLite) { 2810 return element; 2811 } 2812 2813 var argIsString; 2814 2815 if (isString(element)) { 2816 element = trim(element); 2817 argIsString = true; 2818 } 2819 if (!(this instanceof JQLite)) { 2820 if (argIsString && element.charAt(0) != '<') { 2821 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2822 } 2823 return new JQLite(element); 2824 } 2825 2826 if (argIsString) { 2827 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2828 } else { 2829 jqLiteAddNodes(this, element); 2830 } 2831} 2832 2833function jqLiteClone(element) { 2834 return element.cloneNode(true); 2835} 2836 2837function jqLiteDealoc(element, onlyDescendants) { 2838 if (!onlyDescendants) jqLiteRemoveData(element); 2839 2840 if (element.querySelectorAll) { 2841 var descendants = element.querySelectorAll('*'); 2842 for (var i = 0, l = descendants.length; i < l; i++) { 2843 jqLiteRemoveData(descendants[i]); 2844 } 2845 } 2846} 2847 2848function jqLiteOff(element, type, fn, unsupported) {
2849 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2850 2851 var expandoStore = jqLiteExpandoStore(element); 2852 var events = expandoStore && expandoStore.events; 2853 var handle = expandoStore && expandoStore.handle; 2854 2855 if (!handle) return; //no listeners registered 2856 2857 if (!type) { 2858 for (type in events) { 2859 if (type !== '$destroy') { 2860 removeEventListenerFn(element, type, handle); 2861 } 2862 delete events[type]; 2863 } 2864 } else { 2865 2866 var removeHandler = function(type) { 2867 var listenerFns = events[type]; 2868 if (isDefined(fn)) { 2869 arrayRemove(listenerFns || [], fn); 2870 } 2871 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { 2872 removeEventListenerFn(element, type, handle); 2873 delete events[type]; 2874 } 2875 }; 2876 2877 forEach(type.split(' '), function(type) { 2878 removeHandler(type); 2879 if (MOUSE_EVENT_MAP[type]) { 2880 removeHandler(MOUSE_EVENT_MAP[type]); 2881 } 2882 }); 2883 } 2884} 2885 2886function jqLiteRemoveData(element, name) { 2887 var expandoId = element.ng339; 2888 var expandoStore = expandoId && jqCache[expandoId]; 2889 2890 if (expandoStore) { 2891 if (name) { 2892 delete expandoStore.data[name]; 2893 return; 2894 } 2895 2896 if (expandoStore.handle) { 2897 if (expandoStore.events.$destroy) { 2898 expandoStore.handle({}, '$destroy'); 2899 } 2900 jqLiteOff(element); 2901 } 2902 delete jqCache[expandoId]; 2903 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2904 } 2905} 2906 2907 2908function jqLiteExpandoStore(element, createIfNecessary) { 2909 var expandoId = element.ng339, 2910 expandoStore = expandoId && jqCache[expandoId]; 2911 2912 if (createIfNecessary && !expandoStore) { 2913 element.ng339 = expandoId = jqNextId(); 2914 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 2915 } 2916 2917 return expandoStore; 2918} 2919 2920 2921function jqLiteData(element, key, value) { 2922 if (jqLiteAcceptsData(element)) { 2923 2924 var isSimpleSetter = isDefined(value); 2925 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 2926 var massGetter = !key; 2927 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 2928 var data = expandoStore && expandoStore.data; 2929 2930 if (isSimpleSetter) { // data('key', value) 2931 data[key] = value; 2932 } else { 2933 if (massGetter) { // data() 2934 return data; 2935 } else { 2936 if (isSimpleGetter) { // data('key') 2937 // don't force creation of expandoStore if it doesn't exist yet 2938 return data && data[key]; 2939 } else { // mass-setter: data({key1: val1, key2: val2}) 2940 extend(data, key); 2941 } 2942 } 2943 } 2944 } 2945} 2946 2947function jqLiteHasClass(element, selector) { 2948 if (!element.getAttribute) return false; 2949 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2950 indexOf(" " + selector + " ") > -1); 2951} 2952 2953function jqLiteRemoveClass(element, cssClasses) { 2954 if (cssClasses && element.setAttribute) { 2955 forEach(cssClasses.split(' '), function(cssClass) { 2956 element.setAttribute('class', trim( 2957 (" " + (element.getAttribute('class') || '') + " ") 2958 .replace(/[\n\t]/g, " ") 2959 .replace(" " + trim(cssClass) + " ", " ")) 2960 ); 2961 }); 2962 } 2963} 2964 2965function jqLiteAddClass(element, cssClasses) { 2966 if (cssClasses && element.setAttribute) { 2967 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2968 .replace(/[\n\t]/g, " "); 2969 2970 forEach(cssClasses.split(' '), function(cssClass) { 2971 cssClass = trim(cssClass); 2972 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2973 existingClasses += cssClass + ' '; 2974 } 2975 }); 2976 2977 element.setAttribute('class', trim(existingClasses)); 2978 } 2979} 2980 2981 2982function jqLiteAddNodes(root, elements) { 2983 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 2984 2985 if (elements) { 2986 2987 // if a Node (the most common case) 2988 if (elements.nodeType) { 2989 root[root.length++] = elements; 2990 } else { 2991 var length = elements.length; 2992 2993 // if an Array or NodeList and not a Window 2994 if (typeof length === 'number' && elements.window !== elements) { 2995 if (length) { 2996 for (var i = 0; i < length; i++) { 2997 root[root.length++] = elements[i]; 2998 } 2999 } 3000 } else { 3001 root[root.length++] = elements; 3002 } 3003 } 3004 } 3005} 3006 3007 3008function jqLiteController(element, name) { 3009 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 3010} 3011 3012function jqLiteInheritedData(element, name, value) { 3013 // if element is the document object work with the html element instead 3014 // this makes $(document).scope() possible 3015 if (element.nodeType == NODE_TYPE_DOCUMENT) { 3016 element = element.documentElement; 3017 } 3018 var names = isArray(name) ? name : [name]; 3019 3020 while (element) { 3021 for (var i = 0, ii = names.length; i < ii; i++) { 3022 if (isDefined(value = jqLite.data(element, names[i]))) return value; 3023 } 3024 3025 // If dealing with a document fragment node with a host element, and no parent, use the host 3026 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 3027 // to lookup parent controllers. 3028 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 3029 } 3030} 3031 3032function jqLiteEmpty(element) { 3033 jqLiteDealoc(element, true); 3034 while (element.firstChild) { 3035 element.removeChild(element.firstChild); 3036 } 3037} 3038 3039function jqLiteRemove(element, keepData) { 3040 if (!keepData) jqLiteDealoc(element); 3041 var parent = element.parentNode; 3042 if (parent) parent.removeChild(element); 3043} 3044 3045 3046function jqLiteDocumentLoaded(action, win) { 3047 win = win || window; 3048 if (win.document.readyState === 'complete') { 3049 // Force the action to be run async for consistent behaviour 3050 // from the action's point of view 3051 // i.e. it will definitely not be in a $apply 3052 win.setTimeout(action); 3053 } else { 3054 // No need to unbind this handler as load is only ever called once 3055 jqLite(win).on('load', action); 3056 } 3057} 3058
3059////////////////////////////////////////// 3060// Functions which are declared directly. 3061////////////////////////////////////////// 3062var JQLitePrototype = JQLite.prototype = { 3063 ready: function(fn) { 3064 var fired = false; 3065 3066 function trigger() { 3067 if (fired) return; 3068 fired = true; 3069 fn(); 3070 } 3071 3072 // check if document is already loaded 3073 if (document.readyState === 'complete') { 3074 setTimeout(trigger); 3075 } else { 3076 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 3077 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 3078 // jshint -W064 3079 JQLite(window).on('load', trigger); // fallback to window.onload for others 3080 // jshint +W064 3081 } 3082 }, 3083 toString: function() { 3084 var value = []; 3085 forEach(this, function(e) { value.push('' + e);}); 3086 return '[' + value.join(', ') + ']'; 3087 }, 3088 3089 eq: function(index) { 3090 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 3091 }, 3092 3093 length: 0, 3094 push: push, 3095 sort: [].sort, 3096 splice: [].splice 3097}; 3098 3099////////////////////////////////////////// 3100// Functions iterating getter/setters. 3101// these functions return self on setter and 3102// value on get. 3103////////////////////////////////////////// 3104var BOOLEAN_ATTR = {}; 3105forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3106 BOOLEAN_ATTR[lowercase(value)] = value; 3107}); 3108var BOOLEAN_ELEMENTS = {}; 3109forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3110 BOOLEAN_ELEMENTS[value] = true; 3111}); 3112var ALIASED_ATTR = { 3113 'ngMinlength': 'minlength', 3114 'ngMaxlength': 'maxlength', 3115 'ngMin': 'min', 3116 'ngMax': 'max', 3117 'ngPattern': 'pattern' 3118}; 3119 3120function getBooleanAttrName(element, name) { 3121 // check dom last since we will most likely fail on name 3122 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3123 3124 // booleanAttr is here twice to minimize DOM access 3125 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3126} 3127 3128function getAliasedAttrName(name) { 3129 return ALIASED_ATTR[name]; 3130} 3131 3132forEach({ 3133 data: jqLiteData, 3134 removeData: jqLiteRemoveData, 3135 hasData: jqLiteHasData 3136}, function(fn, name) { 3137 JQLite[name] = fn; 3138}); 3139 3140forEach({ 3141 data: jqLiteData, 3142 inheritedData: jqLiteInheritedData, 3143 3144 scope: function(element) { 3145 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3146 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3147 }, 3148 3149 isolateScope: function(element) { 3150 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3151 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3152 }, 3153 3154 controller: jqLiteController, 3155 3156 injector: function(element) { 3157 return jqLiteInheritedData(element, '$injector'); 3158 }, 3159 3160 removeAttr: function(element, name) { 3161 element.removeAttribute(name); 3162 }, 3163 3164 hasClass: jqLiteHasClass, 3165 3166 css: function(element, name, value) { 3167 name = camelCase(name); 3168 3169 if (isDefined(value)) { 3170 element.style[name] = value; 3171 } else { 3172 return element.style[name]; 3173 } 3174 }, 3175 3176 attr: function(element, name, value) { 3177 var nodeType = element.nodeType; 3178 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 3179 return; 3180 } 3181 var lowercasedName = lowercase(name); 3182 if (BOOLEAN_ATTR[lowercasedName]) { 3183 if (isDefined(value)) { 3184 if (!!value) { 3185 element[name] = true; 3186 element.setAttribute(name, lowercasedName); 3187 } else { 3188 element[name] = false; 3189 element.removeAttribute(lowercasedName); 3190 } 3191 } else { 3192 return (element[name] || 3193 (element.attributes.getNamedItem(name) || noop).specified) 3194 ? lowercasedName 3195 : undefined; 3196 } 3197 } else if (isDefined(value)) { 3198 element.setAttribute(name, value); 3199 } else if (element.getAttribute) { 3200 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 3201 // some elements (e.g. Document) don't have get attribute, so return undefined 3202 var ret = element.getAttribute(name, 2); 3203 // normalize non-existing attributes to undefined (as jQuery) 3204 return ret === null ? undefined : ret; 3205 } 3206 }, 3207 3208 prop: function(element, name, value) { 3209 if (isDefined(value)) { 3210 element[name] = value; 3211 } else { 3212 return element[name]; 3213 } 3214 }, 3215 3216 text: (function() { 3217 getText.$dv = ''; 3218 return getText; 3219 3220 function getText(element, value) { 3221 if (isUndefined(value)) { 3222 var nodeType = element.nodeType; 3223 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3224 } 3225 element.textContent = value; 3226 } 3227 })(), 3228 3229 val: function(element, value) { 3230 if (isUndefined(value)) { 3231 if (element.multiple && nodeName_(element) === 'select') { 3232 var result = [];
vendor: 10,377 bytes, lines 3233-3577
3233 forEach(element.options, function(option) { 3234 if (option.selected) { 3235 result.push(option.value || option.text); 3236 } 3237 }); 3238 return result.length === 0 ? null : result; 3239 } 3240 return element.value; 3241 } 3242 element.value = value; 3243 }, 3244 3245 html: function(element, value) { 3246 if (isUndefined(value)) { 3247 return element.innerHTML; 3248 } 3249 jqLiteDealoc(element, true); 3250 element.innerHTML = value; 3251 }, 3252 3253 empty: jqLiteEmpty 3254}, function(fn, name) { 3255 /** 3256 * Properties: writes return selection, reads return first value 3257 */ 3258 JQLite.prototype[name] = function(arg1, arg2) { 3259 var i, key; 3260 var nodeCount = this.length; 3261 3262 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3263 // in a way that survives minification. 3264 // jqLiteEmpty takes no arguments but is a setter. 3265 if (fn !== jqLiteEmpty && 3266 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 3267 if (isObject(arg1)) { 3268 3269 // we are a write, but the object properties are the key/values 3270 for (i = 0; i < nodeCount; i++) { 3271 if (fn === jqLiteData) { 3272 // data() takes the whole object in jQuery 3273 fn(this[i], arg1); 3274 } else { 3275 for (key in arg1) { 3276 fn(this[i], key, arg1[key]); 3277 } 3278 } 3279 } 3280 // return self for chaining 3281 return this; 3282 } else { 3283 // we are a read, so read the first child. 3284 // TODO: do we still need this? 3285 var value = fn.$dv; 3286 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3287 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 3288 for (var j = 0; j < jj; j++) { 3289 var nodeValue = fn(this[j], arg1, arg2); 3290 value = value ? value + nodeValue : nodeValue; 3291 } 3292 return value; 3293 } 3294 } else { 3295 // we are a write, so apply to all children 3296 for (i = 0; i < nodeCount; i++) { 3297 fn(this[i], arg1, arg2); 3298 } 3299 // return self for chaining 3300 return this; 3301 } 3302 }; 3303}); 3304 3305function createEventHandler(element, events) { 3306 var eventHandler = function(event, type) { 3307 // jQuery specific api 3308 event.isDefaultPrevented = function() { 3309 return event.defaultPrevented; 3310 }; 3311 3312 var eventFns = events[type || event.type]; 3313 var eventFnsLength = eventFns ? eventFns.length : 0; 3314 3315 if (!eventFnsLength) return; 3316 3317 if (isUndefined(event.immediatePropagationStopped)) { 3318 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3319 event.stopImmediatePropagation = function() { 3320 event.immediatePropagationStopped = true; 3321 3322 if (event.stopPropagation) { 3323 event.stopPropagation(); 3324 } 3325 3326 if (originalStopImmediatePropagation) { 3327 originalStopImmediatePropagation.call(event); 3328 } 3329 }; 3330 } 3331 3332 event.isImmediatePropagationStopped = function() { 3333 return event.immediatePropagationStopped === true; 3334 }; 3335 3336 // Some events have special handlers that wrap the real handler 3337 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; 3338 3339 // Copy event handlers in case event handlers array is modified during execution. 3340 if ((eventFnsLength > 1)) { 3341 eventFns = shallowCopy(eventFns); 3342 } 3343 3344 for (var i = 0; i < eventFnsLength; i++) { 3345 if (!event.isImmediatePropagationStopped()) { 3346 handlerWrapper(element, event, eventFns[i]); 3347 } 3348 } 3349 }; 3350 3351 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3352 // events on `element` 3353 eventHandler.elem = element; 3354 return eventHandler; 3355} 3356 3357function defaultHandlerWrapper(element, event, handler) { 3358 handler.call(element, event); 3359} 3360 3361function specialMouseHandlerWrapper(target, event, handler) { 3362 // Refer to jQuery's implementation of mouseenter & mouseleave 3363 // Read about mouseenter and mouseleave: 3364 // http://www.quirksmode.org/js/events_mouse.html#link8 3365 var related = event.relatedTarget; 3366 // For mousenter/leave call the handler if related is outside the target. 3367 // NB: No relatedTarget if the mouse left/entered the browser window 3368 if (!related || (related !== target && !jqLiteContains.call(target, related))) { 3369 handler.call(target, event); 3370 } 3371} 3372 3373////////////////////////////////////////// 3374// Functions iterating traversal. 3375// These functions chain results into a single 3376// selector. 3377////////////////////////////////////////// 3378forEach({ 3379 removeData: jqLiteRemoveData, 3380 3381 on: function jqLiteOn(element, type, fn, unsupported) { 3382 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3383 3384 // Do not add event handlers to non-elements because they will not be cleaned up. 3385 if (!jqLiteAcceptsData(element)) { 3386 return; 3387 } 3388 3389 var expandoStore = jqLiteExpandoStore(element, true); 3390 var events = expandoStore.events; 3391 var handle = expandoStore.handle; 3392 3393 if (!handle) { 3394 handle = expandoStore.handle = createEventHandler(element, events); 3395 } 3396 3397 // http://jsperf.com/string-indexof-vs-split 3398 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3399 var i = types.length; 3400 3401 var addHandler = function(type, specialHandlerWrapper, noEventListener) { 3402 var eventFns = events[type]; 3403 3404 if (!eventFns) { 3405 eventFns = events[type] = []; 3406 eventFns.specialHandlerWrapper = specialHandlerWrapper; 3407 if (type !== '$destroy' && !noEventListener) { 3408 addEventListenerFn(element, type, handle); 3409 } 3410 } 3411 3412 eventFns.push(fn); 3413 }; 3414 3415 while (i--) { 3416 type = types[i]; 3417 if (MOUSE_EVENT_MAP[type]) { 3418 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); 3419 addHandler(type, undefined, true); 3420 } else { 3421 addHandler(type); 3422 } 3423 } 3424 }, 3425 3426 off: jqLiteOff, 3427 3428 one: function(element, type, fn) { 3429 element = jqLite(element); 3430 3431 //add the listener twice so that when it is called 3432 //you can remove the original function and still be 3433 //able to call element.off(ev, fn) normally 3434 element.on(type, function onFn() { 3435 element.off(type, fn); 3436 element.off(type, onFn); 3437 }); 3438 element.on(type, fn); 3439 }, 3440 3441 replaceWith: function(element, replaceNode) { 3442 var index, parent = element.parentNode; 3443 jqLiteDealoc(element); 3444 forEach(new JQLite(replaceNode), function(node) { 3445 if (index) { 3446 parent.insertBefore(node, index.nextSibling); 3447 } else { 3448 parent.replaceChild(node, element); 3449 } 3450 index = node; 3451 }); 3452 }, 3453 3454 children: function(element) { 3455 var children = []; 3456 forEach(element.childNodes, function(element) { 3457 if (element.nodeType === NODE_TYPE_ELEMENT) { 3458 children.push(element); 3459 } 3460 }); 3461 return children; 3462 }, 3463 3464 contents: function(element) { 3465 return element.contentDocument || element.childNodes || []; 3466 }, 3467 3468 append: function(element, node) { 3469 var nodeType = element.nodeType; 3470 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 3471 3472 node = new JQLite(node); 3473 3474 for (var i = 0, ii = node.length; i < ii; i++) { 3475 var child = node[i]; 3476 element.appendChild(child); 3477 } 3478 }, 3479 3480 prepend: function(element, node) { 3481 if (element.nodeType === NODE_TYPE_ELEMENT) { 3482 var index = element.firstChild; 3483 forEach(new JQLite(node), function(child) { 3484 element.insertBefore(child, index); 3485 }); 3486 } 3487 }, 3488 3489 wrap: function(element, wrapNode) { 3490 wrapNode = jqLite(wrapNode).eq(0).clone()[0]; 3491 var parent = element.parentNode; 3492 if (parent) { 3493 parent.replaceChild(wrapNode, element); 3494 } 3495 wrapNode.appendChild(element); 3496 }, 3497 3498 remove: jqLiteRemove, 3499 3500 detach: function(element) { 3501 jqLiteRemove(element, true); 3502 }, 3503 3504 after: function(element, newElement) { 3505 var index = element, parent = element.parentNode; 3506 newElement = new JQLite(newElement); 3507 3508 for (var i = 0, ii = newElement.length; i < ii; i++) { 3509 var node = newElement[i]; 3510 parent.insertBefore(node, index.nextSibling); 3511 index = node; 3512 } 3513 }, 3514 3515 addClass: jqLiteAddClass, 3516 removeClass: jqLiteRemoveClass, 3517 3518 toggleClass: function(element, selector, condition) { 3519 if (selector) { 3520 forEach(selector.split(' '), function(className) { 3521 var classCondition = condition; 3522 if (isUndefined(classCondition)) { 3523 classCondition = !jqLiteHasClass(element, className); 3524 } 3525 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3526 }); 3527 } 3528 }, 3529 3530 parent: function(element) { 3531 var parent = element.parentNode; 3532 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 3533 }, 3534 3535 next: function(element) { 3536 return element.nextElementSibling; 3537 }, 3538 3539 find: function(element, selector) { 3540 if (element.getElementsByTagName) { 3541 return element.getElementsByTagName(selector); 3542 } else { 3543 return []; 3544 } 3545 }, 3546 3547 clone: jqLiteClone, 3548 3549 triggerHandler: function(element, event, extraParameters) { 3550 3551 var dummyEvent, eventFnsCopy, handlerArgs; 3552 var eventName = event.type || event; 3553 var expandoStore = jqLiteExpandoStore(element); 3554 var events = expandoStore && expandoStore.events; 3555 var eventFns = events && events[eventName]; 3556 3557 if (eventFns) { 3558 // Create a dummy event to pass to the handlers 3559 dummyEvent = { 3560 preventDefault: function() { this.defaultPrevented = true; }, 3561 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3562 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 3563 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 3564 stopPropagation: noop, 3565 type: eventName, 3566 target: element 3567 }; 3568 3569 // If a custom event was provided then extend our dummy event with it 3570 if (event.type) { 3571 dummyEvent = extend(dummyEvent, event); 3572 } 3573 3574 // Copy event handlers in case event handlers array is modified during execution. 3575 eventFnsCopy = shallowCopy(eventFns); 3576 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3577
3578 forEach(eventFnsCopy, function(fn) { 3579 if (!dummyEvent.isImmediatePropagationStopped()) { 3580 fn.apply(element, handlerArgs); 3581 } 3582 }); 3583 } 3584 } 3585}, function(fn, name) { 3586 /** 3587 * chaining functions 3588 */ 3589 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3590 var value; 3591 3592 for (var i = 0, ii = this.length; i < ii; i++) { 3593 if (isUndefined(value)) { 3594 value = fn(this[i], arg1, arg2, arg3); 3595 if (isDefined(value)) { 3596 // any function which returns a value needs to be wrapped 3597 value = jqLite(value); 3598 } 3599 } else { 3600 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3601 } 3602 } 3603 return isDefined(value) ? value : this; 3604 }; 3605 3606 // bind legacy bind/unbind to on/off 3607 JQLite.prototype.bind = JQLite.prototype.on; 3608 JQLite.prototype.unbind = JQLite.prototype.off; 3609}); 3610 3611 3612// Provider for private $$jqLite service 3613function $$jqLiteProvider() { 3614 this.$get = function $$jqLite() { 3615 return extend(JQLite, { 3616 hasClass: function(node, classes) { 3617 if (node.attr) node = node[0]; 3618 return jqLiteHasClass(node, classes); 3619 }, 3620 addClass: function(node, classes) { 3621 if (node.attr) node = node[0]; 3622 return jqLiteAddClass(node, classes); 3623 }, 3624 removeClass: function(node, classes) { 3625 if (node.attr) node = node[0]; 3626 return jqLiteRemoveClass(node, classes); 3627 } 3628 }); 3629 }; 3630} 3631 3632/** 3633 * Computes a hash of an 'obj'. 3634 * Hash of a: 3635 * string is string 3636 * number is number as string 3637 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3638 * that is also assigned to the $$hashKey property of the object. 3639 * 3640 * @param obj 3641 * @returns {string} hash string such that the same input will have the same hash string. 3642 * The resulting string key is in 'type:hashKey' format. 3643 */ 3644function hashKey(obj, nextUidFn) { 3645 var key = obj && obj.$$hashKey; 3646 3647 if (key) { 3648 if (typeof key === 'function') { 3649 key = obj.$$hashKey(); 3650 } 3651 return key; 3652 } 3653 3654 var objType = typeof obj; 3655 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3656 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 3657 } else { 3658 key = objType + ':' + obj; 3659 } 3660 3661 return key; 3662} 3663 3664/** 3665 * HashMap which can use objects as keys 3666 */ 3667function HashMap(array, isolatedUid) { 3668 if (isolatedUid) { 3669 var uid = 0; 3670 this.nextUid = function() { 3671 return ++uid; 3672 }; 3673 } 3674 forEach(array, this.put, this); 3675} 3676HashMap.prototype = { 3677 /** 3678 * Store key value pair 3679 * @param key key to store can be any type 3680 * @param value value to store can be any type 3681 */ 3682 put: function(key, value) { 3683 this[hashKey(key, this.nextUid)] = value; 3684 }, 3685 3686 /** 3687 * @param key 3688 * @returns {Object} the value for the key 3689 */ 3690 get: function(key) { 3691 return this[hashKey(key, this.nextUid)]; 3692 }, 3693 3694 /** 3695 * Remove the key/value pair 3696 * @param key 3697 */ 3698 remove: function(key) { 3699 var value = this[key = hashKey(key, this.nextUid)]; 3700 delete this[key]; 3701 return value; 3702 } 3703}; 3704 3705var $$HashMapProvider = [function() { 3706 this.$get = [function() { 3707 return HashMap; 3708 }]; 3709}]; 3710 3711/** 3712 * @ngdoc function 3713 * @module ng 3714 * @name angular.injector 3715 * @kind function 3716 * 3717 * @description 3718 * Creates an injector object that can be used for retrieving services as well as for 3719 * dependency injection (see {@link guide/di dependency injection}). 3720 * 3721 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 3722 * {@link angular.module}. The `ng` module must be explicitly added. 3723 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 3724 * disallows argument name annotation inference. 3725 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3726 * 3727 * @example 3728 * Typical usage 3729 * ```js 3730 * // create an injector 3731 * var $injector = angular.injector(['ng']); 3732 * 3733 * // use the injector to kick off your application 3734 * // use the type inference to auto inject arguments, or use impl
3734icit injection 3735 * $injector.invoke(function($rootScope, $compile, $document) { 3736 * $compile($document)($rootScope); 3737 * $rootScope.$digest(); 3738 * }); 3739 * ``` 3740 * 3741 * Sometimes you want to get access to the injector of a currently running Angular app 3742 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3743 * application has been bootstrapped. You can do this using the extra `injector()` added 3744 * to JQuery/jqLite elements. See {@link angular.element}. 3745 * 3746 * *This is fairly rare but could be the case if a third party library is injecting the 3747 * markup.* 3748 * 3749 * In the following example a new block of HTML containing a `ng-controller` 3750 * directive is added to the end of the document body by JQuery. We then compile and link 3751 * it into the current AngularJS scope. 3752 * 3753 * ```js 3754 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3755 * $(document.body).append($div); 3756 * 3757 * angular.element(document).injector().invoke(function($compile) { 3758 * var scope = angular.element($div).scope(); 3759 * $compile($div)(scope); 3760 * }); 3761 * ``` 3762 */ 3763 3764 3765/** 3766 * @ngdoc module 3767 * @name auto 3768 * @description 3769 * 3770 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3771 */ 3772 3773var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; 3774var FN_ARG_SPLIT = /,/; 3775var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3776var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3777var $injectorMinErr = minErr('$injector'); 3778 3779function anonFn(fn) { 3780 // For anonymous functions, showing at the very least the function signature can help in 3781 // debugging. 3782 var fnText = fn.toString().replace(STRIP_COMMENTS, ''), 3783 args = fnText.match(FN_ARGS); 3784 if (args) { 3785 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 3786 } 3787 return 'fn'; 3788} 3789 3790function annotate(fn, strictDi, name) { 3791 var $inject, 3792 fnText, 3793 argDecl, 3794 last; 3795 3796 if (typeof fn === 'function') { 3797 if (!($inject = fn.$inject)) { 3798 $inject = []; 3799 if (fn.length) { 3800 if (strictDi) { 3801 if (!isString(name) || !name) { 3802 name = fn.name || anonFn(fn); 3803 } 3804 throw $injectorMinErr('strictdi', 3805 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 3806 } 3807 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3808 argDecl = fnText.match(FN_ARGS); 3809 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 3810 arg.replace(FN_ARG, function(all, underscore, name) { 3811 $inject.push(name); 3812 }); 3813 }); 3814 } 3815 fn.$inject = $inject; 3816 } 3817 } else if (isArray(fn)) { 3818 last = fn.length - 1; 3819 assertArgFn(fn[last], 'fn'); 3820 $inject = fn.slice(0, last); 3821 } else { 3822 assertArgFn(fn, 'fn', true); 3823 } 3824 return $inject; 3825} 3826 3827/////////////////////////////////////// 3828 3829/** 3830 * @ngdoc service 3831 * @name $injector 3832 * 3833 * @description 3834 * 3835 * `$injector` is used to retrieve object instances as defined by 3836 * {@link auto.$provide provider}, instantiate types, invoke methods, 3837 * and load modules. 3838 * 3839 * The following always holds true: 3840 * 3841 * ```js 3842 * var $injector = angular.injector(); 3843 * expect($injector.get('$injector')).toBe($injector); 3844 * expect($injector.invoke(function($injector) { 3845 * return $injector; 3846 * })).toBe($injector); 3847 * ``` 3848 * 3849 * # Injection Function Annotation 3850 * 3851 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3852 * following are all valid ways of annotating function with injection arguments and are equivalent. 3853 * 3854 * ```js 3855 * // inferred (only works if code not minified/obfuscated) 3856 * $injector.invoke(function(serviceA){}); 3857 * 3858 * // annotated 3859 * function explicit(serviceA) {}; 3860 * explicit.$inject = ['serviceA']; 3861 * $injector.invoke(explicit); 3862 * 3863 * // inline 3864 * $injector.invoke(['serviceA', function(serviceA){}]); 3865 * ``` 3866 * 3867 * ## Inference 3868 * 3869 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3870 * can then be parsed and the function arguments can be extracted. This method of discovering 3871 * annotations is disallowed when the injector is in strict mode. 3872 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 3873 * argument names. 3874 * 3875 * ## `$inject` Annotation 3876 * By adding an `$inject` property onto a function the injection parameters can be specified. 3877 * 3878 * ## Inline 3879 * As an array of injection names, where the last item in the array is the function to call. 3880 */ 3881 3882/** 3883 * @ngdoc method 3884 * @name $injector#get 3885 * 3886 * @description 3887 * Return an instance of the service. 3888 * 3889 * @param {string} name The name of the instance to retrieve. 3890 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 3891 * @return {*} The instance. 3892 */ 3893 3894/** 3895 * @ngdoc method 3896 * @name $injector#invoke 3897 * 3898 * @description 3899 * Invoke the method and supply the method arguments from the `$injector`. 3900 * 3901 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 3902 * injected according to the {@link guide/di $inject Annotation} rules. 3903 * @param {Object=} self The `this` for the invoked method. 3904 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3905 * object first, before the `$injector` is consulted. 3906 * @returns {*} the value returned by the invoked `fn` function. 3907 */ 3908 3909/** 3910 * @ngdoc method 3911 * @name $injector#has 3912 * 3913 * @description 3914 * Allows the user to query if the particular service exists. 3915 * 3916 * @param {string} name Name of the service to query. 3917 * @returns {boolean} `true` if injector has given service. 3918 */ 3919 3920/** 3921 * @ngdoc method 3922 * @name $injector#instantiate 3923 * @description 3924 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3925 * operator, and supplies all of the arguments to the constructor function as specified by the 3926 * constructor annotation. 3927 * 3928 * @param {Function} Type Annotated constructor function. 3929 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3930 * object first, before the `$injector` is consulted. 3931 * @returns {Object} new instance of `Type`. 3932 */ 3933 3934/** 3935 * @ngdoc method 3936 * @name $injector#annotate 3937 * 3938 * @description 3939 * Returns an array of service names which the function is requesting for injection. This API is 3940 * used by the injector to determine which services need to be injected into the function when the 3941 * function is invoked. There are three ways in which the function can be annotated with the needed 3942 * dependencies. 3943 * 3944 * # Argument names 3945 * 3946 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3947 * by converting the function into a string using `toString()` method and extracting the argument 3948 * names. 3949 * ```js 3950 * // Given 3951 * function MyController($scope, $route) { 3952 * // ... 3953 * } 3954 * 3955 * // Then 3956 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3957 * ``` 3958 * 3959 * You can disallow this method by using strict injection mode. 3960 * 3961 * This method does not work with code minification / obfuscation. For this reason the following 3962 * annotation strategies are supported. 3963 * 3964 * # The `$inject` property 3965 * 3966 * If a function has an `$inject` property and its value is an array of strings, then the strings 3967 * represent names of services to be injected into the function. 3968 * ```js 3969 * // Given 3970 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3971 * // ... 3972 * } 3973 * // Define function dependencies 3974 * MyController['$inject'] = ['$scope', '$route']; 3975 * 3976 * // Then 3977 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3978 * ``` 3979 * 3980 * # The array notation 3981 * 3982 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3983 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3984 * a way that survives minification is a better choice: 3985 * 3986 * ```js 3987 * // We wish to write this (not minification / obfuscation safe) 3988 * injector.invoke(function($compile, $rootScope) { 3989 * // ... 3990 * }); 3991 * 3992 * // We are forced to write break inlining 3993 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3994 * // ... 3995 * }; 3996 * tmpFn.$inject = ['$compile', '$rootScope']; 3997 * injector.invoke(tmpFn); 3998 * 3999 * // To better support inline function the inline annotation is supported 4000 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 4001 * // ... 4002 * }]); 4003 * 4004 * // Therefore 4005 * expect(injector.annotate( 4006 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 4007 * ).toEqual(['$compile', '$rootScope']); 4008 * ``` 4009 * 4010 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 4011 * be retrieved as described above. 4012 * 4013 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 4014 * 4015 * @returns {Array.<string>} The names of the services which the function requires. 4016 */ 4017 4018 4019 4020 4021/** 4022 * @ngdoc service 4023 * @name $provide 4024 * 4025 * @description 4026 * 4027 * The {@link auto.$provide $provide} service has a number of methods for registering components 4028 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 4029 * {@link angular.Module}. 4030 * 4031 * An Angular **service** is a singleton object created by a **service factory**. These **service 4032 * factories** are functions which, in turn, are created by a **service provider**. 4033 * The **service providers** are constructor functions. When instantiated they must contain a 4034 * property called `$get`, which holds the **service factory** function. 4035 * 4036 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 4037 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 4038 * function to get the instance of the **service**. 4039 * 4040 * Often services have no configuration options and there is no need to add methods to the service 4041 * provider. The provider will be no more than a constructor function with a `$get` property. For 4042 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 4043 * services without specifying a provider. 4044 * 4045 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 4046 * {@link auto.$injector $injector} 4047 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 4048 * providers and services. 4049 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 4050 * services, not providers. 4051 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 4052 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 4053 * given factory function. 4054 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 4055 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 4056 * a new object using the given constructor function. 4057 * 4058 * See the individual methods for more information and examples. 4059 */ 4060 4061/** 4062 * @ngdoc method 4063 * @name $provide#provider 4064 * @description 4065 * 4066 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 4067 * are constructor functions, whose instances are responsible for "providing" a factory for a 4068 * service. 4069 * 4070 * Service provider names start with the name of the service they provide followed by `Provider`. 4071 * For example, the {@link ng.$log $log} service has a provider called 4072 * {@link ng.$logProvider $logProvider}. 4073 * 4074 * Service provider objects can have additional methods which allow configuration of the provider 4075 * and its service. Importantly, you can configure what kind of service is created by the `$get` 4076 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 4077 * method {@link ng.$logProvider#debugEnabled debugEnabled} 4078 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 4079 * console or not. 4080 * 4081 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 4082 'Provider'` key. 4083 * @param {(Object|function())} provider If the provider is: 4084 * 4085 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 4086 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 4087 * - `Constructor`: a new instance of the provider will be created using 4088 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 4089 * 4090 * @returns {Object} registered provider instance 4091 4092 * @example 4093 * 4094 * The following example shows how to create a simple event tracking service and register it using 4095 * {@link auto.$provide#provider $provide.provider()}. 4096 * 4097 * ```js 4098 * // Define the eventTracker provider 4099 * function EventTrackerProvider() { 4100 * var trackingUrl = '/track'; 4101 * 4102 * // A provider method for configuring where the tracked events should been saved 4103 * this.setTrackingUrl = function(url) { 4104 * trackingUrl = url; 4105 * }; 4106 * 4107 * // The service factory function 4108 * this.$get = ['$http', function($http) { 4109 * var trackedEvents = {}; 4110 * return { 4111 * // Call this to track an event 4112 * event: function(event) { 4113 * var count = trackedEvents[event] || 0; 4114 * count += 1; 4115 * trackedEvents[event] = count; 4116 * return count; 4117 * }, 4118 * // Call this to save the tracked events to the trackingUrl 4119 * save: function() { 4120 * $http.post(trackingUrl, trackedEvents); 4121 * } 4122 * }; 4123 * }]; 4124 * } 4125 * 4126 * describe('eventTracker', function() { 4127 * var postSpy; 4128 * 4129 * beforeEach(module(function($provide) { 4130 * // Register the eventTracker provider 4131 * $provide.provider('eventTracker', EventTrackerProvider); 4132 * })); 4133 * 4134 * beforeEach(module(function(eventTrackerProvider) { 4135 * // Configure eventTracker provider 4136 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4137 * })); 4138 * 4139 * it('tracks events', inject(function(eventTracker) { 4140 * expect(eventTracker.event('login')).toEqual(1); 4141 * expect(eventTracker.event('login')).toEqual(2); 4142 * })); 4143 * 4144 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4145 * postSpy = spyOn($http, 'post'); 4146 * eventTracker.event('login'); 4147 * eventTracker.save(); 4148 * expect(postSpy).toHaveBeenCalled(); 4149 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4150 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4151 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4152 * })); 4153 * }); 4154 * ``` 4155 */ 4156 4157/** 4158 * @ngdoc method 4159 * @name $provide#factory 4160 * @description 4161 * 4162 * Register a **service factory**, which will be called to return the service instance. 4163 * This is short for registering a service where its provider consists of only a `$get` property, 4164 * which is the given service factory function. 4165 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4166 * configure your service in a provider. 4167 * 4168 * @param {string} name The name of the instance. 4169 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4170 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4171 * @returns {Object} registered provider instance 4172 * 4173 * @example 4174 * Here is an example of registering a service 4175 * ```js 4176 * $provide.factory('ping', ['$http', function($http) { 4177 * return function ping() { 4178 * return $http.send('/ping'); 4179 * }; 4180 * }]); 4181 * ``` 4182 * You would then inject and use this service like this: 4183 * ```js 4184 * someModule.controller('Ctrl', ['ping', function(ping) { 4185 * ping(); 4186 * }]); 4187 * ``` 4188 */ 4189 4190 4191/** 4192 * @ngdoc method 4193 * @name $provide#service 4194 * @description 4195 * 4196 * Register a **service constructor**, which will be invoked with `new` to create the service 4197 * instance. 4198 * This is short for registering a service where its provider's `$get` property is the service 4199 * constructor function that will be used to instantiate the service instance. 4200 * 4201 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4202 * as a type/class. 4203 * 4204 * @param {string} name The name of the instance. 4205 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4206 * that will be instantiated. 4207 * @returns {Object} registered provider instance 4208 * 4209 * @example 4210 * Here is an example of registering a service using 4211 * {@link auto.$provide#service $provide.service(class)}. 4212 * ```js 4213 * var Ping = function($http) { 4214 * this.$http = $http; 4215 * }; 4216 * 4217 * Ping.$inject = ['$http']; 4218 * 4219 * Ping.prototype.send = function() { 4220 * return this.$http.get('/ping'); 4221 * }; 4222 * $provide.service('ping', Ping); 4223 * ``` 4224 * You would then inject and use this service like this: 4225 * ```js 4226 * someModule.controller('Ctrl', ['ping', function(ping) { 4227 * ping.send(); 4228 * }]); 4229 * ``` 4230 */ 4231 4232 4233/** 4234 * @ngdoc method 4235 * @name $provide#value 4236 * @description 4237 * 4238 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4239 * number, an array, an object or a function. This is short for registering a service where its 4240 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4241 * service**. 4242 * 4243 * Value services are similar to constant services, except that they cannot be injected into a 4244 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4245 * an Angular 4246 * {@link auto.$provide#decorator decorator}. 4247 * 4248 * @param {string} name The name of the instance. 4249 * @param {*} value The value. 4250 * @returns {Object} registered provider instance 4251 * 4252 * @example 4253 * Here are some examples of creating value services. 4254 * ```js 4255 * $provide.value('ADMIN_USER', 'admin'); 4256 *
4257 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4258 * 4259 * $provide.value('halfOf', function(value) { 4260 * return value / 2; 4261 * }); 4262 * ``` 4263 */ 4264 4265 4266/** 4267 * @ngdoc method 4268 * @name $provide#constant 4269 * @description 4270 * 4271 * Register a **constant service**, such as a string, a number, an array, an object or a function, 4272 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 4273 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4274 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 4275 * 4276 * @param {string} name The name of the constant. 4277 * @param {*} value The constant value. 4278 * @returns {Object} registered instance 4279 * 4280 * @example 4281 * Here a some examples of creating constants: 4282 * ```js 4283 * $provide.constant('SHARD_HEIGHT', 306); 4284 * 4285 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4286 * 4287 * $provide.constant('double', function(value) { 4288 * return value * 2; 4289 * }); 4290 * ``` 4291 */ 4292 4293 4294/** 4295 * @ngdoc method 4296 * @name $provide#decorator 4297 * @description 4298 * 4299 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 4300 * intercepts the creation of a service, allowing it to override or modify the behavior of the 4301 * service. The object returned by the decorator may be the original service, or a new service 4302 * object which replaces or wraps and delegates to the original service. 4303 * 4304 * @param {string} name The name of the service to decorate. 4305 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4306 * instantiated and should return the decorated service instance. The function is called using 4307 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4308 * Local injection arguments: 4309 * 4310 * * `$delegate` - The original service instance, which can be monkey patched, configured, 4311 * decorated or delegated to. 4312 * 4313 * @example 4314 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4315 * calls to {@link ng.$log#error $log.warn()}. 4316 * ```js 4317 * $provide.decorator('$log', ['$delegate', function($delegate) { 4318 * $delegate.warn = $delegate.error; 4319 * return $delegate; 4320 * }]); 4321 * ``` 4322 */ 4323 4324 4325function createInjector(modulesToLoad, strictDi) { 4326 strictDi = (strictDi === true); 4327 var INSTANTIATING = {}, 4328 providerSuffix = 'Provider', 4329 path = [], 4330 loadedModules = new HashMap([], true), 4331 providerCache = { 4332 $provide: { 4333 provider: supportObject(provider), 4334 factory: supportObject(factory), 4335 service: supportObject(service), 4336 value: supportObject(value), 4337 constant: supportObject(constant), 4338 decorator: decorator 4339 } 4340 }, 4341 providerInjector = (providerCache.$injector = 4342 createInternalInjector(providerCache, function(serviceName, caller) { 4343 if (angular.isString(caller)) { 4344 path.push(caller); 4345 } 4346 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 4347 })), 4348 instanceCache = {}, 4349 instanceInjector = (instanceCache.$injector = 4350 createInternalInjector(instanceCache, function(serviceName, caller) { 4351 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4352 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); 4353 })); 4354 4355 4356 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); }); 4357 4358 return instanceInjector; 4359 4360 //////////////////////////////////// 4361 // $provider 4362 //////////////////////////////////// 4363 4364 function supportObject(delegate) { 4365 return function(key, value) { 4366 if (isObject(key)) { 4367 forEach(key, reverseParams(delegate)); 4368 } else { 4369 return delegate(key, value); 4370 } 4371 }; 4372 } 4373 4374 function provider(name, provider_) { 4375 assertNotHasOwnProperty(name, 'service'); 4376 if (isFunction(provider_) || isArray(provider_)) { 4377 provider_ = providerInjector.instantiate(provider_); 4378 } 4379 if (!provider_.$get) { 4380 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 4381 } 4382 return providerCache[name + providerSuffix] = provider_; 4383 } 4384 4385 function enforceReturnValue(name, factory) { 4386 return function enforcedReturnValue() { 4387 var result = instanceInjector.invoke(factory, this); 4388 if (isUndefined(result)) { 4389 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 4390 } 4391 return result; 4392 }; 4393 } 4394 4395 function factory(name, factoryFn, enforce) { 4396 return provider(name, { 4397 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 4398 }); 4399 } 4400 4401 function service(name, constructor) { 4402 return factory(name, ['$injector', function($injector) { 4403 return $injector.instantiate(constructor); 4404 }]); 4405 } 4406 4407 function value(name, val) { return factory(name, valueFn(val), false); } 4408 4409 function constant(name, value) { 4410 assertNotHasOwnProperty(name, 'constant'); 4411 providerCache[name] = value; 4412 instanceCache[name] = value; 4413 } 4414 4415 function decorator(serviceName, decorFn) { 4416 var origProvider = providerInjector.get(serviceName + providerSuffix), 4417 orig$get = origProvider.$get; 4418 4419 origProvider.$get = function() { 4420 var origInstance = instanceInjector.invoke(orig$get, origProvider); 4421 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 4422 }; 4423 } 4424 4425 //////////////////////////////////// 4426 // Module Loading 4427 //////////////////////////////////// 4428 function loadModules(modulesToLoad) { 4429 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 4430 var runBlocks = [], moduleFn;
4431 forEach(modulesToLoad, function(module) { 4432 if (loadedModules.get(module)) return; 4433 loadedModules.put(module, true); 4434 4435 function runInvokeQueue(queue) { 4436 var i, ii; 4437 for (i = 0, ii = queue.length; i < ii; i++) { 4438 var invokeArgs = queue[i], 4439 provider = providerInjector.get(invokeArgs[0]); 4440 4441 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 4442 } 4443 } 4444 4445 try { 4446 if (isString(module)) { 4447 moduleFn = angularModule(module); 4448 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 4449 runInvokeQueue(moduleFn._invokeQueue); 4450 runInvokeQueue(moduleFn._configBlocks); 4451 } else if (isFunction(module)) { 4452 runBlocks.push(providerInjector.invoke(module)); 4453 } else if (isArray(module)) { 4454 runBlocks.push(providerInjector.invoke(module)); 4455 } else { 4456 assertArgFn(module, 'module'); 4457 } 4458 } catch (e) { 4459 if (isArray(module)) { 4460 module = module[module.length - 1]; 4461 } 4462 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 4463 // Safari & FF's stack traces don't contain error.message content 4464 // unlike those of Chrome and IE 4465 // So if stack doesn't contain message, we create a new string that contains both. 4466 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 4467 /* jshint -W022 */ 4468 e = e.message + '\n' + e.stack; 4469 } 4470 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 4471 module, e.stack || e.message || e); 4472 } 4473 }); 4474 return runBlocks; 4475 } 4476 4477 //////////////////////////////////// 4478 // internal Injector 4479 //////////////////////////////////// 4480 4481 function createInternalInjector(cache, factory) { 4482 4483 function getService(serviceName, caller) { 4484 if (cache.hasOwnProperty(serviceName)) { 4485 if (cache[serviceName] === INSTANTIATING) { 4486 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 4487 serviceName + ' <- ' + path.join(' <- ')); 4488 } 4489 return cache[serviceName]; 4490 } else { 4491 try { 4492 path.unshift(serviceName); 4493 cache[serviceName] = INSTANTIATING; 4494 return cache[serviceName] = factory(serviceName, caller); 4495 } catch (err) { 4496 if (cache[serviceName] === INSTANTIATING) { 4497 delete cache[serviceName]; 4498 } 4499 throw err; 4500 } finally { 4501 path.shift(); 4502 } 4503 } 4504 } 4505 4506 function invoke(fn, self, locals, serviceName) { 4507 if (typeof locals === 'string') { 4508 serviceName = locals; 4509 locals = null; 4510 } 4511 4512 var args = [], 4513 $inject = createInjector.$$annotate(fn, strictDi, serviceName), 4514 length, i, 4515 key; 4516 4517 for (i = 0, length = $inject.length; i < length; i++) { 4518 key = $inject[i]; 4519 if (typeof key !== 'string') { 4520 throw $injectorMinErr('itkn', 4521 'Incorrect injection token! Expected service name as string, got {0}', key); 4522 } 4523 args.push( 4524 locals && locals.hasOwnProperty(key) 4525 ? locals[key] 4526 : getService(key, serviceName) 4527 ); 4528 } 4529 if (isArray(fn)) { 4530 fn = fn[length]; 4531 } 4532 4533 // http://jsperf.com/angularjs-invoke-apply-vs-switch 4534 // #5388 4535 return fn.apply(self, args); 4536 } 4537 4538 function instantiate(Type, locals, serviceName) { 4539 // Check if Type is annotated and use just the given function at n-1 as parameter 4540 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 4541 // Object creation: http://jsperf.com/create-constructor/2 4542 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); 4543 var returnedValue = invoke(Type, instance, locals, serviceName); 4544 4545 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 4546 } 4547 4548 return { 4549 invoke: invoke, 4550 instantiate: instantiate, 4551 get: getService, 4552 annotate: createInjector.$$annotate, 4553 has: function(name) { 4554 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 4555 } 4556 }; 4557 } 4558} 4559 4560createInjector.$$annotate = annotate; 4561 4562/** 4563 * @ngdoc provider 4564 * @name $anchorScrollProvider 4565 * 4566 * @description 4567 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 4568 * {@link ng.$location#hash $location.hash()} changes. 4569 */ 4570function $AnchorScrollProvider() { 4571 4572 var autoScrollingEnabled = true; 4573 4574 /** 4575 * @ngdoc method 4576 * @name $anchorScrollProvider#disableAutoScrolling 4577 * 4578 * @description 4579 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4580 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4581 * Use this method to disable automatic scrolling. 4582 * 4583 * If automatic scrolling is disabled, one must explicitly call 4584 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4585 * current hash. 4586 */ 4587 this.disableAutoScrolling = function() { 4588 autoScrollingEnabled = false;
4589 }; 4590 4591 /** 4592 * @ngdoc service 4593 * @name $anchorScroll 4594 * @kind function 4595 * @requires $window 4596 * @requires $location 4597 * @requires $rootScope 4598 * 4599 * @description 4600 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 4601 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 4602 * in the 4603 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document). 4604 * 4605 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 4606 * match any anchor whenever it changes. This can be disabled by calling 4607 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 4608 * 4609 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 4610 * vertical scroll-offset (either fixed or dynamic). 4611 * 4612 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 4613 * {@link ng.$location#hash $location.hash()} will be used. 4614 * 4615 * @property {(number|function|jqLite)} yOffset 4616 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 4617 * positioned elements at the top of the page, such as navbars, headers etc. 4618 * 4619 * `yOffset` can be specified in various ways: 4620 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 4621 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 4622 * a number representing the offset (in pixels).<br /><br /> 4623 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 4624 * the top of the page to the element's bottom will be used as offset.<br /> 4625 * **Note**: The element will be taken into account only as long as its `position` is set to 4626 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 4627 * their height and/or positioning according to the viewport's size. 4628 * 4629 * <br /> 4630 * <div class="alert alert-warning"> 4631 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 4632 * not some child element. 4633 * </div> 4634 * 4635 * @example 4636 <example module="anchorScrollExample"> 4637 <file name="index.html"> 4638 <div id="scrollArea" ng-controller="ScrollController"> 4639 <a ng-click="gotoBottom()">Go to bottom</a> 4640 <a id="bottom"></a> You're at the bottom! 4641 </div> 4642 </file> 4643 <file name="script.js"> 4644 angular.module('anchorScrollExample', []) 4645 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 4646 function ($scope, $location, $anchorScroll) { 4647 $scope.gotoBottom = function() { 4648 // set the location.hash to the id of 4649 // the element you wish to scroll to. 4650 $location.hash('bottom'); 4651 4652 // call $anchorScroll() 4653 $anchorScroll(); 4654 }; 4655 }]); 4656 </file> 4657 <file name="style.css"> 4658 #scrollArea { 4659 height: 280px; 4660 overflow: auto; 4661 } 4662 4663 #bottom { 4664 display: block; 4665 margin-top: 2000px; 4666 } 4667 </file> 4668 </example> 4669 * 4670 * <hr /> 4671 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 4672 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 4673 * 4674 * @example 4675 <example module="anchorScrollOffsetExample"> 4676 <file name="index.html"> 4677 <div class="fixed-header" ng-controller="headerCtrl"> 4678 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 4679 Go to anchor {{x}} 4680 </a> 4681 </div> 4682 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 4683 Anchor {{x}} of 5 4684 </div> 4685 </file> 4686 <file name="script.js"> 4687 angular.module('anchorScrollOffsetExample', []) 4688 .run(['$anchorScroll', function($anchorScroll) { 4689 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 4690 }]) 4691 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 4692 function ($anchorScroll, $location, $scope) {
4693 $scope.gotoAnchor = function(x) { 4694 var newHash = 'anchor' + x; 4695 if ($location.hash() !== newHash) { 4696 // set the $location.hash to `newHash` and 4697 // $anchorScroll will automatically scroll to it 4698 $location.hash('anchor' + x); 4699 } else { 4700 // call $anchorScroll() explicitly, 4701 // since $location.hash hasn't changed 4702 $anchorScroll(); 4703 } 4704 }; 4705 } 4706 ]); 4707 </file> 4708 <file name="style.css"> 4709 body { 4710 padding-top: 50px; 4711 } 4712 4713 .anchor { 4714 border: 2px dashed DarkOrchid; 4715 padding: 10px 10px 200px 10px; 4716 } 4717 4718 .fixed-header { 4719 background-color: rgba(0, 0, 0, 0.2); 4720 height: 50px; 4721 position: fixed; 4722 top: 0; left: 0; right: 0; 4723 } 4724 4725 .fixed-header > a { 4726 display: inline-block; 4727 margin: 5px 15px; 4728 } 4729 </file> 4730 </example> 4731 */ 4732 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4733 var document = $window.document; 4734 4735 // Helper function to get first anchor from a NodeList 4736 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 4737 // and working in all supported browsers.) 4738 function getFirstAnchor(list) { 4739 var result = null; 4740 Array.prototype.some.call(list, function(element) { 4741 if (nodeName_(element) === 'a') { 4742 result = element; 4743 return true; 4744 } 4745 }); 4746 return result; 4747 } 4748 4749 function getYOffset() { 4750 4751 var offset = scroll.yOffset; 4752 4753 if (isFunction(offset)) { 4754 offset = offset(); 4755 } else if (isElement(offset)) { 4756 var elem = offset[0]; 4757 var style = $window.getComputedStyle(elem); 4758 if (style.position !== 'fixed') { 4759 offset = 0; 4760 } else { 4761 offset = elem.getBoundingClientRect().bottom; 4762 } 4763 } else if (!isNumber(offset)) { 4764 offset = 0; 4765 } 4766 4767 return offset; 4768 } 4769 4770 function scrollTo(elem) { 4771 if (elem) { 4772 elem.scrollIntoView(); 4773 4774 var offset = getYOffset(); 4775 4776 if (offset) { 4777 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 4778 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 4779 // top of the viewport. 4780 // 4781 // IF the number of pixels from the top of `elem` to the end of the page's content is less 4782 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 4783 // way down the page. 4784 // 4785 // This is often the case for elements near the bottom of the page. 4786 // 4787 // In such cases we do not need to scroll the whole `offset` up, just the difference between 4788 // the top of the element and the offset, which is enough to align the top of `elem` at the 4789 // desired position. 4790 var elemTop = elem.getBoundingClientRect().top; 4791 $window.scrollBy(0, elemTop - offset); 4792 } 4793 } else { 4794 $window.scrollTo(0, 0); 4795 } 4796 } 4797 4798 function scroll(hash) { 4799 hash = isString(hash) ? hash : $location.hash(); 4800 var elm; 4801 4802 // empty hash, scroll to the top of the page 4803 if (!hash) scrollTo(null); 4804 4805 // element with given id 4806 else if ((elm = document.getElementById(hash))) scrollTo(elm); 4807 4808 // first anchor with given name :-D 4809 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 4810 4811 // no element and hash == 'top', scroll to the top of the page 4812 else if (hash === 'top') scrollTo(null); 4813 } 4814 4815 // does not scroll when user clicks on anchor link that is currently on 4816 // (no url change, no $location.hash() change), browser native does scroll 4817 if (autoScrollingEnabled) { 4818 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4819 function autoScrollWatchAction(newVal, oldVal) { 4820 // skip the initial scroll if $location.hash is empty 4821 if (newVal === oldVal && newVal === '') return; 4822 4823 jqLiteDocumentLoaded(function() { 4824 $rootScope.$evalAsync(scroll); 4825 }); 4826 }); 4827 } 4828 4829 return scroll; 4830 }]; 4831} 4832 4833var $animateMinErr = minErr('$animate'); 4834var ELEMENT_NODE = 1; 4835var NG_ANIMATE_CLASSNAME = 'ng-animate'; 4836 4837function mergeClasses(a,b) { 4838 if (!a && !b) return ''; 4839 if (!a) return b; 4840 if (!b) return a; 4841 if (isArray(a)) a = a.join(' '); 4842 if (isArray(b)) b = b.join(' '); 4843 return a + ' ' + b; 4844} 4845 4846function extractElementNode(element) { 4847 for (var i = 0; i < element.length; i++) { 4848 var elm = element[i]; 4849 if (elm.nodeType === ELEMENT_NODE) { 4850 return elm; 4851 } 4852 } 4853} 4854 4855function splitClasses(classes) { 4856 if (isString(classes)) { 4857 classes = classes.split(' '); 4858 } 4859 4860 // Use createMap() to prevent class assumptions involving property names in 4861 // Object.prototype 4862 var obj = createMap(); 4863 forEach(classes, function(klass) { 4864 // sometimes the split leaves empty string values 4865 // incase extra spaces were applied to the options 4866 if (klass.length) { 4867 obj[klass] = true; 4868 } 4869 }); 4870 return obj; 4871} 4872 4873// if any other type of options value besides an Object value is 4874// passed into the $animate.method() animation then this helper code 4875// will be run which will ignore it. While this patch is not the 4876// greatest solution to this, a lot of existing plugins depend on 4877// $animate to either call the callback (< 1.2) or return a promise 4878// that can be changed. This helper function ensures that the options 4879// are wiped clean incase a callback function is provided. 4880function prepareAnimateOptions(options) { 4881 return isObject(options) 4882 ? options 4883 : {}; 4884} 4885
4886var $$CoreAnimateJsProvider = function() { 4887 this.$get = function() {}; 4888}; 4889 4890// this is prefixed with Core since it conflicts with 4891// the animateQueueProvider defined in ngAnimate/animateQueue.js 4892var $$CoreAnimateQueueProvider = function() { 4893 var postDigestQueue = new HashMap(); 4894 var postDigestElements = []; 4895 4896 this.$get = ['$$AnimateRunner', '$rootScope', 4897 function($$AnimateRunner, $rootScope) { 4898 return { 4899 enabled: noop, 4900 on: noop, 4901 off: noop, 4902 pin: noop, 4903 4904 push: function(element, event, options, domOperation) { 4905 domOperation && domOperation(); 4906 4907 options = options || {}; 4908 options.from && element.css(options.from); 4909 options.to && element.css(options.to); 4910 4911 if (options.addClass || options.removeClass) { 4912 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 4913 } 4914 4915 var runner = new $$AnimateRunner(); // jshint ignore:line 4916 4917 // since there are no animations to run the runner needs to be 4918 // notified that the animation call is complete. 4919 runner.complete(); 4920 return runner; 4921 } 4922 }; 4923 4924 4925 function updateData(data, classes, value) { 4926 var changed = false; 4927 if (classes) { 4928 classes = isString(classes) ? classes.split(' ') : 4929 isArray(classes) ? classes : []; 4930 forEach(classes, function(className) { 4931 if (className) { 4932 changed = true; 4933 data[className] = value; 4934 } 4935 }); 4936 } 4937 return changed; 4938 } 4939 4940 function handleCSSClassChanges() { 4941 forEach(postDigestElements, function(element) { 4942 var data = postDigestQueue.get(element); 4943 if (data) { 4944 var existing = splitClasses(element.attr('class')); 4945 var toAdd = ''; 4946 var toRemove = ''; 4947 forEach(data, function(status, className) { 4948 var hasClass = !!existing[className]; 4949 if (status !== hasClass) { 4950 if (status) { 4951 toAdd += (toAdd.length ? ' ' : '') + className; 4952 } else { 4953 toRemove += (toRemove.length ? ' ' : '') + className; 4954 } 4955 } 4956 }); 4957 4958 forEach(element, function(elm) { 4959 toAdd && jqLiteAddClass(elm, toAdd); 4960 toRemove && jqLiteRemoveClass(elm, toRemove); 4961 }); 4962 postDigestQueue.remove(element); 4963 } 4964 }); 4965 postDigestElements.length = 0; 4966 } 4967 4968 4969 function addRemoveClassesPostDigest(element, add, remove) { 4970 var data = postDigestQueue.get(element) || {}; 4971 4972 var classesAdded = updateData(data, add, true); 4973 var classesRemoved = updateData(data, remove, false); 4974 4975 if (classesAdded || classesRemoved) { 4976 4977 postDigestQueue.put(element, data); 4978 postDigestElements.push(element); 4979 4980 if (postDigestElements.length === 1) { 4981 $rootScope.$$postDigest(handleCSSClassChanges); 4982 } 4983 } 4984 } 4985 }]; 4986}; 4987 4988/** 4989 * @ngdoc provider 4990 * @name $animateProvider 4991 * 4992 * @description 4993 * Default implementation of $animate that doesn't perform any animations, instead just 4994 * synchronously performs DOM updates and resolves the returned runner promise. 4995 * 4996 * In order to enable animations the `ngAnimate` module has to be loaded. 4997 * 4998 * To see the functional implementation check out `src/ngAnimate/animate.js`. 4999 */ 5000var $AnimateProvider = ['$provide', function($provide) { 5001 var provider = this; 5002 5003 this.$$registeredAnimations = Object.create(null); 5004 5005 /** 5006 * @ngdoc method 5007 * @name $animateProvider#register 5008 * 5009 * @description 5010 * Registers a new injectable animation factory function. The factory function produces the 5011 * animation object which contains callback functions for each event that is expected to be 5012 * animated. 5013 * 5014 * * `eventFn`: `function(element, ... , doneFunction, options)` 5015 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 5016 * on the type of animation additional arguments will be injected into the animation function. The 5017 * list below explains the function signatures for the different animation methods: 5018 *
5019 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 5020 * - addClass: function(element, addedClasses, doneFunction, options) 5021 * - removeClass: function(element, removedClasses, doneFunction, options) 5022 * - enter, leave, move: function(element, doneFunction, options) 5023 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 5024 * 5025 * Make sure to trigger the `doneFunction` once the animation is fully complete. 5026 * 5027 * ```js 5028 * return { 5029 * //enter, leave, move signature 5030 * eventFn : function(element, done, options) { 5031 * //code to run the animation 5032 * //once complete, then run done() 5033 * return function endFunction(wasCancelled) { 5034 * //code to cancel the animation 5035 * } 5036 * } 5037 * } 5038 * ``` 5039 * 5040 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 5041 * @param {Function} factory The factory function that will be executed to return the animation 5042 * object. 5043 */ 5044 this.register = function(name, factory) { 5045 if (name && name.charAt(0) !== '.') { 5046 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 5047 } 5048 5049 var key = name + '-animation'; 5050 provider.$$registeredAnimations[name.substr(1)] = key; 5051 $provide.factory(key, factory); 5052 }; 5053 5054 /** 5055 * @ngdoc method 5056 * @name $animateProvider#classNameFilter 5057 * 5058 * @description 5059 * Sets and/or returns the CSS class regular expression that is checked when performing 5060 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 5061 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 5062 * When setting the `classNameFilter` value, animations will only be performed on elements 5063 * that successfully match the filter expression. This in turn can boost performance 5064 * for low-powered devices as well as applications containing a lot of structural operations. 5065 * @param {RegExp=} expression The className expression which will be checked against all animations 5066 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 5067 */ 5068 this.classNameFilter = function(expression) { 5069 if (arguments.length === 1) { 5070 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 5071 if (this.$$classNameFilter) { 5072 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 5073 if (reservedRegex.test(this.$$classNameFilter.toString())) { 5074 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 5075 5076 } 5077 } 5078 } 5079 return this.$$classNameFilter; 5080 }; 5081 5082 this.$get = ['$$animateQueue', function($$animateQueue) { 5083 function domInsert(element, parentElement, afterElement) { 5084 // if for some reason the previous element was removed 5085 // from the dom sometime before this code runs then let's 5086 // just stick to using the parent element as the anchor 5087 if (afterElement) { 5088 var afterNode = extractElementNode(afterElement); 5089 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 5090 afterElement = null; 5091 } 5092 } 5093 afterElement ? afterElement.after(element) : parentElement.prepend(element); 5094 } 5095 5096 /** 5097 * @ngdoc service 5098 * @name $animate 5099 * @description The $animate service exposes a series of DOM utility methods that provide support 5100 * for animation hooks. The default behavior is the application of DOM operations, however, 5101 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5102 * to ensure that animation runs with the triggered DOM operation. 5103 * 5104 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't 5105 * included and only when it is active then the animation hooks that `$animate` triggers will be 5106 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5107 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5108 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5109 * 5110 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5111 * 5112 * To learn more about enabling animation support, click here to visit the 5113 * {@link ngAnimate ngAnimate module page}. 5114 */ 5115 return { 5116 // we don't call it directly since non-existant arguments may 5117 // be interpreted as null within the sub enabled function 5118 5119 /** 5120 * 5121 * @ngdoc method 5122 * @name $animate#on 5123 * @kind function
5124 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5125 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5126 * is fired with the following params: 5127 * 5128 * ```js 5129 * $animate.on('enter', container, 5130 * function callback(element, phase) { 5131 * // cool we detected an enter animation within the container 5132 * } 5133 * ); 5134 * ``` 5135 * 5136 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5137 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5138 * as well as among its children 5139 * @param {Function} callback the callback function that will be fired when the listener is triggered 5140 * 5141 * The arguments present in the callback function are: 5142 * * `element` - The captured DOM element that the animation was fired on. 5143 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5144 */ 5145 on: $$animateQueue.on, 5146 5147 /** 5148 * 5149 * @ngdoc method 5150 * @name $animate#off 5151 * @kind function 5152 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5153 * can be used in three different ways depending on the arguments: 5154 * 5155 * ```js 5156 * // remove all the animation event listeners listening for `enter` 5157 * $animate.off('enter'); 5158 * 5159 * // remove all the animation event listeners listening for `enter` on the given element and its children 5160 * $animate.off('enter', container); 5161 * 5162 * // remove the event listener function provided by `listenerFn` that is set 5163 * // to listen for `enter` on the given `element` as well as its children 5164 * $animate.off('enter', container, callback); 5165 * ``` 5166 * 5167 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) 5168 * @param {DOMElement=} container the container element the event listener was placed on 5169 * @param {Function=} callback the callback function that was registered as the listener 5170 */ 5171 off: $$animateQueue.off, 5172 5173 /** 5174 * @ngdoc method 5175 * @name $animate#pin 5176 * @kind function 5177 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5178 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 5179 * element despite being outside the realm of the application or within another application. Say for example if the application 5180 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5181 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5182 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5183 * 5184 * Note that this feature is only active when the `ngAnimate` module is used. 5185 * 5186 * @param {DOMElement} element the external element that will be pinned 5187 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5188 */ 5189 pin: $$animateQueue.pin, 5190 5191 /** 5192 * 5193 * @ngdoc method 5194 * @name $animate#enabled 5195 * @kind function 5196 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5197 * function can be called in four ways: 5198 * 5199 * ```js 5200 * // returns true or false 5201 * $animate.enabled(); 5202 * 5203 * // changes the enabled state for all animations 5204 * $animate.enabled(false); 5205 * $animate.enabled(true); 5206 * 5207 * // returns true or false if animations are enabled for an element 5208 * $animate.enabled(element); 5209 * 5210 * // changes the enabled state for an element and its children 5211 * $animate.enabled(element, true); 5212 * $animate.enabled(element, false); 5213 * ``` 5214 * 5215 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5216 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5217 * 5218 * @return {boolean} whether or not animations are enabled 5219 */ 5220 enabled: $$animateQueue.enabled, 5221 5222 /** 5223 * @ngdoc method 5224 * @name $animate#cancel 5225 * @kind function
5226 * @description Cancels the provided animation. 5227 * 5228 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 5229 */ 5230 cancel: function(runner) { 5231 runner.end && runner.end(); 5232 }, 5233 5234 /** 5235 * 5236 * @ngdoc method 5237 * @name $animate#enter 5238 * @kind function 5239 * @description Inserts the element into the DOM either after the `after` element (if provided) or 5240 * as the first child within the `parent` element and then triggers an animation. 5241 * A promise is returned that will be resolved during the next digest once the animation 5242 * has completed. 5243 * 5244 * @param {DOMElement} element the element which will be inserted into the DOM 5245 * @param {DOMElement} parent the parent element which will append the element as 5246 * a child (so long as the after element is not present) 5247 * @param {DOMElement=} after the sibling element after which the element will be appended 5248 * @param {object=} options an optional collection of options/styles that will be applied to the element 5249 * 5250 * @return {Promise} the animation callback promise 5251 */ 5252 enter: function(element, parent, after, options) { 5253 parent = parent && jqLite(parent); 5254 after = after && jqLite(after); 5255 parent = parent || after.parent(); 5256 domInsert(element, parent, after); 5257 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 5258 }, 5259 5260 /** 5261 * 5262 * @ngdoc method 5263 * @name $animate#move 5264 * @kind function 5265 * @description Inserts (moves) the element into its new position in the DOM either after 5266 * the `after` element (if provided) or as the first child within the `parent` element 5267 * and then triggers an animation. A promise is returned that will be resolved 5268 * during the next digest once the animation has completed. 5269 * 5270 * @param {DOMElement} element the element which will be moved into the new DOM position 5271 * @param {DOMElement} parent the parent element which will append the element as 5272 * a child (so long as the after element is not present) 5273 * @param {DOMElement=} after the sibling element after which the element will be appended 5274 * @param {object=} options an optional collection of options/styles that will be applied to the element 5275 * 5276 * @return {Promise} the animation callback promise 5277 */ 5278 move: function(element, parent, after, options) { 5279 parent = parent && jqLite(parent); 5280 after = after && jqLite(after); 5281 parent = parent || after.parent(); 5282 domInsert(element, parent, after); 5283 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 5284 }, 5285 5286 /** 5287 * @ngdoc method 5288 * @name $animate#leave 5289 * @kind function 5290 * @description Triggers an animation and then removes the element from the DOM. 5291 * When the function is called a promise is returned that will be resolved during the next 5292 * digest once the animation has completed. 5293 * 5294 * @param {DOMElement} element the element which will be removed from the DOM 5295 * @param {object=} options an optional collection of options/styles that will be applied to the element 5296 * 5297 * @return {Promise} the animation callback promise 5298 */ 5299 leave: function(element, options) { 5300 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 5301 element.remove(); 5302 }); 5303 }, 5304 5305 /** 5306 * @ngdoc method 5307 * @name $animate#addClass 5308 * @kind function 5309 * 5310 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 5311 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 5312 * animation if element already contains the CSS class or if the class is removed at a later step. 5313 * Note that class-based animations are treated differently compared to structural animations 5314 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5315 * depending if CSS or JavaScript animations are used. 5316 * 5317 * @param {DOMElement} element the element which the CSS classes will be applied to 5318 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 5319 * @param {object=} options an optional collection of options/styles that will be applied to the element 5320 * 5321 * @return {Promise} the animation callback promise 5322 */ 5323 addClass: function(element, className, options) { 5324 options = prepareAnimateOptions(options); 5325 options.addClass = mergeClasses(options.addclass, className); 5326 return $$animateQueue.push(element, 'addClass', options); 5327 }, 5328 5329 /** 5330 * @ngdoc method 5331 * @name $animate#removeClass 5332 * @kind function 5333 *
5334 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 5335 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 5336 * animation if element does not contain the CSS class or if the class is added at a later step. 5337 * Note that class-based animations are treated differently compared to structural animations 5338 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5339 * depending if CSS or JavaScript animations are used. 5340 * 5341 * @param {DOMElement} element the element which the CSS classes will be applied to 5342 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 5343 * @param {object=} options an optional collection of options/styles that will be applied to the element 5344 * 5345 * @return {Promise} the animation callback promise 5346 */ 5347 removeClass: function(element, className, options) { 5348 options = prepareAnimateOptions(options); 5349 options.removeClass = mergeClasses(options.removeClass, className); 5350 return $$animateQueue.push(element, 'removeClass', options); 5351 }, 5352 5353 /** 5354 * @ngdoc method 5355 * @name $animate#setClass 5356 * @kind function 5357 * 5358 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 5359 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 5360 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 5361 * passed. Note that class-based animations are treated differently compared to structural animations 5362 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5363 * depending if CSS or JavaScript animations are used. 5364 * 5365 * @param {DOMElement} element the element which the CSS classes will be applied to 5366 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 5367 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 5368 * @param {object=} options an optional collection of options/styles that will be applied to the element 5369 * 5370 * @return {Promise} the animation callback promise 5371 */ 5372 setClass: function(element, add, remove, options) { 5373 options = prepareAnimateOptions(options); 5374 options.addClass = mergeClasses(options.addClass, add); 5375 options.removeClass = mergeClasses(options.removeClass, remove); 5376 return $$animateQueue.push(element, 'setClass', options); 5377 }, 5378 5379 /** 5380 * @ngdoc method 5381 * @name $animate#animate 5382 * @kind function 5383 * 5384 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 5385 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take 5386 * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and 5387 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding 5388 * style in `to`, the style in `from` is applied immediately, and no animation is run. 5389 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` 5390 * method (or as part of the `options` parameter): 5391 * 5392 * ```js 5393 * ngModule.animation('.my-inline-animation', function() { 5394 * return { 5395 * animate : function(element, from, to, done, options) { 5396 * //animation 5397 * done(); 5398 * } 5399 * } 5400 * }); 5401 * ``` 5402 * 5403 * @param {DOMElement} element the element which the CSS styles will be applied to 5404 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 5405 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 5406 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 5407 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 5408 * (Note that if no animation is detected then this value will not be appplied to the element.) 5409 * @param {object=} options an optional collection of options/styles that will be applied to the element 5410 * 5411 * @return {Promise} the animation callback promise 5412 */ 5413 animate: function(element, from, to, className, options) { 5414 options = prepareAnimateOptions(options); 5415 options.from = options.from ? extend(options.from, from) : from; 5416 options.to = options.to ? extend(options.to, to) : to; 5417 5418 className = className || 'ng-inline-animate';
vendor: 4,147 bytes, lines 5419-5596
5419 options.tempClasses = mergeClasses(options.tempClasses, className); 5420 return $$animateQueue.push(element, 'animate', options); 5421 } 5422 }; 5423 }]; 5424}]; 5425 5426var $$AnimateAsyncRunFactoryProvider = function() { 5427 this.$get = ['$$rAF', function($$rAF) { 5428 var waitQueue = []; 5429 5430 function waitForTick(fn) { 5431 waitQueue.push(fn); 5432 if (waitQueue.length > 1) return; 5433 $$rAF(function() { 5434 for (var i = 0; i < waitQueue.length; i++) { 5435 waitQueue[i](); 5436 } 5437 waitQueue = []; 5438 }); 5439 } 5440 5441 return function() { 5442 var passed = false; 5443 waitForTick(function() { 5444 passed = true; 5445 }); 5446 return function(callback) { 5447 passed ? callback() : waitForTick(callback); 5448 }; 5449 }; 5450 }]; 5451}; 5452 5453var $$AnimateRunnerFactoryProvider = function() { 5454 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', 5455 function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { 5456 5457 var INITIAL_STATE = 0; 5458 var DONE_PENDING_STATE = 1; 5459 var DONE_COMPLETE_STATE = 2; 5460 5461 AnimateRunner.chain = function(chain, callback) { 5462 var index = 0; 5463 5464 next(); 5465 function next() { 5466 if (index === chain.length) { 5467 callback(true); 5468 return; 5469 } 5470 5471 chain[index](function(response) { 5472 if (response === false) { 5473 callback(false); 5474 return; 5475 } 5476 index++; 5477 next(); 5478 }); 5479 } 5480 }; 5481 5482 AnimateRunner.all = function(runners, callback) { 5483 var count = 0; 5484 var status = true; 5485 forEach(runners, function(runner) { 5486 runner.done(onProgress); 5487 }); 5488 5489 function onProgress(response) { 5490 status = status && response; 5491 if (++count === runners.length) { 5492 callback(status); 5493 } 5494 } 5495 }; 5496 5497 function AnimateRunner(host) { 5498 this.setHost(host); 5499 5500 var rafTick = $$animateAsyncRun(); 5501 var timeoutTick = function(fn) { 5502 $timeout(fn, 0, false); 5503 }; 5504 5505 this._doneCallbacks = []; 5506 this._tick = function(fn) { 5507 var doc = $document[0]; 5508 5509 // the document may not be ready or attached 5510 // to the module for some internal tests 5511 if (doc && doc.hidden) { 5512 timeoutTick(fn); 5513 } else { 5514 rafTick(fn); 5515 } 5516 }; 5517 this._state = 0; 5518 } 5519 5520 AnimateRunner.prototype = { 5521 setHost: function(host) { 5522 this.host = host || {}; 5523 }, 5524 5525 done: function(fn) { 5526 if (this._state === DONE_COMPLETE_STATE) { 5527 fn(); 5528 } else { 5529 this._doneCallbacks.push(fn); 5530 } 5531 }, 5532 5533 progress: noop, 5534 5535 getPromise: function() { 5536 if (!this.promise) { 5537 var self = this; 5538 this.promise = $q(function(resolve, reject) { 5539 self.done(function(status) { 5540 status === false ? reject() : resolve(); 5541 }); 5542 }); 5543 } 5544 return this.promise; 5545 }, 5546 5547 then: function(resolveHandler, rejectHandler) { 5548 return this.getPromise().then(resolveHandler, rejectHandler); 5549 }, 5550 5551 'catch': function(handler) { 5552 return this.getPromise()['catch'](handler); 5553 }, 5554 5555 'finally': function(handler) { 5556 return this.getPromise()['finally'](handler); 5557 }, 5558 5559 pause: function() { 5560 if (this.host.pause) { 5561 this.host.pause(); 5562 } 5563 }, 5564 5565 resume: function() { 5566 if (this.host.resume) { 5567 this.host.resume(); 5568 } 5569 }, 5570 5571 end: function() { 5572 if (this.host.end) { 5573 this.host.end(); 5574 } 5575 this._resolve(true); 5576 }, 5577 5578 cancel: function() { 5579 if (this.host.cancel) { 5580 this.host.cancel(); 5581 } 5582 this._resolve(false); 5583 }, 5584 5585 complete: function(response) { 5586 var self = this; 5587 if (self._state === INITIAL_STATE) { 5588 self._state = DONE_PENDING_STATE; 5589 self._tick(function() { 5590 self._resolve(response); 5591 }); 5592 } 5593 }, 5594 5595 _resolve: function(response) { 5596 if (this._state !== DONE_COMPLETE_STATE) {
5597 forEach(this._doneCallbacks, function(fn) { 5598 fn(response); 5599 }); 5600 this._doneCallbacks.length = 0; 5601 this._state = DONE_COMPLETE_STATE; 5602 } 5603 } 5604 }; 5605 5606 return AnimateRunner; 5607 }]; 5608}; 5609 5610/** 5611 * @ngdoc service 5612 * @name $animateCss 5613 * @kind object 5614 * 5615 * @description 5616 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 5617 * then the `$animateCss` service will actually perform animations. 5618 * 5619 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 5620 */ 5621var $CoreAnimateCssProvider = function() { 5622 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { 5623 5624 return function(element, initialOptions) { 5625 // all of the animation functions should create 5626 // a copy of the options data, however, if a 5627 // parent service has already created a copy then 5628 // we should stick to using that 5629 var options = initialOptions || {}; 5630 if (!options.$$prepared) { 5631 options = copy(options); 5632 } 5633 5634 // there is no point in applying the styles since 5635 // there is no animation that goes on at all in 5636 // this version of $animateCss. 5637 if (options.cleanupStyles) { 5638 options.from = options.to = null; 5639 } 5640 5641 if (options.from) { 5642 element.css(options.from); 5643 options.from = null; 5644 } 5645 5646 /* jshint newcap: false*/ 5647 var closed, runner = new $$AnimateRunner(); 5648 return { 5649 start: run, 5650 end: run 5651 }; 5652 5653 function run() { 5654 $$rAF(function() { 5655 applyAnimationContents(); 5656 if (!closed) { 5657 runner.complete(); 5658 } 5659 closed = true; 5660 }); 5661 return runner; 5662 } 5663 5664 function applyAnimationContents() { 5665 if (options.addClass) { 5666 element.addClass(options.addClass); 5667 options.addClass = null; 5668 } 5669 if (options.removeClass) { 5670 element.removeClass(options.removeClass); 5671 options.removeClass = null; 5672 } 5673 if (options.to) { 5674 element.css(options.to); 5675 options.to = null; 5676 } 5677 } 5678 }; 5679 }]; 5680}; 5681 5682/* global stripHash: true */ 5683 5684/** 5685 * ! This is a private undocumented service ! 5686 * 5687 * @name $browser 5688 * @requires $log 5689 * @description 5690 * This object has two goals: 5691 * 5692 * - hide all the global state in the browser caused by the window object 5693 * - abstract away all the browser specific features and inconsistencies 5694 * 5695 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 5696 * service, which can be used for convenient testing of the application without the interaction with 5697 * the real browser apis. 5698 */ 5699/** 5700 * @param {object} window The global window object. 5701 * @param {object} document jQuery wrapped document. 5702 * @param {object} $log window.console or an object with the same interface. 5703 * @param {object} $sniffer $sniffer service 5704 */ 5705function Browser(window, document, $log, $sniffer) { 5706 var self = this, 5707 rawDocument = document[0], 5708 location = window.location, 5709 history = window.history, 5710 setTimeout = window.setTimeout, 5711 clearTimeout = window.clearTimeout, 5712 pendingDeferIds = {}; 5713 5714 self.isMock = false; 5715 5716 var outstandingRequestCount = 0; 5717 var outstandingRequestCallbacks = []; 5718 5719 // TODO(vojta): remove this temporary api 5720 self.$$completeOutstandingRequest = completeOutstandingRequest; 5721 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 5722 5723 /** 5724 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 5725 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 5726 */ 5727 function completeOutstandingRequest(fn) { 5728 try { 5729 fn.apply(null, sliceArgs(arguments, 1)); 5730 } finally { 5731 outstandingRequestCount--; 5732 if (outstandingRequestCount === 0) { 5733 while (outstandingRequestCallbacks.length) { 5734 try { 5735 outstandingRequestCallbacks.pop()(); 5736 } catch (e) { 5737 $log.error(e); 5738 } 5739 } 5740 } 5741 } 5742 } 5743 5744 function getHash(url) { 5745 var index = url.indexOf('#'); 5746 return index === -1 ? '' : url.substr(index); 5747 } 5748 5749 /** 5750 * @private 5751 * Note: this method is used only by scenario runner 5752 * TODO(vojta): prefix this method with $$ ? 5753 * @param {function()} callback Function that will be called when no outstanding request 5754 */ 5755 self.notifyWhenNoOutstandingRequests = function(callback) { 5756 if (outstandingRequestCount === 0) { 5757 callback(); 5758 } else { 5759 outstandingRequestCallbacks.push(callback); 5760 } 5761 }; 5762 5763 ////////////////////////////////////////////////////////////// 5764 // URL API 5765 ////////////////////////////////////////////////////////////// 5766 5767 var cachedState, lastHistoryState, 5768 lastBrowserUrl = location.href, 5769 baseElement = document.find('base'), 5770 pendingLocation = null; 5771 5772 cacheState(); 5773 lastHistoryState = cachedState; 5774 5775 /** 5776 * @name $browser#url 5777 * 5778 * @description 5779 * GETTER: 5780 * Without any argument, this method just returns current value of location.href. 5781 * 5782 * SETTER: 5783 * With at least one argument, this method sets url to new value. 5784 * If html5 history api supported, pushState/replaceState is used, otherwise 5785 * location.href/location.replace is used. 5786 * Returns its own instance to allow chaining 5787 * 5788 * NOTE: this api is intended for use only by the $location service. Please use the 5789 * {@link ng.$location $location service} to change url. 5790 * 5791 * @param {string} url New url (when used as setter) 5792 * @param {boolean=} replace Should new url replace current history record? 5793 * @param {object=} state object to use with pushState/replaceState 5794 */ 5795 self.url = function(url, replace, state) { 5796 // In modern browsers `history.state` is `null` by default; treating it separately 5797 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 5798 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 5799 if (isUndefined(state)) { 5800 state = null; 5801 } 5802 5803 // Android Browser BFCache causes location, history reference to become stale. 5804 if (location !== window.location) location = window.location; 5805 if (history !== window.history) history = window.history; 5806 5807 // setter 5808 if (url) { 5809 var sameState = lastHistoryState === state; 5810 5811 // Don't change anything if previous and current URLs and states match. This also prevents 5812 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 5813 // See https://github.com/angular/angular.js/commit/ffb2701 5814 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 5815 return self; 5816 } 5817 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 5818 lastBrowserUrl = url; 5819 lastHistoryState = state; 5820 // Don't use history API if only the hash changed 5821 // due to a bug in IE10/IE11 which leads 5822 // to not firing a `hashchange` nor `popstate` event 5823 // in some cases (see #9143). 5824 if ($sniffer.history && (!sameBase || !sameState)) { 5825 history[replace ? 'replaceState' : 'pushState'](state, '', url); 5826 cacheState(); 5827 // Do the assignment again so that those two variables are referentially identical. 5828 lastHistoryState = cachedState; 5829 } else { 5830 if (!sameBase || pendingLocation) { 5831 pendingLocation = url; 5832 } 5833 if (replace) { 5834 location.replace(url); 5835 } else if (!sameBase) { 5836 location.href = url; 5837 } else { 5838 location.hash = getHash(url); 5839 } 5840 if (location.href !== url) { 5841 pendingLocation = url; 5842 } 5843 } 5844 return self; 5845 // getter 5846 } else { 5847 // - pendingLocation is needed as browsers don't allow to read out 5848 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 5849 // https://openradar.appspot.com/22186109). 5850 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 5851 return pendingLocation || location.href.replace(/%27/g,"'"); 5852 } 5853 }; 5854 5855 /** 5856 * @name $browser#state 5857 * 5858 * @description 5859 * This method is a getter. 5860 * 5861 * Return history.state or null if history.state is undefined. 5862 * 5863 * @returns {object} state 5864 */ 5865 self.state = function() { 5866 return cachedState; 5867 }; 5868 5869 var urlChangeListeners = [], 5870 urlChangeInit = false;
5871 5872 function cacheStateAndFireUrlChange() { 5873 pendingLocation = null; 5874 cacheState(); 5875 fireUrlChange(); 5876 } 5877 5878 function getCurrentState() { 5879 try { 5880 return history.state; 5881 } catch (e) { 5882 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 5883 } 5884 } 5885 5886 // This variable should be used *only* inside the cacheState function. 5887 var lastCachedState = null; 5888 function cacheState() { 5889 // This should be the only place in $browser where `history.state` is read. 5890 cachedState = getCurrentState(); 5891 cachedState = isUndefined(cachedState) ? null : cachedState; 5892 5893 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 5894 if (equals(cachedState, lastCachedState)) { 5895 cachedState = lastCachedState; 5896 } 5897 lastCachedState = cachedState; 5898 } 5899 5900 function fireUrlChange() { 5901 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 5902 return; 5903 } 5904 5905 lastBrowserUrl = self.url(); 5906 lastHistoryState = cachedState; 5907 forEach(urlChangeListeners, function(listener) { 5908 listener(self.url(), cachedState); 5909 }); 5910 } 5911 5912 /** 5913 * @name $browser#onUrlChange 5914 * 5915 * @description 5916 * Register callback function that will be called, when url changes. 5917 * 5918 * It's only called when the url is changed from outside of angular: 5919 * - user types different url into address bar 5920 * - user clicks on history (forward/back) button 5921 * - user clicks on a link 5922 * 5923 * It's not called when url is changed by $browser.url() method 5924 * 5925 * The listener gets called with new url as parameter. 5926 * 5927 * NOTE: this api is intended for use only by the $location service. Please use the 5928 * {@link ng.$location $location service} to monitor url changes in angular apps. 5929 * 5930 * @param {function(string)} listener Listener function to be called when url changes. 5931 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 5932 */ 5933 self.onUrlChange = function(callback) { 5934 // TODO(vojta): refactor to use node's syntax for events 5935 if (!urlChangeInit) { 5936 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 5937 // don't fire popstate when user change the address bar and don't fire hashchange when url 5938 // changed by push/replaceState 5939 5940 // html5 history api - popstate event 5941 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 5942 // hashchange event 5943 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 5944 5945 urlChangeInit = true; 5946 } 5947 5948 urlChangeListeners.push(callback); 5949 return callback; 5950 }; 5951 5952 /** 5953 * @private 5954 * Remove popstate and hashchange handler from window. 5955 * 5956 * NOTE: this api is intended for use only by $rootScope. 5957 */ 5958 self.$$applicationDestroyed = function() { 5959 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 5960 }; 5961 5962 /** 5963 * Checks whether the url has changed outside of Angular. 5964 * Needs to be exported to be able to check for changes that have been done in sync, 5965 * as hashchange/popstate events fire in async. 5966 */ 5967 self.$$checkUrlChange = fireUrlChange; 5968 5969 ////////////////////////////////////////////////////////////// 5970 // Misc API 5971 ////////////////////////////////////////////////////////////// 5972 5973 /** 5974 * @name $browser#baseHref 5975 * 5976 * @description 5977 * Returns current <base href> 5978 * (always relative - without domain) 5979 * 5980 * @returns {string} The current base href 5981 */ 5982 self.baseHref = function() { 5983 var href = baseElement.attr('href'); 5984 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 5985 }; 5986 5987 /** 5988 * @name $browser#defer 5989 * @param {function()} fn A function, who's execution should be deferred. 5990 * @param {number=} [delay=0] of milliseconds to defer the function execution. 5991 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 5992 * 5993 * @description 5994 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 5995 * 5996 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 5997 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 5998 * via `$browser.defer.flush()`. 5999 * 6000 */ 6001 self.defer = function(fn, delay) { 6002 var timeoutId; 6003 outstandingRequestCount++; 6004 timeoutId = setTimeout(function() {
6005 delete pendingDeferIds[timeoutId]; 6006 completeOutstandingRequest(fn); 6007 }, delay || 0); 6008 pendingDeferIds[timeoutId] = true; 6009 return timeoutId; 6010 }; 6011 6012 6013 /** 6014 * @name $browser#defer.cancel 6015 * 6016 * @description 6017 * Cancels a deferred task identified with `deferId`. 6018 * 6019 * @param {*} deferId Token returned by the `$browser.defer` function. 6020 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 6021 * canceled. 6022 */ 6023 self.defer.cancel = function(deferId) { 6024 if (pendingDeferIds[deferId]) { 6025 delete pendingDeferIds[deferId]; 6026 clearTimeout(deferId); 6027 completeOutstandingRequest(noop); 6028 return true; 6029 } 6030 return false; 6031 }; 6032 6033} 6034 6035function $BrowserProvider() { 6036 this.$get = ['$window', '$log', '$sniffer', '$document', 6037 function($window, $log, $sniffer, $document) { 6038 return new Browser($window, $document, $log, $sniffer); 6039 }]; 6040} 6041 6042/** 6043 * @ngdoc service 6044 * @name $cacheFactory 6045 * 6046 * @description 6047 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 6048 * them. 6049 * 6050 * ```js 6051 * 6052 * var cache = $cacheFactory('cacheId'); 6053 * expect($cacheFactory.get('cacheId')).toBe(cache); 6054 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 6055 * 6056 * cache.put("key", "value"); 6057 * cache.put("another key", "another value"); 6058 * 6059 * // We've specified no options on creation 6060 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 6061 * 6062 * ``` 6063 * 6064 * 6065 * @param {string} cacheId Name or id of the newly created cache. 6066 * @param {object=} options Options object that specifies the cache behavior. Properties: 6067 * 6068 * - `{number=}` `capacity` â turns the cache into LRU cache. 6069 * 6070 * @returns {object} Newly created cache object with the following set of methods: 6071 * 6072 * - `{object}` `info()` â Returns id, size, and options of cache. 6073 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 6074 * it. 6075 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 6076 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 6077 * - `{void}` `removeAll()` â Removes all cached values. 6078 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 6079 * 6080 * @example 6081 <example module="cacheExampleApp"> 6082 <file name="index.html"> 6083 <div ng-controller="CacheController"> 6084 <input ng-model="newCacheKey" placeholder="Key"> 6085 <input ng-model="newCacheValue" placeholder="Value"> 6086 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 6087 6088 <p ng-if="keys.length">Cached Values</p> 6089 <div ng-repeat="key in keys"> 6090 <span ng-bind="key"></span> 6091 <span>: </span> 6092 <b ng-bind="cache.get(key)"></b> 6093 </div> 6094 6095 <p>Cache Info</p> 6096 <div ng-repeat="(key, value) in cache.info()"> 6097 <span ng-bind="key"></span> 6098 <span>: </span> 6099 <b ng-bind="value"></b> 6100 </div> 6101 </div> 6102 </file> 6103 <file name="script.js"> 6104 angular.module('cacheExampleApp', []). 6105 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 6106 $scope.keys = []; 6107 $scope.cache = $cacheFactory('cacheId'); 6108 $scope.put = function(key, value) { 6109 if (angular.isUndefined($scope.cache.get(key))) { 6110 $scope.keys.push(key); 6111 } 6112 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 6113 }; 6114 }]); 6115 </file> 6116 <file name="style.css"> 6117 p { 6118 margin: 10px 0 3px; 6119 } 6120 </file> 6121 </example> 6122 */ 6123function $CacheFactoryProvider() { 6124 6125 this.$get = function() { 6126 var caches = {}; 6127 6128 function cacheFactory(cacheId, options) { 6129 if (cacheId in caches) { 6130 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 6131 } 6132 6133 var size = 0, 6134 stats = extend({}, options, {id: cacheId}), 6135 data = createMap(), 6136 capacity = (options && options.capacity) || Number.MAX_VALUE, 6137 lruHash = createMap(), 6138 freshEnd = null, 6139 staleEnd = null; 6140 6141 /** 6142 * @ngdoc type 6143 * @name $cacheFactory.Cache 6144 *
6145 * @description 6146 * A cache object used to store and retrieve data, primarily used by 6147 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 6148 * templates and other data. 6149 * 6150 * ```js 6151 * angular.module('superCache') 6152 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 6153 * return $cacheFactory('super-cache'); 6154 * }]); 6155 * ``` 6156 * 6157 * Example test: 6158 * 6159 * ```js 6160 * it('should behave like a cache', inject(function(superCache) { 6161 * superCache.put('key', 'value'); 6162 * superCache.put('another key', 'another value'); 6163 * 6164 * expect(superCache.info()).toEqual({ 6165 * id: 'super-cache', 6166 * size: 2 6167 * }); 6168 * 6169 * superCache.remove('another key'); 6170 * expect(superCache.get('another key')).toBeUndefined(); 6171 * 6172 * superCache.removeAll(); 6173 * expect(superCache.info()).toEqual({ 6174 * id: 'super-cache', 6175 * size: 0 6176 * }); 6177 * })); 6178 * ``` 6179 */ 6180 return caches[cacheId] = { 6181 6182 /** 6183 * @ngdoc method 6184 * @name $cacheFactory.Cache#put 6185 * @kind function 6186 * 6187 * @description 6188 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 6189 * retrieved later, and incrementing the size of the cache if the key was not already 6190 * present in the cache. If behaving like an LRU cache, it will also remove stale 6191 * entries from the set. 6192 * 6193 * It will not insert undefined values into the cache. 6194 * 6195 * @param {string} key the key under which the cached data is stored. 6196 * @param {*} value the value to store alongside the key. If it is undefined, the key 6197 * will not be stored. 6198 * @returns {*} the value stored. 6199 */ 6200 put: function(key, value) { 6201 if (isUndefined(value)) return; 6202 if (capacity < Number.MAX_VALUE) { 6203 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 6204 6205 refresh(lruEntry); 6206 } 6207 6208 if (!(key in data)) size++; 6209 data[key] = value; 6210 6211 if (size > capacity) { 6212 this.remove(staleEnd.key); 6213 } 6214 6215 return value; 6216 }, 6217 6218 /** 6219 * @ngdoc method 6220 * @name $cacheFactory.Cache#get 6221 * @kind function 6222 * 6223 * @description 6224 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 6225 * 6226 * @param {string} key the key of the data to be retrieved 6227 * @returns {*} the value stored. 6228 */ 6229 get: function(key) { 6230 if (capacity < Number.MAX_VALUE) { 6231 var lruEntry = lruHash[key]; 6232 6233 if (!lruEntry) return; 6234 6235 refresh(lruEntry); 6236 } 6237 6238 return data[key]; 6239 }, 6240 6241 6242 /** 6243 * @ngdoc method 6244 * @name $cacheFactory.Cache#remove 6245 * @kind function 6246 * 6247 * @description 6248 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 6249 * 6250 * @param {string} key the key of the entry to be removed 6251 */ 6252 remove: function(key) { 6253 if (capacity < Number.MAX_VALUE) { 6254 var lruEntry = lruHash[key]; 6255 6256 if (!lruEntry) return; 6257 6258 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 6259 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 6260 link(lruEntry.n,lruEntry.p); 6261 6262 delete lruHash[key]; 6263 } 6264 6265 if (!(key in data)) return; 6266 6267 delete data[key]; 6268 size--; 6269 }, 6270 6271 6272 /** 6273 * @ngdoc method 6274 * @name $cacheFactory.Cache#removeAll 6275 * @kind function 6276 * 6277 * @description 6278 * Clears the cache object of any entries. 6279 */ 6280 removeAll: function() { 6281 data = createMap(); 6282 size = 0; 6283 lruHash = createMap(); 6284 freshEnd = staleEnd = null; 6285 }, 6286 6287 6288 /** 6289 * @ngdoc method 6290 * @name $cacheFactory.Cache#destroy 6291 * @kind function 6292 *
6293 * @description 6294 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 6295 * removing it from the {@link $cacheFactory $cacheFactory} set. 6296 */ 6297 destroy: function() { 6298 data = null; 6299 stats = null; 6300 lruHash = null; 6301 delete caches[cacheId]; 6302 }, 6303 6304 6305 /** 6306 * @ngdoc method 6307 * @name $cacheFactory.Cache#info 6308 * @kind function 6309 * 6310 * @description 6311 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 6312 * 6313 * @returns {object} an object with the following properties: 6314 * <ul> 6315 * <li>**id**: the id of the cache instance</li> 6316 * <li>**size**: the number of entries kept in the cache instance</li> 6317 * <li>**...**: any additional properties from the options object when creating the 6318 * cache.</li> 6319 * </ul> 6320 */ 6321 info: function() { 6322 return extend({}, stats, {size: size}); 6323 } 6324 }; 6325 6326 6327 /** 6328 * makes the `entry` the freshEnd of the LRU linked list 6329 */ 6330 function refresh(entry) { 6331 if (entry != freshEnd) { 6332 if (!staleEnd) { 6333 staleEnd = entry; 6334 } else if (staleEnd == entry) { 6335 staleEnd = entry.n; 6336 } 6337 6338 link(entry.n, entry.p); 6339 link(entry, freshEnd); 6340 freshEnd = entry; 6341 freshEnd.n = null; 6342 } 6343 } 6344 6345 6346 /** 6347 * bidirectionally links two entries of the LRU linked list 6348 */ 6349 function link(nextEntry, prevEntry) { 6350 if (nextEntry != prevEntry) { 6351 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 6352 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 6353 } 6354 } 6355 } 6356 6357 6358 /** 6359 * @ngdoc method 6360 * @name $cacheFactory#info 6361 * 6362 * @description 6363 * Get information about all the caches that have been created 6364 * 6365 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 6366 */ 6367 cacheFactory.info = function() { 6368 var info = {}; 6369 forEach(caches, function(cache, cacheId) { 6370 info[cacheId] = cache.info(); 6371 }); 6372 return info; 6373 }; 6374 6375 6376 /** 6377 * @ngdoc method 6378 * @name $cacheFactory#get 6379 * 6380 * @description 6381 * Get access to a cache object by the `cacheId` used when it was created. 6382 * 6383 * @param {string} cacheId Name or id of a cache to access. 6384 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 6385 */ 6386 cacheFactory.get = function(cacheId) { 6387 return caches[cacheId]; 6388 }; 6389 6390 6391 return cacheFactory; 6392 }; 6393} 6394 6395/** 6396 * @ngdoc service 6397 * @name $templateCache 6398 * 6399 * @description 6400 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 6401 * can load templates directly into the cache in a `script` tag, or by consuming the 6402 * `$templateCache` service directly. 6403 * 6404 * Adding via the `script` tag: 6405 * 6406 * ```html 6407 * <script type="text/ng-template" id="templateId.html"> 6408 * <p>This is the content of the template</p> 6409 * </script> 6410 * ``` 6411 * 6412 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 6413 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 6414 * element with ng-app attribute), otherwise the template will be ignored. 6415 * 6416 * Adding via the `$templateCache` service: 6417 * 6418 * ```js 6419 * var myApp = angular.module('myApp', []); 6420 * myApp.run(function($templateCache) { 6421 * $templateCache.put('templateId.html', 'This is the content of the template'); 6422 * }); 6423 * ``` 6424 * 6425 * To retrieve the template later, simply use it in your HTML: 6426 * ```html 6427 * <div ng-include=" 'templateId.html' "></div> 6428 * ``` 6429 * 6430 * or get it via Javascript: 6431 * ```js 6432 * $templateCache.get('templateId.html') 6433 * ``` 6434 * 6435 * See {@link ng.$cacheFactory $cacheFactory}. 6436 * 6437 */ 6438function $TemplateCacheProvider() { 6439 this.$get = ['$cacheFactory', function($cacheFactory) { 6440 return $cacheFactory('templates'); 6441 }]; 6442} 6443 6444/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 6445 * Any commits to this file should be reviewed with security in mind. * 6446 * Changes to this file can potentially create security vulnerabilities. * 6447 * An approval from 2 Core members with history of modifying * 6448 * this file is required. * 6449 * * 6450 * Does the change somehow allow for arbitrary javascript to be executed? * 6451 * Or allows for someone to change the prototype of built-in objects? * 6452 * Or gives undesired access to variables likes document or window? * 6453 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 6454
6455/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 6456 * 6457 * DOM-related variables: 6458 * 6459 * - "node" - DOM Node 6460 * - "element" - DOM Element or Node 6461 * - "$node" or "$element" - jqLite-wrapped node or element 6462 * 6463 * 6464 * Compiler related stuff: 6465 * 6466 * - "linkFn" - linking fn of a single directive 6467 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 6468 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 6469 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 6470 */ 6471 6472 6473/** 6474 * @ngdoc service 6475 * @name $compile 6476 * @kind function 6477 * 6478 * @description 6479 * Compiles an HTML string or DOM into a template and produces a template function, which 6480 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 6481 * 6482 * The compilation is a process of walking the DOM tree and matching DOM elements to 6483 * {@link ng.$compileProvider#directive directives}. 6484 * 6485 * <div class="alert alert-warning"> 6486 * **Note:** This document is an in-depth reference of all directive options. 6487 * For a gentle introduction to directives with examples of common use cases, 6488 * see the {@link guide/directive directive guide}. 6489 * </div> 6490 * 6491 * ## Comprehensive Directive API 6492 * 6493 * There are many different options for a directive. 6494 * 6495 * The difference resides in the return value of the factory function. 6496 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 6497 * or just the `postLink` function (all other properties will have the default values). 6498 * 6499 * <div class="alert alert-success"> 6500 * **Best Practice:** It's recommended to use the "directive definition object" form. 6501 * </div> 6502 * 6503 * Here's an example directive declared with a Directive Definition Object: 6504 * 6505 * ```js 6506 * var myModule = angular.module(...); 6507 * 6508 * myModule.directive('directiveName', function factory(injectables) { 6509 * var directiveDefinitionObject = { 6510 * priority: 0, 6511 * template: '<div></div>
6511', // or // function(tElement, tAttrs) { ... }, 6512 * // or 6513 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 6514 * transclude: false, 6515 * restrict: 'A', 6516 * templateNamespace: 'html', 6517 * scope: false, 6518 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 6519 * controllerAs: 'stringIdentifier', 6520 * bindToController: false, 6521 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 6522 * compile: function compile(tElement, tAttrs, transclude) { 6523 * return { 6524 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6525 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 6526 * } 6527 * // or 6528 * // return function postLink( ... ) { ... } 6529 * }, 6530 * // or 6531 * // link: { 6532 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6533 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 6534 * // } 6535 * // or 6536 * // link: function postLink( ... ) { ... } 6537 * }; 6538 * return directiveDefinitionObject; 6539 * }); 6540 * ``` 6541 * 6542 * <div class="alert alert-warning"> 6543 * **Note:** Any unspecified options will use the default value. You can see the default values below. 6544 * </div> 6545 * 6546 * Therefore the above can be simplified as: 6547 * 6548 * ```js 6549 * var myModule = angular.module(...); 6550 * 6551 * myModule.directive('directiveName', function factory(injectables) { 6552 * var directiveDefinitionObject = { 6553 * link: function postLink(scope, iElement, iAttrs) { ... } 6554 * }; 6555 * return directiveDefinitionObject; 6556 * // or 6557 * // return function postLink(scope, iElement, iAttrs) { ... } 6558 * }); 6559 * ``` 6560 * 6561 * 6562 * 6563 * ### Directive Definition Object 6564 * 6565 * The directive definition object provides instructions to the {@link ng.$compile 6566 * compiler}. The attributes are: 6567 * 6568 * #### `multiElement` 6569 * When this property is set to true, the HTML compiler will collect DOM nodes between 6570 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 6571 * together as the directive elements. It is recommended that this feature be used on directives 6572 * which are not strictly behavioural (such as {@link ngClick}), and which 6573 * do not manipulate or replace child nodes (such as {@link ngInclude}). 6574 * 6575 * #### `priority` 6576 * When there are multiple directives defined on a single DOM element, sometimes it 6577 * is necessary to specify the order in which the directives are applied. The `priority` is used 6578 * to sort the directives before their `compile` functions get called. Priority is defined as a 6579 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 6580 * are also run in priority order, but post-link functions are run in reverse order. The order 6581 * of directives with the same priority is undefined. The default priority is `0`. 6582 * 6583 * #### `terminal` 6584 * If set to true then the current `priority` will be the last set of directives 6585 * which will execute (any directives at the current priority will still execute 6586 * as the order of execution on same `priority` is undefined). Note that expressions 6587 * and other directives used in the directive's template will also be excluded from execution. 6588 * 6589 * #### `scope` 6590 * The scope property can be `true`, an object or a falsy value: 6591 * 6592 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. 6593 * 6594 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 6595 * the directive's element. If multiple directives on the same element request a new scope, 6596 * only one new scope is created. The new scope rule does not apply for the root of the template 6597 * since the root of the template always gets a new scope. 6598 * 6599 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The 6600 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent 6601 * scope. This is useful when creating reusable components, which should not accidentally read or modify 6602 * data in the parent scope. 6603 * 6604 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 6605 * directive's element. These local properties are useful for aliasing values for templates. The keys in 6606 * the object hash map to the name of the property on the isolate scope; the values define how the property 6607 * is bound to the parent scope, via matching attributes on the directive's element: 6608 * 6609 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6610 * always a string since DOM attributes are strings. If no `attr` name is specified then the 6611 * attribute name is assumed to be the same as the local name. 6612 * Given `<widget my-attr="hello {{name}}">` and widget definition 6613 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 6614 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 6615 * `localName` property on the widget scope. The `name` is read from the parent scope (not 6616 * component scope). 6617 * 6618 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 6619 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 6620 * name is specified then the attribute name is assumed to be the same as the local name. 6621 * Given `<widget my-attr="parentModel">` and widget definition of 6622 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 6623 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 6624 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 6625 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 6626 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If 6627 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use 6628 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). 6629 * 6630 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 6631 * If no `attr` name is specified then the attribute name is assumed to be the same as the 6632 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 6633 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 6634 * a function wrapper for the `count = count + value` expression. Often it's desirable to 6635 * pass data from the isolated scope via an expression to the parent scope, this can be 6636 * done by passing a map of local variable names and values into the expression wrapper fn. 6637 * For example, if the expression is `increment(amount)` then we can specify the amount value 6638 * by calling the `localFn` as `localFn({amount: 22})`. 6639 * 6640 * In general it's possible to apply more than one directive to one element, but there might be limitations 6641 * depending on the type of scope required by the directives. The following points will help explain these limitations. 6642 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 6643 * 6644 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 6645 * * **child scope** + **no scope** => Both directives will share one single child scope 6646 * * **child scope** + **child scope** => Both directives will share one single child scope 6647 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use 6648 * its parent's scope 6649 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 6650 * be applied to the same element. 6651 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 6652 * cannot be applied to the same element. 6653 * 6654 * 6655 * #### `bindToController` 6656 * This property is used to bind scope properties directly to the controller. It can be either 6657 * `true` or an object hash with the same format as the `scope` property. Additionally, a controller 6658 * alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller 6659 * definition: `controller: 'myCtrl as myAlias'`. 6660 * 6661 * When an isolate scope is used for a directive (see above), `bindToController: true` will 6662 * allow a component to have its properties bound to the controller, rather than to scope. When the controller 6663 * is instantiated, the initial values of the isolate scope bindings are already available. 6664 * 6665 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. 6666 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used 6667 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate 6668 * scope (useful for component directives). 6669 * 6670 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. 6671 * 6672 * 6673 * #### `controller` 6674 * Controller constructor function. The controller is instantiated before the 6675 * pre-linking phase and can be accessed by other directives (see 6676 * `require` attribute). This allows the directives to communicate with each other and augment 6677 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 6678 * 6679 * * `$scope` - Current scope associated with the element 6680 * * `$element` - Current element 6681 * * `$attrs` - Current attributes object for the element 6682 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 6683 * `function([scope], cloneLinkingFn, futureParentElement)`. 6684 * * `scope`: optional argument to override the scope. 6685 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. 6686 * * `futureParentElement`: 6687 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 6688 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 6689 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 6690 * and when the `cloneLinkinFn` is passed, 6691 * as those elements need to created and cloned in a special way when they are defined outside their 6692 * usual containers (e.g. like `<svg>`). 6693 * * See also the `directive.templateNamespace` property. 6694 * 6695 * 6696 * #### `require` 6697 * Require another directive and inject its controller as the fourth argument to the linking function. The
6698 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 6699 * injected argument will be an array in corresponding order. If no such directive can be 6700 * found, or if the directive does not have a controller, then an error is raised (unless no link function 6701 * is specified, in which case error checking is skipped). The name can be prefixed with: 6702 * 6703 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 6704 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 6705 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 6706 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 6707 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 6708 * `null` to the `link` fn if not found. 6709 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 6710 * `null` to the `link` fn if not found. 6711 * 6712 * 6713 * #### `controllerAs` 6714 * Identifier name for a reference to the controller in the directive's scope. 6715 * This allows the controller to be referenced from the directive template. This is especially 6716 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 6717 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 6718 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 6719 * 6720 * 6721 * #### `restrict` 6722 * String of subset of `EACM` which restricts the directive to a specific directive 6723 * declaration style. If omitted, the defaults (elements and attributes) are used. 6724 * 6725 * * `E` - Element name (default): `<my-directive></my-directive>` 6726 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 6727 * * `C` - Class: `<div class="my-directive: exp;"></div>` 6728 * * `M` - Comment: `<!-- directive: my-directive exp -->` 6729 * 6730 * 6731 * #### `templateNamespace` 6732 * String representing the document type used by the markup in the template. 6733 * AngularJS needs this information as those elements need to be created and cloned 6734 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 6735 * 6736 * * `html` - All root nodes in the template are HTML. Root nodes may also be 6737 * top-level elements such as `<svg>` or `<math>`. 6738 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 6739 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 6740 * 6741 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 6742 * 6743 * #### `template` 6744 * HTML markup that may: 6745 * * Replace the contents of the directive's element (default). 6746 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 6747 * * Wrap the contents of the directive's element (if `transclude` is true). 6748 * 6749 * Value may be: 6750 * 6751 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 6752 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 6753 * function api below) and returns a string value. 6754 * 6755 * 6756 * #### `templateUrl` 6757 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 6758 * 6759 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 6760 * for later when the template has been resolved. In the meantime it will continue to compile and link 6761 * sibling and parent elements as though this element had not contained any directives. 6762 * 6763 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 6764 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 6765 * case when only one deeply nested directive has `templateUrl`. 6766 * 6767 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 6768 * 6769 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6770 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 6771 * a string value representing the url. In either case, the template URL is passed through {@link 6772 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 6773 * 6774 * 6775 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 6776 * specify what the template should replace. Defaults to `false`. 6777 * 6778 * * `true` - the template will replace the directive's element. 6779 * * `false` - the template will replace the contents of the directive's element. 6780 * 6781 * The replacement process migrates all of the attributes / classes from the old element to the new 6782 * one. See the {@link guide/directive#template-expanding-directive 6783 * Directives Guide} for an example. 6784 * 6785 * There are very few scenarios where element replacement is required for the application function, 6786 * the main one being reusable custom components that are used within SVG contexts 6787 * (because SVG doesn't work with custom elements in the DOM tree). 6788 * 6789 * #### `transclude` 6790 * Extract the contents of the element where the directive appears and make it available to the directive. 6791 * The contents are compiled and provided to the directive as a **transclusion function**. See the 6792 * {@link $compile#transclusion Transclusion} section below. 6793 * 6794 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the 6795 * directive's element or the entire element: 6796 * 6797 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 6798 * * `'element'` - transclude the whole of the directive's element including any directives on this 6799 * element that defined at a lower priority than this directive. When used, the `template` 6800 * property is ignored. 6801 * 6802 * 6803 * #### `compile` 6804 * 6805 * ```js 6806 * function compile(tElement, tAttrs, transclude) { ... } 6807 * ``` 6808 * 6809 * The compile function deals with transforming the template DOM. Since most directives do not do 6810 * template transformation, it is not used often. The compile function takes the following arguments: 6811 * 6812 * * `tElement` - template element - The element where the directive has been declared. It is 6813 * safe to do template transformation on the element and child elements only. 6814 * 6815 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 6816 * between all directive compile functions. 6817 * 6818 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 6819 * 6820 * <div class="alert alert-warning"> 6821 * **Note:** The template instance and the link instance may be different objects if the template has 6822 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 6823 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 6824 * should be done in a linking function rather than in a compile function. 6825 * </div> 6826 6827 * <div class="alert alert-warning"> 6828 * **Note:** The compile function cannot handle directives that recursively use themselves in their 6829 * own templates or compile functions. Compiling these directives results in an infinite loop and a 6830 * stack overflow errors. 6831 * 6832 * This can be avoided by manually using $compile in the postLink function to imperatively compile 6833 * a directive's template instead of relying on automatic template compilation via `template` or 6834 * `templateUrl` declaration or manual compilation inside the compile function. 6835 * </div> 6836 * 6837 * <div class="alert alert-danger"> 6838 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 6839 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 6840 * to the link function instead. 6841 * </div> 6842 6843 * A compile function can have a return value which can be either a function or an object. 6844 * 6845 * * returning a (post-link) function - is equivalent to registering the linking function via the 6846 * `link` property of the config object when the compile function is empty. 6847 * 6848 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 6849 * control when a linking function should be called during the linking phase. See info about 6850 * pre-linking and post-linking functions below. 6851 * 6852 * 6853 * #### `link` 6854 * This property is used only if the `compile` property is not defined. 6855 * 6856 * ```js 6857 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 6858 * ``` 6859 * 6860 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 6861 * executed after the template has been cloned. This is where most of the directive logic will be 6862 * put. 6863 * 6864 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 6865 * directive for registering {@link ng.$rootScope.Scope#$watch w
6865atches}. 6866 * 6867 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 6868 * manipulate the children of the element only in `postLink` function since the children have 6869 * already been linked. 6870 * 6871 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 6872 * between all directive linking functions. 6873 * 6874 * * `controller` - the directive's required controller instance(s) - Instances are shared 6875 * among all directives, which allows the directives to use the controllers as a communication 6876 * channel. The exact value depends on the directive's `require` property: 6877 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 6878 * * `string`: the controller instance 6879 * * `array`: array of controller instances 6880 * 6881 * If a required controller cannot be found, and it is optional, the instance is `null`, 6882 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 6883 * 6884 * Note that you can also require the directive's own controller - it will be made available like 6885 * any other controller. 6886 * 6887 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 6888 * This is the same as the `$transclude` 6889 * parameter of directive controllers, see there for details. 6890 * `function([scope], cloneLinkingFn, futureParentElement)`. 6891 * 6892 * #### Pre-linking function 6893 * 6894 * Executed before the child elements are linked. Not safe to do DOM transformation since the 6895 * compiler linking function will fail to locate the correct elements for linking. 6896 * 6897 * #### Post-linking function 6898 * 6899 * Executed after the child elements are linked. 6900 *
6901 * Note that child elements that contain `templateUrl` directives will not have been compiled 6902 * and linked since they are waiting for their template to load asynchronously and their own 6903 * compilation and linking has been suspended until that occurs. 6904 * 6905 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 6906 * for their async templates to be resolved. 6907 * 6908 * 6909 * ### Transclusion 6910 * 6911 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 6912 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 6913 * scope from where they were taken. 6914 * 6915 * Transclusion is used (often with {@link ngTransclude}) to insert the 6916 * original contents of a directive's element into a specified place in the template of the directive. 6917 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 6918 * content has access to the properties on the scope from which it was taken, even if the directive 6919 * has isolated scope. 6920 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 6921 * 6922 * This makes it possible for the widget to have private state for its template, while the transcluded 6923 * content has access to its originating scope. 6924 * 6925 * <div class="alert alert-warning"> 6926 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 6927 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 6928 * Testing Transclusion Directives}. 6929 * </div> 6930 * 6931 * #### Transclusion Functions 6932 * 6933 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 6934 * function** to the directive's `link` function and `controller`. This transclusion function is a special 6935 * **linking function** that will return the compiled contents linked to a new transclusion scope. 6936 * 6937 * <div class="alert alert-info"> 6938 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 6939 * ngTransclude will deal with it for us. 6940 * </div> 6941 * 6942 * If you want to manually control the insertion and removal of the transcluded content in your directive 6943 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery 6944 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 6945 * 6946 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 6947 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 6948 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 6949 * 6950 * <div class="alert alert-info"> 6951 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function 6952 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 6953 * </div> 6954 * 6955 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 6956 * attach function**: 6957 * 6958 * ```js 6959 * var transcludedContent, transclusionScope; 6960 * 6961 * $transclude(function(clone, scope) { 6962 * element.append(clone); 6963 * transcludedContent = clone; 6964 * transclusionScope = scope; 6965 * }); 6966 * ``` 6967 * 6968 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 6969 * associated transclusion scope: 6970 * 6971 * ```js 6972 * transcludedContent.remove(); 6973 * transclusionScope.$destroy(); 6974 * ``` 6975 * 6976 * <div class="alert alert-info"> 6977 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 6978 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 6979 * then you are also responsible for calling `$destroy` on the transclusion scope. 6980 * </div> 6981 * 6982 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 6983 * automatically destroy their transluded clones as necessary so you do not need to worry about this if 6984 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 6985 * 6986 * 6987 * #### Transclusion Scopes 6988 * 6989 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 6990 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 6991 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 6992 * was taken. 6993 * 6994 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 6995 * like this: 6996 * 6997 * ```html 6998 * <div ng-app> 6999 * <div isolate> 7000 * <div transclusion>
7001 * </div> 7002 * </div> 7003 * </div> 7004 * ``` 7005 * 7006 * The `$parent` scope hierarchy will look like this: 7007 * 7008 ``` 7009 - $rootScope 7010 - isolate 7011 - transclusion 7012 ``` 7013 * 7014 * but the scopes will inherit prototypically from different scopes to their `$parent`. 7015 * 7016 ``` 7017 - $rootScope 7018 - transclusion 7019 - isolate 7020 ``` 7021 * 7022 * 7023 * ### Attributes 7024 * 7025 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 7026 * `link()` or `compile()` functions. It has a variety of uses. 7027 * 7028 * accessing *Normalized attribute names:* 7029 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 7030 * the attributes object allows for normalized access to 7031 * the attributes. 7032 * 7033 * * *Directive inter-communication:* All directives share the same instance of the attributes 7034 * object which allows the directives to use the attributes object as inter directive 7035 * communication. 7036 * 7037 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 7038 * allowing other directives to read the interpolated value. 7039 * 7040 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 7041 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 7042 * the only way to easily get the actual value because during the linking phase the interpolation 7043 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 7044 * 7045 * ```js 7046 * function linkingFn(scope, elm, attrs, ctrl) { 7047 * // get the attribute value 7048 * console.log(attrs.ngModel); 7049 * 7050 * // change the attribute 7051 * attrs.$set('ngModel', 'new value'); 7052 * 7053 * // observe changes to interpolated attribute 7054 * attrs.$observe('ngModel', function(value) { 7055 * console.log('ngModel has changed value to ' + value); 7056 * }); 7057 * } 7058 * ``` 7059 * 7060 * ## Example 7061 * 7062 * <div class="alert alert-warning"> 7063 * **Note**: Typically directives are registered with `module.directive`. The example below is 7064 * to illustrate how `$compile` works. 7065 * </div> 7066 * 7067 <example module="compileExample"> 7068 <file name="index.html"> 7069 <script> 7070 angular.module('compileExample', [], function($compileProvider) { 7071 // configure new 'compile' directive by passing a directive 7072 // factory function. The factory function injects the '$compile' 7073 $compileProvider.directive('compile', function($compile) { 7074 // directive factory creates a link function 7075 return function(scope, element, attrs) { 7076 scope.$watch( 7077 function(scope) { 7078 // watch the 'compile' expression for changes 7079 return scope.$eval(attrs.compile); 7080 }, 7081 function(value) { 7082 // when the 'compile' expression changes 7083 // assign it into the current DOM 7084 element.html(value); 7085 7086 // compile the new DOM and link it to the current 7087 // scope. 7088 // NOTE: we only compile .childNodes so that 7089 // we don't get into infinite loop compiling ourselves 7090 $compile(element.contents())(scope); 7091 } 7092 ); 7093 }; 7094 }); 7095 }) 7096 .controller('GreeterController', ['$scope', function($scope) { 7097 $scope.name = 'Angular'; 7098 $scope.html = 'Hello {{name}}'; 7099 }]); 7100 </script> 7101 <div ng-controller="GreeterController"> 7102 <input ng-model="name"> <br/> 7103 <textarea ng-model="html"></textarea> <br/> 7104 <div compile="html"></div> 7105 </div> 7106 </file> 7107 <file name="protractor.js" type="protractor"> 7108 it('should auto compile', function() { 7109 var textarea = $('textarea'); 7110 var output = $('div[compile]'); 7111 // The initial state reads 'Hello Angular'. 7112 expect(output.getText()).toBe('Hello Angular'); 7113 textarea.clear(); 7114 textarea.sendKeys('{{name}}!'); 7115 expect(output.getText()).toBe('Angular!'); 7116 }); 7117 </file> 7118 </example> 7119 7120 * 7121 * 7122 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 7123 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 7124 * 7125 * <div class="alert alert-danger"> 7126 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 7127 * e.g. will not use the right outer scope. Please pass the transclude function as a 7128 * `parentBoundTranscludeFn` to the link function instead. 7129 * </div> 7130 * 7131 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7132 * root element(s), not their children) 7133 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 7134 * (a DOM element/tree) to a scope. Where: 7135 * 7136 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 7137 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 7138 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 7139 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 7140 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 7141 * 7142 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 7143 * * `scope` - is the current scope with which the linking function is working with. 7144 * 7145 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 7146 * keys may be used to control linking behavior: 7147 * 7148 * * `parentBoundTranscludeFn` - the transclude function made available to 7149 * directives; if given, it will be passed through to the link functions of 7150 * directives found in `element` during compilation. 7151 * * `transcludeControllers` - an object hash with keys that map controller names 7152 * to a hash with the key `instance`, which maps to the controller instance; 7153 * if given, it will make the controllers available to directives on the compileNode: 7154 * ``` 7155 * { 7156 * parent: { 7157 * instance: parentControllerInstance 7158 * } 7159 * } 7160 * ``` 7161 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 7162 * the cloned elements; only needed for transcludes that are allowed to contain non html 7163 * elements (e.g. SVG elements). See also the directive.controller property. 7164 * 7165 * Calling the linking function returns the element of the template. It is either the original 7166 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 7167 * 7168 * After linking the view is not updated until after a call to $digest which typically is done by 7169 * Angular automatically. 7170 * 7171 * If you need access to the bound view, there are two ways to do it: 7172 * 7173 * - If you are not asking the linking function to clone the template, create the DOM element(s) 7174 * before you send them to the compiler and keep this reference around. 7175 * ```js 7176 * var element = $compile('<p>{{total}}</p>')(scope); 7177 * ``` 7178 * 7179 * - if on the other hand, you need the element to be cloned, the view reference from the original 7180 * example would not point to the clone, but rather to the original template that was cloned. In 7181 * this case, you can access the clone via the cloneAttachFn: 7182 * ```js 7183 * var templateElement = angular.element('<p>{{total}}</p>'), 7184 * scope = ....; 7185 * 7186 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 7187 * //attach the clone to DOM document at the right place 7188 * }); 7189 * 7190 * //now we have reference to the cloned DOM via `clonedElement` 7191 * ``` 7192 * 7193 * 7194 * For information on how the compiler works, see the 7195 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 7196 */ 7197 7198var $compileMinErr = minErr('$compile'); 7199 7200/** 7201 * @ngdoc provider 7202 * @name $compileProvider 7203 * 7204 * @description 7205 */ 7206$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 7207function $CompileProvider($provide, $$sanitizeUriProvider) { 7208 var hasDirectives = {}, 7209 Suffix = 'Directive', 7210 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 7211 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 7212 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 7213 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 7214 7215 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 7216 // The assumption is that future DOM event attribute names will begin with 7217 // 'on' and be composed of only English letters. 7218 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 7219 7220 function parseIsolateBindings(scope, directiveName, isController) { 7221 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; 7222 7223 var bindings = {}; 7224
vendor: 5,044 bytes, lines 7225-7350
7225 forEach(scope, function(definition, scopeName) { 7226 var match = definition.match(LOCAL_REGEXP); 7227 7228 if (!match) { 7229 throw $compileMinErr('iscp', 7230 "Invalid {3} for directive '{0}'." + 7231 " Definition: {... {1}: '{2}' ...}", 7232 directiveName, scopeName, definition, 7233 (isController ? "controller bindings definition" : 7234 "isolate scope definition")); 7235 } 7236 7237 bindings[scopeName] = { 7238 mode: match[1][0], 7239 collection: match[2] === '*', 7240 optional: match[3] === '?', 7241 attrName: match[4] || scopeName 7242 }; 7243 }); 7244 7245 return bindings; 7246 } 7247 7248 function parseDirectiveBindings(directive, directiveName) { 7249 var bindings = { 7250 isolateScope: null, 7251 bindToController: null 7252 }; 7253 if (isObject(directive.scope)) { 7254 if (directive.bindToController === true) { 7255 bindings.bindToController = parseIsolateBindings(directive.scope, 7256 directiveName, true); 7257 bindings.isolateScope = {}; 7258 } else { 7259 bindings.isolateScope = parseIsolateBindings(directive.scope, 7260 directiveName, false); 7261 } 7262 } 7263 if (isObject(directive.bindToController)) { 7264 bindings.bindToController = 7265 parseIsolateBindings(directive.bindToController, directiveName, true); 7266 } 7267 if (isObject(bindings.bindToController)) { 7268 var controller = directive.controller; 7269 var controllerAs = directive.controllerAs; 7270 if (!controller) { 7271 // There is no controller, there may or may not be a controllerAs property 7272 throw $compileMinErr('noctrl', 7273 "Cannot bind to controller without directive '{0}'s controller.", 7274 directiveName); 7275 } else if (!identifierForController(controller, controllerAs)) { 7276 // There is a controller, but no identifier or controllerAs property 7277 throw $compileMinErr('noident', 7278 "Cannot bind to controller without identifier for directive '{0}'.", 7279 directiveName); 7280 } 7281 } 7282 return bindings; 7283 } 7284 7285 function assertValidDirectiveName(name) { 7286 var letter = name.charAt(0); 7287 if (!letter || letter !== lowercase(letter)) { 7288 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name); 7289 } 7290 if (name !== name.trim()) { 7291 throw $compileMinErr('baddir', 7292 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 7293 name); 7294 } 7295 } 7296 7297 /** 7298 * @ngdoc method 7299 * @name $compileProvider#directive 7300 * @kind function 7301 * 7302 * @description 7303 * Register a new directive with the compiler. 7304 * 7305 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 7306 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 7307 * names and the values are the factories. 7308 * @param {Function|Array} directiveFactory An injectable directive factory function. See 7309 * {@link guide/directive} for more info. 7310 * @returns {ng.$compileProvider} Self for chaining. 7311 */ 7312 this.directive = function registerDirective(name, directiveFactory) { 7313 assertNotHasOwnProperty(name, 'directive'); 7314 if (isString(name)) { 7315 assertValidDirectiveName(name); 7316 assertArg(directiveFactory, 'directiveFactory'); 7317 if (!hasDirectives.hasOwnProperty(name)) { 7318 hasDirectives[name] = []; 7319 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 7320 function($injector, $exceptionHandler) { 7321 var directives = []; 7322 forEach(hasDirectives[name], function(directiveFactory, index) { 7323 try { 7324 var directive = $injector.invoke(directiveFactory); 7325 if (isFunction(directive)) { 7326 directive = { compile: valueFn(directive) }; 7327 } else if (!directive.compile && directive.link) { 7328 directive.compile = valueFn(directive.link); 7329 } 7330 directive.priority = directive.priority || 0; 7331 directive.index = index; 7332 directive.name = directive.name || name; 7333 directive.require = directive.require || (directive.controller && directive.name); 7334 directive.restrict = directive.restrict || 'EA'; 7335 var bindings = directive.$$bindings = 7336 parseDirectiveBindings(directive, directive.name); 7337 if (isObject(bindings.isolateScope)) { 7338 directive.$$isolateBindings = bindings.isolateScope; 7339 } 7340 directive.$$moduleName = directiveFactory.$$moduleName; 7341 directives.push(directive); 7342 } catch (e) { 7343 $exceptionHandler(e); 7344 } 7345 }); 7346 return directives; 7347 }]); 7348 } 7349 hasDirectives[name].push(directiveFactory); 7350 } else {
7351 forEach(name, reverseParams(registerDirective)); 7352 } 7353 return this; 7354 }; 7355 7356 7357 /** 7358 * @ngdoc method 7359 * @name $compileProvider#aHrefSanitizationWhitelist 7360 * @kind function 7361 * 7362 * @description 7363 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7364 * urls during a[href] sanitization. 7365 * 7366 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 7367 * 7368 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 7369 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 7370 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7371 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7372 * 7373 * @param {RegExp=} regexp New regexp to whitelist urls with. 7374 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7375 * chaining otherwise. 7376 */ 7377 this.aHrefSanitizationWhitelist = function(regexp) { 7378 if (isDefined(regexp)) { 7379 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 7380 return this; 7381 } else { 7382 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 7383 } 7384 }; 7385 7386 7387 /** 7388 * @ngdoc method 7389 * @name $compileProvider#imgSrcSanitizationWhitelist 7390 * @kind function 7391 * 7392 * @description 7393 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7394 * urls during img[src] sanitization. 7395 * 7396 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 7397 * 7398 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 7399 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 7400 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7401 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7402 * 7403 * @param {RegExp=} regexp New regexp to whitelist urls with. 7404 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7405 * chaining otherwise. 7406 */ 7407 this.imgSrcSanitizationWhitelist = function(regexp) { 7408 if (isDefined(regexp)) { 7409 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 7410 return this; 7411 } else { 7412 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 7413 } 7414 }; 7415 7416 /** 7417 * @ngdoc method 7418 * @name $compileProvider#debugInfoEnabled 7419 * 7420 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 7421 * current debugInfoEnabled state 7422 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7423 * 7424 * @kind function 7425 * 7426 * @description 7427 * Call this method to enable/disable various debug runtime information in the compiler such as adding 7428 * binding information and a reference to the current scope on to DOM elements. 7429 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 7430 * * `ng-binding` CSS class 7431 * * `$binding` data property containing an array of the binding expressions 7432 * 7433 * You may want to disable this in production for a significant performance boost. See 7434 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 7435 * 7436 * The default value is true. 7437 */ 7438 var debugInfoEnabled = true; 7439 this.debugInfoEnabled = function(enabled) { 7440 if (isDefined(enabled)) { 7441 debugInfoEnabled = enabled; 7442 return this; 7443 } 7444 return debugInfoEnabled; 7445 }; 7446 7447 this.$get = [ 7448 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 7449 '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', 7450 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 7451 $controller, $rootScope, $sce, $animate, $$sanitizeUri) { 7452 7453 var Attributes = function(element, attributesToCopy) { 7454 if (attributesToCopy) { 7455 var keys = Object.keys(attributesToCopy); 7456 var i, l, key; 7457
7458 for (i = 0, l = keys.length; i < l; i++) { 7459 key = keys[i]; 7460 this[key] = attributesToCopy[key]; 7461 } 7462 } else { 7463 this.$attr = {}; 7464 } 7465 7466 this.$$element = element; 7467 }; 7468 7469 Attributes.prototype = { 7470 /** 7471 * @ngdoc method 7472 * @name $compile.directive.Attributes#$normalize 7473 * @kind function 7474 * 7475 * @description 7476 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 7477 * `data-`) to its normalized, camelCase form. 7478 * 7479 * Also there is special case for Moz prefix starting with upper case letter. 7480 * 7481 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7482 * 7483 * @param {string} name Name to normalize 7484 */ 7485 $normalize: directiveNormalize, 7486 7487 7488 /** 7489 * @ngdoc method 7490 * @name $compile.directive.Attributes#$addClass 7491 * @kind function 7492 * 7493 * @description 7494 * Adds the CSS class value specified by the classVal parameter to the element. If animations 7495 * are enabled then an animation will be triggered for the class addition. 7496 * 7497 * @param {string} classVal The className value that will be added to the element 7498 */ 7499 $addClass: function(classVal) { 7500 if (classVal && classVal.length > 0) { 7501 $animate.addClass(this.$$element, classVal); 7502 } 7503 }, 7504 7505 /** 7506 * @ngdoc method 7507 * @name $compile.directive.Attributes#$removeClass 7508 * @kind function 7509 * 7510 * @description 7511 * Removes the CSS class value specified by the classVal parameter from the element. If 7512 * animations are enabled then an animation will be triggered for the class removal. 7513 * 7514 * @param {string} classVal The className value that will be removed from the element 7515 */ 7516 $removeClass: function(classVal) { 7517 if (classVal && classVal.length > 0) { 7518 $animate.removeClass(this.$$element, classVal); 7519 } 7520 }, 7521 7522 /** 7523 * @ngdoc method 7524 * @name $compile.directive.Attributes#$updateClass 7525 * @kind function 7526 * 7527 * @description 7528 * Adds and removes the appropriate CSS class values to the element based on the difference 7529 * between the new and old CSS class values (specified as newClasses and oldClasses). 7530 * 7531 * @param {string} newClasses The current CSS className value 7532 * @param {string} oldClasses The former CSS className value 7533 */ 7534 $updateClass: function(newClasses, oldClasses) { 7535 var toAdd = tokenDifference(newClasses, oldClasses); 7536 if (toAdd && toAdd.length) { 7537 $animate.addClass(this.$$element, toAdd); 7538 } 7539 7540 var toRemove = tokenDifference(oldClasses, newClasses); 7541 if (toRemove && toRemove.length) { 7542 $animate.removeClass(this.$$element, toRemove); 7543 } 7544 }, 7545 7546 /** 7547 * Set a normalized attribute on the element in a way such that all directives 7548 * can share the attribute. This function properly handles boolean attributes. 7549 * @param {string} key Normalized key. (ie ngAttribute) 7550 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 7551 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 7552 * Defaults to true. 7553 * @param {string=} attrName Optional none normalized name. Defaults to key. 7554 */ 7555 $set: function(key, value, writeAttr, attrName) { 7556 // TODO: decide whether or not to throw an error if "class" 7557 //is set through this function since it may cause $updateClass to 7558 //become unstable. 7559 7560 var node = this.$$element[0], 7561 booleanKey = getBooleanAttrName(node, key), 7562 aliasedKey = getAliasedAttrName(key), 7563 observer = key, 7564 nodeName; 7565 7566 if (booleanKey) { 7567 this.$$element.prop(key, value); 7568 attrName = booleanKey; 7569 } else if (aliasedKey) { 7570 this[aliasedKey] = value; 7571 observer = aliasedKey; 7572 } 7573 7574 this[key] = value; 7575 7576 // translate normalized key to actual key 7577 if (attrName) { 7578 this.$attr[key] = attrName; 7579 } else { 7580 attrName = this.$attr[key]; 7581 if (!attrName) { 7582 this.$attr[key] = attrName = snake_case(key, '-'); 7583 } 7584 } 7585 7586 nodeName = nodeName_(this.$$element); 7587 7588 if ((nodeName === 'a' && key === 'href') || 7589 (nodeName === 'img' && key === 'src')) { 7590 // sanitize a[href] and img[src] values 7591 this[key] = value = $$sanitizeUri(value, key === 'src'); 7592 } else if (nodeName === 'img' && key === 'srcset') { 7593 // sanitize img[srcset] values 7594 var result = ""; 7595
7596 // first check if there are spaces because it's not the same pattern 7597 var trimmedSrcset = trim(value); 7598 // ( 999x ,| 999w ,| ,|, ) 7599 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 7600 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 7601 7602 // split srcset into tuple of uri and descriptor except for the last item 7603 var rawUris = trimmedSrcset.split(pattern); 7604 7605 // for each tuples 7606 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 7607 for (var i = 0; i < nbrUrisWith2parts; i++) { 7608 var innerIdx = i * 2; 7609 // sanitize the uri 7610 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 7611 // add the descriptor 7612 result += (" " + trim(rawUris[innerIdx + 1])); 7613 } 7614 7615 // split the last item into uri and descriptor 7616 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 7617 7618 // sanitize the last uri 7619 result += $$sanitizeUri(trim(lastTuple[0]), true); 7620 7621 // and add the last descriptor if any 7622 if (lastTuple.length === 2) { 7623 result += (" " + trim(lastTuple[1])); 7624 } 7625 this[key] = value = result; 7626 } 7627 7628 if (writeAttr !== false) { 7629 if (value === null || isUndefined(value)) { 7630 this.$$element.removeAttr(attrName); 7631 } else { 7632 this.$$element.attr(attrName, value); 7633 } 7634 } 7635 7636 // fire observers 7637 var $$observers = this.$$observers; 7638 $$observers && forEach($$observers[observer], function(fn) { 7639 try { 7640 fn(value); 7641 } catch (e) { 7642 $exceptionHandler(e); 7643 } 7644 }); 7645 }, 7646 7647 7648 /** 7649 * @ngdoc method 7650 * @name $compile.directive.Attributes#$observe 7651 * @kind function 7652 * 7653 * @description 7654 * Observes an interpolated attribute. 7655 * 7656 * The observer function will be invoked once during the next `$digest` following 7657 * compilation. The observer is then invoked whenever the interpolated value 7658 * changes. 7659 * 7660 * @param {string} key Normalized key. (ie ngAttribute) . 7661 * @param {function(interpolatedValue)} fn Function that will be called whenever 7662 the interpolated value of the attribute changes. 7663 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation 7664 * guide} for more info. 7665 * @returns {function()} Returns a deregistration function for this observer. 7666 */ 7667 $observe: function(key, fn) { 7668 var attrs = this, 7669 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 7670 listeners = ($$observers[key] || ($$observers[key] = [])); 7671 7672 listeners.push(fn); 7673 $rootScope.$evalAsync(function() { 7674 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 7675 // no one registered attribute interpolation function, so lets call it manually 7676 fn(attrs[key]); 7677 } 7678 }); 7679 7680 return function() { 7681 arrayRemove(listeners, fn); 7682 }; 7683 } 7684 }; 7685 7686 7687 function safeAddClass($element, className) { 7688 try { 7689 $element.addClass(className); 7690 } catch (e) { 7691 // ignore, since it means that we are trying to set class on 7692 // SVG element, where class name is read-only. 7693 } 7694 } 7695 7696 7697 var startSymbol = $interpolate.startSymbol(), 7698 endSymbol = $interpolate.endSymbol(), 7699 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 7700 ? identity 7701 : function denormalizeTemplate(template) { 7702 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 7703 }, 7704 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 7705 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; 7706 7707 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 7708 var bindings = $element.data('$binding') || []; 7709 7710 if (isArray(binding)) { 7711 bindings = bindings.concat(binding); 7712 } else { 7713 bindings.push(binding); 7714 } 7715 7716 $element.data('$binding', bindings); 7717 } : noop; 7718 7719 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 7720 safeAddClass($element, 'ng-binding'); 7721 } : noop; 7722 7723 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 7724 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope')
7724: '$scope'; 7725 $element.data(dataName, scope); 7726 } : noop; 7727 7728 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 7729 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 7730 } : noop; 7731 7732 return compile; 7733 7734 //================================ 7735 7736 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 7737 previousCompileContext) { 7738 if (!($compileNodes instanceof jqLite)) { 7739 // jquery always rewraps, whereas we need to preserve the original selector so that we can 7740 // modify it. 7741 $compileNodes = jqLite($compileNodes); 7742 } 7743 // We can not compile top level text elements since text nodes can be merged and we will 7744 // not be able to attach scope data to them, so we will wrap them in <span> 7745 forEach($compileNodes, function(node, index) { 7746 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { 7747 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; 7748 } 7749 }); 7750 var compositeLinkFn = 7751 compileNodes($compileNodes, transcludeFn, $compileNodes, 7752 maxPriority, ignoreDirective, previousCompileContext); 7753 compile.$$addScopeClass($compileNodes); 7754 var namespace = null; 7755 return function publicLinkFn(scope, cloneConnectFn, options) { 7756 assertArg(scope, 'scope'); 7757 7758 if (previousCompileContext && previousCompileContext.needsNewScope) { 7759 // A parent directive did a replace and a directive on this element asked 7760 // for transclusion, which caused us to lose a layer of element on which 7761 // we could hold the new transclusion scope, so we will create it manually 7762 // here. 7763 scope = scope.$parent.$new(); 7764 } 7765 7766 options = options || {}; 7767 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 7768 transcludeControllers = options.transcludeControllers, 7769 futureParentElement = options.futureParentElement; 7770 7771 // When `parentBoundTranscludeFn` is passed, it is a 7772 // `controllersBoundTransclude` function (it was previously passed 7773 // as `transclude` to directive.link) so we must unwrap it to get 7774 // its `boundTranscludeFn` 7775 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 7776 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 7777 } 7778 7779 if (!namespace) { 7780 namespace = detectNamespaceForChildElements(futureParentElement); 7781 } 7782 var $linkNode; 7783 if (namespace !== 'html') { 7784 // When using a directive with replace:true and templateUrl the $compileNodes 7785 // (or a child element inside of them) 7786 // might change, so we need to recreate the namespace adapted compileNodes 7787 // for call to the link function. 7788 // Note: This will already clone the nodes... 7789 $linkNode = jqLite( 7790 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 7791 ); 7792 } else if (cloneConnectFn) { 7793 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 7794 // and sometimes changes the structure of the DOM. 7795 $linkNode = JQLitePrototype.clone.call($compileNodes); 7796 } else { 7797 $linkNode = $compileNodes; 7798 } 7799 7800 if (transcludeControllers) { 7801 for (var controllerName in transcludeControllers) { 7802 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 7803 } 7804 } 7805 7806 compile.$$addScopeInfo($linkNode, scope); 7807 7808 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 7809 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 7810 return $linkNode; 7811 }; 7812 } 7813 7814 function detectNamespaceForChildElements(parentElement) { 7815 // TODO: Make this detect MathML as well... 7816 var node = parentElement && parentElement[0]; 7817 if (!node) { 7818 return 'html'; 7819 } else { 7820 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; 7821 } 7822 } 7823 7824 /** 7825 * Compile function matches each node in nodeList against the directives. Once all directives 7826 * for a particular node are collected their compile functions are executed. The compile 7827 * functions return values - the linking functions - are combined into a composite linking 7828 * function, which is the a linking function for the node. 7829 * 7830 * @param {NodeList} nodeList an array of nodes or NodeList to compile 7831 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7832 * scope argument is auto-generated to the new child of the transcluded parent scope. 7833 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 7834 * the rootElement must be set the jqLite collection of the compile root. This is
7835 * needed so that the jqLite collection items can be replaced with widgets. 7836 * @param {number=} maxPriority Max directive priority. 7837 * @returns {Function} A composite linking function of all of the matched directives or null. 7838 */ 7839 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 7840 previousCompileContext) { 7841 var linkFns = [], 7842 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 7843 7844 for (var i = 0; i < nodeList.length; i++) { 7845 attrs = new Attributes(); 7846 7847 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 7848 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 7849 ignoreDirective); 7850 7851 nodeLinkFn = (directives.length) 7852 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 7853 null, [], [], previousCompileContext) 7854 : null; 7855 7856 if (nodeLinkFn && nodeLinkFn.scope) { 7857 compile.$$addScopeClass(attrs.$$element); 7858 } 7859 7860 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 7861 !(childNodes = nodeList[i].childNodes) || 7862 !childNodes.length) 7863 ? null 7864 : compileNodes(childNodes, 7865 nodeLinkFn ? ( 7866 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 7867 && nodeLinkFn.transclude) : transcludeFn); 7868 7869 if (nodeLinkFn || childLinkFn) { 7870 linkFns.push(i, nodeLinkFn, childLinkFn); 7871 linkFnFound = true; 7872 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 7873 } 7874 7875 //use the previous context only for the first element in the virtual group 7876 previousCompileContext = null; 7877 } 7878 7879 // return a linking function if we have found anything, null otherwise 7880 return linkFnFound ? compositeLinkFn : null; 7881 7882 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 7883 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 7884 var stableNodeList; 7885 7886 7887 if (nodeLinkFnFound) { 7888 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 7889 // offsets don't get screwed up 7890 var nodeListLength = nodeList.length; 7891 stableNodeList = new Array(nodeListLength); 7892 7893 // create a sparse array by only copying the elements which have a linkFn 7894 for (i = 0; i < linkFns.length; i+=3) { 7895 idx = linkFns[i]; 7896 stableNodeList[idx] = nodeList[idx]; 7897 } 7898 } else { 7899 stableNodeList = nodeList; 7900 } 7901 7902 for (i = 0, ii = linkFns.length; i < ii;) { 7903 node = stableNodeList[linkFns[i++]]; 7904 nodeLinkFn = linkFns[i++]; 7905 childLinkFn = linkFns[i++]; 7906 7907 if (nodeLinkFn) { 7908 if (nodeLinkFn.scope) { 7909 childScope = scope.$new(); 7910 compile.$$addScopeInfo(jqLite(node), childScope); 7911 } else { 7912 childScope = scope; 7913 } 7914 7915 if (nodeLinkFn.transcludeOnThisElement) { 7916 childBoundTranscludeFn = createBoundTranscludeFn( 7917 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 7918 7919 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 7920 childBoundTranscludeFn = parentBoundTranscludeFn; 7921 7922 } else if (!parentBoundTranscludeFn && transcludeFn) { 7923 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 7924 7925 } else { 7926 childBoundTranscludeFn = null; 7927 } 7928 7929 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 7930 7931 } else if (childLinkFn) { 7932 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 7933 } 7934 } 7935 } 7936 } 7937 7938 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 7939 7940 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 7941 7942 if (!transcludedScope) { 7943 transcludedScope = scope.$new(false, containingScope); 7944 transcludedScope.$$transcluded = true; 7945 } 7946 7947 return transcludeFn(transcludedScope, cloneFn, { 7948 parentBoundTranscludeFn: previousBoundTranscludeFn, 7949 transcludeControllers: controllers, 7950 futureParentElement: futureParentElement 7951 }); 7952 }; 7953 7954 return boundTranscludeFn; 7955 } 7956 7957 /** 7958 * Looks for directives on the given node and adds them to the directive collection which is 7959 * sorted. 7960 * 7961 * @param node Node to search. 7962 * @param directives An array to which the directives are added to. This array is sorted before 7963 * the function returns. 7964 * @param attrs The shared attrs object which is used to populate the normalized attributes. 7965 * @param {number=} maxPriority Max directive priority. 7966 */ 7967 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 7968 var nodeType = node.nodeType, 7969 attrsMap = attrs.$attr, 7970 match, 7971 className; 7972 7973 switch (nodeType) { 7974 case NODE_TYPE_ELEMENT: /* Element */ 7975 // use the node name: <directive> 7976 addDirective(directives, 7977 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 7978 7979 // iterate over the attributes 7980 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 7981 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 7982 var attrStartName = false;
7983 var attrEndName = false; 7984 7985 attr = nAttrs[j]; 7986 name = attr.name; 7987 value = trim(attr.value); 7988 7989 // support ngAttr attribute binding 7990 ngAttrName = directiveNormalize(name); 7991 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 7992 name = name.replace(PREFIX_REGEXP, '') 7993 .substr(8).replace(/_(.)/g, function(match, letter) { 7994 return letter.toUpperCase(); 7995 }); 7996 } 7997 7998 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); 7999 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { 8000 attrStartName = name; 8001 attrEndName = name.substr(0, name.length - 5) + 'end'; 8002 name = name.substr(0, name.length - 6); 8003 } 8004 8005 nName = directiveNormalize(name.toLowerCase()); 8006 attrsMap[nName] = name; 8007 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 8008 attrs[nName] = value; 8009 if (getBooleanAttrName(node, nName)) { 8010 attrs[nName] = true; // presence means true 8011 } 8012 } 8013 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 8014 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 8015 attrEndName); 8016 } 8017 8018 // use class as directive 8019 className = node.className; 8020 if (isObject(className)) { 8021 // Maybe SVGAnimatedString 8022 className = className.animVal; 8023 } 8024 if (isString(className) && className !== '') { 8025 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 8026 nName = directiveNormalize(match[2]); 8027 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 8028 attrs[nName] = trim(match[3]); 8029 } 8030 className = className.substr(match.index + match[0].length); 8031 } 8032 } 8033 break; 8034 case NODE_TYPE_TEXT: /* Text Node */ 8035 if (msie === 11) { 8036 // Workaround for #11781 8037 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 8038 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 8039 node.parentNode.removeChild(node.nextSibling); 8040 } 8041 } 8042 addTextInterpolateDirective(directives, node.nodeValue); 8043 break; 8044 case NODE_TYPE_COMMENT: /* Comment */ 8045 try { 8046 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 8047 if (match) { 8048 nName = directiveNormalize(match[1]); 8049 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 8050 attrs[nName] = trim(match[2]); 8051 } 8052 } 8053 } catch (e) { 8054 // turns out that under some circumstances IE9 throws errors when one attempts to read 8055 // comment's node value. 8056 // Just ignore it and continue. (Can't seem to reproduce in test case.) 8057 } 8058 break; 8059 } 8060 8061 directives.sort(byPriority); 8062 return directives; 8063 } 8064 8065 /** 8066 * Given a node with an directive-start it collects all of the siblings until it finds 8067 * directive-end. 8068 * @param node 8069 * @param attrStart 8070 * @param attrEnd 8071 * @returns {*} 8072 */ 8073 function groupScan(node, attrStart, attrEnd) { 8074 var nodes = []; 8075 var depth = 0; 8076 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 8077 do { 8078 if (!node) { 8079 throw $compileMinErr('uterdir', 8080 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 8081 attrStart, attrEnd); 8082 } 8083 if (node.nodeType == NODE_TYPE_ELEMENT) { 8084 if (node.hasAttribute(attrStart)) depth++; 8085 if (node.hasAttribute(attrEnd)) depth--; 8086 } 8087 nodes.push(node); 8088 node = node.nextSibling; 8089 } while (depth > 0); 8090 } else { 8091 nodes.push(node); 8092 } 8093 8094 return jqLite(nodes); 8095 } 8096 8097 /** 8098 * Wrapper for linking function which converts normal linking function into a grouped 8099 * linking function. 8100 * @param linkFn 8101 * @param attrStart 8102 * @param attrEnd 8103 * @returns {Function} 8104 */ 8105 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 8106 return function(scope, element, attrs, controllers, transcludeFn) { 8107 element = groupScan(element[0], attrStart, attrEnd); 8108 return linkFn(scope, element, attrs, controllers, transcludeFn); 8109 }; 8110 } 8111 8112 /** 8113 * Once the directives have been collected, their compile functions are executed. This method 8114 * is responsible for inlining directive templates as well as terminating the application 8115 * of the directives if the terminal directive has been reached. 8116 * 8117 * @param {Array} directives Array of collected directives to execute their compile function. 8118 * this needs to be pre-sorted by priority order. 8119 * @param {Node} compileNode The raw DOM node to apply the compile functions to 8120 * @param {Object} templateAttrs The shared attribute function 8121 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 8122 * scope argument is auto-generated to the new 8123 * child of the transcluded parent scope. 8124 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 8125 * argument has the root jqLite array so that we can replace nodes 8126 * on it. 8127 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 8128 * compiling the transclusion. 8129 * @param {Array.<Function>} preLinkFns 8130 * @param {Array.<Function>} postLinkFns 8131 * @param {Object} previousCompileContext Context used for previous compilation of the current 8132 * node 8133 * @returns {Function} linkFn 8134 */ 8135 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 8136 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 8137 previousCompileContext) { 8138 previousCompileContext = previousCompileContext || {}; 8139 8140 var terminalPriority = -Number.MAX_VALUE, 8141 newScopeDirective = previousCompileContext.newScopeDirective, 8142 controllerDirectives = previousCompileContext.controllerDirectives, 8143 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 8144 templateDirective = previousCompileContext.templateDirective, 8145 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 8146 hasTranscludeDirective = false, 8147 hasTemplate = false, 8148 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 8149 $compileNode = templateAttrs.$$element = jqLite(compileNode), 8150 directive, 8151 directiveName, 8152 $template, 8153 replaceDirective = originalReplaceDirective, 8154 childTranscludeFn = transcludeFn, 8155 linkFn, 8156 directiveValue; 8157 8158 // executes all directives on the current element 8159 for (var i = 0, ii = directives.length; i < ii; i++) { 8160 directive = directives[i]; 8161 var attrStart = directive.$$start; 8162 var attrEnd = directive.$$end; 8163 8164 // collect multiblock sections 8165 if (attrStart) { 8166 $compileNode = groupScan(compileNode, attrStart, attrEnd); 8167 } 8168 $template = undefined; 8169 8170 if (terminalPriority > directive.priority) { 8171 break; // prevent further processing of directives 8172 } 8173 8174 if (directiveValue = directive.scope) { 8175 8176 // skip the check for directives with async templates, we'll check the derived sync 8177 // directive when the template arrives 8178 if (!directive.templateUrl) { 8179 if (isObject(directiveValue)) { 8180 // This directive is trying to add an isolated scope. 8181 // Check that there is no scope of any kind already 8182 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 8183 directive, $compileNode); 8184 newIsolateScopeDirective = directive; 8185 } else { 8186 // This directive is trying to add a child scope. 8187 // Check that there is no isolated scope already 8188 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 8189 $compileNode); 8190 } 8191 } 8192 8193 newScopeDirective = newScopeDirective || directive; 8194 } 8195 8196 directiveName = directive.name; 8197 8198 if (!directive.templateUrl && directive.controller) { 8199 directiveValue = directive.controller; 8200 controllerDirectives = controllerDirectives || createMap(); 8201 assertNoDuplicate("'" + directiveName + "' controller", 8202 controllerDirectives[directiveName], directive, $compileNode); 8203 controllerDirectives[directiveName] = directive; 8204 } 8205 8206 if (directiveValue = directive.transclude) { 8207 hasTranscludeDirective = true; 8208 8209 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 8210 // This option should only be used by directives that know how to safely handle element transclusion, 8211 // where the transcluded nodes are added or replaced after linking. 8212 if (!directive.$$tlb) { 8213 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 8214 nonTlbTranscludeDirective = directive; 8215 } 8216 8217 if (directiveValue == 'element') { 8218 hasElementTranscludeDirective = true; 8219 terminalPriority = directive.priority; 8220 $template = $compileNode; 8221 $compileNode = templateAttrs.$$element = 8222 jqLite(document.createComment(' ' + directiveName + ': ' + 8223 templateAttrs[directiveName] + ' ')); 8224 compileNode = $compileNode[0]; 8225 replaceWith(jqCollection, sliceArgs($template), compileNode); 8226 8227 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 8228 replaceDirective && replaceDirective.name, { 8229 // Don't pass in: 8230 // - controllerDirectives - otherwise we'll create duplicates controllers 8231 // - newIsolateScopeDirective or templateDirective - comb
vendor: 5,222 bytes, lines 8231-8343
8231ining templates with 8232 // element transclusion doesn't make sense. 8233 // 8234 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 8235 // on the same element more than once. 8236 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8237 }); 8238 } else { 8239 $template = jqLite(jqLiteClone(compileNode)).contents(); 8240 $compileNode.empty(); // clear contents 8241 childTranscludeFn = compile($template, transcludeFn, undefined, 8242 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); 8243 } 8244 } 8245 8246 if (directive.template) { 8247 hasTemplate = true; 8248 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8249 templateDirective = directive; 8250 8251 directiveValue = (isFunction(directive.template)) 8252 ? directive.template($compileNode, templateAttrs) 8253 : directive.template; 8254 8255 directiveValue = denormalizeTemplate(directiveValue); 8256 8257 if (directive.replace) { 8258 replaceDirective = directive; 8259 if (jqLiteIsTextNode(directiveValue)) { 8260 $template = []; 8261 } else { 8262 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 8263 } 8264 compileNode = $template[0]; 8265 8266 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8267 throw $compileMinErr('tplrt', 8268 "Template for directive '{0}' must have exactly one root element. {1}", 8269 directiveName, ''); 8270 } 8271 8272 replaceWith(jqCollection, $compileNode, compileNode); 8273 8274 var newTemplateAttrs = {$attr: {}}; 8275 8276 // combine directives from the original node and from the template: 8277 // - take the array of directives for this element 8278 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 8279 // - collect directives from the template and sort them by priority 8280 // - combine directives as: processed + template + unprocessed 8281 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 8282 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 8283 8284 if (newIsolateScopeDirective || newScopeDirective) { 8285 // The original directive caused the current element to be replaced but this element 8286 // also needs to have a new scope, so we need to tell the template directives 8287 // that they would need to get their scope from further up, if they require transclusion 8288 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); 8289 } 8290 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 8291 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 8292 8293 ii = directives.length; 8294 } else { 8295 $compileNode.html(directiveValue); 8296 } 8297 } 8298 8299 if (directive.templateUrl) { 8300 hasTemplate = true; 8301 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8302 templateDirective = directive; 8303 8304 if (directive.replace) { 8305 replaceDirective = directive; 8306 } 8307 8308 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 8309 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 8310 controllerDirectives: controllerDirectives, 8311 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 8312 newIsolateScopeDirective: newIsolateScopeDirective, 8313 templateDirective: templateDirective, 8314 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8315 }); 8316 ii = directives.length; 8317 } else if (directive.compile) { 8318 try { 8319 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 8320 if (isFunction(linkFn)) { 8321 addLinkFns(null, linkFn, attrStart, attrEnd); 8322 } else if (linkFn) { 8323 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 8324 } 8325 } catch (e) { 8326 $exceptionHandler(e, startingTag($compileNode)); 8327 } 8328 } 8329 8330 if (directive.terminal) { 8331 nodeLinkFn.terminal = true; 8332 terminalPriority = Math.max(terminalPriority, directive.priority); 8333 } 8334 8335 } 8336 8337 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 8338 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 8339 nodeLinkFn.templateOnThisElement = hasTemplate; 8340 nodeLinkFn.transclude = childTranscludeFn; 8341 8342 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 8343
8344 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 8345 return nodeLinkFn; 8346 8347 //////////////////// 8348 8349 function addLinkFns(pre, post, attrStart, attrEnd) { 8350 if (pre) { 8351 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 8352 pre.require = directive.require; 8353 pre.directiveName = directiveName; 8354 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8355 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 8356 } 8357 preLinkFns.push(pre); 8358 } 8359 if (post) { 8360 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 8361 post.require = directive.require; 8362 post.directiveName = directiveName; 8363 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8364 post = cloneAndAnnotateFn(post, {isolateScope: true}); 8365 } 8366 postLinkFns.push(post); 8367 } 8368 } 8369 8370 8371 function getControllers(directiveName, require, $element, elementControllers) { 8372 var value; 8373 8374 if (isString(require)) { 8375 var match = require.match(REQUIRE_PREFIX_REGEXP); 8376 var name = require.substring(match[0].length); 8377 var inheritType = match[1] || match[3]; 8378 var optional = match[2] === '?'; 8379 8380 //If only parents then start at the parent element 8381 if (inheritType === '^^') { 8382 $element = $element.parent(); 8383 //Otherwise attempt getting the controller from elementControllers in case 8384 //the element is transcluded (and has no data) and to avoid .data if possible 8385 } else { 8386 value = elementControllers && elementControllers[name]; 8387 value = value && value.instance; 8388 } 8389 8390 if (!value) { 8391 var dataName = '$' + name + 'Controller'; 8392 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 8393 } 8394 8395 if (!value && !optional) { 8396 throw $compileMinErr('ctreq', 8397 "Controller '{0}', required by directive '{1}', can't be found!", 8398 name, directiveName); 8399 } 8400 } else if (isArray(require)) { 8401 value = []; 8402 for (var i = 0, ii = require.length; i < ii; i++) { 8403 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 8404 } 8405 } 8406 8407 return value || null; 8408 } 8409 8410 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) { 8411 var elementControllers = createMap(); 8412 for (var controllerKey in controllerDirectives) { 8413 var directive = controllerDirectives[controllerKey]; 8414 var locals = { 8415 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 8416 $element: $element, 8417 $attrs: attrs, 8418 $transclude: transcludeFn 8419 }; 8420 8421 var controller = directive.controller; 8422 if (controller == '@') { 8423 controller = attrs[directive.name]; 8424 } 8425 8426 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 8427 8428 // For directives with element transclusion the element is a comment, 8429 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 8430 // clean up (http://bugs.jquery.com/ticket/8335). 8431 // Instead, we save the controllers for the element in a local hash and attach to .data 8432 // later, once we have the actual element. 8433 elementControllers[directive.name] = controllerInstance; 8434 if (!hasElementTranscludeDirective) { 8435 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 8436 } 8437 } 8438 return elementControllers; 8439 } 8440 8441 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 8442 var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, 8443 attrs, removeScopeBindingWatches, removeControllerBindingWatches; 8444 8445 if (compileNode === linkNode) { 8446 attrs = templateAttrs; 8447 $element = templateAttrs.$$element; 8448 } else { 8449 $element = jqLite(linkNode); 8450 attrs = new Attributes($element, templateAttrs); 8451 } 8452 8453 controllerScope = scope; 8454 if (newIsolateScopeDirective) { 8455 isolateScope = scope.$new(true); 8456 } else if (newScopeDirective) { 8457 controllerScope = scope.$parent; 8458 } 8459 8460 if (boundTranscludeFn) { 8461 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 8462 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 8463 transcludeFn = controllersBoundTransclude; 8464 transcludeFn.$$boundTransclude = boundTranscludeFn; 8465 } 8466 8467 if (controllerDirectives) { 8468 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope); 8469 } 8470 8471 if (newIsolateScopeDirective) { 8472 // Initialize isolate scope bindings for new isolate scope directive. 8473 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 8474 templateDirective === newIsolateScopeDirective.$$originalDirective))); 8475 compile.$$addScopeClass($element, true); 8476 isolateScope.$$isolateBindings = 8477 newIsolateScopeDirective.$$isolateBindings; 8478 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope, 8479 isolateScope.$$isolateBindings, 8480 newIsolateScopeDirective); 8481 if (removeScopeBindingWatches) { 8482 isolateScope.$on('$destroy', removeScopeBindingWatches); 8483 } 8484 } 8485 8486 // Initialize bindToController bindings 8487 for (var name in elementControllers) { 8488 var controllerDirective = controllerDirectives[name]; 8489 var controller = elementControllers[name]; 8490 var bindings = controllerDirective.$$bindings.bindToController; 8491 8492 if (controller.identifier && bindings) { 8493 removeControllerBindingWatches = 8494 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 8495 } 8496 8497 var controllerResult = controller(); 8498 if (controllerResult !== controller.instance) { 8499 // If the controller constructor has a return value, overwrite the instance 8500 // from setupControllers 8501 controller.instance = controllerResult; 8502 $element.data('$' + controllerDirective.name + 'Controller', controllerResult); 8503 removeControllerBindingWatches && removeControllerBindingWatches(); 8504 removeControllerBindingWatches = 8505 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 8506 } 8507 } 8508 8509 // PRELINKING 8510 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 8511 linkFn = preLinkFns[i]; 8512 invokeLinkFn(linkFn, 8513 linkFn.isolateScope ? isolateScope : scope, 8514 $element, 8515 attrs, 8516 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8517 transcludeFn 8518 ); 8519 } 8520 8521 // RECURSION 8522 // We only pass the isolate scope, if the isolate directive has a template, 8523 // otherwise the child elements do not belong to the isolate directive. 8524 var scopeToChild = scope; 8525 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 8526 scopeToChild = isolateScope; 8527 } 8528 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 8529 8530 // POSTLINKING 8531 for (i = postLinkFns.length - 1; i >= 0; i--) { 8532 linkFn = postLinkFns[i]; 8533 invokeLinkFn(linkFn, 8534 linkFn.isolateScope ? isolateScope : scope, 8535 $element, 8536 attrs, 8537 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8538 transcludeFn 8539 ); 8540 } 8541 8542 // This is the function that is injected as `$transclude`. 8543 // Note: all arguments are optional! 8544 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { 8545 var transcludeControllers; 8546 8547 // No scope passed in: 8548 if (!isScope(scope)) { 8549 futureParentElement = cloneAttachFn; 8550 cloneAttachFn = scope; 8551 scope = undefined; 8552 } 8553 8554 if (hasElementTranscludeDirective) { 8555 transcludeControllers = elementControllers; 8556 } 8557 if (!futureParentElement) { 8558 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 8559 } 8560 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 8561 } 8562 } 8563 } 8564 8565 // Depending upon the context in which a directive finds itself it might need to have a new isolated 8566 // or child scope created. For instance: 8567 // * if the directive has been pulled into a template because another directive with a higher priority 8568 // asked for element transclusion 8569 // * if the directive itself asks for transclusion but it is at the root of a template and the original 8570 // element was replaced. See https://github.com/angular/angular.js/issues/12936 8571 function markDirectiveScope(directives, isolateScope, newScope) { 8572 for (var j = 0, jj = directives.length; j < jj; j++) { 8573 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); 8574 } 8575 } 8576 8577 /** 8578 * looks up the directive and decorates it with exception handling and proper parameters. We 8579 * call this the boundDirective. 8580 * 8581 * @param {string} name name of the directive to look up. 8582 * @param {string} location The directive must be found in specific format. 8583 * String containing any of theses characters: 8584 * 8585 * * `E`: element name 8586 * * `A': attribute 8587 * * `C`: class 8588 * * `M`: comment 8589 * @returns {boolean} true if directive was added. 8590 */ 8591 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 8592 endAttrName) { 8593 if (name === ignoreDirective) return null; 8594 var match = null; 8595 if (hasDirectives.hasOwnProperty(name)) { 8596 for (var directive, directives = $injector.get(name + Suffix), 8597 i = 0, ii = directives.length; i < ii; i++) { 8598 try { 8599 directive = directives[i]; 8600 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 8601 directive.restrict.indexOf(location) != -1) { 8602 if (startAttrName) { 8603 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 8604 } 8605 tDirectives.push(directive); 8606 match = directive; 8607 } 8608 } catch (e) { $exceptionHandler(e); } 8609 } 8610 } 8611 return match; 8612 } 8613 8614 8615 /** 8616 * looks up the directive and returns true if it is a multi-element directive, 8617 * and therefore requires DOM nodes between -start and -end markers to be grouped 8618 * together. 8619 * 8620 * @param {string} name name of the directive to look up. 8621 * @returns true if directive was registered as multi-element. 8622 */ 8623 function directiveIsMultiElement(name) { 8624 if (hasDirectives.hasOwnProperty(name)) { 8625 for (var directive, directives = $injector.get(name + Suffix), 8626 i = 0, ii = directives.length; i < ii; i++) { 8627 directive = directives[i]; 8628 if (directive.multiElement) { 8629 return true; 8630 } 8631 } 8632 } 8633 return false;
8634 } 8635 8636 /** 8637 * When the element is replaced with HTML template then the new attributes 8638 * on the template need to be merged with the existing attributes in the DOM. 8639 * The desired effect is to have both of the attributes present. 8640 * 8641 * @param {object} dst destination attributes (original DOM) 8642 * @param {object} src source attributes (from the directive template) 8643 */ 8644 function mergeTemplateAttributes(dst, src) { 8645 var srcAttr = src.$attr, 8646 dstAttr = dst.$attr, 8647 $element = dst.$$element; 8648 8649 // reapply the old attributes to the new element 8650 forEach(dst, function(value, key) { 8651 if (key.charAt(0) != '$') { 8652 if (src[key] && src[key] !== value) { 8653 value += (key === 'style' ? ';' : ' ') + src[key]; 8654 } 8655 dst.$set(key, value, true, srcAttr[key]); 8656 } 8657 }); 8658 8659 // copy the new attributes on the old attrs object 8660 forEach(src, function(value, key) { 8661 if (key == 'class') { 8662 safeAddClass($element, value); 8663 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 8664 } else if (key == 'style') { 8665 $element.attr('style', $element.attr('style') + ';' + value); 8666 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 8667 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 8668 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 8669 // have an attribute like "has-own-property" or "data-has-own-property", etc. 8670 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 8671 dst[key] = value; 8672 dstAttr[key] = srcAttr[key]; 8673 } 8674 }); 8675 } 8676 8677 8678 function compileTemplateUrl(directives, $compileNode, tAttrs, 8679 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 8680 var linkQueue = [], 8681 afterTemplateNodeLinkFn, 8682 afterTemplateChildLinkFn, 8683 beforeTemplateCompileNode = $compileNode[0], 8684 origAsyncDirective = directives.shift(), 8685 derivedSyncDirective = inherit(origAsyncDirective, { 8686 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 8687 }), 8688 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 8689 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 8690 : origAsyncDirective.templateUrl, 8691 templateNamespace = origAsyncDirective.templateNamespace; 8692 8693 $compileNode.empty(); 8694 8695 $templateRequest(templateUrl) 8696 .then(function(content) { 8697 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 8698 8699 content = denormalizeTemplate(content); 8700 8701 if (origAsyncDirective.replace) { 8702 if (jqLiteIsTextNode(content)) { 8703 $template = []; 8704 } else { 8705 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 8706 } 8707 compileNode = $template[0]; 8708 8709 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8710 throw $compileMinErr('tplrt', 8711 "Template for directive '{0}' must have exactly one root element. {1}", 8712 origAsyncDirective.name, templateUrl); 8713 } 8714 8715 tempTemplateAttrs = {$attr: {}}; 8716 replaceWith($rootElement, $compileNode, compileNode); 8717 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 8718 8719 if (isObject(origAsyncDirective.scope)) { 8720 // the original directive that caused the template to be loaded async required 8721 // an isolate scope 8722 markDirectiveScope(templateDirectives, true); 8723 } 8724 directives = templateDirectives.concat(directives); 8725 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 8726 } else { 8727 compileNode = beforeTemplateCompileNode; 8728 $compileNode.html(content); 8729 } 8730 8731 directives.unshift(derivedSyncDirective); 8732 8733 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 8734 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 8735 previousCompileContext);
8736 forEach($rootElement, function(node, i) { 8737 if (node == compileNode) { 8738 $rootElement[i] = $compileNode[0]; 8739 } 8740 }); 8741 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 8742 8743 while (linkQueue.length) { 8744 var scope = linkQueue.shift(), 8745 beforeTemplateLinkNode = linkQueue.shift(), 8746 linkRootElement = linkQueue.shift(), 8747 boundTranscludeFn = linkQueue.shift(), 8748 linkNode = $compileNode[0]; 8749 8750 if (scope.$$destroyed) continue; 8751 8752 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 8753 var oldClasses = beforeTemplateLinkNode.className; 8754 8755 if (!(previousCompileContext.hasElementTranscludeDirective && 8756 origAsyncDirective.replace)) { 8757 // it was cloned therefore we have to clone as well. 8758 linkNode = jqLiteClone(compileNode); 8759 } 8760 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 8761 8762 // Copy in CSS classes from original node 8763 safeAddClass(jqLite(linkNode), oldClasses); 8764 } 8765 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8766 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8767 } else { 8768 childBoundTranscludeFn = boundTranscludeFn; 8769 } 8770 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 8771 childBoundTranscludeFn); 8772 } 8773 linkQueue = null; 8774 }); 8775 8776 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 8777 var childBoundTranscludeFn = boundTranscludeFn; 8778 if (scope.$$destroyed) return; 8779 if (linkQueue) { 8780 linkQueue.push(scope, 8781 node, 8782 rootElement, 8783 childBoundTranscludeFn); 8784 } else { 8785 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8786 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8787 } 8788 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 8789 } 8790 }; 8791 } 8792 8793 8794 /** 8795 * Sorting function for bound directives. 8796 */ 8797 function byPriority(a, b) { 8798 var diff = b.priority - a.priority; 8799 if (diff !== 0) return diff; 8800 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 8801 return a.index - b.index; 8802 } 8803 8804 function assertNoDuplicate(what, previousDirective, directive, element) { 8805 8806 function wrapModuleNameIfDefined(moduleName) { 8807 return moduleName ? 8808 (' (module: ' + moduleName + ')') : 8809 ''; 8810 } 8811 8812 if (previousDirective) { 8813 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 8814 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 8815 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 8816 } 8817 } 8818 8819 8820 function addTextInterpolateDirective(directives, text) { 8821 var interpolateFn = $interpolate(text, true); 8822 if (interpolateFn) { 8823 directives.push({ 8824 priority: 0, 8825 compile: function textInterpolateCompileFn(templateNode) { 8826 var templateNodeParent = templateNode.parent(), 8827 hasCompileParent = !!templateNodeParent.length; 8828 8829 // When transcluding a template that has bindings in the root 8830 // we don't have a parent and thus need to add the class during linking fn. 8831 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 8832 8833 return function textInterpolateLinkFn(scope, node) { 8834 var parent = node.parent(); 8835 if (!hasCompileParent) compile.$$addBindingClass(parent); 8836 compile.$$addBindingInfo(parent, interpolateFn.expressions); 8837 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 8838 node[0].nodeValue = value; 8839 }); 8840 }; 8841 } 8842 }); 8843 } 8844 } 8845 8846 8847 function wrapTemplate(type, template) { 8848 type = lowercase(type || 'html'); 8849 switch (type) { 8850 case 'svg': 8851 case 'math': 8852 var wrapper = document.createElement('div'); 8853 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 8854 return wrapper.childNodes[0].childNodes; 8855 default: 8856 return template; 8857 } 8858 } 8859 8860 8861 function getTrustedContext(node, attrNormalizedName) { 8862 if (attrNormalizedName == "srcdoc") { 8863 return $sce.HTML; 8864 } 8865 var tag = nodeName_(node); 8866 // maction[xlink:href] can source SVG. It's not limited to <maction>. 8867 if (attrNormalizedName == "xlinkHref" || 8868 (tag == "form" && attrNormalizedName == "action") || 8869 (tag != "img" && (attrNormalizedName == "src" || 8870 attrNormalizedName == "ngSrc"))) { 8871 return $sce.RESOURCE_URL; 8872 } 8873 } 8874 8875 8876 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 8877 var trustedContext = getTrustedContext(node, name); 8878 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 8879 8880 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 8881 8882 // no interpolation found -> ignore 8883 if (!interpolateFn) return; 8884 8885 8886 if (name === "multiple" && nodeName_(node) === "select") { 8887 throw $compileMinErr("selmulti", 8888 "Binding to the 'multiple' attribute is not supported. Element: {0}", 8889 startingTag(node)); 8890 } 8891 8892 directives.push({ 8893 priority: 100, 8894 compile: function() { 8895 return { 8896 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 8897 var $$observers = (attr.$$observers || (attr.$$observers = createMap())
8897); 8898 8899 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 8900 throw $compileMinErr('nodomevents', 8901 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 8902 "ng- versions (such as ng-click instead of onclick) instead."); 8903 } 8904 8905 // If the attribute has changed since last $interpolate()ed 8906 var newValue = attr[name]; 8907 if (newValue !== value) { 8908 // we need to interpolate again since the attribute value has been updated 8909 // (e.g. by another directive's compile function) 8910 // ensure unset/empty values make interpolateFn falsy 8911 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 8912 value = newValue; 8913 } 8914 8915 // if attribute was updated so that there is no interpolation going on we don't want to 8916 // register any observers 8917 if (!interpolateFn) return; 8918 8919 // initialize attr object so that it's ready in case we need the value for isolate 8920 // scope initialization, otherwise the value would not be available from isolate 8921 // directive's linking fn during linking phase 8922 attr[name] = interpolateFn(scope); 8923 8924 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 8925 (attr.$$observers && attr.$$observers[name].$$scope || scope). 8926 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 8927 //special case for class attribute addition + removal 8928 //so that class changes can tap into the animation 8929 //hooks provided by the $animate service. Be sure to 8930 //skip animations when the first digest occurs (when 8931 //both the new and the old values are the same) since 8932 //the CSS classes are the non-interpolated values 8933 if (name === 'class' && newValue != oldValue) { 8934 attr.$updateClass(newValue, oldValue); 8935 } else { 8936 attr.$set(name, newValue); 8937 } 8938 }); 8939 } 8940 }; 8941 } 8942 }); 8943 } 8944 8945 8946 /** 8947 * This is a special jqLite.replaceWith, which can replace items which 8948 * have no parents, provided that the containing jqLite collection is provided. 8949 * 8950 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 8951 * in the root of the tree. 8952 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 8953 * the shell, but replace its DOM node reference. 8954 * @param {Node} newNode The new DOM node. 8955 */ 8956 function replaceWith($rootElement, elementsToRemove, newNode) { 8957 var firstElementToRemove = elementsToRemove[0], 8958 removeCount = elementsToRemove.length, 8959 parent = firstElementToRemove.parentNode, 8960 i, ii; 8961 8962 if ($rootElement) { 8963 for (i = 0, ii = $rootElement.length; i < ii; i++) { 8964 if ($rootElement[i] == firstElementToRemove) { 8965 $rootElement[i++] = newNode; 8966 for (var j = i, j2 = j + removeCount - 1, 8967 jj = $rootElement.length; 8968 j < jj; j++, j2++) { 8969 if (j2 < jj) { 8970 $rootElement[j] = $rootElement[j2]; 8971 } else { 8972 delete $rootElement[j]; 8973 } 8974 } 8975 $rootElement.length -= removeCount - 1; 8976 8977 // If the replaced element is also the jQuery .context then replace it 8978 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 8979 // http://api.jquery.com/context/ 8980 if ($rootElement.context === firstElementToRemove) { 8981 $rootElement.context = newNode; 8982 } 8983 break; 8984 } 8985 } 8986 } 8987 8988 if (parent) { 8989 parent.replaceChild(newNode, firstElementToRemove); 8990 } 8991 8992 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? 8993 var fragment = document.createDocumentFragment(); 8994 fragment.appendChild(firstElementToRemove); 8995 8996 if (jqLite.hasData(firstElementToRemove)) { 8997 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 8998 // data here because there's no public interface in jQuery to do that and copying over 8999 // event listeners (which is the main use of private data) wouldn't work anyway. 9000 jqLite.data(newNode, jqLite.data(firstElementToRemove)); 9001 9002 // Remove data of the replaced element. We cannot just call .remove() 9003 // on the element it since that would deallocate scope that is needed 9004 // for the new node. Instead, remove the data "manually". 9005 if (!jQuery) { 9006 delete jqLite.cache[firstElementToRemove[jqLite.expando]]; 9007 } else { 9008 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after 9009 // the replaced element. The cleanData version monkey-patched by Angular would cause 9010 // the scope to be trashed and we do need the very same scope to work with the new 9011 // element. However, we cannot just cache the non-patched version and use it here as 9012 // that would break if another library patches the method after Angular does (one
9013 // example is jQuery UI). Instead, set a flag indicating scope destroying should be 9014 // skipped this one time. 9015 skipDestroyOnNextJQueryCleanData = true; 9016 jQuery.cleanData([firstElementToRemove]); 9017 } 9018 } 9019 9020 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 9021 var element = elementsToRemove[k]; 9022 jqLite(element).remove(); // must do this way to clean up expando 9023 fragment.appendChild(element); 9024 delete elementsToRemove[k]; 9025 } 9026 9027 elementsToRemove[0] = newNode; 9028 elementsToRemove.length = 1; 9029 } 9030 9031 9032 function cloneAndAnnotateFn(fn, annotation) { 9033 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 9034 } 9035 9036 9037 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 9038 try { 9039 linkFn(scope, $element, attrs, controllers, transcludeFn); 9040 } catch (e) { 9041 $exceptionHandler(e, startingTag($element)); 9042 } 9043 } 9044 9045 9046 // Set up $watches for isolate scope and controller bindings. This process 9047 // only occurs for isolate scopes and new scopes with controllerAs. 9048 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { 9049 var removeWatchCollection = []; 9050 forEach(bindings, function(definition, scopeName) { 9051 var attrName = definition.attrName, 9052 optional = definition.optional, 9053 mode = definition.mode, // @, =, or & 9054 lastValue, 9055 parentGet, parentSet, compare; 9056 9057 switch (mode) { 9058 9059 case '@': 9060 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 9061 destination[scopeName] = attrs[attrName] = void 0; 9062 } 9063 attrs.$observe(attrName, function(value) { 9064 if (isString(value)) { 9065 destination[scopeName] = value; 9066 } 9067 }); 9068 attrs.$$observers[attrName].$$scope = scope; 9069 if (isString(attrs[attrName])) { 9070 // If the attribute has been provided then we trigger an interpolation to ensure 9071 // the value is there for use in the link fn 9072 destination[scopeName] = $interpolate(attrs[attrName])(scope); 9073 } 9074 break; 9075 9076 case '=': 9077 if (!hasOwnProperty.call(attrs, attrName)) { 9078 if (optional) break; 9079 attrs[attrName] = void 0; 9080 } 9081 if (optional && !attrs[attrName]) break; 9082 9083 parentGet = $parse(attrs[attrName]); 9084 if (parentGet.literal) { 9085 compare = equals; 9086 } else { 9087 compare = function(a, b) { return a === b || (a !== a && b !== b); }; 9088 } 9089 parentSet = parentGet.assign || function() { 9090 // reset the change, or we will throw this exception on every $digest 9091 lastValue = destination[scopeName] = parentGet(scope); 9092 throw $compileMinErr('nonassign', 9093 "Expression '{0}' used with directive '{1}' is non-assignable!", 9094 attrs[attrName], directive.name); 9095 }; 9096 lastValue = destination[scopeName] = parentGet(scope); 9097 var parentValueWatch = function parentValueWatch(parentValue) { 9098 if (!compare(parentValue, destination[scopeName])) { 9099 // we are out of sync and need to copy 9100 if (!compare(parentValue, lastValue)) { 9101 // parent changed and it has precedence 9102 destination[scopeName] = parentValue; 9103 } else { 9104 // if the parent can be assigned then do so 9105 parentSet(scope, parentValue = destination[scopeName]); 9106 } 9107 } 9108 return lastValue = parentValue; 9109 }; 9110 parentValueWatch.$stateful = true; 9111 var removeWatch; 9112 if (definition.collection) { 9113 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 9114 } else { 9115 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 9116 } 9117 removeWatchCollection.push(removeWatch); 9118 break; 9119 9120 case '&': 9121 // Don't assign Object.prototype method to scope 9122 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 9123 9124 // Don't assign noop to destination if expression is not val
9124id 9125 if (parentGet === noop && optional) break; 9126 9127 destination[scopeName] = function(locals) { 9128 return parentGet(scope, locals); 9129 }; 9130 break; 9131 } 9132 }); 9133 9134 return removeWatchCollection.length && function removeWatches() { 9135 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { 9136 removeWatchCollection[i](); 9137 } 9138 }; 9139 } 9140 }]; 9141} 9142 9143var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 9144/** 9145 * Converts all accepted directives format into proper directive name. 9146 * @param name Name to normalize 9147 */ 9148function directiveNormalize(name) { 9149 return camelCase(name.replace(PREFIX_REGEXP, '')); 9150} 9151 9152/** 9153 * @ngdoc type 9154 * @name $compile.directive.Attributes 9155 * 9156 * @description 9157 * A shared object between directive compile / linking functions which contains normalized DOM 9158 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 9159 * needed since all of these are treated as equivalent in Angular: 9160 * 9161 * ``` 9162 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 9163 * ``` 9164 */ 9165 9166/** 9167 * @ngdoc property 9168 * @name $compile.directive.Attributes#$attr 9169 * 9170 * @description 9171 * A map of DOM element attribute names to the normalized name. This is 9172 * needed to do reverse lookup from normalized name back to actual name. 9173 */ 9174 9175 9176/** 9177 * @ngdoc method 9178 * @name $compile.directive.Attributes#$set 9179 * @kind function 9180 * 9181 * @description 9182 * Set DOM element attribute value. 9183 * 9184 * 9185 * @param {string} name Normalized element attribute name of the property to modify. The name is 9186 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 9187 * property to the original name. 9188 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 9189 */ 9190 9191 9192 9193/** 9194 * Closure compiler type information 9195 */ 9196 9197function nodesetLinkingFn( 9198 /* angular.Scope */ scope, 9199 /* NodeList */ nodeList, 9200 /* Element */ rootElement, 9201 /* function(Function) */ boundTranscludeFn 9202) {} 9203 9204function directiveLinkingFn( 9205 /* nodesetLinkingFn */ nodesetLinkingFn, 9206 /* angular.Scope */ scope, 9207 /* Node */ node, 9208 /* Element */ rootElement, 9209 /* function(Function) */ boundTranscludeFn 9210) {} 9211 9212function tokenDifference(str1, str2) { 9213 var values = '', 9214 tokens1 = str1.split(/\s+/), 9215 tokens2 = str2.split(/\s+/); 9216 9217 outer: 9218 for (var i = 0; i < tokens1.length; i++) { 9219 var token = tokens1[i]; 9220 for (var j = 0; j < tokens2.length; j++) { 9221 if (token == tokens2[j]) continue outer; 9222 } 9223 values += (values.length > 0 ? ' ' : '') + token; 9224 } 9225 return values; 9226} 9227 9228function removeComments(jqNodes) { 9229 jqNodes = jqLite(jqNodes); 9230 var i = jqNodes.length; 9231 9232 if (i <= 1) { 9233 return jqNodes; 9234 } 9235 9236 while (i--) { 9237 var node = jqNodes[i]; 9238 if (node.nodeType === NODE_TYPE_COMMENT) { 9239 splice.call(jqNodes, i, 1); 9240 } 9241 } 9242 return jqNodes; 9243} 9244 9245var $controllerMinErr = minErr('$controller'); 9246 9247 9248var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; 9249function identifierForController(controller, ident) { 9250 if (ident && isString(ident)) return ident; 9251 if (isString(controller)) { 9252 var match = CNTRL_REG.exec(controller); 9253 if (match) return match[3]; 9254 } 9255} 9256 9257 9258/** 9259 * @ngdoc provider 9260 * @name $controllerProvider 9261 * @description 9262 * The {@link ng.$controller $controller service} is used by Angular to create new 9263 * controllers. 9264 * 9265 * This provider allows controller registration via the 9266 * {@link ng.$controllerProvider#register register} method. 9267 */ 9268function $ControllerProvider() { 9269 var controllers = {}, 9270 globals = false; 9271 9272 /** 9273 * @ngdoc method 9274 * @name $controllerProvider#register 9275 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 9276 * the names and the values are the constructors. 9277 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 9278 * annotations in the array notation). 9279 */ 9280 this.register = function(name, constructor) { 9281 assertNotHasOwnProperty(name, 'controller'); 9282 if (isObject(name)) { 9283 extend(controllers, name); 9284 } else { 9285 controllers[name] = constructor; 9286 } 9287 }; 9288 9289 /** 9290 * @ngdoc method 9291 * @name $controllerProvider#allowGlobals 9292 * @description If called, allows `$controller` to find controller constructors on `window` 9293 */ 9294 this.allowGlobals = function() { 9295 globals = true; 9296 }; 9297 9298 9299 this.$get = ['$injector', '$window', function($injector, $window) { 9300 9301 /** 9302 * @ngdoc service 9303 * @name $controller 9304 * @requires $injector 9305 * 9306 * @param {Function|string} constructor If called with a function then it's considered to be the 9307 * controller constructor function. Otherwise it's considered to be a string which is used 9308 * to retrieve the controller constructor using the following steps: 9309 * 9310 * * check if a controller with given name is registered via `$controllerProvider` 9311 * * check if evaluating the string on the current scope returns a constructor 9312 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 9313 * `window` object (not recommended) 9314 * 9315 * The string can use the `controller as property` syntax, where the controller instance is published 9316 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 9317 * to work correctly. 9318 * 9319 * @param {Object} locals Injection locals for Controller. 9320 * @return {Object} Instance of given controller. 9321 *
9322 * @description 9323 * `$controller` service is responsible for instantiating controllers. 9324 * 9325 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 9326 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 9327 */ 9328 return function(expression, locals, later, ident) { 9329 // PRIVATE API: 9330 // param `later` --- indicates that the controller's constructor is invoked at a later time. 9331 // If true, $controller will allocate the object with the correct 9332 // prototype chain, but will not invoke the controller until a returned 9333 // callback is invoked. 9334 // param `ident` --- An optional label which overrides the label parsed from the controller 9335 // expression, if any. 9336 var instance, match, constructor, identifier; 9337 later = later === true; 9338 if (ident && isString(ident)) { 9339 identifier = ident; 9340 } 9341 9342 if (isString(expression)) { 9343 match = expression.match(CNTRL_REG); 9344 if (!match) { 9345 throw $controllerMinErr('ctrlfmt', 9346 "Badly formed controller string '{0}'. " + 9347 "Must match `__name__ as __id__` or `__name__`.", expression); 9348 } 9349 constructor = match[1], 9350 identifier = identifier || match[3]; 9351 expression = controllers.hasOwnProperty(constructor) 9352 ? controllers[constructor] 9353 : getter(locals.$scope, constructor, true) || 9354 (globals ? getter($window, constructor, true) : undefined); 9355 9356 assertArgFn(expression, constructor, true); 9357 } 9358 9359 if (later) { 9360 // Instantiate controller later: 9361 // This machinery is used to create an instance of the object before calling the 9362 // controller's constructor itself. 9363 // 9364 // This allows properties to be added to the controller before the constructor is 9365 // invoked. Primarily, this is used for isolate scope bindings in $compile. 9366 // 9367 // This feature is not intended for use by applications, and is thus not documented 9368 // publicly. 9369 // Object creation: http://jsperf.com/create-constructor/2 9370 var controllerPrototype = (isArray(expression) ? 9371 expression[expression.length - 1] : expression).prototype; 9372 instance = Object.create(controllerPrototype || null); 9373 9374 if (identifier) { 9375 addIdentifier(locals, identifier, instance, constructor || expression.name); 9376 } 9377 9378 var instantiate; 9379 return instantiate = extend(function() { 9380 var result = $injector.invoke(expression, instance, locals, constructor); 9381 if (result !== instance && (isObject(result) || isFunction(result))) { 9382 instance = result; 9383 if (identifier) { 9384 // If result changed, re-assign controllerAs value to scope. 9385 addIdentifier(locals, identifier, instance, constructor || expression.name); 9386 } 9387 } 9388 return instance; 9389 }, { 9390 instance: instance, 9391 identifier: identifier 9392 }); 9393 } 9394 9395 instance = $injector.instantiate(expression, locals, constructor); 9396 9397 if (identifier) { 9398 addIdentifier(locals, identifier, instance, constructor || expression.name); 9399 } 9400 9401 return instance; 9402 }; 9403 9404 function addIdentifier(locals, identifier, instance, name) { 9405 if (!(locals && isObject(locals.$scope))) { 9406 throw minErr('$controller')('noscp', 9407 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 9408 name, identifier); 9409 } 9410 9411 locals.$scope[identifier] = instance; 9412 } 9413 }]; 9414} 9415 9416/** 9417 * @ngdoc service 9418 * @name $document 9419 * @requires $window 9420 * 9421 * @description 9422 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 9423 * 9424 * @example 9425 <example module="documentExample"> 9426 <file name="index.html">
9427 <div ng-controller="ExampleController"> 9428 <p>$document title: <b ng-bind="title"></b></p> 9429 <p>window.document title: <b ng-bind="windowTitle"></b></p> 9430 </div> 9431 </file> 9432 <file name="script.js"> 9433 angular.module('documentExample', []) 9434 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 9435 $scope.title = $document[0].title; 9436 $scope.windowTitle = angular.element(window.document)[0].title; 9437 }]); 9438 </file> 9439 </example> 9440 */ 9441function $DocumentProvider() { 9442 this.$get = ['$window', function(window) { 9443 return jqLite(window.document); 9444 }]; 9445} 9446 9447/** 9448 * @ngdoc service 9449 * @name $exceptionHandler 9450 * @requires ng.$log 9451 * 9452 * @description 9453 * Any uncaught exception in angular expressions is delegated to this service. 9454 * The default implementation simply delegates to `$log.error` which logs it into 9455 * the browser console. 9456 * 9457 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 9458 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 9459 * 9460 * ## Example: 9461 * 9462 * ```js 9463 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 9464 * return function(exception, cause) { 9465 * exception.message += ' (caused by "' + cause + '")'; 9466 * throw exception; 9467 * }; 9468 * }); 9469 * ``` 9470 * 9471 * This example will override the normal action of `$exceptionHandler`, to make angular 9472 * exceptions fail hard when they happen, instead of just logging to the console. 9473 * 9474 * <hr /> 9475 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 9476 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 9477 * (unless executed during a digest). 9478 * 9479 * If you wish, you can manually delegate exceptions, e.g. 9480 * `try { ... } catch(e) { $exceptionHandler(e); }` 9481 * 9482 * @param {Error} exception Exception associated with the error. 9483 * @param {string=} cause optional information about the context in which 9484 * the error was thrown. 9485 * 9486 */ 9487function $ExceptionHandlerProvider() { 9488 this.$get = ['$log', function($log) { 9489 return function(exception, cause) { 9490 $log.error.apply($log, arguments); 9491 }; 9492 }]; 9493} 9494 9495var $$ForceReflowProvider = function() { 9496 this.$get = ['$document', function($document) { 9497 return function(domNode) { 9498 //the line below will force the browser to perform a repaint so 9499 //that all the animated elements within the animation frame will 9500 //be properly updated and drawn on screen. This is required to 9501 //ensure that the preparation animation is properly flushed so that 9502 //the active state picks up from there. DO NOT REMOVE THIS LINE. 9503 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 9504 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 9505 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 9506 if (domNode) { 9507 if (!domNode.nodeType && domNode instanceof jqLite) { 9508 domNode = domNode[0]; 9509 } 9510 } else { 9511 domNode = $document[0].body; 9512 } 9513 return domNode.offsetWidth + 1; 9514 }; 9515 }]; 9516}; 9517 9518var APPLICATION_JSON = 'application/json'; 9519var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 9520var JSON_START = /^\[|^\{(?!\{)/; 9521var JSON_ENDS = { 9522 '[': /]$/, 9523 '{': /}$/ 9524}; 9525var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 9526var $httpMinErr = minErr('$http'); 9527var $httpMinErrLegacyFn = function(method) { 9528 return function() { 9529 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); 9530 }; 9531}; 9532 9533function serializeValue(v) { 9534 if (isObject(v)) { 9535 return isDate(v) ? v.toISOString() : toJson(v); 9536 } 9537 return v; 9538} 9539 9540 9541function $HttpParamSerializerProvider() { 9542 /** 9543 * @ngdoc service 9544 * @name $httpParamSerializer 9545 * @description 9546 * 9547 * Default {@link $http `$http`} params serializer that converts objects to strings 9548 * according to the following rules: 9549 * 9550 * * `{'foo': 'bar'}` results in `foo=bar` 9551 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 9552 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 9553 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 9554 * 9555 * Note that serializer will sort the request parameters alphabetically. 9556 * */ 9557 9558 this.$get = function() { 9559 return function ngParamSerializer(params) { 9560 if (!params) return ''; 9561 var parts = [];
vendor: 3,682 bytes, lines 9562-9688
9562 forEachSorted(params, function(value, key) { 9563 if (value === null || isUndefined(value)) return; 9564 if (isArray(value)) { 9565 forEach(value, function(v, k) { 9566 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 9567 }); 9568 } else { 9569 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 9570 } 9571 }); 9572 9573 return parts.join('&'); 9574 }; 9575 }; 9576} 9577 9578function $HttpParamSerializerJQLikeProvider() { 9579 /** 9580 * @ngdoc service 9581 * @name $httpParamSerializerJQLike 9582 * @description 9583 * 9584 * Alternative {@link $http `$http`} params serializer that follows 9585 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 9586 * The serializer will also sort the params alphabetically. 9587 * 9588 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 9589 * 9590 * ```js 9591 * $http({ 9592 * url: myUrl, 9593 * method: 'GET', 9594 * params: myParams, 9595 * paramSerializer: '$httpParamSerializerJQLike' 9596 * }); 9597 * ``` 9598 * 9599 * It is also possible to set it as the default `paramSerializer` in the 9600 * {@link $httpProvider#defaults `$httpProvider`}. 9601 * 9602 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 9603 * form data for submission: 9604 * 9605 * ```js 9606 * .controller(function($http, $httpParamSerializerJQLike) { 9607 * //... 9608 * 9609 * $http({ 9610 * url: myUrl, 9611 * method: 'POST', 9612 * data: $httpParamSerializerJQLike(myData), 9613 * headers: { 9614 * 'Content-Type': 'application/x-www-form-urlencoded' 9615 * } 9616 * }); 9617 * 9618 * }); 9619 * ``` 9620 * 9621 * */ 9622 this.$get = function() { 9623 return function jQueryLikeParamSerializer(params) { 9624 if (!params) return ''; 9625 var parts = []; 9626 serialize(params, '', true); 9627 return parts.join('&'); 9628 9629 function serialize(toSerialize, prefix, topLevel) { 9630 if (toSerialize === null || isUndefined(toSerialize)) return; 9631 if (isArray(toSerialize)) { 9632 forEach(toSerialize, function(value, index) { 9633 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 9634 }); 9635 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 9636 forEachSorted(toSerialize, function(value, key) { 9637 serialize(value, prefix + 9638 (topLevel ? '' : '[') + 9639 key + 9640 (topLevel ? '' : ']')); 9641 }); 9642 } else { 9643 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 9644 } 9645 } 9646 }; 9647 }; 9648} 9649 9650function defaultHttpResponseTransform(data, headers) { 9651 if (isString(data)) { 9652 // Strip json vulnerability protection prefix and trim whitespace 9653 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 9654 9655 if (tempData) { 9656 var contentType = headers('Content-Type'); 9657 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 9658 data = fromJson(tempData); 9659 } 9660 } 9661 } 9662 9663 return data; 9664} 9665 9666function isJsonLike(str) { 9667 var jsonStart = str.match(JSON_START); 9668 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 9669} 9670 9671/** 9672 * Parse headers into key value object 9673 * 9674 * @param {string} headers Raw headers as a string 9675 * @returns {Object} Parsed headers as key value object 9676 */ 9677function parseHeaders(headers) { 9678 var parsed = createMap(), i; 9679 9680 function fillInParsed(key, val) { 9681 if (key) { 9682 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 9683 } 9684 } 9685 9686 if (isString(headers)) { 9687 forEach(headers.split('\n'), function(line) { 9688 i = line.indexOf(':');
vendor: 4,257 bytes, lines 9689-9826
9689 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 9690 }); 9691 } else if (isObject(headers)) { 9692 forEach(headers, function(headerVal, headerKey) { 9693 fillInParsed(lowercase(headerKey), trim(headerVal)); 9694 }); 9695 } 9696 9697 return parsed; 9698} 9699 9700 9701/** 9702 * Returns a function that provides access to parsed headers. 9703 * 9704 * Headers are lazy parsed when first requested. 9705 * @see parseHeaders 9706 * 9707 * @param {(string|Object)} headers Headers to provide access to. 9708 * @returns {function(string=)} Returns a getter function which if called with: 9709 * 9710 * - if called with single an argument returns a single header value or null 9711 * - if called with no arguments returns an object containing all headers. 9712 */ 9713function headersGetter(headers) { 9714 var headersObj; 9715 9716 return function(name) { 9717 if (!headersObj) headersObj = parseHeaders(headers); 9718 9719 if (name) { 9720 var value = headersObj[lowercase(name)]; 9721 if (value === void 0) { 9722 value = null; 9723 } 9724 return value; 9725 } 9726 9727 return headersObj; 9728 }; 9729} 9730 9731 9732/** 9733 * Chain all given functions 9734 * 9735 * This function is used for both request and response transforming 9736 * 9737 * @param {*} data Data to transform. 9738 * @param {function(string=)} headers HTTP headers getter fn. 9739 * @param {number} status HTTP status code of the response. 9740 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 9741 * @returns {*} Transformed data. 9742 */ 9743function transformData(data, headers, status, fns) { 9744 if (isFunction(fns)) { 9745 return fns(data, headers, status); 9746 } 9747 9748 forEach(fns, function(fn) { 9749 data = fn(data, headers, status); 9750 }); 9751 9752 return data; 9753} 9754 9755 9756function isSuccess(status) { 9757 return 200 <= status && status < 300; 9758} 9759 9760 9761/** 9762 * @ngdoc provider 9763 * @name $httpProvider 9764 * @description 9765 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 9766 * */ 9767function $HttpProvider() { 9768 /** 9769 * @ngdoc property 9770 * @name $httpProvider#defaults 9771 * @description 9772 * 9773 * Object containing default values for all {@link ng.$http $http} requests. 9774 * 9775 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} 9776 * that will provide the cache for all requests who set their `cache` property to `true`. 9777 * If you set the `defaults.cache = false` then only requests that specify their own custom 9778 * cache object will be cached. See {@link $http#caching $http Caching} for more information. 9779 * 9780 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 9781 * Defaults value is `'XSRF-TOKEN'`. 9782 * 9783 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 9784 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 9785 * 9786 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 9787 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 9788 * setting default headers. 9789 * - **`defaults.headers.common`** 9790 * - **`defaults.headers.post`** 9791 * - **`defaults.headers.put`** 9792 * - **`defaults.headers.patch`** 9793 * 9794 * 9795 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 9796 * used to the prepare string representation of request parameters (specified as an object). 9797 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 9798 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 9799 * 9800 **/ 9801 var defaults = this.defaults = { 9802 // transform incoming response data 9803 transformResponse: [defaultHttpResponseTransform], 9804 9805 // transform outgoing request data 9806 transformRequest: [function(d) { 9807 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 9808 }], 9809 9810 // default headers 9811 headers: { 9812 common: { 9813 'Accept': 'application/json, text/plain, */*' 9814 }, 9815 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9816 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9817 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 9818 }, 9819 9820 xsrfCookieName: 'XSRF-TOKEN', 9821 xsrfHeaderName: 'X-XSRF-TOKEN', 9822 9823 paramSerializer: '$httpParamSerializer' 9824 }; 9825 9826 var useApplyAsync = false;
vendor: 4,307 bytes, lines 9827-9937
9827 /** 9828 * @ngdoc method 9829 * @name $httpProvider#useApplyAsync 9830 * @description 9831 * 9832 * Configure $http service to combine processing of multiple http responses received at around 9833 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 9834 * significant performance improvement for bigger applications that make many HTTP requests 9835 * concurrently (common during application bootstrap). 9836 * 9837 * Defaults to false. If no value is specified, returns the current configured value. 9838 * 9839 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 9840 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 9841 * to load and share the same digest cycle. 9842 * 9843 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 9844 * otherwise, returns the current configured value. 9845 **/ 9846 this.useApplyAsync = function(value) { 9847 if (isDefined(value)) { 9848 useApplyAsync = !!value; 9849 return this; 9850 } 9851 return useApplyAsync; 9852 }; 9853 9854 var useLegacyPromise = true; 9855 /** 9856 * @ngdoc method 9857 * @name $httpProvider#useLegacyPromiseExtensions 9858 * @description 9859 * 9860 * Configure `$http` service to return promises without the shorthand methods `success` and `error`. 9861 * This should be used to make sure that applications work without these methods. 9862 * 9863 * Defaults to true. If no value is specified, returns the current configured value. 9864 * 9865 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods. 9866 * 9867 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 9868 * otherwise, returns the current configured value. 9869 **/ 9870 this.useLegacyPromiseExtensions = function(value) { 9871 if (isDefined(value)) { 9872 useLegacyPromise = !!value; 9873 return this; 9874 } 9875 return useLegacyPromise; 9876 }; 9877 9878 /** 9879 * @ngdoc property 9880 * @name $httpProvider#interceptors 9881 * @description 9882 * 9883 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 9884 * pre-processing of request or postprocessing of responses. 9885 * 9886 * These service factories are ordered by request, i.e. they are applied in the same order as the 9887 * array, on request, but reverse order, on response. 9888 * 9889 * {@link ng.$http#interceptors Interceptors detailed info} 9890 **/ 9891 var interceptorFactories = this.interceptors = []; 9892 9893 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 9894 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 9895 9896 var defaultCache = $cacheFactory('$http'); 9897 9898 /** 9899 * Make sure that default param serializer is exposed as a function 9900 */ 9901 defaults.paramSerializer = isString(defaults.paramSerializer) ? 9902 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 9903 9904 /** 9905 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 9906 * The reversal is needed so that we can build up the interception chain around the 9907 * server request. 9908 */ 9909 var reversedInterceptors = []; 9910 9911 forEach(interceptorFactories, function(interceptorFactory) { 9912 reversedInterceptors.unshift(isString(interceptorFactory) 9913 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 9914 }); 9915 9916 /** 9917 * @ngdoc service 9918 * @kind function 9919 * @name $http 9920 * @requires ng.$httpBackend 9921 * @requires $cacheFactory 9922 * @requires $rootScope 9923 * @requires $q 9924 * @requires $injector 9925 * 9926 * @description 9927 * The `$http` service is a core Angular service that facilitates communication with the remote 9928 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 9929 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 9930 * 9931 * For unit testing applications that use `$http` service, see 9932 * {@link ngMock.$httpBackend $httpBackend mock}. 9933 * 9934 * For a higher level of abstraction, please check out the {@link ngResource.$resource 9935 * $resource} service. 9936 * 9937 * The $http API is based on the {@link ng.$q deferred/promise APIs}
9937 exposed by 9938 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 9939 * it is important to familiarize yourself with these APIs and the guarantees they provide. 9940 * 9941 * 9942 * ## General usage 9943 * The `$http` service is a function which takes a single argument â a {@link $http#usage configuration object} â 9944 * that is used to generate an HTTP request and returns a {@link ng.$q promise}. 9945 * 9946 * ```js 9947 * // Simple GET request example: 9948 * $http({ 9949 * method: 'GET', 9950 * url: '/someUrl' 9951 * }).then(function successCallback(response) { 9952 * // this callback will be called asynchronously 9953 * // when the response is available 9954 * }, function errorCallback(response) { 9955 * // called asynchronously if an error occurs 9956 * // or server returns response with an error status. 9957 * }); 9958 * ``` 9959 * 9960 * The response object has these properties: 9961 * 9962 * - **data** â `{string|Object}` â The response body transformed with the transform 9963 * functions. 9964 * - **status** â `{number}` â HTTP status code of the response. 9965 * - **headers** â `{function([headerName])}` â Header getter function. 9966 * - **config** â `{Object}` â The configuration object that was used to generate the request. 9967 * - **statusText** â `{string}` â HTTP status text of the response. 9968 * 9969 * A response status code between 200 and 299 is considered a success status and 9970 * will result in the success callback being called. Note that if the response is a redirect, 9971 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 9972 * called for such responses. 9973 * 9974 * 9975 * ## Shortcut methods 9976 * 9977 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 9978 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 9979 * last argument. 9980 * 9981 * ```js 9982 * $http.get('/someUrl', config).then(successCallback, errorCallback); 9983 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 9984 * ``` 9985 * 9986 * Complete list of shortcut methods: 9987 * 9988 * - {@link ng.$http#get $http.get} 9989 * - {@link ng.$http#head $http.head} 9990 * - {@link ng.$http#post $http.post} 9991 * - {@link ng.$http#put $http.put} 9992 * - {@link ng.$http#delete $http.delete} 9993 * - {@link ng.$http#jsonp $http.jsonp} 9994 * - {@link ng.$http#patch $http.patch} 9995 * 9996 * 9997 * ## Writing Unit Tests that use $http 9998 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 9999 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 10000 * request using trained responses. 10001 * 10002 * ``` 10003 * $httpBackend.expectGET(...); 10004 * $http.get(...); 10005 * $httpBackend.flush(); 10006 * ``` 10007 * 10008 * ## Deprecation Notice 10009 * <div class="alert alert-danger"> 10010 * The `$http` legacy promise methods `success` and `error` have been deprecated. 10011 * Use the standard `then` method instead. 10012 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to 10013 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. 10014 * </div> 10015 * 10016 * ## Setting HTTP Headers 10017 * 10018 * The $http service will automatically add certain HTTP headers to all requests. These defaults 10019 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 10020 * object, which currently contains this default configuration: 10021 * 10022 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 10023 * - `Accept: application/json, text/plain, * / *` 10024 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 10025 * - `Content-Type: application/json` 10026 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 10027 * - `Content-Type: application/json` 10028 *
10029 * To add or overwrite these defaults, simply add or remove a property from these configuration 10030 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 10031 * with the lowercased HTTP method name as the key, e.g. 10032 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 10033 * 10034 * The defaults can also be set at runtime via the `$http.defaults` object in the same 10035 * fashion. For example: 10036 * 10037 * ``` 10038 * module.run(function($http) { 10039 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 10040 * }); 10041 * ``` 10042 * 10043 * In addition, you can supply a `headers` property in the config object passed when 10044 * calling `$http(config)`, which overrides the defaults without changing them globally. 10045 * 10046 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 10047 * Use the `headers` property, setting the desired header to `undefined`. For example: 10048 * 10049 * ```js 10050 * var req = { 10051 * method: 'POST', 10052 * url: 'http://example.com', 10053 * headers: { 10054 * 'Content-Type': undefined 10055 * }, 10056 * data: { test: 'test' } 10057 * } 10058 * 10059 * $http(req).then(function(){...}, function(){...}); 10060 * ``` 10061 * 10062 * ## Transforming Requests and Responses 10063 * 10064 * Both requests and responses can be transformed using transformation functions: `transformRequest` 10065 * and `transformResponse`. These properties can be a single function that returns 10066 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 10067 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 10068 * 10069 * ### Default Transformations 10070 * 10071 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 10072 * `defaults.transformResponse` properties. If a request does not provide its own transformations 10073 * then these will be applied. 10074 * 10075 * You can augment or replace the default transformations by modifying these properties by adding to or 10076 * replacing the array. 10077 * 10078 * Angular provides the following default transformations: 10079 * 10080 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 10081 * 10082 * - If the `data` property of the request configuration object contains an object, serialize it 10083 * into JSON format. 10084 * 10085 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 10086 * 10087 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 10088 * - If JSON response is detected, deserialize it using a JSON parser. 10089 * 10090 * 10091 * ### Overriding the Default Transformations Per Request 10092 * 10093 * If you wish override the request/response transformations only for a single request then provide 10094 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 10095 * into `$http`. 10096 * 10097 * Note that if you provide these properties on the config object the default transformations will be 10098 * overwritten. If you wish to augment the default transformations then you must include them in your 10099 * local transformation array. 10100 * 10101 * The following code demonstrates adding a new response transformation to be run after the default response 10102 * transformations have been run. 10103 * 10104 * ```js 10105 * function appendTransform(defaults, transform) { 10106 * 10107 * // We can't guarantee that the default transformation is an array 10108 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 10109 * 10110 * // Append the new transformation to the defaults 10111 * return defaults.concat(transform); 10112 * } 10113 * 10114 * $http({ 10115 * url: '...', 10116 * method: 'GET', 10117 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 10118 * return doTransform(value); 10119 * }) 10120 * }); 10121 * ``` 10122 * 10123 * 10124 * ## Caching 10125 *
10126 * To enable caching, set the request configuration `cache` property to `true` (to use default 10127 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 10128 * When the cache is enabled, `$http` stores the response from the server in the specified 10129 * cache. The next time the same request is made, the response is served from the cache without 10130 * sending a request to the server. 10131 * 10132 * Note that even if the response is served from cache, delivery of the data is asynchronous in 10133 * the same way that real requests are. 10134 * 10135 * If there are multiple GET requests for the same URL that should be cached using the same 10136 * cache, but the cache is not populated yet, only one request to the server will be made and 10137 * the remaining requests will be fulfilled using the response from the first request. 10138 * 10139 * You can change the default cache to a new object (built with 10140 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 10141 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set 10142 * their `cache` property to `true` will now use this cache object. 10143 * 10144 * If you set the default cache to `false` then only requests that specify their own custom 10145 * cache object will be cached. 10146 * 10147 * ## Interceptors 10148 * 10149 * Before you start creating interceptors, be sure to understand the 10150 * {@link ng.$q $q and deferred/promise APIs}. 10151 * 10152 * For purposes of global error handling, authentication, or any kind of synchronous or 10153 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 10154 * able to intercept requests before they are handed to the server and 10155 * responses before they are handed over to the application code that 10156 * initiated these requests. The interceptors leverage the {@link ng.$q 10157 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 10158 * 10159 * The interceptors are service factories that are registered with the `$httpProvider` by 10160 * adding them to the `$httpProvider.interceptors` array. The factory is called and 10161 * injected with dependencies (if specified) and returns the interceptor. 10162 * 10163 * There are two kinds of interceptors (and two kinds of rejection interceptors): 10164 * 10165 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 10166 * modify the `config` object or create a new one. The function needs to return the `config` 10167 * object directly, or a promise containing the `config` or a new `config` object. 10168 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 10169 * resolved with a rejection. 10170 * * `response`: interceptors get called with http `response` object. The function is free to 10171 * modify the `response` object or create a new one. The function needs to return the `response` 10172 * object directly, or as a promise containing the `response` or a new `response` object. 10173 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 10174 * resolved with a rejection. 10175 * 10176 * 10177 * ```js 10178 * // register the interceptor as a service 10179 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 10180 * return { 10181 * // optional method 10182 * 'request': function(config) { 10183 * // do something on success 10184 * return config; 10185 * }, 10186 * 10187 * // optional method 10188 * 'requestError': function(rejection) { 10189 * // do something on error 10190 * if (canRecover(rejection)) { 10191 * return responseOrNewPromise 10192 * } 10193 * return $q.reject(rejection); 10194 * }, 10195 * 10196 * 10197 * 10198 * // optional method 10199 * 'response': function(response) { 10200 * // do something on success 10201 * return response; 10202 * }, 10203 * 10204 * // optional method 10205 * 'responseError': function(rejection) {
10206 * // do something on error 10207 * if (canRecover(rejection)) { 10208 * return responseOrNewPromise 10209 * } 10210 * return $q.reject(rejection); 10211 * } 10212 * }; 10213 * }); 10214 * 10215 * $httpProvider.interceptors.push('myHttpInterceptor'); 10216 * 10217 * 10218 * // alternatively, register the interceptor via an anonymous factory 10219 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 10220 * return { 10221 * 'request': function(config) { 10222 * // same as above 10223 * }, 10224 * 10225 * 'response': function(response) { 10226 * // same as above 10227 * } 10228 * }; 10229 * }); 10230 * ``` 10231 * 10232 * ## Security Considerations 10233 * 10234 * When designing web applications, consider security threats from: 10235 * 10236 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10237 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 10238 * 10239 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 10240 * pre-configured with strategies that address these issues, but for this to work backend server 10241 * cooperation is required. 10242 * 10243 * ### JSON Vulnerability Protection 10244 * 10245 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10246 * allows third party website to turn your JSON resource URL into 10247 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 10248 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 10249 * Angular will automatically strip the prefix before processing it as JSON. 10250 * 10251 * For example if your server needs to return: 10252 * ```js 10253 * ['one','two'] 10254 * ``` 10255 * 10256 * which is vulnerable to attack, your server can return: 10257 * ```js 10258 * )]}', 10259 * ['one','two'] 10260 * ``` 10261 * 10262 * Angular will strip the prefix, before processing the JSON. 10263 * 10264 * 10265 * ### Cross Site Request Forgery (XSRF) Protection 10266 * 10267 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 10268 * an unauthorized site can gain your user's private data. Angular provides a mechanism 10269 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 10270 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 10271 * JavaScript that runs on your domain could read the cookie, your server can be assured that 10272 * the XHR came from JavaScript running on your domain. The header will not be set for 10273 * cross-domain requests. 10274 * 10275 * To take advantage of this, your server needs to set a token in a JavaScript readable session 10276 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 10277 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 10278 * that only JavaScript running on your domain could have sent the request. The token must be 10279 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 10280 * making up its own tokens). We recommend that the token is a digest of your site's 10281 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 10282 * for added security. 10283 * 10284 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 10285 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 10286 * or the per-request config object. 10287 * 10288 * In order to prevent collisions in environments where multiple Angular apps share the 10289 * same domain or subdomain, we recommend that each application uses unique cookie name. 10290 * 10291 * @param {object} config Object describing the request to be made and how it should be 10292 * processed. The object has following properties: 10293 * 10294 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 10295 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 10296 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 10297 * with the `paramSerializer` and appended as GET parameters. 10298 * - **data** â `{string|Object}` â Data to be sent as the request message data. 10299 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 10300 * HTTP headers to send to the server. If the return value of a function is null, the 10301 * header will not be sent. Functions accept a config object as an argument. 10302 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 10303 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 10304 * - **transformRequest** â 10305 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
10305}` â 10306 * transform function or an array of such functions. The transform function takes the http 10307 * request body and headers and returns its transformed (typically serialized) version. 10308 * See {@link ng.$http#overriding-the-default-transformations-per-request 10309 * Overriding the Default Transformations} 10310 * - **transformResponse** â 10311 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 10312 * transform function or an array of such functions. The transform function takes the http 10313 * response body, headers and status and returns its transformed (typically deserialized) version. 10314 * See {@link ng.$http#overriding-the-default-transformations-per-request 10315 * Overriding the Default TransformationjqLiks} 10316 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 10317 * prepare the string representation of request parameters (specified as an object). 10318 * If specified as string, it is interpreted as function registered with the 10319 * {@link $injector $injector}, which means you can create your own serializer 10320 * by registering it as a {@link auto.$provide#service service}. 10321 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 10322 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 10323 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 10324 * GET request, otherwise if a cache instance built with 10325 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 10326 * caching. 10327 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 10328 * that should abort the request when resolved. 10329 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 10330 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 10331 * for more information. 10332 * - **responseType** - `{string}` - see 10333 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 10334 * 10335 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object 10336 * when the request succeeds or fails. 10337 * 10338 * 10339 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 10340 * requests. This is primarily meant to be used for debugging purposes. 10341 * 10342 * 10343 * @example 10344<example module="httpExample"> 10345<file name="index.html"> 10346 <div ng-controller="FetchController"> 10347 <select ng-model="method" aria-label="Request method"> 10348 <option>GET</option> 10349 <option>JSONP</option> 10350 </select> 10351 <input type="text" ng-model="url" size="80" aria-label="URL" /> 10352 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 10353 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 10354 <button id="samplejsonpbtn" 10355 ng-click="updateModel('JSONP', 10356 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 10357 Sample JSONP 10358 </button> 10359 <button id="invalidjsonpbtn" 10360 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 10361 Invalid JSONP 10362 </button> 10363 <pre>http status code: {{status}}</pre> 10364 <pre>http response data: {{data}}</pre> 10365 </div> 10366</file> 10367<file name="script.js"> 10368 angular.module('httpExample', []) 10369 .controller('FetchController', ['$scope', '$http', '$templateCache', 10370 function($scope, $http, $templateCache) { 10371 $scope.method = 'GET'; 10372 $scope.url = 'http-hello.html'; 10373 10374 $scope.fetch = function() { 10375 $scope.code = null; 10376 $scope.response = null; 10377 10378 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 10379 then(function(response) { 10380 $scope.status = response.status; 10381 $scope.data = response.data; 10382 }, function(response) { 10383 $scope.data = response.data || "Request failed"; 10384 $scope.status = response.status; 10385 }); 10386 }; 10387 10388 $scope.updateModel = function(method, url) { 10389 $scope.method = method; 10390 $scope.url = url; 10391 }; 10392 }]); 10393</file> 10394<file name="http-hello.html"> 10395 Hello, $http! 10396</file> 10397<file name="protractor.js" type="protractor"> 10398 var status = element(by.binding('status')); 10399 var data = element(by.binding('data')); 10400 var fetchBtn = element(by.id('fetchbtn')); 10401 var sampleGetBtn = element(by.id('samplegetbtn')); 10402 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 10403 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 10404 10405 it('should make an xhr GET request', function() { 10406 sampleGetBtn.click(); 10407 fetchBtn.click(); 10408 expect(status.getText()).toMatch('200'); 10409 expect(data.getText()).toMatch(/Hello, \$http!/); 10410 }); 10411 10412// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 10413// it('should make a JSONP request to angularjs.org', function() { 10414// sampleJsonpBtn.click(); 10415// fetchBtn.click(); 10416// expect(status.getText()).toMatch('200'); 10417// expect(data.getText()).toMatch(/Super Hero!/); 10418// }); 10419
10420 it('should make JSONP request to invalid URL and invoke the error handler', 10421 function() { 10422 invalidJsonpBtn.click(); 10423 fetchBtn.click(); 10424 expect(status.getText()).toMatch('0'); 10425 expect(data.getText()).toMatch('Request failed'); 10426 }); 10427</file> 10428</example> 10429 */ 10430 function $http(requestConfig) { 10431 10432 if (!angular.isObject(requestConfig)) { 10433 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 10434 } 10435 10436 if (!isString(requestConfig.url)) { 10437 throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); 10438 } 10439 10440 var config = extend({ 10441 method: 'get', 10442 transformRequest: defaults.transformRequest, 10443 transformResponse: defaults.transformResponse, 10444 paramSerializer: defaults.paramSerializer 10445 }, requestConfig); 10446 10447 config.headers = mergeHeaders(requestConfig); 10448 config.method = uppercase(config.method); 10449 config.paramSerializer = isString(config.paramSerializer) ? 10450 $injector.get(config.paramSerializer) : config.paramSerializer; 10451 10452 var serverRequest = function(config) { 10453 var headers = config.headers; 10454 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 10455 10456 // strip content-type if data is undefined 10457 if (isUndefined(reqData)) { 10458 forEach(headers, function(value, header) { 10459 if (lowercase(header) === 'content-type') { 10460 delete headers[header]; 10461 } 10462 }); 10463 } 10464 10465 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 10466 config.withCredentials = defaults.withCredentials; 10467 } 10468 10469 // send request 10470 return sendReq(config, reqData).then(transformResponse, transformResponse); 10471 }; 10472 10473 var chain = [serverRequest, undefined]; 10474 var promise = $q.when(config); 10475 10476 // apply interceptors 10477 forEach(reversedInterceptors, function(interceptor) { 10478 if (interceptor.request || interceptor.requestError) { 10479 chain.unshift(interceptor.request, interceptor.requestError); 10480 } 10481 if (interceptor.response || interceptor.responseError) { 10482 chain.push(interceptor.response, interceptor.responseError); 10483 } 10484 }); 10485 10486 while (chain.length) { 10487 var thenFn = chain.shift(); 10488 var rejectFn = chain.shift(); 10489 10490 promise = promise.then(thenFn, rejectFn); 10491 } 10492 10493 if (useLegacyPromise) { 10494 promise.success = function(fn) { 10495 assertArgFn(fn, 'fn'); 10496 10497 promise.then(function(response) { 10498 fn(response.data, response.status, response.headers, config); 10499 }); 10500 return promise; 10501 }; 10502 10503 promise.error = function(fn) { 10504 assertArgFn(fn, 'fn'); 10505 10506 promise.then(null, function(response) { 10507 fn(response.data, response.status, response.headers, config); 10508 }); 10509 return promise; 10510 }; 10511 } else { 10512 promise.success = $httpMinErrLegacyFn('success'); 10513 promise.error = $httpMinErrLegacyFn('error'); 10514 } 10515 10516 return promise; 10517 10518 function transformResponse(response) { 10519 // make a copy since the response must be cacheable 10520 var resp = extend({}, response); 10521 resp.data = transformData(response.data, response.headers, response.status, 10522 config.transformResponse); 10523 return (isSuccess(response.status)) 10524 ? resp 10525 : $q.reject(resp); 10526 } 10527 10528 function executeHeaderFns(headers, config) { 10529 var headerContent, processedHeaders = {}; 10530 10531 forEach(headers, function(headerFn, header) { 10532 if (isFunction(headerFn)) { 10533 headerContent = headerFn(config); 10534 if (headerContent != null) { 10535 processedHeaders[header] = headerContent; 10536 } 10537 } else { 10538 processedHeaders[header] = headerFn; 10539 } 10540 }); 10541 10542 return processedHeaders; 10543 } 10544 10545 function mergeHeaders(config) { 10546 var defHeaders = defaults.headers, 10547 reqHeaders = extend({}, config.headers), 10548 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 10549 10550 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 10551
vendor: 9,098 bytes, lines 10552-10852
10552 // using for-in instead of forEach to avoid unecessary iteration after header has been found 10553 defaultHeadersIteration: 10554 for (defHeaderName in defHeaders) { 10555 lowercaseDefHeaderName = lowercase(defHeaderName); 10556 10557 for (reqHeaderName in reqHeaders) { 10558 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 10559 continue defaultHeadersIteration; 10560 } 10561 } 10562 10563 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 10564 } 10565 10566 // execute if header value is a function for merged headers 10567 return executeHeaderFns(reqHeaders, shallowCopy(config)); 10568 } 10569 } 10570 10571 $http.pendingRequests = []; 10572 10573 /** 10574 * @ngdoc method 10575 * @name $http#get 10576 * 10577 * @description 10578 * Shortcut method to perform `GET` request. 10579 * 10580 * @param {string} url Relative or absolute URL specifying the destination of the request 10581 * @param {Object=} config Optional configuration object 10582 * @returns {HttpPromise} Future object 10583 */ 10584 10585 /** 10586 * @ngdoc method 10587 * @name $http#delete 10588 * 10589 * @description 10590 * Shortcut method to perform `DELETE` request. 10591 * 10592 * @param {string} url Relative or absolute URL specifying the destination of the request 10593 * @param {Object=} config Optional configuration object 10594 * @returns {HttpPromise} Future object 10595 */ 10596 10597 /** 10598 * @ngdoc method 10599 * @name $http#head 10600 * 10601 * @description 10602 * Shortcut method to perform `HEAD` request. 10603 * 10604 * @param {string} url Relative or absolute URL specifying the destination of the request 10605 * @param {Object=} config Optional configuration object 10606 * @returns {HttpPromise} Future object 10607 */ 10608 10609 /** 10610 * @ngdoc method 10611 * @name $http#jsonp 10612 * 10613 * @description 10614 * Shortcut method to perform `JSONP` request. 10615 * 10616 * @param {string} url Relative or absolute URL specifying the destination of the request. 10617 * The name of the callback should be the string `JSON_CALLBACK`. 10618 * @param {Object=} config Optional configuration object 10619 * @returns {HttpPromise} Future object 10620 */ 10621 createShortMethods('get', 'delete', 'head', 'jsonp'); 10622 10623 /** 10624 * @ngdoc method 10625 * @name $http#post 10626 * 10627 * @description 10628 * Shortcut method to perform `POST` request. 10629 * 10630 * @param {string} url Relative or absolute URL specifying the destination of the request 10631 * @param {*} data Request content 10632 * @param {Object=} config Optional configuration object 10633 * @returns {HttpPromise} Future object 10634 */ 10635 10636 /** 10637 * @ngdoc method 10638 * @name $http#put 10639 * 10640 * @description 10641 * Shortcut method to perform `PUT` request. 10642 * 10643 * @param {string} url Relative or absolute URL specifying the destination of the request 10644 * @param {*} data Request content 10645 * @param {Object=} config Optional configuration object 10646 * @returns {HttpPromise} Future object 10647 */ 10648 10649 /** 10650 * @ngdoc method 10651 * @name $http#patch 10652 * 10653 * @description 10654 * Shortcut method to perform `PATCH` request. 10655 * 10656 * @param {string} url Relative or absolute URL specifying the destination of the request 10657 * @param {*} data Request content 10658 * @param {Object=} config Optional configuration object 10659 * @returns {HttpPromise} Future object 10660 */ 10661 createShortMethodsWithData('post', 'put', 'patch'); 10662 10663 /** 10664 * @ngdoc property 10665 * @name $http#defaults 10666 * 10667 * @description 10668 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 10669 * default headers, withCredentials as well as request and response transformations. 10670 * 10671 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 10672 */ 10673 $http.defaults = defaults; 10674 10675 10676 return $http; 10677 10678 10679 function createShortMethods(names) { 10680 forEach(arguments, function(name) { 10681 $http[name] = function(url, config) { 10682 return $http(extend({}, config || {}, { 10683 method: name, 10684 url: url 10685 })); 10686 }; 10687 }); 10688 } 10689 10690 10691 function createShortMethodsWithData(name) { 10692 forEach(arguments, function(name) { 10693 $http[name] = function(url, data, config) { 10694 return $http(extend({}, config || {}, { 10695 method: name, 10696 url: url, 10697 data: data 10698 })); 10699 }; 10700 }); 10701 } 10702 10703 10704 /** 10705 * Makes the request. 10706 * 10707 * !!! ACCESSES CLOSURE VARS: 10708 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 10709 */ 10710 function sendReq(config, reqData) { 10711 var deferred = $q.defer(), 10712 promise = deferred.promise, 10713 cache, 10714 cachedResp, 10715 reqHeaders = config.headers, 10716 url = buildUrl(config.url, config.paramSerializer(config.params)); 10717 10718 $http.pendingRequests.push(config); 10719 promise.then(removePendingReq, removePendingReq); 10720 10721 10722 if ((config.cache || defaults.cache) && config.cache !== false && 10723 (config.method === 'GET' || config.method === 'JSONP')) { 10724 cache = isObject(config.cache) ? config.cache 10725 : isObject(defaults.cache) ? defaults.cache 10726 : defaultCache; 10727 } 10728 10729 if (cache) { 10730 cachedResp = cache.get(url); 10731 if (isDefined(cachedResp)) { 10732 if (isPromiseLike(cachedResp)) { 10733 // cached request has already been sent, but there is no response yet 10734 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 10735 } else { 10736 // serving from cache 10737 if (isArray(cachedResp)) { 10738 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 10739 } else { 10740 resolvePromise(cachedResp, 200, {}, 'OK'); 10741 } 10742 } 10743 } else { 10744 // put the promise for the non-transformed response into cache as a placeholder 10745 cache.put(url, promise); 10746 } 10747 } 10748 10749 10750 // if we won't have the response in cache, set the xsrf headers and 10751 // send the request to the backend 10752 if (isUndefined(cachedResp)) { 10753 var xsrfValue = urlIsSameOrigin(config.url) 10754 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 10755 : undefined; 10756 if (xsrfValue) { 10757 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 10758 } 10759 10760 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 10761 config.withCredentials, config.responseType); 10762 } 10763 10764 return promise; 10765 10766 10767 /** 10768 * Callback registered to $httpBackend(): 10769 * - caches the response if desired 10770 * - resolves the raw $http promise 10771 * - calls $apply 10772 */ 10773 function done(status, response, headersString, statusText) { 10774 if (cache) { 10775 if (isSuccess(status)) { 10776 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 10777 } else { 10778 // remove promise from the cache 10779 cache.remove(url); 10780 } 10781 } 10782 10783 function resolveHttpPromise() { 10784 resolvePromise(response, status, headersString, statusText); 10785 } 10786 10787 if (useApplyAsync) { 10788 $rootScope.$applyAsync(resolveHttpPromise); 10789 } else { 10790 resolveHttpPromise(); 10791 if (!$rootScope.$$phase) $rootScope.$apply(); 10792 } 10793 } 10794 10795 10796 /** 10797 * Resolves the raw $http promise. 10798 */ 10799 function resolvePromise(response, status, headers, statusText) { 10800 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 10801 status = status >= -1 ? status : 0; 10802 10803 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 10804 data: response, 10805 status: status, 10806 headers: headersGetter(headers), 10807 config: config, 10808 statusText: statusText 10809 }); 10810 } 10811 10812 function resolvePromiseWithResult(result) { 10813 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 10814 } 10815 10816 function removePendingReq() { 10817 var idx = $http.pendingRequests.indexOf(config); 10818 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 10819 } 10820 } 10821 10822 10823 function buildUrl(url, serializedParams) { 10824 if (serializedParams.length > 0) { 10825 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 10826 } 10827 return url; 10828 } 10829 }]; 10830} 10831 10832/** 10833 * @ngdoc service 10834 * @name $xhrFactory 10835 * 10836 * @description 10837 * Factory function used to create XMLHttpRequest objects. 10838 * 10839 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 10840 * 10841 * ``` 10842 * angular.module('myApp', []) 10843 * .factory('$xhrFactory', function() { 10844 * return function createXhr(method, url) { 10845 * return new window.XMLHttpRequest({mozSystem: true}); 10846 * }; 10847 * }); 10848 * ``` 10849 * 10850 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 10851 * @param {string} url URL of the request. 10852 */
10853function $xhrFactoryProvider() { 10854 this.$get = function() { 10855 return function createXhr() { 10856 return new window.XMLHttpRequest(); 10857 }; 10858 }; 10859} 10860 10861/** 10862 * @ngdoc service 10863 * @name $httpBackend 10864 * @requires $window 10865 * @requires $document 10866 * @requires $xhrFactory 10867 * 10868 * @description 10869 * HTTP backend used by the {@link ng.$http service} that delegates to 10870 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 10871 * 10872 * You should never need to use this service directly, instead use the higher-level abstractions: 10873 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 10874 * 10875 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 10876 * $httpBackend} which can be trained with responses. 10877 */ 10878function $HttpBackendProvider() { 10879 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) { 10880 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]); 10881 }]; 10882} 10883 10884function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 10885 // TODO(vojta): fix the signature 10886 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 10887 $browser.$$incOutstandingRequestCount(); 10888 url = url || $browser.url(); 10889 10890 if (lowercase(method) == 'jsonp') { 10891 var callbackId = '_' + (callbacks.counter++).toString(36); 10892 callbacks[callbackId] = function(data) { 10893 callbacks[callbackId].data = data; 10894 callbacks[callbackId].called = true; 10895 }; 10896 10897 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 10898 callbackId, function(status, text) { 10899 completeRequest(callback, status, callbacks[callbackId].data, "", text); 10900 callbacks[callbackId] = noop; 10901 }); 10902 } else { 10903 10904 var xhr = createXhr(method, url); 10905 10906 xhr.open(method, url, true); 10907 forEach(headers, function(value, key) { 10908 if (isDefined(value)) { 10909 xhr.setRequestHeader(key, value); 10910 } 10911 }); 10912 10913 xhr.onload = function requestLoaded() { 10914 var statusText = xhr.statusText || ''; 10915 10916 // responseText is the old-school way of retrieving response (supported by IE9) 10917 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 10918 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 10919 10920 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 10921 var status = xhr.status === 1223 ? 204 : xhr.status; 10922 10923 // fix status code when it is 0 (0 status is undocumented). 10924 // Occurs when accessing file resources or on Android 4.1 stock browser 10925 // while retrieving files from application cache. 10926 if (status === 0) { 10927 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 10928 } 10929 10930 completeRequest(callback, 10931 status, 10932 response, 10933 xhr.getAllResponseHeaders(), 10934 statusText); 10935 }; 10936 10937 var requestError = function() { 10938 // The response is always empty 10939 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 10940 completeRequest(callback, -1, null, null, ''); 10941 }; 10942 10943 xhr.onerror = requestError; 10944 xhr.onabort = requestError; 10945 10946 if (withCredentials) { 10947 xhr.withCredentials = true; 10948 } 10949 10950 if (responseType) { 10951 try { 10952 xhr.responseType = responseType; 10953 } catch (e) { 10954 // WebKit added support for the json responseType value on 09/03/2013 10955 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 10956 // known to throw when setting the value "json" as the response type. Other older 10957 // browsers implementing the responseType 10958 // 10959 // The json response type can be ignored if not supported, because JSON payloads are 10960 // parsed on the client-side regardless. 10961 if (responseType !== 'json') { 10962 throw e; 10963 } 10964 } 10965 } 10966
10967 xhr.send(isUndefined(post) ? null : post); 10968 } 10969 10970 if (timeout > 0) { 10971 var timeoutId = $browserDefer(timeoutRequest, timeout); 10972 } else if (isPromiseLike(timeout)) { 10973 timeout.then(timeoutRequest); 10974 } 10975 10976 10977 function timeoutRequest() { 10978 jsonpDone && jsonpDone(); 10979 xhr && xhr.abort(); 10980 } 10981 10982 function completeRequest(callback, status, response, headersString, statusText) { 10983 // cancel timeout and subsequent timeout promise resolution 10984 if (isDefined(timeoutId)) { 10985 $browserDefer.cancel(timeoutId); 10986 } 10987 jsonpDone = xhr = null; 10988 10989 callback(status, response, headersString, statusText); 10990 $browser.$$completeOutstandingRequest(noop); 10991 } 10992 }; 10993 10994 function jsonpReq(url, callbackId, done) { 10995 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 10996 // - fetches local scripts via XHR and evals them 10997 // - adds and immediately removes script elements from the document 10998 var script = rawDocument.createElement('script'), callback = null; 10999 script.type = "text/javascript"; 11000 script.src = url; 11001 script.async = true; 11002 11003 callback = function(event) { 11004 removeEventListenerFn(script, "load", callback); 11005 removeEventListenerFn(script, "error", callback); 11006 rawDocument.body.removeChild(script); 11007 script = null; 11008 var status = -1; 11009 var text = "unknown"; 11010 11011 if (event) { 11012 if (event.type === "load" && !callbacks[callbackId].called) { 11013 event = { type: "error" }; 11014 } 11015 text = event.type; 11016 status = event.type === "error" ? 404 : 200; 11017 } 11018 11019 if (done) { 11020 done(status, text); 11021 } 11022 }; 11023 11024 addEventListenerFn(script, "load", callback); 11025 addEventListenerFn(script, "error", callback); 11026 rawDocument.body.appendChild(script); 11027 return callback; 11028 } 11029} 11030 11031var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 11032$interpolateMinErr.throwNoconcat = function(text) { 11033 throw $interpolateMinErr('noconcat', 11034 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 11035 "interpolations that concatenate multiple expressions when a trusted value is " + 11036 "required. See http://docs.angularjs.org/api/ng.$sce", text); 11037}; 11038 11039$interpolateMinErr.interr = function(text, err) { 11040 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 11041}; 11042 11043/** 11044 * @ngdoc provider 11045 * @name $interpolateProvider 11046 * 11047 * @description 11048 *
11049 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 11050 * 11051 * @example 11052<example name="custom-interpolation-markup" module="customInterpolationApp"> 11053<file name="index.html"> 11054<script> 11055 var customInterpolationApp = angular.module('customInterpolationApp', []); 11056 11057 customInterpolationApp.config(function($interpolateProvider) { 11058 $interpolateProvider.startSymbol('//'); 11059 $interpolateProvider.endSymbol('//'); 11060 }); 11061 11062 11063 customInterpolationApp.controller('DemoController', function() { 11064 this.label = "This binding is brought you by // interpolation symbols."; 11065 }); 11066</script> 11067<div ng-controller="DemoController as demo"> 11068 //demo.label// 11069</div> 11070</file> 11071<file name="protractor.js" type="protractor"> 11072 it('should interpolate binding with custom symbols', function() { 11073 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 11074 }); 11075</file> 11076</example> 11077 */ 11078function $InterpolateProvider() { 11079 var startSymbol = '{{'; 11080 var endSymbol = '}}'; 11081 11082 /** 11083 * @ngdoc method 11084 * @name $interpolateProvider#startSymbol 11085 * @description 11086 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 11087 * 11088 * @param {string=} value new value to set the starting symbol to. 11089 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 11090 */ 11091 this.startSymbol = function(value) { 11092 if (value) { 11093 startSymbol = value; 11094 return this; 11095 } else { 11096 return startSymbol; 11097 } 11098 }; 11099 11100 /** 11101 * @ngdoc method 11102 * @name $interpolateProvider#endSymbol 11103 * @description 11104 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 11105 * 11106 * @param {string=} value new value to set the ending symbol to. 11107 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 11108 */ 11109 this.endSymbol = function(value) { 11110 if (value) { 11111 endSymbol = value; 11112 return this; 11113 } else { 11114 return endSymbol; 11115 } 11116 }; 11117 11118 11119 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 11120 var startSymbolLength = startSymbol.length, 11121 endSymbolLength = endSymbol.length, 11122 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 11123 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 11124 11125 function escape(ch) { 11126 return '\\\\\\' + ch; 11127 } 11128 11129 function unescapeText(text) { 11130 return text.replace(escapedStartRegexp, startSymbol). 11131 replace(escapedEndRegexp, endSymbol); 11132 } 11133 11134 function stringify(value) { 11135 if (value == null) { // null || undefined 11136 return ''; 11137 } 11138 switch (typeof value) { 11139 case 'string': 11140 break; 11141 case 'number': 11142 value = '' + value; 11143 break; 11144 default: 11145 value = toJson(value); 11146 } 11147 11148 return value; 11149 } 11150 11151 /** 11152 * @ngdoc service 11153 * @name $interpolate 11154 * @kind function 11155 * 11156 * @requires $parse 11157 * @requires $sce 11158 * 11159 * @description 11160 * 11161 * Compiles a string with markup into an interpolation function. This service is used by the 11162 * HTML {@link ng.$compile $compile} service for data binding. See 11163 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 11164 * interpolation markup. 11165 * 11166 * 11167 * ```js 11168 * var $interpolate = ...; // injected 11169 * var exp = $interpolate('Hello {{name | uppercase}}!'); 11170 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); 11171 * ``` 11172 * 11173 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 11174 * `true`, the interpolation function will return `undefined` unless all embedded expressions 11175 * evaluate to a value other than `undefined`. 11176 * 11177 * ```js 11178 * var $interpolate = ...; // injected 11179 * var context = {greeting: 'Hello', name: undefined }; 11180 * 11181 * // default "forgiving" mode 11182 * var exp = $interpolate('{{greeting}} {{name}}!'); 11183 * expect(exp(context)).toEqual('Hello !'); 11184 * 11185 * // "allOrNothing" mode 11186 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 11187 * expect(exp(context)).toBeUndefined(); 11188 * context.name = 'Angular'; 11189 * expect(exp(context)).toEqual('Hello Angular!'); 11190 * ``` 11191 * 11192 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 11193 * 11194 * ####Escaped Interpolation 11195 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 11196 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 11197 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 11198 * or binding. 11199 * 11200 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 11201 * degree, while also enabling code examples to work without relying on the 11202 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 11203 * 11204 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 11205 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 11206 * interpolation start/end markers with their escaped counterparts.** 11207 * 11208 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 11209 * output when the $interpolate service processes the text. So, for HTML elements interpolated 11210 * by {@link ng.$compile $compile}
11210, or otherwise interpolated with the `mustHaveExpression` parameter 11211 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 11212 * this is typically useful only when user-data is used in rendering a template from the server, or 11213 * when otherwise untrusted data is used by a directive. 11214 * 11215 * <example> 11216 * <file name="index.html"> 11217 * <div ng-init="username='A user'"> 11218 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 11219 * </p> 11220 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 11221 * application, but fails to accomplish their task, because the server has correctly 11222 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 11223 * characters.</p> 11224 * <p>Instead, the result of the attempted script injection is visible, and can be removed 11225 * from the database by an administrator.</p> 11226 * </div> 11227 * </file> 11228 * </example> 11229 * 11230 * @param {string} text The text with markup to interpolate. 11231 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 11232 * embedded expression in order to return an interpolation function. Strings with no 11233 * embedded expression will return null for the interpolation function. 11234 * @param {string=} trustedContext when provided, the returned function passes the interpolated 11235 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 11236 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 11237 * provides Strict Contextual Escaping for details. 11238 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 11239 * unless all embedded expressions evaluate to a value other than `undefined`. 11240 * @returns {function(context)} an interpolation function which is used to compute the 11241 * interpolated string. The function has these parameters: 11242 * 11243 * - `context`: evaluation context for all expressions embedded in the interpolated text 11244 */ 11245 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 11246 allOrNothing = !!allOrNothing; 11247 var startIndex, 11248 endIndex, 11249 index = 0, 11250 expressions = [], 11251 parseFns = [], 11252 textLength = text.length, 11253 exp, 11254 concat = [], 11255 expressionPositions = []; 11256 11257 while (index < textLength) { 11258 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 11259 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 11260 if (index !== startIndex) { 11261 concat.push(unescapeText(text.substring(index, startIndex))); 11262 } 11263 exp = text.substring(startIndex + startSymbolLength, endIndex); 11264 expressions.push(exp); 11265 parseFns.push($parse(exp, parseStringifyInterceptor)); 11266 index = endIndex + endSymbolLength; 11267 expressionPositions.push(concat.length); 11268 concat.push(''); 11269 } else { 11270 // we did not find an interpolation, so we have to add the remainder to the separators array 11271 if (index !== textLength) { 11272 concat.push(unescapeText(text.substring(index))); 11273 } 11274 break; 11275 } 11276 } 11277 11278 // Concatenating expressions makes it hard to reason about whether some combination of 11279 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 11280 // single expression be used for iframe[src], object[src], etc., we ensure that the value 11281 // that's used is assigned or constructed by some JS code somewhere that is more testable or 11282 // make it obvious that you bound the value to some user controlled value. This helps reduce 11283 // the load when auditing for XSS issues. 11284 if (trustedContext && concat.length > 1) { 11285 $interpolateMinErr.throwNoconcat(text); 11286 } 11287 11288 if (!mustHaveExpression || expressions.length) { 11289 var compute = function(values) { 11290 for (var i = 0, ii = expressions.length; i < ii; i++) { 11291 if (allOrNothing && isUndefined(values[i])) return; 11292 concat[expressionPositions[i]] = values[i]; 11293 } 11294 return concat.join(''); 11295 }; 11296 11297 var getValue = function(value) { 11298 return trustedContext ? 11299 $sce.getTrusted(trustedContext, value) : 11300 $sce.valueOf(value); 11301 }; 11302 11303 return extend(function interpolationFn(context) { 11304 var i = 0; 11305 var ii = expressions.length; 11306 var values = new Array(ii); 11307 11308 try { 11309 for (; i < ii; i++) { 11310 values[i] = parseFns[i](context); 11311 } 11312 11313 return compute(values); 11314 } catch (err) { 11315 $exceptionHandler($interpolateMinErr.interr(text, err)); 11316 } 11317 11318 }, { 11319 // all of these properties are undocumented for now 11320 exp: text, //just for compatibility with regular watchers created via $watch 11321 expressions: expressions, 11322 $$watchDelegate: function(scope, listener) { 11323 var lastValue; 11324 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 11325 var currValue = compute(values); 11326 if (isFunction(listener)) { 11327 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 11328 }
11329 lastValue = currValue; 11330 }); 11331 } 11332 }); 11333 } 11334 11335 function parseStringifyInterceptor(value) { 11336 try { 11337 value = getValue(value); 11338 return allOrNothing && !isDefined(value) ? value : stringify(value); 11339 } catch (err) { 11340 $exceptionHandler($interpolateMinErr.interr(text, err)); 11341 } 11342 } 11343 } 11344 11345 11346 /** 11347 * @ngdoc method 11348 * @name $interpolate#startSymbol 11349 * @description 11350 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 11351 * 11352 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 11353 * the symbol. 11354 * 11355 * @returns {string} start symbol. 11356 */ 11357 $interpolate.startSymbol = function() { 11358 return startSymbol; 11359 }; 11360 11361 11362 /** 11363 * @ngdoc method 11364 * @name $interpolate#endSymbol 11365 * @description 11366 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 11367 * 11368 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 11369 * the symbol. 11370 * 11371 * @returns {string} end symbol. 11372 */ 11373 $interpolate.endSymbol = function() { 11374 return endSymbol; 11375 }; 11376 11377 return $interpolate; 11378 }]; 11379} 11380 11381function $IntervalProvider() { 11382 this.$get = ['$rootScope', '$window', '$q', '$$q', 11383 function($rootScope, $window, $q, $$q) { 11384 var intervals = {}; 11385 11386 11387 /** 11388 * @ngdoc service 11389 * @name $interval 11390 * 11391 * @description 11392 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 11393 * milliseconds. 11394 * 11395 * The return value of registering an interval function is a promise. This promise will be 11396 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 11397 * run indefinitely if `count` is not defined. The value of the notification will be the 11398 * number of iterations that have run. 11399 * To cancel an interval, call `$interval.cancel(promise)`. 11400 * 11401 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 11402 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 11403 * time. 11404 * 11405 * <div class="alert alert-warning"> 11406 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 11407 * with them. In particular they are not automatically destroyed when a controller's scope or a 11408 * directive's element are destroyed. 11409 * You should take this into consideration and make sure to always cancel the interval at the 11410 * appropriate moment. See the example below for more details on how and when to do this. 11411 * </div> 11412 * 11413 * @param {function()} fn A function that should be called repeatedly. 11414 * @param {number} delay Number of milliseconds between each function call. 11415 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 11416 * indefinitely. 11417 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 11418 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 11419 * @param {...*=} Pass additional parameters to the executed function. 11420 * @returns {promise} A promise which will be notified on each iteration. 11421 * 11422 * @example 11423 * <example module="intervalExample"> 11424 * <file name="index.html"> 11425 * <script> 11426 * angular.module('intervalExample', []) 11427 * .controller('ExampleController', ['$scope', '$interval', 11428 * function($scope, $interval) { 11429 * $scope.format = 'M/d/yy h:mm:ss a'; 11430 * $scope.blood_1 = 100; 11431 * $scope.blood_2 = 120; 11432 * 11433 * var stop; 11434 * $scope.fight = function() { 11435 * // Don't start a new fight if we are already fighting 11436 * if ( angular.isDefined(stop) ) return; 11437 * 11438 * stop = $interval(function() { 11439 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 11440 * $scope.blood_1 = $scope.blood_1 - 3; 11441 * $scope.blood_2 = $scope.blood_2 - 4; 11442 * } else { 11443 * $scope.stopFight(); 11444 * } 11445 * }, 100); 11446 * }; 11447 * 11448 * $scope.stopFight = function() { 11449 * if (angular.isDefined(stop)) { 11450 * $interval.cancel(stop); 11451 * stop = undefined; 11452 * } 11453 * }; 11454 * 11455 * $scope.resetFight = function() { 11456 * $scope.blood_1 = 100; 11457 * $scope.blood_2 = 120; 11458 * }; 11459 * 11460 * $scope.$on('$destroy', function() { 11461 * // Make sure that the interval is destroyed too 11462 * $scope.stopFight(); 11463 * }); 11464 * }]) 11465 * // Register the 'myCurrentTime' directive factory method. 11466 * // We inject $interval and dateFilter service since the factory method is DI. 11467 * .directive('myCurrentTime', ['$interval', 'dateFilter', 11468 * function($interval, dateFilter) { 11469 * // return the directive link function. (compile function not needed) 11470 * return function(scope, element, attrs) { 11471 * var format, // date format 11472 * stopTime; // so that we can cancel the time updates 11473 * 11474 * // used to update the UI 11475 * function updateTime() { 11476 * element.text(dateFilter(new Date(), format)); 11477 * } 11478 * 11479 * // watch the expression, and update the UI on change. 11480 * scope.$watch(attrs.myCurrentTime, function(value) { 11481 * format = value; 11482 * updateTime(); 11483 * }); 11484 * 11485 * stopTime = $interval(updateTime, 1000); 11486 * 11487 * // listen on DOM destroy (removal) event, and cancel the next UI update 11488 * // to prevent updating time after the DOM element was removed. 11489 * element.on('$destroy', function() { 11490 * $interval.cancel(stopTime); 11491 * }); 11492 * } 11493 * }]); 11494 * </script> 11495 * 11496 * <div>
11497 * <div ng-controller="ExampleController"> 11498 * <label>Date format: <input ng-model="format"></label> <hr/> 11499 * Current time is: <span my-current-time="format"></span> 11500 * <hr/> 11501 * Blood 1 : <font color='red'>{{blood_1}}</font> 11502 * Blood 2 : <font color='red'>{{blood_2}}</font> 11503 * <button type="button" data-ng-click="fight()">Fight</button> 11504 * <button type="button" data-ng-click="stopFight()">StopFight</button> 11505 * <button type="button" data-ng-click="resetFight()">resetFight</button> 11506 * </div> 11507 * </div> 11508 * 11509 * </file> 11510 * </example> 11511 */ 11512 function interval(fn, delay, count, invokeApply) { 11513 var hasParams = arguments.length > 4, 11514 args = hasParams ? sliceArgs(arguments, 4) : [], 11515 setInterval = $window.setInterval, 11516 clearInterval = $window.clearInterval, 11517 iteration = 0, 11518 skipApply = (isDefined(invokeApply) && !invokeApply), 11519 deferred = (skipApply ? $$q : $q).defer(), 11520 promise = deferred.promise; 11521 11522 count = isDefined(count) ? count : 0; 11523 11524 promise.then(null, null, (!hasParams) ? fn : function() { 11525 fn.apply(null, args); 11526 }); 11527 11528 promise.$$intervalId = setInterval(function tick() { 11529 deferred.notify(iteration++); 11530 11531 if (count > 0 && iteration >= count) { 11532 deferred.resolve(iteration); 11533 clearInterval(promise.$$intervalId); 11534 delete intervals[promise.$$intervalId]; 11535 } 11536 11537 if (!skipApply) $rootScope.$apply(); 11538 11539 }, delay); 11540 11541 intervals[promise.$$intervalId] = deferred; 11542 11543 return promise; 11544 } 11545 11546 11547 /** 11548 * @ngdoc method 11549 * @name $interval#cancel 11550 * 11551 * @description 11552 * Cancels a task associated with the `promise`. 11553 * 11554 * @param {Promise=} promise returned by the `$interval` function. 11555 * @returns {boolean} Returns `true` if the task was successfully canceled. 11556 */ 11557 interval.cancel = function(promise) { 11558 if (promise && promise.$$intervalId in intervals) { 11559 intervals[promise.$$intervalId].reject('canceled'); 11560 $window.clearInterval(promise.$$intervalId); 11561 delete intervals[promise.$$intervalId]; 11562 return true; 11563 } 11564 return false; 11565 }; 11566 11567 return interval; 11568 }]; 11569} 11570 11571/** 11572 * @ngdoc service 11573 * @name $locale 11574 * 11575 * @description 11576 * $locale service provides localization rules for various Angular components. As of right now the 11577 * only public api is: 11578 * 11579 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 11580 */ 11581 11582var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 11583 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 11584var $locationMinErr = minErr('$location'); 11585 11586 11587/** 11588 * Encode path using encodeUriSegment, ignoring forward slashes 11589 * 11590 * @param {string} path Path to encode 11591 * @returns {string} 11592 */ 11593function encodePath(path) { 11594 var segments = path.split('/'), 11595 i = segments.length; 11596 11597 while (i--) { 11598 segments[i] = encodeUriSegment(segments[i]); 11599 } 11600 11601 return segments.join('/'); 11602} 11603 11604function parseAbsoluteUrl(absoluteUrl, locationObj) { 11605 var parsedUrl = urlResolve(absoluteUrl); 11606 11607 locationObj.$$protocol = parsedUrl.protocol; 11608 locationObj.$$host = parsedUrl.hostname; 11609 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 11610} 11611 11612 11613function parseAppUrl(relativeUrl, locationObj) { 11614 var prefixed = (relativeUrl.charAt(0) !== '/'); 11615 if (prefixed) { 11616 relativeUrl = '/' + relativeUrl; 11617 } 11618 var match = urlResolve(relativeUrl); 11619 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 11620 match.pathname.substring(1) : match.pathname); 11621 locationObj.$$search = parseKeyValue(match.search); 11622 locationObj.$$hash = decodeURIComponent(match.hash); 11623 11624 // make sure path starts with '/'; 11625 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 11626 locationObj.$$path = '/' + locationObj.$$path; 11627 } 11628} 11629 11630 11631/** 11632 * 11633 * @param {string} begin 11634 * @param {string} whole 11635 * @returns {string} returns text from whole after begin or undefined if it does not begin with 11636 * expected string. 11637 */ 11638function beginsWith(begin, whole) { 11639 if (whole.indexOf(begin) === 0) { 11640 return whole.substr(begin.length); 11641 } 11642} 11643 11644 11645function stripHash(url) { 11646 var index = url.indexOf('#'); 11647 return index == -1 ? url : url.substr(0, index); 11648} 11649 11650function trimEmptyHash(url) { 11651 return url.replace(/(#.+)|#$/, '$1'); 11652} 11653 11654 11655function stripFile(url) { 11656 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 11657} 11658 11659/* return the server only (scheme://host:port) */ 11660function serverBase(url) { 11661 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 11662} 11663 11664 11665/** 11666 * LocationHtml5Url represents an url 11667 * This object is exposed as $location service when HTML5 mode is enabled and supported 11668 * 11669 * @constructor 11670 * @param {string} appBase application base URL 11671 * @param {string} appBaseNoFile application base URL stripped of any filename 11672 * @param {string} basePrefix url path prefix 11673 */ 11674function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 11675 this.$$html5 = true; 11676 basePrefix = basePrefix || ''; 11677 parseAbsoluteUrl(appBase, this); 11678 11679 11680 /** 11681 * Parse given html5 (regular) url string into properties 11682 * @param {string} url HTML5 url 11683 * @private 11684 */ 11685 this.$$parse = function(url) { 11686 var pathUrl = beginsWith(appBaseNoFile, url); 11687 if (!isString(pathUrl)) { 11688 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 11689 appBaseNoFile); 11690 } 11691 11692 parseAppUrl(pathUrl, this); 11693 11694 if (!this.$$path) { 11695 this.$$path = '/'; 11696 } 11697 11698 this.$$compose(); 11699 }; 11700 11701 /** 11702 * Compose url and update `absUrl` property 11703 * @private 11704 */ 11705 this.$$compose = function() {
11706 var search = toKeyValue(this.$$search), 11707 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11708 11709 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11710 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 11711 }; 11712 11713 this.$$parseLinkUrl = function(url, relHref) { 11714 if (relHref && relHref[0] === '#') { 11715 // special case for links to hash fragments: 11716 // keep the old url and only replace the hash fragment 11717 this.hash(relHref.slice(1)); 11718 return true; 11719 } 11720 var appUrl, prevAppUrl; 11721 var rewrittenUrl; 11722 11723 if (isDefined(appUrl = beginsWith(appBase, url))) { 11724 prevAppUrl = appUrl; 11725 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) { 11726 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 11727 } else { 11728 rewrittenUrl = appBase + prevAppUrl; 11729 } 11730 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) { 11731 rewrittenUrl = appBaseNoFile + appUrl; 11732 } else if (appBaseNoFile == url + '/') { 11733 rewrittenUrl = appBaseNoFile; 11734 } 11735 if (rewrittenUrl) { 11736 this.$$parse(rewrittenUrl); 11737 } 11738 return !!rewrittenUrl; 11739 }; 11740} 11741 11742 11743/** 11744 * LocationHashbangUrl represents url 11745 * This object is exposed as $location service when developer doesn't opt into html5 mode. 11746 * It also serves as the base class for html5 mode fallback on legacy browsers. 11747 * 11748 * @constructor 11749 * @param {string} appBase application base URL 11750 * @param {string} appBaseNoFile application base URL stripped of any filename 11751 * @param {string} hashPrefix hashbang prefix 11752 */ 11753function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 11754 11755 parseAbsoluteUrl(appBase, this); 11756 11757 11758 /** 11759 * Parse given hashbang url into properties 11760 * @param {string} url Hashbang url 11761 * @private 11762 */ 11763 this.$$parse = function(url) { 11764 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 11765 var withoutHashUrl; 11766 11767 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 11768 11769 // The rest of the url starts with a hash so we have 11770 // got either a hashbang path or a plain hash fragment 11771 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 11772 if (isUndefined(withoutHashUrl)) { 11773 // There was no hashbang prefix so we just have a hash fragment 11774 withoutHashUrl = withoutBaseUrl; 11775 } 11776 11777 } else { 11778 // There was no hashbang path nor hash fragment: 11779 // If we are in HTML5 mode we use what is left as the path; 11780 // Otherwise we ignore what is left 11781 if (this.$$html5) { 11782 withoutHashUrl = withoutBaseUrl; 11783 } else { 11784 withoutHashUrl = ''; 11785 if (isUndefined(withoutBaseUrl)) { 11786 appBase = url; 11787 this.replace(); 11788 } 11789 } 11790 } 11791 11792 parseAppUrl(withoutHashUrl, this); 11793 11794 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 11795 11796 this.$$compose(); 11797 11798 /* 11799 * In Windows, on an anchor node on documents loaded from 11800 * the filesystem, the browser will return a pathname 11801 * prefixed with the drive name ('/C:/path') when a 11802 * pathname without a drive is set: 11803 * * a.setAttribute('href', '/foo') 11804 * * a.pathname === '/C:/foo' //true 11805 * 11806 * Inside of Angular, we're always using pathnames that 11807 * do not include drive names for routing. 11808 */ 11809 function removeWindowsDriveName(path, url, base) { 11810 /* 11811 Matches paths for file protocol on windows, 11812 such as /C:/foo/bar, and captures only /foo/bar. 11813 */ 11814 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 11815 11816 var firstPathSegmentMatch; 11817 11818 //Get the relative path from the input URL. 11819 if (url.indexOf(base) === 0) { 11820 url = url.replace(base, ''); 11821 } 11822 11823 // The input URL intentionally contains a first path segment that ends with a colon. 11824 if (windowsFilePathExp.exec(url)) { 11825 return path; 11826 } 11827 11828 firstPathSegmentMatch = windowsFilePathExp.exec(path); 11829 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 11830 } 11831 }; 11832 11833 /** 11834 * Compose hashbang url and update `absUrl` property 11835 * @private 11836 */ 11837 this.$$compose = function() {
11838 var search = toKeyValue(this.$$search), 11839 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11840 11841 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11842 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 11843 }; 11844 11845 this.$$parseLinkUrl = function(url, relHref) { 11846 if (stripHash(appBase) == stripHash(url)) { 11847 this.$$parse(url); 11848 return true; 11849 } 11850 return false; 11851 }; 11852} 11853 11854 11855/** 11856 * LocationHashbangUrl represents url 11857 * This object is exposed as $location service when html5 history api is enabled but the browser 11858 * does not support it. 11859 * 11860 * @constructor 11861 * @param {string} appBase application base URL 11862 * @param {string} appBaseNoFile application base URL stripped of any filename 11863 * @param {string} hashPrefix hashbang prefix 11864 */ 11865function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 11866 this.$$html5 = true; 11867 LocationHashbangUrl.apply(this, arguments); 11868 11869 this.$$parseLinkUrl = function(url, relHref) { 11870 if (relHref && relHref[0] === '#') { 11871 // special case for links to hash fragments: 11872 // keep the old url and only replace the hash fragment 11873 this.hash(relHref.slice(1)); 11874 return true; 11875 } 11876 11877 var rewrittenUrl; 11878 var appUrl; 11879 11880 if (appBase == stripHash(url)) { 11881 rewrittenUrl = url; 11882 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 11883 rewrittenUrl = appBase + hashPrefix + appUrl; 11884 } else if (appBaseNoFile === url + '/') { 11885 rewrittenUrl = appBaseNoFile; 11886 } 11887 if (rewrittenUrl) { 11888 this.$$parse(rewrittenUrl); 11889 } 11890 return !!rewrittenUrl; 11891 }; 11892 11893 this.$$compose = function() { 11894 var search = toKeyValue(this.$$search), 11895 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11896 11897 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11898 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 11899 this.$$absUrl = appBase + hashPrefix + this.$$url; 11900 }; 11901 11902} 11903 11904 11905var locationPrototype = { 11906 11907 /** 11908 * Are we in html5 mode? 11909 * @private 11910 */ 11911 $$html5: false, 11912 11913 /** 11914 * Has any change been replacing? 11915 * @private 11916 */ 11917 $$replace: false, 11918 11919 /** 11920 * @ngdoc method 11921 * @name $location#absUrl 11922 * 11923 * @description 11924 * This method is getter only. 11925 * 11926 * Return full url representation with all segments encoded according to rules specified in 11927 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 11928 * 11929 * 11930 * ```js 11931 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11932 * var absUrl = $location.absUrl(); 11933 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 11934 * ``` 11935 * 11936 * @return {string} full url 11937 */ 11938 absUrl: locationGetter('$$absUrl'), 11939 11940 /** 11941 * @ngdoc method 11942 * @name $location#url 11943 * 11944 * @description 11945 * This method is getter / setter. 11946 * 11947 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 11948 * 11949 * Change path, search and hash, when called with parameter and return `$location`. 11950 * 11951 * 11952 * ```js 11953 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11954 * var url = $location.url(); 11955 * // => "/some/path?foo=bar&baz=xoxo" 11956 * ``` 11957 * 11958 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 11959 * @return {string} url 11960 */ 11961 url: function(url) { 11962 if (isUndefined(url)) { 11963 return this.$$url; 11964 } 11965 11966 var match = PATH_MATCH.exec(url); 11967 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 11968 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 11969 this.hash(match[5] || ''); 11970 11971 return this; 11972 }, 11973 11974 /** 11975 * @ngdoc method 11976 * @name $location#protocol 11977 * 11978 * @description 11979 * This method is getter only. 11980 * 11981 * Return protocol of current url. 11982 * 11983 * 11984 * ```js 11985 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11986 * var protocol = $location.protocol(); 11987 * // => "http" 11988 * ``` 11989 * 11990 * @return {string} protocol of current url 11991 */ 11992 protocol: locationGetter('$$protocol'), 11993 11994 /** 11995 * @ngdoc method 11996 * @name $location#host 11997 * 11998 * @description 11999 * This method is getter only. 12000 * 12001 * Return host of current url. 12002 * 12003 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 12004 * 12005 * 12006 * ```js
12007 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12008 * var host = $location.host(); 12009 * // => "example.com" 12010 * 12011 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 12012 * host = $location.host(); 12013 * // => "example.com" 12014 * host = location.host; 12015 * // => "example.com:8080" 12016 * ``` 12017 * 12018 * @return {string} host of current url. 12019 */ 12020 host: locationGetter('$$host'), 12021 12022 /** 12023 * @ngdoc method 12024 * @name $location#port 12025 * 12026 * @description 12027 * This method is getter only. 12028 * 12029 * Return port of current url. 12030 * 12031 * 12032 * ```js 12033 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12034 * var port = $location.port(); 12035 * // => 80 12036 * ``` 12037 * 12038 * @return {Number} port 12039 */ 12040 port: locationGetter('$$port'), 12041 12042 /** 12043 * @ngdoc method 12044 * @name $location#path 12045 * 12046 * @description 12047 * This method is getter / setter. 12048 * 12049 * Return path of current url when called without any parameter. 12050 * 12051 * Change path when called with parameter and return `$location`. 12052 * 12053 * Note: Path should always begin with forward slash (/), this method will add the forward slash 12054 * if it is missing. 12055 * 12056 * 12057 * ```js 12058 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12059 * var path = $location.path(); 12060 * // => "/some/path" 12061 * ``` 12062 * 12063 * @param {(string|number)=} path New path 12064 * @return {string} path 12065 */ 12066 path: locationGetterSetter('$$path', function(path) { 12067 path = path !== null ? path.toString() : ''; 12068 return path.charAt(0) == '/' ? path : '/' + path; 12069 }), 12070 12071 /** 12072 * @ngdoc method 12073 * @name $location#search 12074 * 12075 * @description 12076 * This method is getter / setter. 12077 * 12078 * Return search part (as object) of current url when called without any parameter. 12079 * 12080 * Change search part when called with parameter and return `$location`. 12081 * 12082 * 12083 * ```js 12084 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12085 * var searchObject = $location.search(); 12086 * // => {foo: 'bar', baz: 'xoxo'} 12087 * 12088 * // set foo to 'yipee' 12089 * $location.search('foo', 'yipee'); 12090 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 12091 * ``` 12092 * 12093 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 12094 * hash object. 12095 * 12096 * When called with a single argument the method acts as a setter, setting the `search` component 12097 * of `$location` to the specified value. 12098 * 12099 * If the argument is a hash object containing an array of values, these values will be encoded 12100 * as duplicate search parameters in the url. 12101 * 12102 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 12103 * will override only a single search property. 12104 * 12105 * If `paramValue` is an array, it will override the property of the `search` component of 12106 * `$location` specified via the first argument. 12107 * 12108 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 12109 * 12110 * If `paramValue` is `true`, the property specified via the first argument will be added with no 12111 * value nor trailing equal sign. 12112 * 12113 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 12114 * one or more arguments returns `$location` object itself. 12115 */ 12116 search: function(search, paramValue) { 12117 switch (arguments.length) { 12118 case 0: 12119 return this.$$search; 12120 case 1: 12121 if (isString(search) || isNumber(search)) { 12122 search = search.toString(); 12123 this.$$search = parseKeyValue(search); 12124 } else if (isObject(search)) { 12125 search = copy(search, {}); 12126 // remove object undefined or null properties 12127 forEach(search, function(value, key) { 12128 if (value == null) delete search[key]; 12129 }); 12130 12131 this.$$search = search; 12132 } else { 12133 throw $locationMinErr('isrcharg', 12134 'The first argument of the `$location#search()` call must be a string or an object.'); 12135 } 12136 break; 12137 default: 12138 if (isUndefined(paramValue) || paramValue === null) { 12139 delete this.$$search[search]; 12140 } else { 12141 this.$$search[search] = paramValue; 12142 } 12143 } 12144 12145 this.$$compose(); 12146 return this; 12147 }, 12148 12149 /** 12150 * @ngdoc method 12151 * @name $location#hash 12152 * 12153 * @description 12154 * This method is getter / setter. 12155 * 12156 * Returns the hash fragment when called without any parameters. 12157 * 12158 * Changes the hash fragment when called with a parameter and returns `$location`. 12159 * 12160 * 12161 * ```js
12162 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 12163 * var hash = $location.hash(); 12164 * // => "hashValue" 12165 * ``` 12166 * 12167 * @param {(string|number)=} hash New hash fragment 12168 * @return {string} hash 12169 */ 12170 hash: locationGetterSetter('$$hash', function(hash) { 12171 return hash !== null ? hash.toString() : ''; 12172 }), 12173 12174 /** 12175 * @ngdoc method 12176 * @name $location#replace 12177 * 12178 * @description 12179 * If called, all changes to $location during the current `$digest` will replace the current history 12180 * record, instead of adding a new one. 12181 */ 12182 replace: function() { 12183 this.$$replace = true; 12184 return this; 12185 } 12186}; 12187 12188forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 12189 Location.prototype = Object.create(locationPrototype); 12190 12191 /** 12192 * @ngdoc method 12193 * @name $location#state 12194 * 12195 * @description 12196 * This method is getter / setter. 12197 * 12198 * Return the history state object when called without any parameter. 12199 * 12200 * Change the history state object when called with one parameter and return `$location`. 12201 * The state object is later passed to `pushState` or `replaceState`. 12202 * 12203 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 12204 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 12205 * older browsers (like IE9 or Android < 4.0), don't use this method. 12206 * 12207 * @param {object=} state State object for pushState or replaceState 12208 * @return {object} state 12209 */ 12210 Location.prototype.state = function(state) { 12211 if (!arguments.length) { 12212 return this.$$state; 12213 } 12214 12215 if (Location !== LocationHtml5Url || !this.$$html5) { 12216 throw $locationMinErr('nostate', 'History API state support is available only ' + 12217 'in HTML5 mode and only in browsers supporting HTML5 History API'); 12218 } 12219 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 12220 // but we're changing the $$state reference to $browser.state() during the $digest 12221 // so the modification window is narrow. 12222 this.$$state = isUndefined(state) ? null : state; 12223 12224 return this; 12225 }; 12226}); 12227 12228 12229function locationGetter(property) { 12230 return function() { 12231 return this[property]; 12232 }; 12233} 12234 12235 12236function locationGetterSetter(property, preprocess) { 12237 return function(value) { 12238 if (isUndefined(value)) { 12239 return this[property]; 12240 } 12241 12242 this[property] = preprocess(value); 12243 this.$$compose(); 12244 12245 return this; 12246 }; 12247} 12248 12249 12250/** 12251 * @ngdoc service 12252 * @name $location 12253 * 12254 * @requires $rootElement 12255 * 12256 * @description 12257 * The $location service parses the URL in the browser address bar (based on the 12258 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 12259 * available to your application. Changes to the URL in the address bar are reflected into 12260 * $location service and changes to $location are reflected into the browser address bar. 12261 * 12262 * **The $location service:** 12263 * 12264 * - Exposes the current URL in the browser address bar, so you can 12265 * - Watch and observe the URL. 12266 * - Change the URL. 12267 * - Synchronizes the URL with the browser when the user 12268 * - Changes the address bar. 12269 * - Clicks the back or forward button (or clicks a History link). 12270 * - Clicks on a link. 12271 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 12272 * 12273 * For more information see {@link guide/$location Developer Guide: Using $location} 12274 */ 12275 12276/** 12277 * @ngdoc provider 12278 * @name $locationProvider 12279 * @description 12280 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 12281 */ 12282function $LocationProvider() { 12283 var hashPrefix = '', 12284 html5Mode = { 12285 enabled: false, 12286 requireBase: true, 12287 rewriteLinks: true 12288 }; 12289 12290 /** 12291 * @ngdoc method 12292 * @name $locationProvider#hashPrefix 12293 * @description 12294 * @param {string=} prefix Prefix for hash part (containing path and search) 12295 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12296 */ 12297 this.hashPrefix = function(prefix) { 12298 if (isDefined(prefix)) { 12299 hashPrefix = prefix; 12300 return this; 12301 } else { 12302 return hashPrefix; 12303 } 12304 }; 12305 12306 /** 12307 * @ngdoc method 12308 * @name $locationProvider#html5Mode 12309 * @description 12310 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 12311 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 12312 * properties: 12313 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 12314 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 12315 * support `pushState`. 12316 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 12317 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 12318 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 12319 * See the {@link guide/$location $location guide for more information} 12320 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 12321 * enables/disables url rewriting for relative links. 12322 * 12323 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 12324 */ 12325 this.html5Mode = function(mode) { 12326 if (isBoolean(mode)) { 12327 html5Mode.enabled = mode; 12328 return this; 12329 } else if (isObject(mode)) { 12330 12331 if (isBoolean(mode.enabled)) { 12332 html5Mode.enabled = mode.enabled; 12333 } 12334 12335 if (isBoolean(mode.requireBase)) { 12336 html5Mode.requireBase = mode.requireBase; 12337 } 12338 12339 if (isBoolean(mode.rewriteLinks)) { 12340 html5Mode.rewriteLinks = mode.rewriteLinks; 12341 } 12342 12343 return this; 12344 } else { 12345 return html5Mode; 12346 } 12347 }; 12348 12349 /** 12350 * @ngdoc event 12351 * @name $location#$locationChangeStart 12352 * @eventType broadcast on root scope 12353 * @description 12354 * Broadcasted before a URL will change. 12355 * 12356 * This change can be prevented by calling 12357 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 12358 * details about event object. Upon successful change 12359 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 12360 * 12361 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 12362 * the browser supports the HTML5 History API. 12363 * 12364 * @param {Object} angularEvent Synthetic event object. 12365 * @param {string} newUrl New URL 12366 * @param {string=} oldUrl URL that was before it was changed. 12367 * @param {string=} newState New history state object 12368 * @param {string=} oldState History state object that was before it was changed. 12369 */ 12370 12371 /** 12372 * @ngdoc event 12373 * @name $location#$locationChangeSuccess 12374 * @eventType broadcast on root scope 12375 * @description 12376 * Broadcasted after a URL was changed. 12377 * 12378 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 12379 * the browser supports the HTML5 History API. 12380 * 12381 * @param {Object} angularEvent Synthetic event object. 12382 * @param {string} newUrl New URL 12383 * @param {string=} oldUrl URL that was before it was changed. 12384 * @param {string=} newState New history state object 12385 * @param {string=} oldState History state object that was before it was changed. 12386 */ 12387 12388 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 12389 function($rootScope, $browser, $sniffer, $rootElement, $window) { 12390 var $location, 12391 LocationMode, 12392 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 12393 initialUrl = $browser.url(), 12394 appBase; 12395 12396 if (html5Mode.enabled) { 12397 if (!baseHref && html5Mode.requireBase) { 12398 throw $locationMinErr('nobase', 12399 "$location in HTML5 mode requires a <base> tag to be present!"); 12400 } 12401 appBase = serverBase(initialUrl) + (baseHref || '/'); 12402 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 12403 } else { 12404 appBase = stripHash(initialUrl); 12405 LocationMode = LocationHashbangUrl; 12406 } 12407 var appBaseNoFile = stripFile(appBase); 12408
12409 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 12410 $location.$$parseLinkUrl(initialUrl, initialUrl); 12411 12412 $location.$$state = $browser.state(); 12413 12414 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 12415 12416 function setBrowserUrlWithFallback(url, replace, state) { 12417 var oldUrl = $location.url(); 12418 var oldState = $location.$$state; 12419 try { 12420 $browser.url(url, replace, state); 12421 12422 // Make sure $location.state() returns referentially identical (not just deeply equal) 12423 // state object; this makes possible quick checking if the state changed in the digest 12424 // loop. Checking deep equality would be too expensive. 12425 $location.$$state = $browser.state(); 12426 } catch (e) { 12427 // Restore old values if pushState fails 12428 $location.url(oldUrl); 12429 $location.$$state = oldState; 12430 12431 throw e; 12432 } 12433 } 12434 12435 $rootElement.on('click', function(event) { 12436 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 12437 // currently we open nice url link and redirect then 12438 12439 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 12440 12441 var elm = jqLite(event.target); 12442 12443 // traverse the DOM up to find first A tag 12444 while (nodeName_(elm[0]) !== 'a') { 12445 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 12446 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 12447 } 12448 12449 var absHref = elm.prop('href'); 12450 // get the actual href attribute - see 12451 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 12452 var relHref = elm.attr('href') || elm.attr('xlink:href'); 12453 12454 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 12455 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 12456 // an animation. 12457 absHref = urlResolve(absHref.animVal).href; 12458 } 12459 12460 // Ignore when url is started with javascript: or mailto: 12461 if (IGNORE_URI_REGEXP.test(absHref)) return; 12462 12463 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 12464 if ($location.$$parseLinkUrl(absHref, relHref)) { 12465 // We do a preventDefault for all urls that are part of the angular application, 12466 // in html5mode and also without, so that we are able to abort navigation without 12467 // getting double entries in the location history. 12468 event.preventDefault(); 12469 // update location manually 12470 if ($location.absUrl() != $browser.url()) { 12471 $rootScope.$apply(); 12472 // hack to work around FF6 bug 684208 when scenario runner clicks on links 12473 $window.angular['ff-684208-preventDefault'] = true; 12474 } 12475 } 12476 } 12477 }); 12478 12479 12480 // rewrite hashbang url <> html5 url 12481 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 12482 $browser.url($location.absUrl(), true); 12483 } 12484 12485 var initializing = true; 12486 12487 // update $location when $browser url changes 12488 $browser.onUrlChange(function(newUrl, newState) { 12489 12490 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { 12491 // If we are navigating outside of the app then force a reload 12492 $window.location.href = newUrl; 12493 return; 12494 } 12495 12496 $rootScope.$evalAsync(function() { 12497 var oldUrl = $location.absUrl(); 12498 var oldState = $location.$$state; 12499 var defaultPrevented; 12500 newUrl = trimEmptyHash(newUrl); 12501 $location.$$parse(newUrl); 12502 $location.$$state = newState; 12503 12504 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12505 newState, oldState).defaultPrevented; 12506 12507 // if the location was changed by a `$locationChangeStart` handler then stop 12508 // processing this location change 12509 if ($location.absUrl() !== newUrl) return; 12510 12511 if (defaultPrevented) { 12512 $location.$$parse(oldUrl); 12513 $location.$$state = oldState; 12514 setBrowserUrlWithFallback(oldUrl, false, oldState); 12515 } else { 12516 initializing = false;
12517 afterLocationChange(oldUrl, oldState); 12518 } 12519 }); 12520 if (!$rootScope.$$phase) $rootScope.$digest(); 12521 }); 12522 12523 // update browser 12524 $rootScope.$watch(function $locationWatch() { 12525 var oldUrl = trimEmptyHash($browser.url()); 12526 var newUrl = trimEmptyHash($location.absUrl()); 12527 var oldState = $browser.state(); 12528 var currentReplace = $location.$$replace; 12529 var urlOrStateChanged = oldUrl !== newUrl || 12530 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 12531 12532 if (initializing || urlOrStateChanged) { 12533 initializing = false; 12534 12535 $rootScope.$evalAsync(function() { 12536 var newUrl = $location.absUrl(); 12537 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12538 $location.$$state, oldState).defaultPrevented; 12539 12540 // if the location was changed by a `$locationChangeStart` handler then stop 12541 // processing this location change 12542 if ($location.absUrl() !== newUrl) return; 12543 12544 if (defaultPrevented) { 12545 $location.$$parse(oldUrl); 12546 $location.$$state = oldState; 12547 } else { 12548 if (urlOrStateChanged) { 12549 setBrowserUrlWithFallback(newUrl, currentReplace, 12550 oldState === $location.$$state ? null : $location.$$state); 12551 } 12552 afterLocationChange(oldUrl, oldState); 12553 } 12554 }); 12555 } 12556 12557 $location.$$replace = false; 12558 12559 // we don't need to return anything because $evalAsync will make the digest loop dirty when 12560 // there is a change 12561 }); 12562 12563 return $location; 12564 12565 function afterLocationChange(oldUrl, oldState) { 12566 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 12567 $location.$$state, oldState); 12568 } 12569}]; 12570} 12571 12572/** 12573 * @ngdoc service 12574 * @name $log 12575 * @requires $window 12576 * 12577 * @description 12578 * Simple service for logging. Default implementation safely writes the message 12579 * into the browser's console (if present). 12580 * 12581 * The main purpose of this service is to simplify debugging and troubleshooting. 12582 * 12583 * The default is to log `debug` messages. You can use 12584 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 12585 * 12586 * @example 12587 <example module="logExample"> 12588 <file name="script.js"> 12589 angular.module('logExample', []) 12590 .controller('LogController', ['$scope', '$log', function($scope, $log) { 12591 $scope.$log = $log; 12592 $scope.message = 'Hello World!'; 12593 }]); 12594 </file> 12595 <file name="index.html"> 12596 <div ng-controller="LogController"> 12597 <p>Reload this page with open console, enter text and hit the log button...</p> 12598 <label>Message: 12599 <input type="text" ng-model="message" /></label> 12600 <button ng-click="$log.log(message)">log</button> 12601 <button ng-click="$log.warn(message)">warn</button> 12602 <button ng-click="$log.info(message)">info</button> 12603 <button ng-click="$log.error(message)">error</button> 12604 <button ng-click="$log.debug(message)">debug</button> 12605 </div> 12606 </file> 12607 </example> 12608 */ 12609 12610/** 12611 * @ngdoc provider 12612 * @name $logProvider 12613 * @description 12614 * Use the `$logProvider` to configure how the application logs messages 12615 */ 12616function $LogProvider() { 12617 var debug = true, 12618 self = this; 12619 12620 /** 12621 * @ngdoc method 12622 * @name $logProvider#debugEnabled 12623 * @description 12624 * @param {boolean=} flag enable or disable debug level messages 12625 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12626 */ 12627 this.debugEnabled = function(flag) { 12628 if (isDefined(flag)) { 12629 debug = flag; 12630 return this; 12631 } else { 12632 return debug; 12633 } 12634 }; 12635 12636 this.$get = ['$window', function($window) { 12637 return { 12638 /** 12639 * @ngdoc method 12640 * @name $log#log 12641 * 12642 * @description 12643 * Write a log message 12644 */ 12645 log: consoleLog('log'), 12646 12647 /** 12648 * @ngdoc method 12649 * @name $log#info 12650 * 12651 * @description 12652 * Write an information message 12653 */ 12654 info: consoleLog('info'), 12655 12656 /** 12657 * @ngdoc method 12658 * @name $log#warn 12659 *
12660 * @description 12661 * Write a warning message 12662 */ 12663 warn: consoleLog('warn'), 12664 12665 /** 12666 * @ngdoc method 12667 * @name $log#error 12668 * 12669 * @description 12670 * Write an error message 12671 */ 12672 error: consoleLog('error'), 12673 12674 /** 12675 * @ngdoc method 12676 * @name $log#debug 12677 * 12678 * @description 12679 * Write a debug message 12680 */ 12681 debug: (function() { 12682 var fn = consoleLog('debug'); 12683 12684 return function() { 12685 if (debug) { 12686 fn.apply(self, arguments); 12687 } 12688 }; 12689 }()) 12690 }; 12691 12692 function formatError(arg) { 12693 if (arg instanceof Error) { 12694 if (arg.stack) { 12695 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 12696 ? 'Error: ' + arg.message + '\n' + arg.stack 12697 : arg.stack; 12698 } else if (arg.sourceURL) { 12699 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 12700 } 12701 } 12702 return arg; 12703 } 12704 12705 function consoleLog(type) { 12706 var console = $window.console || {}, 12707 logFn = console[type] || console.log || noop, 12708 hasApply = false; 12709 12710 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 12711 // The reason behind this is that console.log has type "object" in IE8... 12712 try { 12713 hasApply = !!logFn.apply; 12714 } catch (e) {} 12715 12716 if (hasApply) { 12717 return function() { 12718 var args = []; 12719 forEach(arguments, function(arg) { 12720 args.push(formatError(arg)); 12721 }); 12722 return logFn.apply(console, args); 12723 }; 12724 } 12725 12726 // we are IE which either doesn't have window.console => this is noop and we do nothing, 12727 // or we are IE where console.log doesn't have apply so we log at least first 2 args 12728 return function(arg1, arg2) { 12729 logFn(arg1, arg2 == null ? '' : arg2); 12730 }; 12731 } 12732 }]; 12733} 12734 12735/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 12736 * Any commits to this file should be reviewed with security in mind. * 12737 * Changes to this file can potentially create security vulnerabilities. * 12738 * An approval from 2 Core members with history of modifying * 12739 * this file is required. * 12740 * * 12741 * Does the change somehow allow for arbitrary javascript to be executed? * 12742 * Or allows for someone to change the prototype of built-in objects? * 12743 * Or gives undesired access to variables likes document or window? * 12744 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 12745 12746var $parseMinErr = minErr('$parse'); 12747 12748// Sandboxing Angular Expressions 12749// ------------------------------ 12750// Angular expressions are generally considered safe because these expressions only have direct 12751// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 12752// obtaining a reference to native JS functions such as the Function constructor. 12753// 12754// As an example, consider the following Angular expression: 12755// 12756// {}.toString.constructor('alert("evil JS code")') 12757// 12758// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 12759// against the expression language, but not to prevent exploits that were enabled by exposing 12760// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 12761// practice and therefore we are not even trying to protect against interaction with an object 12762// explicitly exposed in this way. 12763// 12764// In general, it is not possible to access a Window object from an angular expression unless a 12765// window or some DOM object that has a reference to window is published onto a Scope. 12766// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 12767// native objects. 12768// 12769// See https://docs.angularjs.org/guide/security 12770 12771 12772function ensureSafeMemberName(name, fullExpression) { 12773 if (name === "__defineGetter__" || name === "__defineSetter__" 12774 || name === "__lookupGetter__" || name === "__lookupSetter__" 12775 || name === "__proto__") { 12776 throw $parseMinErr('isecfld', 12777 'Attempting to access a disallowed field in Angular expressions! ' 12778 + 'Expression: {0}', fullExpression); 12779 } 12780 return name; 12781} 12782 12783function getStringValue(name, fullExpression) { 12784 // From the JavaScript docs: 12785 // Property names must be strings. This means that non-string objects cannot be used 12786 // as keys in an object. Any non-string object, including a number, is typecasted 12787 // into a string via the toString method. 12788 // 12789 // So, to ensure that we are checking the same `name` that JavaScript would use, 12790 // we cast it to a string, if possible. 12791 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string, 12792 // this is, this will handle objects that misbehave. 12793 name = name + ''; 12794 if (!isString(name)) { 12795 throw $parseMinErr('iseccst', 12796 'Cannot convert object to primitive value! ' 12797 + 'Expression: {0}', fullExpression); 12798 } 12799 return name; 12800} 12801 12802function ensureSafeObject(obj, fullExpression) { 12803 // nifty check if obj is Function that is fast and works across iframes and other contexts 12804 if (obj) { 12805 if (obj.constructor === obj) { 12806 throw $parseMinErr('isecfn', 12807 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12808 fullExpression); 12809 } else if (// isWindow(obj) 12810 obj.window === obj) { 12811 throw $parseMinErr('isecwindow', 12812 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 12813 fullExpression); 12814 } else if (// isElement(obj) 12815 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 12816 throw $parseMinErr('isecdom', 12817 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 12818 fullExpression); 12819 } else if (// block Object so that we can't get hold of dangerous Object.* methods 12820 obj === Object) { 12821 throw $parseMinErr('isecobj', 12822 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 12823 fullExpression); 12824 } 12825 } 12826 return obj; 12827} 12828 12829var CALL = Function.prototype.call; 12830var APPLY = Function.prototype.apply; 12831var BIND = Function.prototype.bind; 12832 12833function ensureSafeFunction(obj, fullExpression) { 12834 if (obj) { 12835 if (obj.constructor === obj) { 12836 throw $parseMinErr('isecfn', 12837 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12838 fullExpression); 12839 } else if (obj === CALL || obj === APPLY || obj === BIND) { 12840 throw $parseMinErr('isecff', 12841 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 12842 fullExpression); 12843 } 12844 } 12845} 12846 12847function ensureSafeAssignContext(obj, fullExpression) { 12848 if (obj) { 12849 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor || 12850 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) { 12851 throw $parseMinErr('isecaf', 12852 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression); 12853 } 12854 } 12855} 12856 12857var OPERATORS = createMap(); 12858forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 12859var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 12860 12861
vendor: 37,280 bytes, lines 12862-14089
12862///////////////////////////////////////// 12863 12864 12865/** 12866 * @constructor 12867 */ 12868var Lexer = function(options) { 12869 this.options = options; 12870}; 12871 12872Lexer.prototype = { 12873 constructor: Lexer, 12874 12875 lex: function(text) { 12876 this.text = text; 12877 this.index = 0; 12878 this.tokens = []; 12879 12880 while (this.index < this.text.length) { 12881 var ch = this.text.charAt(this.index); 12882 if (ch === '"' || ch === "'") { 12883 this.readString(ch); 12884 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 12885 this.readNumber(); 12886 } else if (this.isIdent(ch)) { 12887 this.readIdent(); 12888 } else if (this.is(ch, '(){}[].,;:?')) { 12889 this.tokens.push({index: this.index, text: ch}); 12890 this.index++; 12891 } else if (this.isWhitespace(ch)) { 12892 this.index++; 12893 } else { 12894 var ch2 = ch + this.peek(); 12895 var ch3 = ch2 + this.peek(2); 12896 var op1 = OPERATORS[ch]; 12897 var op2 = OPERATORS[ch2]; 12898 var op3 = OPERATORS[ch3]; 12899 if (op1 || op2 || op3) { 12900 var token = op3 ? ch3 : (op2 ? ch2 : ch); 12901 this.tokens.push({index: this.index, text: token, operator: true}); 12902 this.index += token.length; 12903 } else { 12904 this.throwError('Unexpected next character ', this.index, this.index + 1); 12905 } 12906 } 12907 } 12908 return this.tokens; 12909 }, 12910 12911 is: function(ch, chars) { 12912 return chars.indexOf(ch) !== -1; 12913 }, 12914 12915 peek: function(i) { 12916 var num = i || 1; 12917 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 12918 }, 12919 12920 isNumber: function(ch) { 12921 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 12922 }, 12923 12924 isWhitespace: function(ch) { 12925 // IE treats non-breaking space as \u00A0 12926 return (ch === ' ' || ch === '\r' || ch === '\t' || 12927 ch === '\n' || ch === '\v' || ch === '\u00A0'); 12928 }, 12929 12930 isIdent: function(ch) { 12931 return ('a' <= ch && ch <= 'z' || 12932 'A' <= ch && ch <= 'Z' || 12933 '_' === ch || ch === '$'); 12934 }, 12935 12936 isExpOperator: function(ch) { 12937 return (ch === '-' || ch === '+' || this.isNumber(ch)); 12938 }, 12939 12940 throwError: function(error, start, end) { 12941 end = end || this.index; 12942 var colStr = (isDefined(start) 12943 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 12944 : ' ' + end); 12945 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 12946 error, colStr, this.text); 12947 }, 12948 12949 readNumber: function() { 12950 var number = ''; 12951 var start = this.index; 12952 while (this.index < this.text.length) { 12953 var ch = lowercase(this.text.charAt(this.index)); 12954 if (ch == '.' || this.isNumber(ch)) { 12955 number += ch; 12956 } else { 12957 var peekCh = this.peek(); 12958 if (ch == 'e' && this.isExpOperator(peekCh)) { 12959 number += ch; 12960 } else if (this.isExpOperator(ch) && 12961 peekCh && this.isNumber(peekCh) && 12962 number.charAt(number.length - 1) == 'e') { 12963 number += ch; 12964 } else if (this.isExpOperator(ch) && 12965 (!peekCh || !this.isNumber(peekCh)) && 12966 number.charAt(number.length - 1) == 'e') { 12967 this.throwError('Invalid exponent'); 12968 } else { 12969 break; 12970 } 12971 } 12972 this.index++; 12973 } 12974 this.tokens.push({ 12975 index: start, 12976 text: number, 12977 constant: true, 12978 value: Number(number) 12979 }); 12980 }, 12981 12982 readIdent: function() { 12983 var start = this.index; 12984 while (this.index < this.text.length) { 12985 var ch = this.text.charAt(this.index); 12986 if (!(this.isIdent(ch) || this.isNumber(ch))) { 12987 break; 12988 } 12989 this.index++; 12990 } 12991 this.tokens.push({ 12992 index: start, 12993 text: this.text.slice(start, this.index), 12994 identifier: true 12995 }); 12996 }, 12997 12998 readString: function(quote) { 12999 var start = this.index; 13000 this.index++; 13001 var string = ''; 13002 var rawString = quote; 13003 var escape = false; 13004 while (this.index < this.text.length) { 13005 var ch = this.text.charAt(this.index); 13006 rawString += ch; 13007 if (escape) { 13008 if (ch === 'u') { 13009 var hex = this.text.substring(this.index + 1, this.index + 5); 13010 if (!hex.match(/[\da-f]{4}/i)) { 13011 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 13012 } 13013 this.index += 4; 13014 string += String.fromCharCode(parseInt(hex, 16)); 13015 } else { 13016 var rep = ESCAPE[ch]; 13017 string = string + (rep || ch); 13018 } 13019 escape = false; 13020 } else if (ch === '\\') { 13021 escape = true; 13022 } else if (ch === quote) { 13023 this.index++; 13024 this.tokens.push({ 13025 index: start, 13026 text: rawString, 13027 constant: true, 13028 value: string 13029 }); 13030 return; 13031 } else { 13032 string += ch; 13033 } 13034 this.index++; 13035 } 13036 this.throwError('Unterminated quote', start); 13037 } 13038}; 13039 13040var AST = function(lexer, options) { 13041 this.lexer = lexer; 13042 this.options = options; 13043}; 13044 13045AST.Program = 'Program'; 13046AST.ExpressionStatement = 'ExpressionStatement'; 13047AST.AssignmentExpression = 'AssignmentExpression'; 13048AST.ConditionalExpression = 'ConditionalExpression'; 13049AST.LogicalExpression = 'LogicalExpression'; 13050AST.BinaryExpression = 'BinaryExpression'; 13051AST.UnaryExpression = 'UnaryExpression'; 13052AST.CallExpression = 'CallExpression'; 13053AST.MemberExpression = 'MemberExpression'; 13054AST.Identifier = 'Identifier'; 13055AST.Literal = 'Literal'; 13056AST.ArrayExpression = 'ArrayExpression'; 13057AST.Property = 'Property'; 13058AST.ObjectExpression = 'ObjectExpression'; 13059AST.ThisExpression = 'ThisExpression'; 13060 13061// Internal use only 13062AST.NGValueParameter = 'NGValueParameter'; 13063 13064AST.prototype = { 13065 ast: function(text) { 13066 this.text = text; 13067 this.tokens = this.lexer.lex(text); 13068 13069 var value = this.program(); 13070 13071 if (this.tokens.length !== 0) { 13072 this.throwError('is an unexpected token', this.tokens[0]); 13073 } 13074 13075 return value; 13076 }, 13077 13078 program: function() { 13079 var body = []; 13080 while (true) { 13081 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 13082 body.push(this.expressionStatement()); 13083 if (!this.expect(';')) { 13084 return { type: AST.Program, body: body}; 13085 } 13086 } 13087 }, 13088 13089 expressionStatement: function() { 13090 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 13091 }, 13092 13093 filterChain: function() { 13094 var left = this.expression(); 13095 var token; 13096 while ((token = this.expect('|'))) { 13097 left = this.filter(left); 13098 } 13099 return left; 13100 }, 13101 13102 expression: function() { 13103 return this.assignment(); 13104 }, 13105 13106 assignment: function() { 13107 var result = this.ternary(); 13108 if (this.expect('=')) { 13109 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 13110 } 13111 return result; 13112 }, 13113 13114 ternary: function() { 13115 var test = this.logicalOR(); 13116 var alternate; 13117 var consequent; 13118 if (this.expect('?')) { 13119 alternate = this.expression(); 13120 if (this.consume(':')) { 13121 consequent = this.expression(); 13122 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 13123 } 13124 } 13125 return test; 13126 }, 13127 13128 logicalOR: function() { 13129 var left = this.logicalAND(); 13130 while (this.expect('||')) { 13131 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 13132 } 13133 return left; 13134 }, 13135 13136 logicalAND: function() { 13137 var left = this.equality(); 13138 while (this.expect('&&')) { 13139 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 13140 } 13141 return left; 13142 }, 13143 13144 equality: function() { 13145 var left = this.relational(); 13146 var token; 13147 while ((token = this.expect('==','!=','===','!=='))) { 13148 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 13149 } 13150 return left; 13151 }, 13152 13153 relational: function() { 13154 var left = this.additive(); 13155 var token; 13156 while ((token = this.expect('<', '>', '<=', '>='))) { 13157 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 13158 } 13159 return left; 13160 }, 13161 13162 additive: function() { 13163 var left = this.multiplicative(); 13164 var token; 13165 while ((token = this.expect('+','-'))) { 13166 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 13167 } 13168 return left; 13169 }, 13170 13171 multiplicative: function() { 13172 var left = this.unary(); 13173 var token; 13174 while ((token = this.expect('*','/','%'))) { 13175 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 13176 } 13177 return left; 13178 }, 13179 13180 unary: function() { 13181 var token; 13182 if ((token = this.expect('+', '-', '!'))) { 13183 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 13184 } else { 13185 return this.primary(); 13186 } 13187 }, 13188 13189 primary: function() { 13190 var primary; 13191 if (this.expect('(')) { 13192 primary = this.filterChain(); 13193 this.consume(')'); 13194 } else if (this.expect('[')) { 13195 primary = this.arrayDeclaration(); 13196 } else if (this.expect('{')) { 13197 primary = this.object(); 13198 } else if (this.constants.hasOwnProperty(this.peek().text)) { 13199 primary = copy(this.constants[this.consume().text]); 13200 } else if (this.peek().identifier) { 13201 primary = this.identifier(); 13202 } else if (this.peek().constant) { 13203 primary = this.constant(); 13204 } else { 13205 this.throwError('not a primary expression', this.peek()); 13206 } 13207 13208 var next; 13209 while ((next = this.expect('(', '[', '.'))) { 13210 if (next.text === '(') { 13211 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 13212 this.consume(')'); 13213 } else if (next.text === '[') { 13214 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 13215 this.consume(']'); 13216 } else if (next.text === '.') { 13217 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 13218 } else { 13219 this.throwError('IMPOSSIBLE'); 13220 } 13221 } 13222 return primary; 13223 }, 13224 13225 filter: function(baseExpression) { 13226 var args = [baseExpression]; 13227 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 13228 13229 while (this.expect(':')) { 13230 args.push(this.expression()); 13231 } 13232 13233 return result; 13234 }, 13235 13236 parseArguments: function() { 13237 var args = []; 13238 if (this.peekToken().text !== ')') { 13239 do { 13240 args.push(this.expression()); 13241 } while (this.expect(',')); 13242 } 13243 return args; 13244 }, 13245 13246 identifier: function() { 13247 var token = this.consume(); 13248 if (!token.identifier) { 13249 this.throwError('is not a valid identifier', token); 13250 } 13251 return { type: AST.Identifier, name: token.text }; 13252 }, 13253 13254 constant: function() { 13255 // TODO check that it is a constant 13256 return { type: AST.Literal, value: this.consume().value }; 13257 }, 13258 13259 arrayDeclaration: function() { 13260 var elements = []; 13261 if (this.peekToken().text !== ']') { 13262 do { 13263 if (this.peek(']')) { 13264 // Support trailing commas per ES5.1. 13265 break; 13266 } 13267 elements.push(this.expression()); 13268 } while (this.expect(',')); 13269 } 13270 this.consume(']'); 13271 13272 return { type: AST.ArrayExpression, elements: elements }; 13273 }, 13274 13275 object: function() { 13276 var properties = [], property; 13277 if (this.peekToken().text !== '}') { 13278 do { 13279 if (this.peek('}')) { 13280 // Support trailing commas per ES5.1. 13281 break; 13282 } 13283 property = {type: AST.Property, kind: 'init'}; 13284 if (this.peek().constant) { 13285 property.key = this.constant(); 13286 } else if (this.peek().identifier) { 13287 property.key = this.identifier(); 13288 } else { 13289 this.throwError("invalid key", this.peek()); 13290 } 13291 this.consume(':'); 13292 property.value = this.expression(); 13293 properties.push(property); 13294 } while (this.expect(',')); 13295 } 13296 this.consume('}'); 13297 13298 return {type: AST.ObjectExpression, properties: properties }; 13299 }, 13300 13301 throwError: function(msg, token) { 13302 throw $parseMinErr('syntax', 13303 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 13304 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 13305 }, 13306 13307 consume: function(e1) { 13308 if (this.tokens.length === 0) { 13309 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 13310 } 13311 13312 var token = this.expect(e1); 13313 if (!token) { 13314 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 13315 } 13316 return token; 13317 }, 13318 13319 peekToken: function() { 13320 if (this.tokens.length === 0) { 13321 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 13322 } 13323 return this.tokens[0]; 13324 }, 13325 13326 peek: function(e1, e2, e3, e4) { 13327 return this.peekAhead(0, e1, e2, e3, e4); 13328 }, 13329 13330 peekAhead: function(i, e1, e2, e3, e4) { 13331 if (this.tokens.length > i) { 13332 var token = this.tokens[i]; 13333 var t = token.text; 13334 if (t === e1 || t === e2 || t === e3 || t === e4 || 13335 (!e1 && !e2 && !e3 && !e4)) { 13336 return token; 13337 } 13338 } 13339 return false; 13340 }, 13341 13342 expect: function(e1, e2, e3, e4) { 13343 var token = this.peek(e1, e2, e3, e4); 13344 if (token) { 13345 this.tokens.shift(); 13346 return token; 13347 } 13348 return false; 13349 }, 13350 13351 13352 /* `undefined` is not a constant, it is an identifier, 13353 * but using it as an identifier is not supported 13354 */ 13355 constants: { 13356 'true': { type: AST.Literal, value: true }, 13357 'false': { type: AST.Literal, value: false }, 13358 'null': { type: AST.Literal, value: null }, 13359 'undefined': {type: AST.Literal, value: undefined }, 13360 'this': {type: AST.ThisExpression } 13361 } 13362}; 13363 13364function ifDefined(v, d) { 13365 return typeof v !== 'undefined' ? v : d; 13366} 13367 13368function plusFn(l, r) { 13369 if (typeof l === 'undefined') return r; 13370 if (typeof r === 'undefined') return l; 13371 return l + r; 13372} 13373 13374function isStateless($filter, filterName) { 13375 var fn = $filter(filterName); 13376 return !fn.$stateful; 13377} 13378 13379function findConstantAndWatchExpressions(ast, $filter) { 13380 var allConstants; 13381 var argsToWatch; 13382 switch (ast.type) { 13383 case AST.Program: 13384 allConstants = true; 13385 forEach(ast.body, function(expr) { 13386 findConstantAndWatchExpressions(expr.expression, $filter); 13387 allConstants = allConstants && expr.expression.constant; 13388 }); 13389 ast.constant = allConstants; 13390 break; 13391 case AST.Literal: 13392 ast.constant = true; 13393 ast.toWatch = []; 13394 break; 13395 case AST.UnaryExpression: 13396 findConstantAndWatchExpressions(ast.argument, $filter); 13397 ast.constant = ast.argument.constant; 13398 ast.toWatch = ast.argument.toWatch; 13399 break; 13400 case AST.BinaryExpression: 13401 findConstantAndWatchExpressions(ast.left, $filter); 13402 findConstantAndWatchExpressions(ast.right, $filter); 13403 ast.constant = ast.left.constant && ast.right.constant; 13404 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 13405 break; 13406 case AST.LogicalExpression: 13407 findConstantAndWatchExpressions(ast.left, $filter); 13408 findConstantAndWatchExpressions(ast.right, $filter); 13409 ast.constant = ast.left.constant && ast.right.constant; 13410 ast.toWatch = ast.constant ? [] : [ast]; 13411 break; 13412 case AST.ConditionalExpression: 13413 findConstantAndWatchExpressions(ast.test, $filter); 13414 findConstantAndWatchExpressions(ast.alternate, $filter); 13415 findConstantAndWatchExpressions(ast.consequent, $filter); 13416 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 13417 ast.toWatch = ast.constant ? [] : [ast]; 13418 break; 13419 case AST.Identifier: 13420 ast.constant = false; 13421 ast.toWatch = [ast]; 13422 break; 13423 case AST.MemberExpression: 13424 findConstantAndWatchExpressions(ast.object, $filter); 13425 if (ast.computed) { 13426 findConstantAndWatchExpressions(ast.property, $filter); 13427 } 13428 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 13429 ast.toWatch = [ast]; 13430 break; 13431 case AST.CallExpression: 13432 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 13433 argsToWatch = []; 13434 forEach(ast.arguments, function(expr) { 13435 findConstantAndWatchExpressions(expr, $filter); 13436 allConstants = allConstants && expr.constant; 13437 if (!expr.constant) { 13438 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13439 } 13440 }); 13441 ast.constant = allConstants; 13442 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 13443 break; 13444 case AST.AssignmentExpression: 13445 findConstantAndWatchExpressions(ast.left, $filter); 13446 findConstantAndWatchExpressions(ast.right, $filter); 13447 ast.constant = ast.left.constant && ast.right.constant; 13448 ast.toWatch = [ast]; 13449 break; 13450 case AST.ArrayExpression: 13451 allConstants = true; 13452 argsToWatch = []; 13453 forEach(ast.elements, function(expr) { 13454 findConstantAndWatchExpressions(expr, $filter); 13455 allConstants = allConstants && expr.constant; 13456 if (!expr.constant) { 13457 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13458 } 13459 }); 13460 ast.constant = allConstants; 13461 ast.toWatch = argsToWatch; 13462 break; 13463 case AST.ObjectExpression: 13464 allConstants = true; 13465 argsToWatch = []; 13466 forEach(ast.properties, function(property) { 13467 findConstantAndWatchExpressions(property.value, $filter); 13468 allConstants = allConstants && property.value.constant; 13469 if (!property.value.constant) { 13470 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 13471 } 13472 }); 13473 ast.constant = allConstants; 13474 ast.toWatch = argsToWatch; 13475 break; 13476 case AST.ThisExpression: 13477 ast.constant = false; 13478 ast.toWatch = []; 13479 break; 13480 } 13481} 13482 13483function getInputs(body) { 13484 if (body.length != 1) return; 13485 var lastExpression = body[0].expression; 13486 var candidate = lastExpression.toWatch; 13487 if (candidate.length !== 1) return candidate; 13488 return candidate[0] !== lastExpression ? candidate : undefined; 13489} 13490 13491function isAssignable(ast) { 13492 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 13493} 13494 13495function assignableAST(ast) { 13496 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 13497 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 13498 } 13499} 13500 13501function isLiteral(ast) { 13502 return ast.body.length === 0 || 13503 ast.body.length === 1 && ( 13504 ast.body[0].expression.type === AST.Literal || 13505 ast.body[0].expression.type === AST.ArrayExpression || 13506 ast.body[0].expression.type === AST.ObjectExpression); 13507} 13508 13509function isConstant(ast) { 13510 return ast.constant; 13511} 13512 13513function ASTCompiler(astBuilder, $filter) { 13514 this.astBuilder = astBuilder; 13515 this.$filter = $filter; 13516} 13517 13518ASTCompiler.prototype = { 13519 compile: function(expression, expensiveChecks) { 13520 var self = this; 13521 var ast = this.astBuilder.ast(expression); 13522 this.state = { 13523 nextId: 0, 13524 filters: {}, 13525 expensiveChecks: expensiveChecks, 13526 fn: {vars: [], body: [], own: {}}, 13527 assign: {vars: [], body: [], own: {}}, 13528 inputs: [] 13529 }; 13530 findConstantAndWatchExpressions(ast, self.$filter); 13531 var extra = ''; 13532 var assignable; 13533 this.stage = 'assign'; 13534 if ((assignable = assignableAST(ast))) { 13535 this.state.computing = 'assign'; 13536 var result = this.nextId(); 13537 this.recurse(assignable, result); 13538 this.return_(result); 13539 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 13540 } 13541 var toWatch = getInputs(ast.body); 13542 self.stage = 'inputs'; 13543 forEach(toWatch, function(watch, key) { 13544 var fnKey = 'fn' + key; 13545 self.state[fnKey] = {vars: [], body: [], own: {}}; 13546 self.state.computing = fnKey; 13547 var intoId = self.nextId(); 13548 self.recurse(watch, intoId); 13549 self.return_(intoId); 13550 self.state.inputs.push(fnKey); 13551 watch.watchId = key; 13552 }); 13553 this.state.computing = 'fn'; 13554 this.stage = 'main'; 13555 this.recurse(ast); 13556 var fnString = 13557 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 13558 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 13559 '"' + this.USE + ' ' + this.STRICT + '";\n' + 13560 this.filterPrefix() + 13561 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 13562 extra + 13563 this.watchFns() + 13564 'return fn;'; 13565 13566 /* jshint -W054 */ 13567 var fn = (new Function('$filter', 13568 'ensureSafeMemberName', 13569 'ensureSafeObject', 13570 'ensureSafeFunction', 13571 'getStringValue', 13572 'ensureSafeAssignContext', 13573 'ifDefined', 13574 'plus', 13575 'text', 13576 fnString))( 13577 this.$filter, 13578 ensureSafeMemberName, 13579 ensureSafeObject, 13580 ensureSafeFunction, 13581 getStringValue, 13582 ensureSafeAssignContext, 13583 ifDefined, 13584 plusFn, 13585 expression); 13586 /* jshint +W054 */ 13587 this.state = this.stage = undefined; 13588 fn.literal = isLiteral(ast); 13589 fn.constant = isConstant(ast); 13590 return fn; 13591 }, 13592 13593 USE: 'use', 13594 13595 STRICT: 'strict', 13596 13597 watchFns: function() { 13598 var result = []; 13599 var fns = this.state.inputs; 13600 var self = this; 13601 forEach(fns, function(name) { 13602 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 13603 }); 13604 if (fns.length) { 13605 result.push('fn.inputs=[' + fns.join(',') + '];'); 13606 } 13607 return result.join(''); 13608 }, 13609 13610 generateFunction: function(name, params) { 13611 return 'function(' + params + '){' + 13612 this.varsPrefix(name) + 13613 this.body(name) + 13614 '};'; 13615 }, 13616 13617 filterPrefix: function() { 13618 var parts = []; 13619 var self = this; 13620 forEach(this.state.filters, function(id, filter) { 13621 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 13622 }); 13623 if (parts.length) return 'var ' + parts.join(',') + ';'; 13624 return ''; 13625 }, 13626 13627 varsPrefix: function(section) { 13628 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 13629 }, 13630 13631 body: function(section) { 13632 return this.state[section].body.join(''); 13633 }, 13634 13635 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13636 var left, right, self = this, args, expression; 13637 recursionFn = recursionFn || noop; 13638 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 13639 intoId = intoId || this.nextId(); 13640 this.if_('i', 13641 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 13642 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 13643 ); 13644 return; 13645 } 13646 switch (ast.type) { 13647 case AST.Program: 13648 forEach(ast.body, function(expression, pos) { 13649 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 13650 if (pos !== ast.body.length - 1) { 13651 self.current().body.push(right, ';'); 13652 } else { 13653 self.return_(right); 13654 } 13655 }); 13656 break; 13657 case AST.Literal: 13658 expression = this.escape(ast.value); 13659 this.assign(intoId, expression); 13660 recursionFn(expression); 13661 break; 13662 case AST.UnaryExpression: 13663 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 13664 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 13665 this.assign(intoId, expression); 13666 recursionFn(expression); 13667 break; 13668 case AST.BinaryExpression: 13669 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 13670 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 13671 if (ast.operator === '+') { 13672 expression = this.plus(left, right); 13673 } else if (ast.operator === '-') { 13674 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 13675 } else { 13676 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 13677 } 13678 this.assign(intoId, expression); 13679 recursionFn(expression); 13680 break; 13681 case AST.LogicalExpression: 13682 intoId = intoId || this.nextId(); 13683 self.recurse(ast.left, intoId); 13684 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 13685 recursionFn(intoId); 13686 break; 13687 case AST.ConditionalExpression: 13688 intoId = intoId || this.nextId(); 13689 self.recurse(ast.test, intoId); 13690 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 13691 recursionFn(intoId); 13692 break; 13693 case AST.Identifier: 13694 intoId = intoId || this.nextId(); 13695 if (nameId) { 13696 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 13697 nameId.computed = false; 13698 nameId.name = ast.name; 13699 } 13700 ensureSafeMemberName(ast.name); 13701 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 13702 function() { 13703 self.if_(self.stage === 'inputs' || 's', function() { 13704 if (create && create !== 1) { 13705 self.if_( 13706 self.not(self.nonComputedMember('s', ast.name)), 13707 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 13708 } 13709 self.assign(intoId, self.nonComputedMember('s', ast.name)); 13710 }); 13711 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 13712 ); 13713 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 13714 self.addEnsureSafeObject(intoId); 13715 } 13716 recursionFn(intoId); 13717 break; 13718 case AST.MemberExpression: 13719 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 13720 intoId = intoId || this.nextId(); 13721 self.recurse(ast.object, left, undefined, function() { 13722 self.if_(self.notNull(left), function() { 13723 if (ast.computed) { 13724 right = self.nextId(); 13725 self.recurse(ast.property, right); 13726 self.getStringValue(right); 13727 self.addEnsureSafeMemberName(right); 13728 if (create && create !== 1) { 13729 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 13730 } 13731 expression = self.ensureSafeObject(self.computedMember(left, right)); 13732 self.assign(intoId, expression); 13733 if (nameId) { 13734 nameId.computed = true; 13735 nameId.name = right; 13736 } 13737 } else { 13738 ensureSafeMemberName(ast.property.name); 13739 if (create && create !== 1) { 13740 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 13741 } 13742 expression = self.nonComputedMember(left, ast.property.name); 13743 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 13744 expression = self.ensureSafeObject(expression); 13745 } 13746 self.assign(intoId, expression); 13747 if (nameId) { 13748 nameId.computed = false; 13749 nameId.name = ast.property.name; 13750 } 13751 } 13752 }, function() { 13753 self.assign(intoId, 'undefined'); 13754 }); 13755 recursionFn(intoId); 13756 }, !!create); 13757 break; 13758 case AST.CallExpression: 13759 intoId = intoId || this.nextId(); 13760 if (ast.filter) { 13761 right = self.filter(ast.callee.name); 13762 args = []; 13763 forEach(ast.arguments, function(expr) { 13764 var argument = self.nextId(); 13765 self.recurse(expr, argument); 13766 args.push(argument); 13767 }); 13768 expression = right + '(' + args.join(',') + ')'; 13769 self.assign(intoId, expression); 13770 recursionFn(intoId); 13771 } else { 13772 right = self.nextId(); 13773 left = {}; 13774 args = []; 13775 self.recurse(ast.callee, right, left, function() { 13776 self.if_(self.notNull(right), function() { 13777 self.addEnsureSafeFunction(right); 13778 forEach(ast.arguments, function(expr) { 13779 self.recurse(expr, self.nextId(), undefined, function(argument) { 13780 args.push(self.ensureSafeObject(argument)); 13781 }); 13782 }); 13783 if (left.name) { 13784 if (!self.state.expensiveChecks) { 13785 self.addEnsureSafeObject(left.context); 13786 } 13787 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 13788 } else { 13789 expression = right + '(' + args.join(',') + ')'; 13790 } 13791 expression = self.ensureSafeObject(expression); 13792 self.assign(intoId, expression); 13793 }, function() { 13794 self.assign(intoId, 'undefined'); 13795 }); 13796 recursionFn(intoId); 13797 }); 13798 } 13799 break; 13800 case AST.AssignmentExpression: 13801 right = this.nextId(); 13802 left = {}; 13803 if (!isAssignable(ast.left)) { 13804 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); 13805 } 13806 this.recurse(ast.left, undefined, left, function() { 13807 self.if_(self.notNull(left.context), function() { 13808 self.recurse(ast.right, right); 13809 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 13810 self.addEnsureSafeAssignContext(left.context); 13811 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 13812 self.assign(intoId, expression); 13813 recursionFn(intoId || expression); 13814 }); 13815 }, 1); 13816 break; 13817 case AST.ArrayExpression: 13818 args = []; 13819 forEach(ast.elements, function(expr) { 13820 self.recurse(expr, self.nextId(), undefined, function(argument) { 13821 args.push(argument); 13822 }); 13823 }); 13824 expression = '[' + args.join(',') + ']'; 13825 this.assign(intoId, expression); 13826 recursionFn(expression); 13827 break; 13828 case AST.ObjectExpression: 13829 args = []; 13830 forEach(ast.properties, function(property) { 13831 self.recurse(property.value, self.nextId(), undefined, function(expr) { 13832 args.push(self.escape( 13833 property.key.type === AST.Identifier ? property.key.name : 13834 ('' + property.key.value)) + 13835 ':' + expr); 13836 }); 13837 }); 13838 expression = '{' + args.join(',') + '}'; 13839 this.assign(intoId, expression); 13840 recursionFn(expression); 13841 break; 13842 case AST.ThisExpression: 13843 this.assign(intoId, 's'); 13844 recursionFn('s'); 13845 break; 13846 case AST.NGValueParameter: 13847 this.assign(intoId, 'v'); 13848 recursionFn('v'); 13849 break; 13850 } 13851 }, 13852 13853 getHasOwnProperty: function(element, property) { 13854 var key = element + '.' + property; 13855 var own = this.current().own; 13856 if (!own.hasOwnProperty(key)) { 13857 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 13858 } 13859 return own[key]; 13860 }, 13861 13862 assign: function(id, value) { 13863 if (!id) return; 13864 this.current().body.push(id, '=', value, ';'); 13865 return id; 13866 }, 13867 13868 filter: function(filterName) { 13869 if (!this.state.filters.hasOwnProperty(filterName)) { 13870 this.state.filters[filterName] = this.nextId(true); 13871 } 13872 return this.state.filters[filterName]; 13873 }, 13874 13875 ifDefined: function(id, defaultValue) { 13876 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 13877 }, 13878 13879 plus: function(left, right) { 13880 return 'plus(' + left + ',' + right + ')'; 13881 }, 13882 13883 return_: function(id) { 13884 this.current().body.push('return ', id, ';'); 13885 }, 13886 13887 if_: function(test, alternate, consequent) { 13888 if (test === true) { 13889 alternate(); 13890 } else { 13891 var body = this.current().body; 13892 body.push('if(', test, '){'); 13893 alternate(); 13894 body.push('}'); 13895 if (consequent) { 13896 body.push('else{'); 13897 consequent(); 13898 body.push('}'); 13899 } 13900 } 13901 }, 13902 13903 not: function(expression) { 13904 return '!(' + expression + ')'; 13905 }, 13906 13907 notNull: function(expression) { 13908 return expression + '!=null'; 13909 }, 13910 13911 nonComputedMember: function(left, right) { 13912 return left + '.' + right; 13913 }, 13914 13915 computedMember: function(left, right) { 13916 return left + '[' + right + ']'; 13917 }, 13918 13919 member: function(left, right, computed) { 13920 if (computed) return this.computedMember(left, right); 13921 return this.nonComputedMember(left, right); 13922 }, 13923 13924 addEnsureSafeObject: function(item) { 13925 this.current().body.push(this.ensureSafeObject(item), ';'); 13926 }, 13927 13928 addEnsureSafeMemberName: function(item) { 13929 this.current().body.push(this.ensureSafeMemberName(item), ';'); 13930 }, 13931 13932 addEnsureSafeFunction: function(item) { 13933 this.current().body.push(this.ensureSafeFunction(item), ';'); 13934 }, 13935 13936 addEnsureSafeAssignContext: function(item) { 13937 this.current().body.push(this.ensureSafeAssignContext(item), ';'); 13938 }, 13939 13940 ensureSafeObject: function(item) { 13941 return 'ensureSafeObject(' + item + ',text)'; 13942 }, 13943 13944 ensureSafeMemberName: function(item) { 13945 return 'ensureSafeMemberName(' + item + ',text)'; 13946 }, 13947 13948 ensureSafeFunction: function(item) { 13949 return 'ensureSafeFunction(' + item + ',text)'; 13950 }, 13951 13952 getStringValue: function(item) { 13953 this.assign(item, 'getStringValue(' + item + ',text)'); 13954 }, 13955 13956 ensureSafeAssignContext: function(item) { 13957 return 'ensureSafeAssignContext(' + item + ',text)'; 13958 }, 13959 13960 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13961 var self = this; 13962 return function() { 13963 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 13964 }; 13965 }, 13966 13967 lazyAssign: function(id, value) { 13968 var self = this; 13969 return function() { 13970 self.assign(id, value); 13971 }; 13972 }, 13973 13974 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 13975 13976 stringEscapeFn: function(c) { 13977 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 13978 }, 13979 13980 escape: function(value) { 13981 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 13982 if (isNumber(value)) return value.toString(); 13983 if (value === true) return 'true'; 13984 if (value === false) return 'false'; 13985 if (value === null) return 'null'; 13986 if (typeof value === 'undefined') return 'undefined'; 13987 13988 throw $parseMinErr('esc', 'IMPOSSIBLE'); 13989 }, 13990 13991 nextId: function(skip, init) { 13992 var id = 'v' + (this.state.nextId++); 13993 if (!skip) { 13994 this.current().vars.push(id + (init ? '=' + init : '')); 13995 } 13996 return id; 13997 }, 13998 13999 current: function() { 14000 return this.state[this.state.computing]; 14001 } 14002}; 14003 14004 14005function ASTInterpreter(astBuilder, $filter) { 14006 this.astBuilder = astBuilder; 14007 this.$filter = $filter; 14008} 14009 14010ASTInterpreter.prototype = { 14011 compile: function(expression, expensiveChecks) { 14012 var self = this; 14013 var ast = this.astBuilder.ast(expression); 14014 this.expression = expression; 14015 this.expensiveChecks = expensiveChecks; 14016 findConstantAndWatchExpressions(ast, self.$filter); 14017 var assignable; 14018 var assign; 14019 if ((assignable = assignableAST(ast))) { 14020 assign = this.recurse(assignable); 14021 } 14022 var toWatch = getInputs(ast.body); 14023 var inputs; 14024 if (toWatch) { 14025 inputs = []; 14026 forEach(toWatch, function(watch, key) { 14027 var input = self.recurse(watch); 14028 watch.input = input; 14029 inputs.push(input); 14030 watch.watchId = key; 14031 }); 14032 } 14033 var expressions = []; 14034 forEach(ast.body, function(expression) { 14035 expressions.push(self.recurse(expression.expression)); 14036 }); 14037 var fn = ast.body.length === 0 ? function() {} : 14038 ast.body.length === 1 ? expressions[0] : 14039 function(scope, locals) { 14040 var lastValue; 14041 forEach(expressions, function(exp) { 14042 lastValue = exp(scope, locals); 14043 }); 14044 return lastValue; 14045 }; 14046 if (assign) { 14047 fn.assign = function(scope, value, locals) { 14048 return assign(scope, locals, value); 14049 }; 14050 } 14051 if (inputs) { 14052 fn.inputs = inputs; 14053 } 14054 fn.literal = isLiteral(ast); 14055 fn.constant = isConstant(ast); 14056 return fn; 14057 }, 14058 14059 recurse: function(ast, context, create) { 14060 var left, right, self = this, args, expression; 14061 if (ast.input) { 14062 return this.inputs(ast.input, ast.watchId); 14063 } 14064 switch (ast.type) { 14065 case AST.Literal: 14066 return this.value(ast.value, context); 14067 case AST.UnaryExpression: 14068 right = this.recurse(ast.argument); 14069 return this['unary' + ast.operator](right, context); 14070 case AST.BinaryExpression: 14071 left = this.recurse(ast.left); 14072 right = this.recurse(ast.right); 14073 return this['binary' + ast.operator](left, right, context); 14074 case AST.LogicalExpression: 14075 left = this.recurse(ast.left); 14076 right = this.recurse(ast.right); 14077 return this['binary' + ast.operator](left, right, context); 14078 case AST.ConditionalExpression: 14079 return this['ternary?:']( 14080 this.recurse(ast.test), 14081 this.recurse(ast.alternate), 14082 this.recurse(ast.consequent), 14083 context 14084 ); 14085 case AST.Identifier: 14086 ensureSafeMemberName(ast.name, self.expression); 14087 return self.identifier(ast.name, 14088 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 14089 context, create, self.expression);
14090 case AST.MemberExpression: 14091 left = this.recurse(ast.object, false, !!create); 14092 if (!ast.computed) { 14093 ensureSafeMemberName(ast.property.name, self.expression); 14094 right = ast.property.name; 14095 } 14096 if (ast.computed) right = this.recurse(ast.property); 14097 return ast.computed ? 14098 this.computedMember(left, right, context, create, self.expression) : 14099 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 14100 case AST.CallExpression: 14101 args = []; 14102 forEach(ast.arguments, function(expr) { 14103 args.push(self.recurse(expr)); 14104 }); 14105 if (ast.filter) right = this.$filter(ast.callee.name); 14106 if (!ast.filter) right = this.recurse(ast.callee, true); 14107 return ast.filter ? 14108 function(scope, locals, assign, inputs) { 14109 var values = []; 14110 for (var i = 0; i < args.length; ++i) { 14111 values.push(args[i](scope, locals, assign, inputs)); 14112 } 14113 var value = right.apply(undefined, values, inputs); 14114 return context ? {context: undefined, name: undefined, value: value} : value; 14115 } : 14116 function(scope, locals, assign, inputs) { 14117 var rhs = right(scope, locals, assign, inputs); 14118 var value; 14119 if (rhs.value != null) { 14120 ensureSafeObject(rhs.context, self.expression); 14121 ensureSafeFunction(rhs.value, self.expression); 14122 var values = []; 14123 for (var i = 0; i < args.length; ++i) { 14124 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 14125 } 14126 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 14127 } 14128 return context ? {value: value} : value; 14129 }; 14130 case AST.AssignmentExpression: 14131 left = this.recurse(ast.left, true, 1); 14132 right = this.recurse(ast.right); 14133 return function(scope, locals, assign, inputs) { 14134 var lhs = left(scope, locals, assign, inputs); 14135 var rhs = right(scope, locals, assign, inputs); 14136 ensureSafeObject(lhs.value, self.expression); 14137 ensureSafeAssignContext(lhs.context); 14138 lhs.context[lhs.name] = rhs; 14139 return context ? {value: rhs} : rhs; 14140 }; 14141 case AST.ArrayExpression: 14142 args = []; 14143 forEach(ast.elements, function(expr) { 14144 args.push(self.recurse(expr)); 14145 }); 14146 return function(scope, locals, assign, inputs) { 14147 var value = []; 14148 for (var i = 0; i < args.length; ++i) { 14149 value.push(args[i](scope, locals, assign, inputs)); 14150 } 14151 return context ? {value: value} : value; 14152 }; 14153 case AST.ObjectExpression: 14154 args = []; 14155 forEach(ast.properties, function(property) { 14156 args.push({key: property.key.type === AST.Identifier ? 14157 property.key.name : 14158 ('' + property.key.value), 14159 value: self.recurse(property.value) 14160 }); 14161 }); 14162 return function(scope, locals, assign, inputs) { 14163 var value = {}; 14164 for (var i = 0; i < args.length; ++i) { 14165 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 14166 } 14167 return context ? {value: value} : value; 14168 }; 14169 case AST.ThisExpression: 14170 return function(scope) { 14171 return context ? {value: scope} : scope; 14172 }; 14173 case AST.NGValueParameter: 14174 return function(scope, locals, assign, inputs) { 14175 return context ? {value: assign} : assign; 14176 }; 14177 } 14178 }, 14179 14180 'unary+': function(argument, context) { 14181 return function(scope, locals, assign, inputs) { 14182 var arg = argument(scope, locals, assign, inputs); 14183 if (isDefined(arg)) { 14184 arg = +arg; 14185 } else { 14186 arg = 0; 14187 } 14188 return context ? {value: arg} : arg; 14189 }; 14190 }, 14191 'unary-': function(argument, context) { 14192 return function(scope, locals, assign, inputs) { 14193 var arg = argument(scope, locals, assign, inputs); 14194 if (isDefined(arg)) { 14195 arg = -arg; 14196 } else { 14197 arg = 0; 14198 } 14199 return context ? {value: arg} : arg; 14200 }; 14201 }, 14202 'unary!': function(argument, context) { 14203 return function(scope, locals, assign, inputs) { 14204 var arg = !argument(scope, locals, assign, inputs); 14205 return context ? {value: arg} : arg; 14206 }; 14207 }, 14208 'binary+': function(left, right, context) { 14209 return function(scope, locals, assign, inputs) { 14210 var lhs = left(scope, locals, assign, inputs); 14211 var rhs = right(scope, locals, assign, inputs); 14212 var arg = plusFn(lhs, rhs); 14213 return context ? {value: arg} : arg; 14214 }; 14215 }, 14216 'binary-': function(left, right, context) { 14217 return function(scope, locals, assign, inputs) { 14218 var lhs = left(scope, locals, assign, inputs); 14219 var rhs = right(scope, locals, assign, inputs); 14220 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 14221 return context ? {value: arg} : arg; 14222 }; 14223 }, 14224 'binary*': function(left, right, context) { 14225 return function(scope, locals, assign, inputs) { 14226 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 14227 return context ? {value: arg} : arg; 14228 }; 14229 }, 14230 'binary/': function(left, right, context) { 14231 return function(scope, locals, assign, inputs) { 14232 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 14233 return context ? {value: arg} : arg; 14234 }; 14235 }, 14236 'binary%': function(left, right, context) { 14237 return function(scope, locals, assign, inputs) { 14238 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 14239 return context ? {value: arg} : arg; 14240 }; 14241 }, 14242 'binary===': function(left, right, context) { 14243 return function(scope, locals, assign, inputs) { 14244 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 14245 return context ? {value: arg} : arg; 14246 }; 14247 }, 14248 'binary!==': function(left, right, context) { 14249 return function(scope, locals, assign, inputs) { 14250 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 14251 return context ? {value: arg} : arg; 14252 }; 14253 }, 14254 'binary==': function(left, right, context) { 14255 return function(scope, locals, assign, inputs) { 14256 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 14257 return context ? {value: arg} : arg; 14258 }; 14259 }, 14260 'binary!=': function(left, right, context) { 14261 return function(scope, locals, assign, inputs) { 14262 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 14263 return context ? {value: arg} : arg; 14264 }; 14265 }, 14266 'binary<': function(left, right, context) { 14267 return function(scope, locals, assign, inputs) { 14268 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 14269 return context ? {value: arg} : arg; 14270 }; 14271 }, 14272 'binary>': function(left, right, context) { 14273 return function(scope, locals, assign, inputs) { 14274 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 14275 return context ? {value: arg} : arg; 14276 }; 14277 }, 14278 'binary<=': function(left, right, context) { 14279 return function(scope, locals, assign, inputs) { 14280 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 14281 return context ? {value: arg} : arg; 14282 }; 14283 }, 14284 'binary>=': function(left, right, context) { 14285 return function(scope, locals, assign, inputs) { 14286 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 14287 return context ? {value: arg} : arg; 14288 }; 14289 }, 14290 'binary&&': function(left, right, context) { 14291 return function(scope, locals, assign, inputs) { 14292 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 14293 return context ? {value: arg} : arg; 14294 }; 14295 }, 14296 'binary||': function(left, right, context) { 14297 return function(scope, locals, assign, inputs) { 14298 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 14299 return context ? {value: arg} : arg; 14300 }; 14301 }, 14302 'ternary?:': function(test, alternate, consequent, context) { 14303 return function(scope, locals, assign, inputs) { 14304 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 14305 return context ? {value: arg} : arg; 14306 }; 14307 }, 14308 value: function(value, context) { 14309 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 14310 }, 14311 identifier: function(name, expensiveChecks, context, create, expression) { 14312 return function(scope, locals, assign, inputs) { 14313 var base = locals && (name in locals) ? locals : scope; 14314 if (create && create !== 1 && base && !(base[name])) { 14315 base[name] = {}; 14316 } 14317 var value = base ? base[name] : undefined; 14318 if (expensiveChecks) { 14319 ensureSafeObject(value, expression); 14320 } 14321 if (context) { 14322 return {context: base, name: name, value: value}; 14323 } else { 14324 return value; 14325 } 14326 }; 14327 }, 14328 computedMember: function(left, right, context, create, expression) { 14329 return function(scope, locals, assign, inputs) { 14330 var lhs = left(scope, locals, assign, inputs); 14331 var rhs; 14332 var value; 14333 if (lhs != null) { 14334 rhs = right(scope, locals, assign, inputs); 14335 rhs = getStringValue(rhs); 14336 ensureSafeMemberName(rhs, expression); 14337 if (create && create !== 1 && lhs && !(lhs[rhs])) { 14338 lhs[rhs] = {}; 14339 } 14340 value = lhs[rhs]; 14341 ensureSafeObject(value, expression); 14342 } 14343 if (context) { 14344 return {context: lhs, name: rhs, value: value}; 14345 } else { 14346 return value; 14347 } 14348 }; 14349 }, 14350 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 14351 return function(scope, locals, assign, inputs) { 14352 var lhs = left(scope, locals, assign, inputs); 14353 if (create && create !== 1 && lhs && !(lhs[right])) { 14354 lhs[right] = {}; 14355 } 14356 var value = lhs != null ? lhs[right] : undefined; 14357 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 14358 ensureSafeObject(value, expression); 14359 } 14360 if (context) { 14361 return {context: lhs, name: right, value: value}; 14362 } else { 14363 return value; 14364 } 14365 }; 14366 }, 14367 inputs: function(input, watchId) { 14368 return function(scope, value, locals, inputs) {
14369 if (inputs) return inputs[watchId]; 14370 return input(scope, value, locals); 14371 }; 14372 } 14373}; 14374 14375/** 14376 * @constructor 14377 */ 14378var Parser = function(lexer, $filter, options) { 14379 this.lexer = lexer; 14380 this.$filter = $filter; 14381 this.options = options; 14382 this.ast = new AST(this.lexer); 14383 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 14384 new ASTCompiler(this.ast, $filter); 14385}; 14386 14387Parser.prototype = { 14388 constructor: Parser, 14389 14390 parse: function(text) { 14391 return this.astCompiler.compile(text, this.options.expensiveChecks); 14392 } 14393}; 14394 14395function isPossiblyDangerousMemberName(name) { 14396 return name == 'constructor'; 14397} 14398 14399var objectValueOf = Object.prototype.valueOf; 14400 14401function getValueOf(value) { 14402 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 14403} 14404 14405/////////////////////////////////// 14406 14407/** 14408 * @ngdoc service 14409 * @name $parse 14410 * @kind function 14411 * 14412 * @description 14413 * 14414 * Converts Angular {@link guide/expression expression} into a function. 14415 * 14416 * ```js 14417 * var getter = $parse('user.name'); 14418 * var setter = getter.assign; 14419 * var context = {user:{name:'angular'}}; 14420 * var locals = {user:{name:'local'}}; 14421 * 14422 * expect(getter(context)).toEqual('angular'); 14423 * setter(context, 'newValue'); 14424 * expect(context.user.name).toEqual('newValue'); 14425 * expect(getter(context, locals)).toEqual('local'); 14426 * ``` 14427 * 14428 * 14429 * @param {string} expression String expression to compile. 14430 * @returns {function(context, locals)} a function which represents the compiled expression: 14431 * 14432 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14433 * are evaluated against (typically a scope object). 14434 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14435 * `context`. 14436 * 14437 * The returned function also has the following properties: 14438 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 14439 * literal. 14440 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 14441 * constant literals. 14442 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 14443 * set to a function to change its value on the given context. 14444 * 14445 */ 14446 14447 14448/** 14449 * @ngdoc provider 14450 * @name $parseProvider 14451 * 14452 * @description 14453 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 14454 * service. 14455 */ 14456function $ParseProvider() { 14457 var cacheDefault = createMap(); 14458 var cacheExpensive = createMap(); 14459 14460 this.$get = ['$filter', function($filter) { 14461 var noUnsafeEval = csp().noUnsafeEval; 14462 var $parseOptions = { 14463 csp: noUnsafeEval, 14464 expensiveChecks: false 14465 }, 14466 $parseOptionsExpensive = { 14467 csp: noUnsafeEval, 14468 expensiveChecks: true 14469 }; 14470 14471 return function $parse(exp, interceptorFn, expensiveChecks) { 14472 var parsedExpression, oneTime, cacheKey; 14473 14474 switch (typeof exp) { 14475 case 'string': 14476 exp = exp.trim(); 14477 cacheKey = exp; 14478 14479 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 14480 parsedExpression = cache[cacheKey]; 14481 14482 if (!parsedExpression) { 14483 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 14484 oneTime = true; 14485 exp = exp.substring(2); 14486 } 14487 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 14488 var lexer = new Lexer(parseOptions); 14489 var parser = new Parser(lexer, $filter, parseOptions); 14490 parsedExpression = parser.parse(exp); 14491 if (parsedExpression.constant) { 14492 parsedExpression.$$watchDelegate = constantWatchDelegate; 14493 } else if (oneTime) { 14494 parsedExpression.$$watchDelegate = parsedExpression.literal ? 14495 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 14496 } else if (parsedExpression.inputs) { 14497 parsedExpression.$$watchDelegate = inputsWatchDelegate; 14498 } 14499 cache[cacheKey] = parsedExpression; 14500 } 14501 return addInterceptor(parsedExpression, interceptorFn); 14502 14503 case 'function': 14504 return addInterceptor(exp, interceptorFn); 14505 14506 default: 14507 return addInterceptor(noop, interceptorFn); 14508 } 14509 }; 14510 14511 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 14512 14513 if (newValue == null || oldValueOfValue == null) { // null/undefined 14514 return newValue === oldValueOfValue; 14515 } 14516 14517 if (typeof newValue === 'object') { 14518 14519 // attempt to convert the value to a primitive type 14520 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 14521 // be cheaply dirty-checked 14522 newValue = getValueOf(newValue); 14523 14524 if (typeof newValue === 'object') { 14525 // objects/arrays are not supported - deep-watching them would be too expensive 14526 return false;
14527 } 14528 14529 // fall-through to the primitive equality check 14530 } 14531 14532 //Primitive or NaN 14533 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 14534 } 14535 14536 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 14537 var inputExpressions = parsedExpression.inputs; 14538 var lastResult; 14539 14540 if (inputExpressions.length === 1) { 14541 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 14542 inputExpressions = inputExpressions[0]; 14543 return scope.$watch(function expressionInputWatch(scope) { 14544 var newInputValue = inputExpressions(scope); 14545 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 14546 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 14547 oldInputValueOf = newInputValue && getValueOf(newInputValue); 14548 } 14549 return lastResult; 14550 }, listener, objectEquality, prettyPrintExpression); 14551 } 14552 14553 var oldInputValueOfValues = []; 14554 var oldInputValues = []; 14555 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14556 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 14557 oldInputValues[i] = null; 14558 } 14559 14560 return scope.$watch(function expressionInputsWatch(scope) { 14561 var changed = false; 14562 14563 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14564 var newInputValue = inputExpressions[i](scope); 14565 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 14566 oldInputValues[i] = newInputValue; 14567 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 14568 } 14569 } 14570 14571 if (changed) { 14572 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 14573 } 14574 14575 return lastResult; 14576 }, listener, objectEquality, prettyPrintExpression); 14577 } 14578 14579 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14580 var unwatch, lastValue; 14581 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14582 return parsedExpression(scope); 14583 }, function oneTimeListener(value, old, scope) { 14584 lastValue = value; 14585 if (isFunction(listener)) { 14586 listener.apply(this, arguments); 14587 } 14588 if (isDefined(value)) { 14589 scope.$$postDigest(function() { 14590 if (isDefined(lastValue)) { 14591 unwatch(); 14592 } 14593 }); 14594 } 14595 }, objectEquality); 14596 } 14597 14598 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14599 var unwatch, lastValue; 14600 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14601 return parsedExpression(scope); 14602 }, function oneTimeListener(value, old, scope) { 14603 lastValue = value; 14604 if (isFunction(listener)) { 14605 listener.call(this, value, old, scope); 14606 } 14607 if (isAllDefined(value)) { 14608 scope.$$postDigest(function() { 14609 if (isAllDefined(lastValue)) unwatch(); 14610 }); 14611 } 14612 }, objectEquality); 14613 14614 function isAllDefined(value) { 14615 var allDefined = true; 14616 forEach(value, function(val) { 14617 if (!isDefined(val)) allDefined = false; 14618 }); 14619 return allDefined; 14620 } 14621 } 14622 14623 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14624 var unwatch; 14625 return unwatch = scope.$watch(function constantWatch(scope) { 14626 return parsedExpression(scope); 14627 }, function constantListener(value, old, scope) { 14628 if (isFunction(listener)) { 14629 listener.apply(this, arguments); 14630 } 14631 unwatch(); 14632 }, objectEquality); 14633 } 14634 14635 function addInterceptor(parsedExpression, interceptorFn) { 14636 if (!interceptorFn) return parsedExpression; 14637 var watchDelegate = parsedExpression.$$watchDelegate; 14638 var useInputs = false; 14639 14640 var regularWatch = 14641 watchDelegate !== oneTimeLiteralWatchDelegate && 14642 watchDelegate !== oneTimeWatchDelegate; 14643 14644 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14645 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); 14646 return interceptorFn(value, scope, locals); 14647 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 14648 var value = parsedExpression(scope, locals, assign, inputs); 14649 var result = interceptorFn(value, scope, locals); 14650 // we only return the interceptor's result if the 14651 // initial value is defined (for bind-once) 14652 return isDefined(value) ? result : value; 14653 }; 14654 14655 // Propagate $$watchDelegates other then inputsWatchDelegate 14656 if (parsedExpression.$$watchDelegate && 14657 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 14658 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 14659 } else if (!interceptorFn.$stateful) { 14660 // If there is an interceptor, but no watchDelegate then treat the interceptor like 14661 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 14662 fn.$$watchDelegate = inputsWatchDelegate; 14663 useInputs = !parsedExpression.inputs; 14664 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 14665 } 14666 14667 return fn; 14668 } 14669 }]; 14670} 14671 14672/** 14673 * @ngdoc service 14674 * @name $q 14675 * @requires $rootScope 14676 * 14677 * @description 14678 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 14679 * when they are done processing. 14680 * 14681 * This is an implementation of promises/deferred objects inspired by 14682 * [Kris Kowal's Q](https://github.com/kriskowal/q). 14683 * 14684 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 14685 * implementations, and the other which resembles ES6 promises to some degree. 14686 * 14687 * # $q constructor 14688 * 14689 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 14690 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, 14691 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 14692 * 14693 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are 14694 * available yet. 14695 * 14696 * It can be used like so: 14697 * 14698 * ```js 14699 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14700 * // are available in the current lexical scope (they could have been injected or passed in). 14701 * 14702 * function asyncGreet(name) { 14703 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 14704 * return $q(function(resolve, reject) { 14705 * setTimeout(function() { 14706 * if (okToGreet(name)) { 14707 * resolve('Hello, ' + name + '!'); 14708 * } else { 14709 * reject('Greeting ' + name + ' is not allowed.'); 14710 * } 14711 * }, 1000); 14712 * }); 14713 * } 14714 * 14715 * var promise = asyncGreet('Robin Hood'); 14716 * promise.then(function(greeting) { 14717 * alert('Success: ' + greeting); 14718 * }, function(reason) { 14719 * alert('Failed: ' + reason); 14720 * }); 14721 * ``` 14722 * 14723 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 14724 * 14725 * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise. 14726 * 14727 * However, the more traditional CommonJS-style usage is still available, and documented below. 14728 * 14729 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 14730 * interface for interacting with an object that represents the result of an action that is 14731 * performed asynchronously, and may or may not be finished at any given point in time. 14732 * 14733 * From the perspective of dealing with error handling, deferred and promise APIs are to 14734 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 14735 * 14736 * ```js 14737 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14738 * // are available in the current lexical scope (they could have been injected or passed in). 14739 * 14740 * function asyncGreet(name) { 14741 * var deferred = $q.defer(); 14742 * 14743 * setTimeout(function() { 14744 * deferred.notify('About to greet ' + name + '.'); 14745 * 14746 * if (okToGreet(name)) { 14747 * deferred.resolve('Hello, ' + name + '!'); 14748 * } else { 14749 * deferred.reject('Greeting ' + name + ' is not allowed.'); 14750 * } 14751 * }, 1000); 14752 * 14753 * return deferred.promise; 14754 * } 14755 * 14756 * var promise = asyncGreet('Robin Hood'); 14757 * promise.then(function(greeting) { 14758 * alert('Success: ' + greeting); 14759 * }, function(reason) { 14760 * alert('Failed: ' + reason); 14761 * }, function(update) { 14762 * alert('Got notification: ' + update); 14763 * }); 14764 * ``` 14765 * 14766 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 14767 * comes in the way of guarantees that promise and deferred APIs make, see 14768 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 14769 *
14770 * Additionally the promise api allows for composition that is very hard to do with the 14771 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 14772 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 14773 * section on serial or parallel joining of promises. 14774 * 14775 * # The Deferred API 14776 * 14777 * A new instance of deferred is constructed by calling `$q.defer()`. 14778 * 14779 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 14780 * that can be used for signaling the successful or unsuccessful completion, as well as the status 14781 * of the task. 14782 * 14783 * **Methods** 14784 * 14785 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 14786 * constructed via `$q.reject`, the promise will be rejected instead. 14787 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 14788 * resolving it with a rejection constructed via `$q.reject`. 14789 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 14790 * multiple times before the promise is either resolved or rejected. 14791 * 14792 * **Properties** 14793 * 14794 * - promise â `{Promise}` â promise object associated with this deferred. 14795 * 14796 * 14797 * # The Promise API 14798 * 14799 * A new promise instance is created when a deferred instance is created and can be retrieved by 14800 * calling `deferred.promise`. 14801 * 14802 * The purpose of the promise object is to allow for interested parties to get access to the result 14803 * of the deferred task when it completes. 14804 * 14805 * **Methods** 14806 * 14807 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 14808 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 14809 * as soon as the result is available. The callbacks are called with a single argument: the result 14810 * or rejection reason. Additionally, the notify callback may be called zero or more times to 14811 * provide a progress indication, before the promise is resolved or rejected. 14812 * 14813 * This method *returns a new promise* which is resolved or rejected via the return value of the 14814 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14815 * with the value which is resolved in that promise using 14816 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 14817 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 14818 * resolved or rejected from the notifyCallback method. 14819 * 14820 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 14821 * 14822 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 14823 * but to do so without modifying the final value. This is useful to release resources or do some 14824 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 14825 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 14826 * more information. 14827 * 14828 * # Chaining promises 14829 * 14830 * Because calling the `then` method of a promise returns a new derived promise, it is easily 14831 * possible to create a chain of promises: 14832 * 14833 * ```js 14834 * promiseB = promiseA.then(function(result) { 14835 * return result + 1; 14836 * }); 14837 * 14838 * // promiseB will be resolved immediately after promiseA is resolved and its value 14839 * // will be the result of promiseA incremented by 1 14840 * ``` 14841 * 14842 * It is possible to create chains of any length and since a promise can be resolved with another 14843 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 14844 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 14845 * $http's response interceptors. 14846 * 14847 * 14848 * # Differences between Kris Kowal's Q and $q 14849 * 14850 * There are two main differences: 14851 * 14852 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 14853 * mechanism in angular, which means faster propagation of resolution or rejection into your 14854 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 14855 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14856 * all the important functionality needed for common async tasks. 14857 * 14858 * # Testing 14859 * 14860 * ```js 14861 * it('should simulate promise', inject(function($q, $rootScope) { 14862 * var deferred = $q.defer(); 14863 * var promise = deferred.promise; 14864 * var resolvedValue; 14865 * 14866 * promise.then(function(value) { resolvedValue = value; }); 14867 * expect(resolvedValue).toBeUndefined(); 14868 * 14869 * // Simulate resolving of promise 14870 * deferred.resolve(123); 14871 * // Note that the 'then' function does not get called synchronously. 14872 * // This is because we want the promise API to always be async, whether or not 14873 * // it got called synchronously or asynchronously. 14874 * expect(resolvedValue).toBeUndefined(); 14875 * 14876 * // Propagate promise resolution to 'then' functions using $apply(). 14877 * $rootScope.$apply(); 14878 * expect(resolvedValue).toEqual(123); 14879 * })); 14880 * ``` 14881 * 14882 * @param {function(function, function)} resolver Function which is responsible for resolving or 14883 * rejecting the newly created promise. The first parameter is a function which resolves the 14884 * promise, the second parameter is a function which rejects the promise. 14885 * 14886 * @returns {Promise} The newly created promise. 14887 */ 14888function $QProvider() { 14889 14890 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 14891 return qFactory(function(callback) { 14892 $rootScope.$evalAsync(callback); 14893 }, $exceptionHandler); 14894 }]; 14895} 14896 14897function $$QProvider() { 14898 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 14899 return qFactory(function(callback) { 14900 $browser.defer(callback); 14901 }, $exceptionHandler); 14902 }]; 14903} 14904 14905/** 14906 * Constructs a promise manager. 14907 * 14908 * @param {function(function)} nextTick Function for executing functions in the next turn. 14909 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 14910 * debugging purposes. 14911 * @returns {object} Promise manager. 14912 */ 14913function qFactory(nextTick, exceptionHandler) { 14914 var $qMinErr = minErr('$q', TypeError); 14915 function callOnce(self, resolveFn, rejectFn) { 14916 var called = false; 14917 function wrap(fn) { 14918 return function(value) { 14919 if (called) return; 14920 called = true; 14921 fn.call(self, value); 14922 }; 14923 } 14924 14925 return [wrap(resolveFn), wrap(rejectFn)]; 14926 } 14927 14928 /** 14929 * @ngdoc method 14930 * @name ng.$q#defer 14931 * @kind function 14932 * 14933 * @description 14934 * Creates a `Deferred` object which represents a task which will finish in the future. 14935 * 14936 * @returns {Deferred} Returns a new instance of deferred. 14937 */ 14938 var defer = function() { 14939 return new Deferred(); 14940 }; 14941 14942 function Promise() { 14943 this.$$state = { status: 0 }; 14944 } 14945 14946 extend(Promise.prototype, { 14947 then: function(onFulfilled, onRejected, progressBack) { 14948 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 14949 return this; 14950 } 14951 var result = new Deferred(); 14952 14953 this.$$state.pending = this.$$state.pending || []; 14954 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 14955 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 14956 14957 return result.promise; 14958 }, 14959 14960 "catch": function(callback) { 14961 return this.then(null, callback); 14962 }, 14963 14964 "finally": function(callback, progressBack) { 14965 return this.then(function(value) { 14966 return handleCallback(value, true, callback); 14967 }, function(error) { 14968 return handleCallback(error, false, callback); 14969 }, progressBack); 14970 } 14971 }); 14972 14973 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 14974 function simpleBind(context, fn) { 14975 return function(value) { 14976 fn.call(context, value); 14977 }; 14978 } 14979 14980 function processQueue(state) { 14981 var fn, deferred, pending; 14982 14983 pending = state.pending; 14984 state.processScheduled = false; 14985 state.pending = undefined; 14986 for (var i = 0, ii = pending.length; i < ii; ++i) { 14987 deferred = pending[i][0]; 14988 fn = pending[i][state.status]; 14989 try { 14990 if (isFunction(fn)) { 14991 deferred.resolve(fn(state.value)); 14992 } else if (state.status === 1) { 14993 deferred.resolve(state.value); 14994 } else { 14995 deferred.reject(state.value); 14996 } 14997 } catch (e) { 14998 deferred.reject(e); 14999 exceptionHandler(e); 15000 } 15001 } 15002 } 15003 15004 function scheduleProcessQueue(state) { 15005 if (state.processScheduled || !state.pending) return; 15006 state.processScheduled = true; 15007 nextTick(function() { processQueue(state); }); 15008 } 15009 15010 function Deferred() { 15011 this.promise = new Promise(); 15012 //Necessary to support unbound execution :/ 15013 this.resolve = simpleBind(this, this.resolve); 15014 this.reject = simpleBind(this, this.reject); 15015 this.notify = simpleBind(this, this.notify); 15016 } 15017 15018 extend(Deferred.prototype, { 15019 resolve: function(val) { 15020 if (this.promise.$$state.status) return; 15021 if (val === this.promise) { 15022 this.$$reject($qMinErr( 15023 'qcycle', 15024 "Expected promise to be resolved with value other than
15024itself '{0}'", 15025 val)); 15026 } else { 15027 this.$$resolve(val); 15028 } 15029 15030 }, 15031 15032 $$resolve: function(val) { 15033 var then, fns; 15034 15035 fns = callOnce(this, this.$$resolve, this.$$reject); 15036 try { 15037 if ((isObject(val) || isFunction(val))) then = val && val.then; 15038 if (isFunction(then)) { 15039 this.promise.$$state.status = -1; 15040 then.call(val, fns[0], fns[1], this.notify); 15041 } else { 15042 this.promise.$$state.value = val; 15043 this.promise.$$state.status = 1; 15044 scheduleProcessQueue(this.promise.$$state); 15045 } 15046 } catch (e) { 15047 fns[1](e); 15048 exceptionHandler(e); 15049 } 15050 }, 15051 15052 reject: function(reason) { 15053 if (this.promise.$$state.status) return; 15054 this.$$reject(reason); 15055 }, 15056 15057 $$reject: function(reason) { 15058 this.promise.$$state.value = reason; 15059 this.promise.$$state.status = 2; 15060 scheduleProcessQueue(this.promise.$$state); 15061 }, 15062 15063 notify: function(progress) { 15064 var callbacks = this.promise.$$state.pending; 15065 15066 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 15067 nextTick(function() { 15068 var callback, result; 15069 for (var i = 0, ii = callbacks.length; i < ii; i++) { 15070 result = callbacks[i][0]; 15071 callback = callbacks[i][3]; 15072 try { 15073 result.notify(isFunction(callback) ? callback(progress) : progress); 15074 } catch (e) { 15075 exceptionHandler(e); 15076 } 15077 } 15078 }); 15079 } 15080 } 15081 }); 15082 15083 /** 15084 * @ngdoc method 15085 * @name $q#reject 15086 * @kind function 15087 * 15088 * @description 15089 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 15090 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 15091 * a promise chain, you don't need to worry about it. 15092 * 15093 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 15094 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 15095 * a promise error callback and you want to forward the error to the promise derived from the 15096 * current promise, you have to "rethrow" the error by returning a rejection constructed via 15097 * `reject`. 15098 * 15099 * ```js 15100 * promiseB = promiseA.then(function(result) { 15101 * // success: do something and resolve promiseB 15102 * // with the old or a new result 15103 * return result; 15104 * }, function(reason) { 15105 * // error: handle the error if possible and 15106 * // resolve promiseB with newPromiseOrValue, 15107 * // otherwise forward the rejection to promiseB 15108 * if (canHandle(reason)) { 15109 * // handle the error and recover 15110 * return newPromiseOrValue; 15111 * } 15112 * return $q.reject(reason); 15113 * }); 15114 * ``` 15115 * 15116 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 15117 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 15118 */ 15119 var reject = function(reason) { 15120 var result = new Deferred(); 15121 result.reject(reason); 15122 return result.promise; 15123 }; 15124 15125 var makePromise = function makePromise(value, resolved) { 15126 var result = new Deferred(); 15127 if (resolved) { 15128 result.resolve(value); 15129 } else { 15130 result.reject(value); 15131 } 15132 return result.promise; 15133 }; 15134 15135 var handleCallback = function handleCallback(value, isResolved, callback) { 15136 var callbackOutput = null; 15137 try { 15138 if (isFunction(callback)) callbackOutput = callback(); 15139 } catch (e) { 15140 return makePromise(e, false); 15141 } 15142 if (isPromiseLike(callbackOutput)) { 15143 return callbackOutput.then(function() { 15144 return makePromise(value, isResolved); 15145 }, function(error) { 15146 return makePromise(error, false); 15147 }); 15148 } else { 15149 return makePromise(value, isResolved); 15150 } 15151 }; 15152 15153 /** 15154 * @ngdoc method 15155 * @name $q#when 15156 * @kind function 15157 * 15158 * @description 15159 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 15160 * This is useful when you are dealing with an object that might or might not be a promise, or if 15161 * the promise comes from a source that can't be trusted. 15162 * 15163 * @param {*} value Value or a promise 15164 * @param {Function=} successCallback 15165 * @param {Function=} errorCallback 15166 * @param {Function=} progressCallback 15167 * @returns {Promise} Returns a promise of the passed value or promise 15168 */ 15169 15170 15171 var when = function(value, callback, errback, progressBack) { 15172 var result = new Deferred(); 15173 result.resolve(value); 15174 return result.promise.then(callback, errback, progressBack); 15175 }; 15176 15177 /** 15178 * @ngdoc method 15179 * @name $q#resolve 15180 * @kind function 15181 * 15182 * @description 15183 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 15184 * 15185 * @param {*} value Value or a promise 15186 * @param {Function=} successCallback 15187 * @param {Function=} errorCallback 15188 * @param {Function=} progressCallback 15189 * @returns {Promise} Returns a promise of the passed value or promise 15190 */ 15191 var resolve = when; 15192 15193 /** 15194 * @ngdoc method 15195 * @name $q#all 15196 * @kind function 15197 * 15198 * @description 15199 * Combines multiple promises into a single promise that is resolved when all of the input 15200 * promises are resolved. 15201 * 15202 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 15203 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 15204 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 15205 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 15206 * with the same rejection value. 15207 */ 15208 15209 function all(promises) { 15210 var deferred = new Deferred(), 15211 counter = 0, 15212 results = isArray(promises) ? [] : {}; 15213
15214 forEach(promises, function(promise, key) { 15215 counter++; 15216 when(promise).then(function(value) { 15217 if (results.hasOwnProperty(key)) return; 15218 results[key] = value; 15219 if (!(--counter)) deferred.resolve(results); 15220 }, function(reason) { 15221 if (results.hasOwnProperty(key)) return; 15222 deferred.reject(reason); 15223 }); 15224 }); 15225 15226 if (counter === 0) { 15227 deferred.resolve(results); 15228 } 15229 15230 return deferred.promise; 15231 } 15232 15233 var $Q = function Q(resolver) { 15234 if (!isFunction(resolver)) { 15235 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 15236 } 15237 15238 if (!(this instanceof Q)) { 15239 // More useful when $Q is the Promise itself. 15240 return new Q(resolver); 15241 } 15242 15243 var deferred = new Deferred(); 15244 15245 function resolveFn(value) { 15246 deferred.resolve(value); 15247 } 15248 15249 function rejectFn(reason) { 15250 deferred.reject(reason); 15251 } 15252 15253 resolver(resolveFn, rejectFn); 15254 15255 return deferred.promise; 15256 }; 15257 15258 $Q.defer = defer; 15259 $Q.reject = reject; 15260 $Q.when = when; 15261 $Q.resolve = resolve; 15262 $Q.all = all; 15263 15264 return $Q; 15265} 15266 15267function $$RAFProvider() { //rAF 15268 this.$get = ['$window', '$timeout', function($window, $timeout) { 15269 var requestAnimationFrame = $window.requestAnimationFrame || 15270 $window.webkitRequestAnimationFrame; 15271 15272 var cancelAnimationFrame = $window.cancelAnimationFrame || 15273 $window.webkitCancelAnimationFrame || 15274 $window.webkitCancelRequestAnimationFrame; 15275 15276 var rafSupported = !!requestAnimationFrame; 15277 var raf = rafSupported 15278 ? function(fn) { 15279 var id = requestAnimationFrame(fn); 15280 return function() { 15281 cancelAnimationFrame(id); 15282 }; 15283 } 15284 : function(fn) { 15285 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 15286 return function() { 15287 $timeout.cancel(timer); 15288 }; 15289 }; 15290 15291 raf.supported = rafSupported; 15292 15293 return raf; 15294 }]; 15295} 15296 15297/** 15298 * DESIGN NOTES 15299 * 15300 * The design decisions behind the scope are heavily favored for speed and memory consumption. 15301 * 15302 * The typical use of scope is to watch the expressions, which most of the time return the same 15303 * value as last time so we optimize the operation. 15304 * 15305 * Closures construction is expensive in terms of speed as well as memory: 15306 * - No closures, instead use prototypical inheritance for API 15307 * - Internal state needs to be stored on scope directly, which means that private state is 15308 * exposed as $$____ properties 15309 * 15310 * Loop operations are optimized by using while(count--) { ... } 15311 * - This means that in order to keep the same order of execution as addition we have to add 15312 * items to the array at the beginning (unshift) instead of at the end (push) 15313 * 15314 * Child scopes are created and removed often 15315 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists 15316 * 15317 * There are fewer watches than observers. This is why you don't want the observer to be implemented 15318 * in the same way as watch. Watch requires return of the initialization function which is expensive 15319 * to construct. 15320 */ 15321 15322 15323/** 15324 * @ngdoc provider 15325 * @name $rootScopeProvider 15326 * @description 15327 * 15328 * Provider for the $rootScope service. 15329 */ 15330 15331/** 15332 * @ngdoc method 15333 * @name $rootScopeProvider#digestTtl 15334 * @description 15335 * 15336 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 15337 * assuming that the model is unstable. 15338 * 15339 * The current default is 10 iterations. 15340 * 15341 * In complex applications it's possible that the dependencies between `$watch`s will result in 15342 * several digest iterations. However if an application needs more than the default 10 digest 15343 * iterations for its model to stabilize then you should investigate what is causing the model to 15344 * continuously change during the digest. 15345 * 15346 * Increasing the TTL could have performance implications, so you should not change it without 15347 * proper justification. 15348 * 15349 * @param {number} limit The number of digest iterations. 15350 */ 15351 15352 15353/** 15354 * @ngdoc service 15355 * @name $rootScope 15356 * @description 15357 * 15358 * Every application has a single root {@link ng.$rootScope.Scope scope}. 15359 * All other scopes are descendant scopes of the root scope. Scopes provide separation 15360 * between the model and the view, via a mechanism for watching the model for changes. 15361 * They also provide event emission/broadcast and subscription facility. See the 15362 * {@link guide/scope developer guide on scopes}. 15363 */ 15364function $RootScopeProvider() { 15365 var TTL = 10; 15366 var $rootScopeMinErr = minErr('$rootScope'); 15367 var lastDirtyWatch = null; 15368 var applyAsyncId = null; 15369 15370 this.digestTtl = function(value) { 15371 if (arguments.length) { 15372 TTL = value; 15373 } 15374 return TTL; 15375 }; 15376 15377 function createChildScopeClass(parent) { 15378 function ChildScope() {
15379 this.$$watchers = this.$$nextSibling = 15380 this.$$childHead = this.$$childTail = null; 15381 this.$$listeners = {}; 15382 this.$$listenerCount = {}; 15383 this.$$watchersCount = 0; 15384 this.$id = nextUid(); 15385 this.$$ChildScope = null; 15386 } 15387 ChildScope.prototype = parent; 15388 return ChildScope; 15389 } 15390 15391 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 15392 function($injector, $exceptionHandler, $parse, $browser) { 15393 15394 function destroyChildScope($event) { 15395 $event.currentScope.$$destroyed = true; 15396 } 15397 15398 function cleanUpScope($scope) { 15399 15400 if (msie === 9) { 15401 // There is a memory leak in IE9 if all child scopes are not disconnected 15402 // completely when a scope is destroyed. So this code will recurse up through 15403 // all this scopes children 15404 // 15405 // See issue https://github.com/angular/angular.js/issues/10706 15406 $scope.$$childHead && cleanUpScope($scope.$$childHead); 15407 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling); 15408 } 15409 15410 // The code below works around IE9 and V8's memory leaks 15411 // 15412 // See: 15413 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 15414 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 15415 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 15416 15417 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = 15418 $scope.$$childTail = $scope.$root = $scope.$$watchers = null; 15419 } 15420 15421 /** 15422 * @ngdoc type 15423 * @name $rootScope.Scope 15424 * 15425 * @description 15426 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 15427 * {@link auto.$injector $injector}. Child scopes are created using the 15428 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 15429 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 15430 * an in-depth introduction and usage examples. 15431 * 15432 * 15433 * # Inheritance 15434 * A scope can inherit from a parent scope, as in this example: 15435 * ```js 15436 var parent = $rootScope; 15437 var child = parent.$new(); 15438 15439 parent.salutation = "Hello"; 15440 expect(child.salutation).toEqual('Hello'); 15441 15442 child.salutation = "Welcome"; 15443 expect(child.salutation).toEqual('Welcome'); 15444 expect(parent.salutation).toEqual('Hello'); 15445 * ``` 15446 * 15447 * When interacting with `Scope` in tests, additional helper methods are available on the 15448 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 15449 * details. 15450 * 15451 * 15452 * @param {Object.<string, function()>=} providers Map of service factory which need to be 15453 * provided for the current scope. Defaults to {@link ng}. 15454 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 15455 * append/override services provided by `providers`. This is handy 15456 * when unit-testing and having the need to override a default 15457 * service. 15458 * @returns {Object} Newly created scope. 15459 * 15460 */ 15461 function Scope() { 15462 this.$id = nextUid(); 15463 this.$$phase = this.$parent = this.$$watchers = 15464 this.$$nextSibling = this.$$prevSibling = 15465 this.$$childHead = this.$$childTail = null; 15466 this.$root = this; 15467 this.$$destroyed = false; 15468 this.$$listeners = {}; 15469 this.$$listenerCount = {}; 15470 this.$$watchersCount = 0; 15471 this.$$isolateBindings = null; 15472 } 15473 15474 /** 15475 * @ngdoc property 15476 * @name $rootScope.Scope#$id 15477 * 15478 * @description 15479 * Unique scope ID (monotonically increasing) useful for debugging. 15480 */ 15481 15482 /** 15483 * @ngdoc property 15484 * @name $rootScope.Scope#$parent 15485 * 15486 * @description 15487 * Reference to the parent scope. 15488 */ 15489 15490 /** 15491 * @ngdoc property 15492 * @name $rootScope.Scope#$root 15493 *
15494 * @description 15495 * Reference to the root scope. 15496 */ 15497 15498 Scope.prototype = { 15499 constructor: Scope, 15500 /** 15501 * @ngdoc method 15502 * @name $rootScope.Scope#$new 15503 * @kind function 15504 * 15505 * @description 15506 * Creates a new child {@link ng.$rootScope.Scope scope}. 15507 * 15508 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 15509 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 15510 * 15511 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 15512 * desired for the scope and its child scopes to be permanently detached from the parent and 15513 * thus stop participating in model change detection and listener notification by invoking. 15514 * 15515 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 15516 * parent scope. The scope is isolated, as it can not see parent scope properties. 15517 * When creating widgets, it is useful for the widget to not accidentally read parent 15518 * state. 15519 * 15520 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 15521 * of the newly created scope. Defaults to `this` scope if not provided. 15522 * This is used when creating a transclude scope to correctly place it 15523 * in the scope hierarchy while maintaining the correct prototypical 15524 * inheritance. 15525 * 15526 * @returns {Object} The newly created child scope. 15527 * 15528 */ 15529 $new: function(isolate, parent) { 15530 var child; 15531 15532 parent = parent || this; 15533 15534 if (isolate) { 15535 child = new Scope(); 15536 child.$root = this.$root; 15537 } else { 15538 // Only create a child scope class if somebody asks for one, 15539 // but cache it to allow the VM to optimize lookups. 15540 if (!this.$$ChildScope) { 15541 this.$$ChildScope = createChildScopeClass(this); 15542 } 15543 child = new this.$$ChildScope(); 15544 } 15545 child.$parent = parent; 15546 child.$$prevSibling = parent.$$childTail; 15547 if (parent.$$childHead) { 15548 parent.$$childTail.$$nextSibling = child; 15549 parent.$$childTail = child; 15550 } else { 15551 parent.$$childHead = parent.$$childTail = child; 15552 } 15553 15554 // When the new scope is not isolated or we inherit from `this`, and 15555 // the parent scope is destroyed, the property `$$destroyed` is inherited 15556 // prototypically. In all other cases, this property needs to be set 15557 // when the parent scope is destroyed. 15558 // The listener needs to be added after the parent is set 15559 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 15560 15561 return child; 15562 }, 15563 15564 /** 15565 * @ngdoc method 15566 * @name $rootScope.Scope#$watch 15567 * @kind function 15568 * 15569 * @description 15570 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 15571 * 15572 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 15573 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 15574 * its value when executed multiple times with the same input because it may be executed multiple 15575 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 15576 * [idempotent](http://en.wikipedia.org/wiki/Idempotence). 15577 * - The `listener` is called only when the value from the current `watchExpression` and the 15578 * previous call to `watchExpression` are not equal (with the exception of the initial run, 15579 * see below). Inequality is determined according to reference inequality, 15580 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 15581 * via the `!==` Javascript operator, unless `objectEquality == true` 15582 * (see next point) 15583 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 15584 * according to the {@link angular.equals} function. To save the value of the object for 15585 * later comparison, the {@link angular.copy} function is used. This therefore means that 15586 * watching complex objects will have adverse memory and performance implications. 15587 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 15588 * This is achieved by rerunning the watchers until no changes are detected. The rerun 15589 * iteration limit is 10 to prevent an infinite loop deadlock. 15590 * 15591 * 15592 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 15593 * you can register a `watchExpression` function with no `listener`. (Be prepared for 15594 * multiple calls to your `watchExpression` because it will execute multiple times in a 15595 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 15596 * 15597 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 15598 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 15599 * watcher. In rare cases, this is undesirable because the listener is called when the result 15600 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 15601 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 15602 * listener was called due to initialization. 15603 * 15604 * 15605 * 15606 * # Example 15607 * ```js 15608 // let's assume that scope was dependency injected as the $rootScope 15609 var scope = $rootScope; 15610 scope.name = 'misko'; 15611 scope.counter = 0; 15612 15613 expect(scope.counter).toEqual(0); 15614 scope.$watch('name', function(newValue, oldValue) { 15615 scope.counter = scope.counter + 1; 15616 }); 15617 expect(scope.counter).toEqual(0); 15618 15619 scope.$digest(); 15620 // the listener is always called during the first $digest loop after it was registered 15621 expect(scope.counter).toEqual(1); 15622 15623 scope.$digest(); 15624 // but now it will not be called unless the value changes 15625 expect(scope.counter).toEqual(1); 15626 15627 scope.name = 'adam'; 15628 scope.$digest(); 15629 expect(scope.counter).toEqual(2); 15630 15631 15632
15633 // Using a function as a watchExpression 15634 var food; 15635 scope.foodCounter = 0; 15636 expect(scope.foodCounter).toEqual(0); 15637 scope.$watch( 15638 // This function returns the value being watched. It is called for each turn of the $digest loop 15639 function() { return food; }, 15640 // This is the change listener, called when the value returned from the above function changes 15641 function(newValue, oldValue) { 15642 if ( newValue !== oldValue ) { 15643 // Only increment the counter if the value changed 15644 scope.foodCounter = scope.foodCounter + 1; 15645 } 15646 } 15647 ); 15648 // No digest has been run so the counter will be zero 15649 expect(scope.foodCounter).toEqual(0); 15650 15651 // Run the digest but since food has not changed count will still be zero 15652 scope.$digest(); 15653 expect(scope.foodCounter).toEqual(0); 15654 15655 // Update food and run digest. Now the counter will increment 15656 food = 'cheeseburger'; 15657 scope.$digest(); 15658 expect(scope.foodCounter).toEqual(1); 15659 15660 * ``` 15661 * 15662 * 15663 * 15664 * @param {(function()|string)} watchExpression Expression that is evaluated on each 15665 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 15666 * a call to the `listener`. 15667 * 15668 * - `string`: Evaluated as {@link guide/expression expression} 15669 * - `function(scope)`: called with current `scope` as a parameter. 15670 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 15671 * of `watchExpression` changes. 15672 * 15673 * - `newVal` contains the current value of the `watchExpression` 15674 * - `oldVal` contains the previous value of the `watchExpression` 15675 * - `scope` refers to the current scope 15676 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 15677 * comparing for reference equality. 15678 * @returns {function()} Returns a deregistration function for this listener. 15679 */ 15680 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 15681 var get = $parse(watchExp); 15682 15683 if (get.$$watchDelegate) { 15684 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 15685 } 15686 var scope = this, 15687 array = scope.$$watchers, 15688 watcher = { 15689 fn: listener, 15690 last: initWatchVal, 15691 get: get, 15692 exp: prettyPrintExpression || watchExp, 15693 eq: !!objectEquality 15694 }; 15695 15696 lastDirtyWatch = null; 15697 15698 if (!isFunction(listener)) { 15699 watcher.fn = noop; 15700 } 15701 15702 if (!array) { 15703 array = scope.$$watchers = []; 15704 } 15705 // we use unshift since we use a while loop in $digest for speed. 15706 // the while loop reads in reverse order. 15707 array.unshift(watcher); 15708 incrementWatchersCount(this, 1); 15709 15710 return function deregisterWatch() { 15711 if (arrayRemove(array, watcher) >= 0) { 15712 incrementWatchersCount(scope, -1); 15713 } 15714 lastDirtyWatch = null; 15715 }; 15716 }, 15717 15718 /** 15719 * @ngdoc method 15720 * @name $rootScope.Scope#$watchGroup 15721 * @kind function 15722 * 15723 * @description 15724 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 15725 * If any one expression in the collection changes the `listener` is executed. 15726 * 15727 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 15728 * call to $digest() to see if any items changes. 15729 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 15730 * 15731 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 15732 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 15733 * 15734 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 15735 * expression in `watchExpressions` changes 15736 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 15737 * those of `watchExpression` 15738 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 15739 * those of `watchExpression` 15740 * The `scope` refers to the current scope. 15741 * @returns {function()} Returns a de-registration function for all listeners. 15742 */ 15743 $watchGroup: function(watchExpressions, listener) { 15744 var oldValues = new Array(watchExpressions.length); 15745 var newValues = new Array(watchExpressions.length); 15746 var deregisterFns = []; 15747 var self = this; 15748 var changeReactionScheduled = false;
vendor: 10,192 bytes, lines 15749-16011
15749 var firstRun = true; 15750 15751 if (!watchExpressions.length) { 15752 // No expressions means we call the listener ASAP 15753 var shouldCall = true; 15754 self.$evalAsync(function() { 15755 if (shouldCall) listener(newValues, newValues, self); 15756 }); 15757 return function deregisterWatchGroup() { 15758 shouldCall = false; 15759 }; 15760 } 15761 15762 if (watchExpressions.length === 1) { 15763 // Special case size of one 15764 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 15765 newValues[0] = value; 15766 oldValues[0] = oldValue; 15767 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 15768 }); 15769 } 15770 15771 forEach(watchExpressions, function(expr, i) { 15772 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 15773 newValues[i] = value; 15774 oldValues[i] = oldValue; 15775 if (!changeReactionScheduled) { 15776 changeReactionScheduled = true; 15777 self.$evalAsync(watchGroupAction); 15778 } 15779 }); 15780 deregisterFns.push(unwatchFn); 15781 }); 15782 15783 function watchGroupAction() { 15784 changeReactionScheduled = false; 15785 15786 if (firstRun) { 15787 firstRun = false; 15788 listener(newValues, newValues, self); 15789 } else { 15790 listener(newValues, oldValues, self); 15791 } 15792 } 15793 15794 return function deregisterWatchGroup() { 15795 while (deregisterFns.length) { 15796 deregisterFns.shift()(); 15797 } 15798 }; 15799 }, 15800 15801 15802 /** 15803 * @ngdoc method 15804 * @name $rootScope.Scope#$watchCollection 15805 * @kind function 15806 * 15807 * @description 15808 * Shallow watches the properties of an object and fires whenever any of the properties change 15809 * (for arrays, this implies watching the array items; for object maps, this implies watching 15810 * the properties). If a change is detected, the `listener` callback is fired. 15811 * 15812 * - The `obj` collection is observed via standard $watch operation and is examined on every 15813 * call to $digest() to see if any items have been added, removed, or moved. 15814 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 15815 * adding, removing, and moving items belonging to an object or array. 15816 * 15817 * 15818 * # Example 15819 * ```js 15820 $scope.names = ['igor', 'matias', 'misko', 'james']; 15821 $scope.dataCount = 4; 15822 15823 $scope.$watchCollection('names', function(newNames, oldNames) { 15824 $scope.dataCount = newNames.length; 15825 }); 15826 15827 expect($scope.dataCount).toEqual(4); 15828 $scope.$digest(); 15829 15830 //still at 4 ... no changes 15831 expect($scope.dataCount).toEqual(4); 15832 15833 $scope.names.pop(); 15834 $scope.$digest(); 15835 15836 //now there's been a change 15837 expect($scope.dataCount).toEqual(3); 15838 * ``` 15839 * 15840 * 15841 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 15842 * expression value should evaluate to an object or an array which is observed on each 15843 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 15844 * collection will trigger a call to the `listener`. 15845 * 15846 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 15847 * when a change is detected. 15848 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 15849 * - The `oldCollection` object is a copy of the former collection data. 15850 * Due to performance considerations, the`oldCollection` value is computed only if the 15851 * `listener` function declares two or more arguments. 15852 * - The `scope` argument refers to the current scope. 15853 * 15854 * @returns {function()} Returns a de-registration function for this listener. When the 15855 * de-registration function is executed, the internal watch operation is terminated. 15856 */ 15857 $watchCollection: function(obj, listener) { 15858 $watchCollectionInterceptor.$stateful = true; 15859 15860 var self = this; 15861 // the current value, updated on each dirty-check run 15862 var newValue; 15863 // a shallow copy of the newValue from the last dirty-check run, 15864 // updated to match newValue during dirty-check run 15865 var oldValue; 15866 // a shallow copy of the newValue from when the last change happened 15867 var veryOldValue; 15868 // only track veryOldValue if the listener is asking for it 15869 var trackVeryOldValue = (listener.length > 1); 15870 var changeDetected = 0; 15871 var changeDetector = $parse(obj, $watchCollectionInterceptor); 15872 var internalArray = []; 15873 var internalObject = {}; 15874 var initRun = true; 15875 var oldLength = 0; 15876 15877 function $watchCollectionInterceptor(_value) { 15878 newValue = _value; 15879 var newLength, key, bothNaN, newItem, oldItem; 15880 15881 // If the new value is undefined, then return undefined as the watch may be a one-time watch 15882 if (isUndefined(newValue)) return; 15883 15884 if (!isObject(newValue)) { // if primitive 15885 if (oldValue !== newValue) { 15886 oldValue = newValue; 15887 changeDetected++; 15888 } 15889 } else if (isArrayLike(newValue)) { 15890 if (oldValue !== internalArray) { 15891 // we are transitioning from something which was not an array into array. 15892 oldValue = internalArray; 15893 oldLength = oldValue.length = 0; 15894 changeDetected++; 15895 } 15896 15897 newLength = newValue.length; 15898 15899 if (oldLength !== newLength) { 15900 // if lengths do not match we need to trigger change notification 15901 changeDetected++; 15902 oldValue.length = oldLength = newLength; 15903 } 15904 // copy the items to oldValue and look for changes. 15905 for (var i = 0; i < newLength; i++) { 15906 oldItem = oldValue[i]; 15907 newItem = newValue[i]; 15908 15909 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15910 if (!bothNaN && (oldItem !== newItem)) { 15911 changeDetected++; 15912 oldValue[i] = newItem; 15913 } 15914 } 15915 } else { 15916 if (oldValue !== internalObject) { 15917 // we are transitioning from something which was not an object into object. 15918 oldValue = internalObject = {}; 15919 oldLength = 0; 15920 changeDetected++; 15921 } 15922 // copy the items to oldValue and look for changes. 15923 newLength = 0; 15924 for (key in newValue) { 15925 if (hasOwnProperty.call(newValue, key)) { 15926 newLength++; 15927 newItem = newValue[key]; 15928 oldItem = oldValue[key]; 15929 15930 if (key in oldValue) { 15931 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15932 if (!bothNaN && (oldItem !== newItem)) { 15933 changeDetected++; 15934 oldValue[key] = newItem; 15935 } 15936 } else { 15937 oldLength++; 15938 oldValue[key] = newItem; 15939 changeDetected++; 15940 } 15941 } 15942 } 15943 if (oldLength > newLength) { 15944 // we used to have more keys, need to find them and destroy them. 15945 changeDetected++; 15946 for (key in oldValue) { 15947 if (!hasOwnProperty.call(newValue, key)) { 15948 oldLength--; 15949 delete oldValue[key]; 15950 } 15951 } 15952 } 15953 } 15954 return changeDetected; 15955 } 15956 15957 function $watchCollectionAction() { 15958 if (initRun) { 15959 initRun = false; 15960 listener(newValue, newValue, self); 15961 } else { 15962 listener(newValue, veryOldValue, self); 15963 } 15964 15965 // make a copy for the next time a collection is changed 15966 if (trackVeryOldValue) { 15967 if (!isObject(newValue)) { 15968 //primitive 15969 veryOldValue = newValue; 15970 } else if (isArrayLike(newValue)) { 15971 veryOldValue = new Array(newValue.length); 15972 for (var i = 0; i < newValue.length; i++) { 15973 veryOldValue[i] = newValue[i]; 15974 } 15975 } else { // if object 15976 veryOldValue = {}; 15977 for (var key in newValue) { 15978 if (hasOwnProperty.call(newValue, key)) { 15979 veryOldValue[key] = newValue[key]; 15980 } 15981 } 15982 } 15983 } 15984 } 15985 15986 return this.$watch(changeDetector, $watchCollectionAction); 15987 }, 15988 15989 /** 15990 * @ngdoc method 15991 * @name $rootScope.Scope#$digest 15992 * @kind function 15993 * 15994 * @description 15995 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 15996 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 15997 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 15998 * until no more listeners are firing. This means that it is possible to get into an infinite 15999 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 16000 * iterations exceeds 10. 16001 * 16002 * Usually, you don't call `$digest()` directly in 16003 * {@link ng.directive:ngController controllers} or in 16004 * {@link ng.$compileProvider#directive directives}. 16005 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 16006 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 16007 * 16008 * If you want to be notified whenever `$digest()` is called, 16009 * you can register a `watchExpression` function with 16010 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 16011 *
16012 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 16013 * 16014 * # Example 16015 * ```js 16016 var scope = ...; 16017 scope.name = 'misko'; 16018 scope.counter = 0; 16019 16020 expect(scope.counter).toEqual(0); 16021 scope.$watch('name', function(newValue, oldValue) { 16022 scope.counter = scope.counter + 1; 16023 }); 16024 expect(scope.counter).toEqual(0); 16025 16026 scope.$digest(); 16027 // the listener is always called during the first $digest loop after it was registered 16028 expect(scope.counter).toEqual(1); 16029 16030 scope.$digest(); 16031 // but now it will not be called unless the value changes 16032 expect(scope.counter).toEqual(1); 16033 16034 scope.name = 'adam'; 16035 scope.$digest(); 16036 expect(scope.counter).toEqual(2); 16037 * ``` 16038 * 16039 */ 16040 $digest: function() { 16041 var watch, value, last, 16042 watchers, 16043 length, 16044 dirty, ttl = TTL, 16045 next, current, target = this, 16046 watchLog = [], 16047 logIdx, logMsg, asyncTask; 16048 16049 beginPhase('$digest'); 16050 // Check for changes to browser url that happened in sync before the call to $digest 16051 $browser.$$checkUrlChange(); 16052 16053 if (this === $rootScope && applyAsyncId !== null) { 16054 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 16055 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 16056 $browser.defer.cancel(applyAsyncId); 16057 flushApplyAsync(); 16058 } 16059 16060 lastDirtyWatch = null; 16061 16062 do { // "while dirty" loop 16063 dirty = false; 16064 current = target; 16065 16066 while (asyncQueue.length) { 16067 try { 16068 asyncTask = asyncQueue.shift(); 16069 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 16070 } catch (e) { 16071 $exceptionHandler(e); 16072 } 16073 lastDirtyWatch = null; 16074 } 16075 16076 traverseScopesLoop: 16077 do { // "traverse the scopes" loop 16078 if ((watchers = current.$$watchers)) { 16079 // process our watches 16080 length = watchers.length; 16081 while (length--) { 16082 try { 16083 watch = watchers[length]; 16084 // Most common watches are on primitives, in which case we can short 16085 // circuit it with === operator, only when === fails do we use .equals 16086 if (watch) { 16087 if ((value = watch.get(current)) !== (last = watch.last) && 16088 !(watch.eq 16089 ? equals(value, last) 16090 : (typeof value === 'number' && typeof last === 'number' 16091 && isNaN(value) && isNaN(last)))) { 16092 dirty = true; 16093 lastDirtyWatch = watch; 16094 watch.last = watch.eq ? copy(value, null) : value; 16095 watch.fn(value, ((last === initWatchVal) ? value : last), current); 16096 if (ttl < 5) { 16097 logIdx = 4 - ttl; 16098 if (!watchLog[logIdx]) watchLog[logIdx] = []; 16099 watchLog[logIdx].push({ 16100 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 16101 newVal: value, 16102 oldVal: last 16103 }); 16104 } 16105 } else if (watch === lastDirtyWatch) { 16106 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 16107 // have already been tested. 16108 dirty = false; 16109 break traverseScopesLoop; 16110 } 16111 } 16112 } catch (e) { 16113 $exceptionHandler(e); 16114 } 16115 } 16116 } 16117 16118 // Insanity Warning: scope depth-first traversal 16119 // yes, this code is a bit crazy, but it works and we have tests to prove it! 16120 // this piece should be kept in sync with the traversal in $broadcast 16121 if (!(next = ((current.$$watchersCount && current.$$childHead) || 16122 (current !== target && current.$$nextSibling)))) { 16123 while (current !== target && !(next = current.$$nextSibling)) { 16124 current = current.$parent; 16125 } 16126 } 16127 } while ((current = next)); 16128 16129 // `break traverseScopesLoop;` takes us to here 16130 16131 if ((dirty || asyncQueue.length) && !(ttl--)) { 16132 clearPhase(); 16133 throw $rootScopeMinErr('infdig', 16134 '{0} $digest() iterations reached. Aborting!\n' + 16135 'Watchers fired in the last 5 iterations: {1}', 16136 TTL, watchLog); 16137 } 16138 16139 }
vendor: 4,632 bytes, lines 16139-16258
16139 while (dirty || asyncQueue.length); 16140 16141 clearPhase(); 16142 16143 while (postDigestQueue.length) { 16144 try { 16145 postDigestQueue.shift()(); 16146 } catch (e) { 16147 $exceptionHandler(e); 16148 } 16149 } 16150 }, 16151 16152 16153 /** 16154 * @ngdoc event 16155 * @name $rootScope.Scope#$destroy 16156 * @eventType broadcast on scope being destroyed 16157 * 16158 * @description 16159 * Broadcasted when a scope and its children are being destroyed. 16160 * 16161 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 16162 * clean up DOM bindings before an element is removed from the DOM. 16163 */ 16164 16165 /** 16166 * @ngdoc method 16167 * @name $rootScope.Scope#$destroy 16168 * @kind function 16169 * 16170 * @description 16171 * Removes the current scope (and all of its children) from the parent scope. Removal implies 16172 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 16173 * propagate to the current scope and its children. Removal also implies that the current 16174 * scope is eligible for garbage collection. 16175 * 16176 * The `$destroy()` is usually used by directives such as 16177 * {@link ng.directive:ngRepeat ngRepeat} for managing the 16178 * unrolling of the loop. 16179 * 16180 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 16181 * Application code can register a `$destroy` event handler that will give it a chance to 16182 * perform any necessary cleanup. 16183 * 16184 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 16185 * clean up DOM bindings before an element is removed from the DOM. 16186 */ 16187 $destroy: function() { 16188 // We can't destroy a scope that has been already destroyed. 16189 if (this.$$destroyed) return; 16190 var parent = this.$parent; 16191 16192 this.$broadcast('$destroy'); 16193 this.$$destroyed = true; 16194 16195 if (this === $rootScope) { 16196 //Remove handlers attached to window when $rootScope is removed 16197 $browser.$$applicationDestroyed(); 16198 } 16199 16200 incrementWatchersCount(this, -this.$$watchersCount); 16201 for (var eventName in this.$$listenerCount) { 16202 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 16203 } 16204 16205 // sever all the references to parent scopes (after this cleanup, the current scope should 16206 // not be retained by any of our references and should be eligible for garbage collection) 16207 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 16208 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 16209 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 16210 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 16211 16212 // Disable listeners, watchers and apply/digest methods 16213 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 16214 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 16215 this.$$listeners = {}; 16216 16217 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too 16218 this.$$nextSibling = null; 16219 cleanUpScope(this); 16220 }, 16221 16222 /** 16223 * @ngdoc method 16224 * @name $rootScope.Scope#$eval 16225 * @kind function 16226 * 16227 * @description 16228 * Executes the `expression` on the current scope and returns the result. Any exceptions in 16229 * the expression are propagated (uncaught). This is useful when evaluating Angular 16230 * expressions. 16231 * 16232 * # Example 16233 * ```js 16234 var scope = ng.$rootScope.Scope(); 16235 scope.a = 1; 16236 scope.b = 2; 16237 16238 expect(scope.$eval('a+b')).toEqual(3); 16239 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 16240 * ``` 16241 * 16242 * @param {(string|function())=} expression An angular expression to be executed. 16243 * 16244 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16245 * - `function(scope)`: execute the function with the current `scope` parameter. 16246 * 16247 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 16248 * @returns {*} The result of evaluating the expression. 16249 */ 16250 $eval: function(expr, locals) { 16251 return $parse(expr)(this, locals); 16252 }, 16253 16254 /** 16255 * @ngdoc method 16256 * @name $rootScope.Scope#$evalAsync 16257 * @kind function 16258 *
16259 * @description 16260 * Executes the expression on the current scope at a later point in time. 16261 * 16262 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 16263 * that: 16264 * 16265 * - it will execute after the function that scheduled the evaluation (preferably before DOM 16266 * rendering). 16267 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 16268 * `expression` execution. 16269 * 16270 * Any exceptions from the execution of the expression are forwarded to the 16271 * {@link ng.$exceptionHandler $exceptionHandler} service. 16272 * 16273 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 16274 * will be scheduled. However, it is encouraged to always call code that changes the model 16275 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 16276 * 16277 * @param {(string|function())=} expression An angular expression to be executed. 16278 * 16279 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16280 * - `function(scope)`: execute the function with the current `scope` parameter. 16281 * 16282 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 16283 */ 16284 $evalAsync: function(expr, locals) { 16285 // if we are outside of an $digest loop and this is the first time we are scheduling async 16286 // task also schedule async auto-flush 16287 if (!$rootScope.$$phase && !asyncQueue.length) { 16288 $browser.defer(function() { 16289 if (asyncQueue.length) { 16290 $rootScope.$digest(); 16291 } 16292 }); 16293 } 16294 16295 asyncQueue.push({scope: this, expression: expr, locals: locals}); 16296 }, 16297 16298 $$postDigest: function(fn) { 16299 postDigestQueue.push(fn); 16300 }, 16301 16302 /** 16303 * @ngdoc method 16304 * @name $rootScope.Scope#$apply 16305 * @kind function 16306 * 16307 * @description 16308 * `$apply()` is used to execute an expression in angular from outside of the angular 16309 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 16310 * Because we are calling into the angular framework we need to perform proper scope life 16311 * cycle of {@link ng.$exceptionHandler exception handling}, 16312 * {@link ng.$rootScope.Scope#$digest executing watches}. 16313 * 16314 * ## Life cycle 16315 * 16316 * # Pseudo-Code of `$apply()` 16317 * ```js 16318 function $apply(expr) { 16319 try { 16320 return $eval(expr); 16321 } catch (e) { 16322 $exceptionHandler(e); 16323 } finally { 16324 $root.$digest(); 16325 } 16326 } 16327 * ``` 16328 * 16329 * 16330 * Scope's `$apply()` method transitions through the following stages: 16331 * 16332 * 1. The {@link guide/expression expression} is executed using the 16333 * {@link ng.$rootScope.Scope#$eval $eval()} method. 16334 * 2. Any exceptions from the execution of the expression are forwarded to the 16335 * {@link ng.$exceptionHandler $exceptionHandler} service. 16336 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 16337 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 16338 * 16339 * 16340 * @param {(string|function())=} exp An angular expression to be executed. 16341 * 16342 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16343 * - `function(scope)`: execute the function with current `scope` parameter. 16344 * 16345 * @returns {*} The result of evaluating the expression. 16346 */ 16347 $apply: function(expr) { 16348 try { 16349 beginPhase('$apply'); 16350 try { 16351 return this.$eval(expr); 16352 } finally { 16353 clearPhase(); 16354 } 16355 } catch (e) { 16356 $exceptionHandler(e); 16357 } finally { 16358 try { 16359 $rootScope.$digest(); 16360 } catch (e) { 16361 $exceptionHandler(e); 16362 throw e; 16363 } 16364 } 16365 }, 16366 16367 /** 16368 * @ngdoc method 16369 * @name $rootScope.Scope#$applyAsync 16370 * @kind function 16371 *
16372 * @description 16373 * Schedule the invocation of $apply to occur at a later time. The actual time difference 16374 * varies across browsers, but is typically around ~10 milliseconds. 16375 * 16376 * This can be used to queue up multiple expressions which need to be evaluated in the same 16377 * digest. 16378 * 16379 * @param {(string|function())=} exp An angular expression to be executed. 16380 * 16381 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16382 * - `function(scope)`: execute the function with current `scope` parameter. 16383 */ 16384 $applyAsync: function(expr) { 16385 var scope = this; 16386 expr && applyAsyncQueue.push($applyAsyncExpression); 16387 scheduleApplyAsync(); 16388 16389 function $applyAsyncExpression() { 16390 scope.$eval(expr); 16391 } 16392 }, 16393 16394 /** 16395 * @ngdoc method 16396 * @name $rootScope.Scope#$on 16397 * @kind function 16398 * 16399 * @description 16400 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 16401 * discussion of event life cycle. 16402 * 16403 * The event listener function format is: `function(event, args...)`. The `event` object 16404 * passed into the listener has the following attributes: 16405 * 16406 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 16407 * `$broadcast`-ed. 16408 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 16409 * event propagates through the scope hierarchy, this property is set to null. 16410 * - `name` - `{string}`: name of the event. 16411 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 16412 * further event propagation (available only for events that were `$emit`-ed). 16413 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 16414 * to true. 16415 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 16416 * 16417 * @param {string} name Event name to listen on. 16418 * @param {function(event, ...args)} listener Function to call when the event is emitted. 16419 * @returns {function()} Returns a deregistration function for this listener. 16420 */ 16421 $on: function(name, listener) { 16422 var namedListeners = this.$$listeners[name]; 16423 if (!namedListeners) { 16424 this.$$listeners[name] = namedListeners = []; 16425 } 16426 namedListeners.push(listener); 16427 16428 var current = this; 16429 do { 16430 if (!current.$$listenerCount[name]) { 16431 current.$$listenerCount[name] = 0; 16432 } 16433 current.$$listenerCount[name]++; 16434 } while ((current = current.$parent)); 16435 16436 var self = this; 16437 return function() { 16438 var indexOfListener = namedListeners.indexOf(listener); 16439 if (indexOfListener !== -1) { 16440 namedListeners[indexOfListener] = null; 16441 decrementListenerCount(self, 1, name); 16442 } 16443 }; 16444 }, 16445 16446 16447 /** 16448 * @ngdoc method 16449 * @name $rootScope.Scope#$emit 16450 * @kind function 16451 * 16452 * @description 16453 * Dispatches an event `name` upwards through the scope hierarchy notifying the 16454 * registered {@link ng.$rootScope.Scope#$on} listeners. 16455 * 16456 * The event life cycle starts at the scope on which `$emit` was called. All 16457 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16458 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 16459 * registered listeners along the way. The event will stop propagating if one of the listeners 16460 * cancels it. 16461 * 16462 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16463 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16464 * 16465 * @param {string} name Event name to emit. 16466 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16467 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 16468 */ 16469 $emit: function(name, args) { 16470 var empty = [], 16471 namedListeners, 16472 scope = this, 16473 stopPropagation = false, 16474 event = { 16475 name: name, 16476 targetScope: scope, 16477 stopPropagation: function() {stopPropagation = true;}, 16478 preventDefault: function() { 16479 event.defaultPrevented = true; 16480 }, 16481 defaultPrevented: false 16482 }, 16483 listenerArgs = concat([event], arguments, 1), 16484 i, length; 16485 16486 do { 16487 namedListeners = scope.$$listeners[name] || empty; 16488 event.currentScope = scope; 16489 for (i = 0, length = namedListeners.length; i < length; i++) { 16490 16491 // if listeners were deregistered, defragment the array 16492 if (!namedListeners[i]) { 16493 namedListeners.splice(i, 1); 16494 i--; 16495 length--; 16496 continue; 16497 } 16498 try { 16499 //allow all listeners attached to the current scope to run 16500 namedListeners[i].apply(null, listenerArgs); 16501 } catch (e) { 16502 $exceptionHandler(e); 16503 } 16504 } 16505 //if any listener on the current scope stops propagation, prevent bubbling 16506 if (stopPropagation) { 16507 event.currentScope = null; 16508 return event; 16509 } 16510 //traverse upwards 16511 scope = scope.$parent; 16512 } while (scope); 16513 16514 event.currentScope = null; 16515 16516 return event; 16517 }, 16518 16519 16520 /** 16521 * @ngdoc method 16522 * @name $rootScope.Scope#$broadcast 16523 * @kind function 16524 *
16525 * @description 16526 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 16527 * registered {@link ng.$rootScope.Scope#$on} listeners. 16528 * 16529 * The event life cycle starts at the scope on which `$broadcast` was called. All 16530 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16531 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 16532 * scope and calls all registered listeners along the way. The event cannot be canceled. 16533 * 16534 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16535 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16536 * 16537 * @param {string} name Event name to broadcast. 16538 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16539 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 16540 */ 16541 $broadcast: function(name, args) { 16542 var target = this, 16543 current = target, 16544 next = target, 16545 event = { 16546 name: name, 16547 targetScope: target, 16548 preventDefault: function() { 16549 event.defaultPrevented = true; 16550 }, 16551 defaultPrevented: false 16552 }; 16553 16554 if (!target.$$listenerCount[name]) return event; 16555 16556 var listenerArgs = concat([event], arguments, 1), 16557 listeners, i, length; 16558 16559 //down while you can, then up and next sibling or up and next sibling until back at root 16560 while ((current = next)) { 16561 event.currentScope = current; 16562 listeners = current.$$listeners[name] || []; 16563 for (i = 0, length = listeners.length; i < length; i++) { 16564 // if listeners were deregistered, defragment the array 16565 if (!listeners[i]) { 16566 listeners.splice(i, 1); 16567 i--; 16568 length--; 16569 continue; 16570 } 16571 16572 try { 16573 listeners[i].apply(null, listenerArgs); 16574 } catch (e) { 16575 $exceptionHandler(e); 16576 } 16577 } 16578 16579 // Insanity Warning: scope depth-first traversal 16580 // yes, this code is a bit crazy, but it works and we have tests to prove it! 16581 // this piece should be kept in sync with the traversal in $digest 16582 // (though it differs due to having the extra check for $$listenerCount) 16583 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 16584 (current !== target && current.$$nextSibling)))) { 16585 while (current !== target && !(next = current.$$nextSibling)) { 16586 current = current.$parent; 16587 } 16588 } 16589 } 16590 16591 event.currentScope = null; 16592 return event; 16593 } 16594 }; 16595 16596 var $rootScope = new Scope(); 16597 16598 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 16599 var asyncQueue = $rootScope.$$asyncQueue = []; 16600 var postDigestQueue = $rootScope.$$postDigestQueue = []; 16601 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 16602 16603 return $rootScope; 16604 16605 16606 function beginPhase(phase) { 16607 if ($rootScope.$$phase) { 16608 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 16609 } 16610 16611 $rootScope.$$phase = phase; 16612 } 16613 16614 function clearPhase() { 16615 $rootScope.$$phase = null; 16616 } 16617 16618 function incrementWatchersCount(current, count) { 16619 do { 16620 current.$$watchersCount += count; 16621 } while ((current = current.$parent)); 16622 } 16623 16624 function decrementListenerCount(current, count, name) { 16625 do { 16626 current.$$listenerCount[name] -= count; 16627 16628 if (current.$$listenerCount[name] === 0) { 16629 delete current.$$listenerCount[name]; 16630 } 16631 } while ((current = current.$parent)); 16632 } 16633 16634 /** 16635 * function used as an initial value for watchers. 16636 * because it's unique we can easily tell it apart from other values 16637 */ 16638 function initWatchVal() {} 16639 16640 function flushApplyAsync() {
vendor: 5,358 bytes, lines 16641-16783
16641 while (applyAsyncQueue.length) { 16642 try { 16643 applyAsyncQueue.shift()(); 16644 } catch (e) { 16645 $exceptionHandler(e); 16646 } 16647 } 16648 applyAsyncId = null; 16649 } 16650 16651 function scheduleApplyAsync() { 16652 if (applyAsyncId === null) { 16653 applyAsyncId = $browser.defer(function() { 16654 $rootScope.$apply(flushApplyAsync); 16655 }); 16656 } 16657 } 16658 }]; 16659} 16660 16661/** 16662 * @ngdoc service 16663 * @name $rootElement 16664 * 16665 * @description 16666 * The root element of Angular application. This is either the element where {@link 16667 * ng.directive:ngApp ngApp} was declared or the element passed into 16668 * {@link angular.bootstrap}. The element represents the root element of application. It is also the 16669 * location where the application's {@link auto.$injector $injector} service gets 16670 * published, and can be retrieved using `$rootElement.injector()`. 16671 */ 16672 16673 16674// the implementation is in angular.bootstrap 16675 16676/** 16677 * @description 16678 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 16679 */ 16680function $$SanitizeUriProvider() { 16681 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 16682 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 16683 16684 /** 16685 * @description 16686 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16687 * urls during a[href] sanitization. 16688 * 16689 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16690 * 16691 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 16692 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 16693 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16694 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16695 * 16696 * @param {RegExp=} regexp New regexp to whitelist urls with. 16697 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16698 * chaining otherwise. 16699 */ 16700 this.aHrefSanitizationWhitelist = function(regexp) { 16701 if (isDefined(regexp)) { 16702 aHrefSanitizationWhitelist = regexp; 16703 return this; 16704 } 16705 return aHrefSanitizationWhitelist; 16706 }; 16707 16708 16709 /** 16710 * @description 16711 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16712 * urls during img[src] sanitization. 16713 * 16714 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16715 * 16716 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 16717 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 16718 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16719 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16720 * 16721 * @param {RegExp=} regexp New regexp to whitelist urls with. 16722 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16723 * chaining otherwise. 16724 */ 16725 this.imgSrcSanitizationWhitelist = function(regexp) { 16726 if (isDefined(regexp)) { 16727 imgSrcSanitizationWhitelist = regexp; 16728 return this; 16729 } 16730 return imgSrcSanitizationWhitelist; 16731 }; 16732 16733 this.$get = function() { 16734 return function sanitizeUri(uri, isImage) { 16735 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 16736 var normalizedVal; 16737 normalizedVal = urlResolve(uri).href; 16738 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 16739 return 'unsafe:' + normalizedVal; 16740 } 16741 return uri; 16742 }; 16743 }; 16744} 16745 16746/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16747 * Any commits to this file should be reviewed with security in mind. * 16748 * Changes to this file can potentially create security vulnerabilities. * 16749 * An approval from 2 Core members with history of modifying * 16750 * this file is required. * 16751 * * 16752 * Does the change somehow allow for arbitrary javascript to be executed? * 16753 * Or allows for someone to change the prototype of built-in objects? * 16754 * Or gives undesired access to variables likes document or window? * 16755 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 16756 16757var $sceMinErr = minErr('$sce'); 16758 16759var SCE_CONTEXTS = { 16760 HTML: 'html', 16761 CSS: 'css', 16762 URL: 'url', 16763 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 16764 // url. (e.g. ng-include, script src, templateUrl) 16765 RESOURCE_URL: 'resourceUrl', 16766 JS: 'js' 16767}; 16768 16769// Helper functions follow. 16770 16771function adjustMatcher(matcher) { 16772 if (matcher === 'self') { 16773 return matcher; 16774 } else if (isString(matcher)) { 16775 // Strings match exactly except for 2 wildcards - '*' and '**'. 16776 // '*' matches any character except those from the set ':/.?&'. 16777 // '**' matches any character (like .* in a RegExp). 16778 // More than 2 *'s raises an error as it's ill defined. 16779 if (matcher.indexOf('***') > -1) { 16780 throw $sceMinErr('iwcard', 16781 'Illegal sequence *** in string matcher. String: {0}', matcher); 16782 } 16783 matcher = escapeForRegexp(matcher).
16784 replace('\\*\\*', '.*'). 16785 replace('\\*', '[^:/.?&;]*'); 16786 return new RegExp('^' + matcher + '$'); 16787 } else if (isRegExp(matcher)) { 16788 // The only other type of matcher allowed is a Regexp. 16789 // Match entire URL / disallow partial matches. 16790 // Flags are reset (i.e. no global, ignoreCase or multiline) 16791 return new RegExp('^' + matcher.source + '$'); 16792 } else { 16793 throw $sceMinErr('imatcher', 16794 'Matchers may only be "self", string patterns or RegExp objects'); 16795 } 16796} 16797 16798 16799function adjustMatchers(matchers) { 16800 var adjustedMatchers = []; 16801 if (isDefined(matchers)) { 16802 forEach(matchers, function(matcher) { 16803 adjustedMatchers.push(adjustMatcher(matcher)); 16804 }); 16805 } 16806 return adjustedMatchers; 16807} 16808 16809 16810/** 16811 * @ngdoc service 16812 * @name $sceDelegate 16813 * @kind function 16814 * 16815 * @description 16816 * 16817 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 16818 * Contextual Escaping (SCE)} services to AngularJS. 16819 * 16820 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 16821 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 16822 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 16823 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 16824 * work because `$sce` delegates to `$sceDelegate` for these operations. 16825 * 16826 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 16827 * 16828 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 16829 * can override it completely to change the behavior of `$sce`, the common case would 16830 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 16831 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 16832 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 16833 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 16834 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16835 */ 16836 16837/** 16838 * @ngdoc provider 16839 * @name $sceDelegateProvider 16840 * @description 16841 * 16842 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 16843 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 16844 * that the URLs used for sourcing Angular templates are safe. Refer {@link 16845 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 16846 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16847 * 16848 * For the general details about this service in Angular, read the main page for {@link ng.$sce 16849 * Strict Contextual Escaping (SCE)}. 16850 * 16851 * **Example**: Consider the following case. <a name="example"></a> 16852 * 16853 * - your app is hosted at url `http://myapp.example.com/` 16854 * - but some of your templates are hosted on other domains you control such as 16855 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 16856 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 16857 * 16858 * Here is what a secure configuration for this scenario might look like: 16859 * 16860 * ``` 16861 * angular.module('myApp', []).config(function($sceDelegateProvider) { 16862 * $sceDelegateProvider.resourceUrlWhitelist([ 16863 * // Allow same origin resource loads. 16864 * 'self', 16865 * // Allow loading from our assets domain. Notice the difference between * and **. 16866 * 'http://srv*.assets.example.com/**' 16867 * ]); 16868 * 16869 * // The blacklist overrides the whitelist so the open redirect here is blocked. 16870 * $sceDelegateProvider.resourceUrlBlacklist([ 16871 * 'http://myapp.example.com/clickThru**' 16872 * ]); 16873 * }); 16874 * ``` 16875 */ 16876 16877function $SceDelegateProvider() { 16878 this.SCE_CONTEXTS = SCE_CONTEXTS; 16879 16880 // Resource URLs can also be trusted by policy. 16881 var resourceUrlWhitelist = ['self'], 16882 resourceUrlBlacklist = []; 16883 16884 /** 16885 * @ngdoc method 16886 * @name $sceDelegateProvider#resourceUrlWhitelist 16887 * @kind function 16888 * 16889 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 16890 * provided. This must be an array or null. A snapshot of this array is used so further 16891 * changes to the array are ignored. 16892 * 16893 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16894 * allowed in this array.
16895 * 16896 * Note: **an empty whitelist array will block all URLs**! 16897 * 16898 * @return {Array} the currently set whitelist array. 16899 * 16900 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 16901 * same origin resource requests. 16902 * 16903 * @description 16904 * Sets/Gets the whitelist of trusted resource URLs. 16905 */ 16906 this.resourceUrlWhitelist = function(value) { 16907 if (arguments.length) { 16908 resourceUrlWhitelist = adjustMatchers(value); 16909 } 16910 return resourceUrlWhitelist; 16911 }; 16912 16913 /** 16914 * @ngdoc method 16915 * @name $sceDelegateProvider#resourceUrlBlacklist 16916 * @kind function 16917 * 16918 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 16919 * provided. This must be an array or null. A snapshot of this array is used so further 16920 * changes to the array are ignored. 16921 * 16922 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16923 * allowed in this array. 16924 * 16925 * The typical usage for the blacklist is to **block 16926 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 16927 * these would otherwise be trusted but actually return content from the redirected domain. 16928 * 16929 * Finally, **the blacklist overrides the whitelist** and has the final say. 16930 * 16931 * @return {Array} the currently set blacklist array. 16932 * 16933 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 16934 * is no blacklist.) 16935 * 16936 * @description 16937 * Sets/Gets the blacklist of trusted resource URLs. 16938 */ 16939 16940 this.resourceUrlBlacklist = function(value) { 16941 if (arguments.length) { 16942 resourceUrlBlacklist = adjustMatchers(value); 16943 } 16944 return resourceUrlBlacklist; 16945 }; 16946 16947 this.$get = ['$injector', function($injector) { 16948 16949 var htmlSanitizer = function htmlSanitizer(html) { 16950 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 16951 }; 16952 16953 if ($injector.has('$sanitize')) { 16954 htmlSanitizer = $injector.get('$sanitize'); 16955 } 16956 16957 16958 function matchUrl(matcher, parsedUrl) { 16959 if (matcher === 'self') { 16960 return urlIsSameOrigin(parsedUrl); 16961 } else { 16962 // definitely a regex. See adjustMatchers() 16963 return !!matcher.exec(parsedUrl.href); 16964 } 16965 } 16966 16967 function isResourceUrlAllowedByPolicy(url) { 16968 var parsedUrl = urlResolve(url.toString()); 16969 var i, n, allowed = false; 16970 // Ensure that at least one item from the whitelist allows this url. 16971 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 16972 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 16973 allowed = true; 16974 break; 16975 } 16976 } 16977 if (allowed) { 16978 // Ensure that no item from the blacklist blocked this url. 16979 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 16980 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 16981 allowed = false; 16982 break; 16983 } 16984 } 16985 } 16986 return allowed; 16987 } 16988 16989 function generateHolderType(Base) { 16990 var holderType = function TrustedValueHolderType(trustedValue) { 16991 this.$$unwrapTrustedValue = function() { 16992 return trustedValue; 16993 }; 16994 }; 16995 if (Base) { 16996 holderType.prototype = new Base(); 16997 } 16998 holderType.prototype.valueOf = function sceValueOf() { 16999 return this.$$unwrapTrustedValue(); 17000 }; 17001 holderType.prototype.toString = function sceToString() { 17002 return this.$$unwrapTrustedValue().toString(); 17003 }; 17004 return holderType; 17005 } 17006 17007 var trustedValueHolderBase = generateHolderType(), 17008 byType = {}; 17009 17010 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 17011 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 17012 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 17013 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 17014 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 17015 17016 /** 17017 * @ngdoc method 17018 * @name $sceDelegate#trustAs 17019 *
17020 * @description 17021 * Returns an object that is trusted by angular for use in specified strict 17022 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 17023 * attribute interpolation, any dom event binding attribute interpolation 17024 * such as for onclick, etc.) that uses the provided value. 17025 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 17026 * 17027 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 17028 * resourceUrl, html, js and css. 17029 * @param {*} value The value that that should be considered trusted/safe. 17030 * @returns {*} A value that can be used to stand in for the provided `value` in places 17031 * where Angular expects a $sce.trustAs() return value. 17032 */ 17033 function trustAs(type, trustedValue) { 17034 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 17035 if (!Constructor) { 17036 throw $sceMinErr('icontext', 17037 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 17038 type, trustedValue); 17039 } 17040 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 17041 return trustedValue; 17042 } 17043 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 17044 // mutable objects, we ensure here that the value passed in is actually a string. 17045 if (typeof trustedValue !== 'string') { 17046 throw $sceMinErr('itype', 17047 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 17048 type); 17049 } 17050 return new Constructor(trustedValue); 17051 } 17052 17053 /** 17054 * @ngdoc method 17055 * @name $sceDelegate#valueOf 17056 * 17057 * @description 17058 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 17059 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 17060 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 17061 * 17062 * If the passed parameter is not a value that had been returned by {@link 17063 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 17064 * 17065 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 17066 * call or anything else. 17067 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 17068 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 17069 * `value` unchanged. 17070 */ 17071 function valueOf(maybeTrusted) { 17072 if (maybeTrusted instanceof trustedValueHolderBase) { 17073 return maybeTrusted.$$unwrapTrustedValue(); 17074 } else { 17075 return maybeTrusted; 17076 } 17077 } 17078 17079 /** 17080 * @ngdoc method 17081 * @name $sceDelegate#getTrusted 17082 * 17083 * @description 17084 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 17085 * returns the originally supplied value if the queried context type is a supertype of the 17086 * created type. If this condition isn't satisfied, throws an exception. 17087 * 17088 * @param {string} type The kind of context in which this value is to be used. 17089 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 17090 * `$sceDelegate.trustAs`} call. 17091 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 17092 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 17093 */ 17094 function getTrusted(type, maybeTrusted) { 17095 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 17096 return maybeTrusted; 17097 } 17098 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 17099 if (constructor && maybeTrusted instanceof constructor) { 17100 return maybeTrusted.$$unwrapTrustedValue(); 17101 } 17102 // If we get here, then we may only take one of two actions. 17103 // 1. sanitize the value for the requested type, or 17104 // 2. throw an exception. 17105 if (type === SCE_CONTEXTS.RESOURCE_URL) { 17106 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 17107 return maybeTrusted; 17108 } else { 17109 throw $sceMinErr('insecurl', 17110 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 17111 maybeTrusted.toString()); 17112 } 17113 } else if (type === SCE_CONTEXTS.HTML) { 17114 return htmlSanitizer(maybeTrusted); 17115 } 17116 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 17117 } 17118 17119 return { trustAs: trustAs, 17120 getTrusted: getTrusted,
17121 valueOf: valueOf }; 17122 }]; 17123} 17124 17125 17126/** 17127 * @ngdoc provider 17128 * @name $sceProvider 17129 * @description 17130 * 17131 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 17132 * - enable/disable Strict Contextual Escaping (SCE) in a module 17133 * - override the default implementation with a custom delegate 17134 * 17135 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 17136 */ 17137 17138/* jshint maxlen: false*/ 17139 17140/** 17141 * @ngdoc service 17142 * @name $sce 17143 * @kind function 17144 * 17145 * @description 17146 * 17147 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 17148 * 17149 * # Strict Contextual Escaping 17150 * 17151 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 17152 * contexts to result in a value that is marked as safe to use for that context. One example of 17153 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 17154 * to these contexts as privileged or SCE contexts. 17155 * 17156 * As of version 1.2, Angular ships with SCE enabled by default. 17157 * 17158 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 17159 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 17160 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17161 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 17162 * to the top of your HTML document. 17163 * 17164 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 17165 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 17166 * 17167 * Here's an example of a binding in a privileged context: 17168 * 17169 * ``` 17170 * <input ng-model="userHtml" aria-label="User input"> 17171 * <div ng-bind-html="userHtml"></div> 17172 * ``` 17173 * 17174 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 17175 * disabled, this application allows the user to render arbitrary HTML into the DIV. 17176 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 17177 * bindings. (HTML is just one example of a context where rendering user controlled input creates 17178 * security vulnerabilities.) 17179 * 17180 * For the case of HTML, you might use a library, either on the client side, or on the server side, 17181 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 17182 * 17183 * How would you ensure that every place that used these types of bindings was bound to a value that 17184 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 17185 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 17186 * properties/fields and forgot to update the binding to the sanitized value? 17187 * 17188 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 17189 * determine that something explicitly says it's safe to use a value for binding in that
17190 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 17191 * for those values that you can easily tell are safe - because they were received from your server, 17192 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 17193 * allowing only the files in a specific directory to do this. Ensuring that the internal API 17194 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 17195 * 17196 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 17197 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 17198 * obtain values that will be accepted by SCE / privileged contexts. 17199 * 17200 * 17201 * ## How does it work? 17202 * 17203 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 17204 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 17205 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 17206 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 17207 * 17208 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 17209 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 17210 * simplified): 17211 * 17212 * ``` 17213 * var ngBindHtmlDirective = ['$sce', function($sce) { 17214 * return function(scope, element, attr) { 17215 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 17216 * element.html(value || ''); 17217 * }); 17218 * }; 17219 * }]; 17220 * ``` 17221 * 17222 * ## Impact on loading templates 17223 * 17224 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 17225 * `templateUrl`'s specified by {@link guide/directive directives}. 17226 * 17227 * By default, Angular only loads templates from the same domain and protocol as the application 17228 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 17229 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 17230 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 17231 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 17232 * 17233 * *Please note*: 17234 * The browser's 17235 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 17236 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 17237 * policy apply in addition to this and may further restrict whether the template is successfully 17238 * loaded. This means that without the right CORS policy, loading templates from a different domain 17239 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 17240 * browsers. 17241 * 17242 * ## This feels like too much overhead 17243 * 17244 * It's important to remember that SCE only applies to interpolation expressions. 17245 * 17246 * If your expressions are constant literals, they're automatically trusted and you don't need to 17247 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 17248 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 17249 * 17250 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 17251 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 17252 * 17253 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 17254 * templates in `ng-include` from your application's domain without having to even know about SCE. 17255 * It blocks loading templates from other domains or loading templates over http from an https 17256 * served document. You can change these by setting your own custom {@link 17257 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 17258 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 17259 * 17260 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 17261 * application that's secure and can be audited to verify that with much more ease than bolting 17262 * security onto an application later. 17263 * 17264 * <a name="contexts"></a> 17265 * ## What trusted context types are supported? 17266 * 17267 * | Context | Notes | 17268 * |---------------------|----------------| 17269 * | `$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. | 17270 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 17271 * | `$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. | 17272 * | `$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. | 17273 * | `$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. | 17274 * 17275 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 17276 *
17277 * Each element in these arrays must be one of the following: 17278 * 17279 * - **'self'** 17280 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 17281 * domain** as the application document using the **same protocol**. 17282 * - **String** (except the special value `'self'`) 17283 * - The string is matched against the full *normalized / absolute URL* of the resource 17284 * being tested (substring matches are not good enough.) 17285 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 17286 * match themselves. 17287 * - `*`: matches zero or more occurrences of any character other than one of the following 6 17288 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use 17289 * in a whitelist. 17290 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 17291 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 17292 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 17293 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 17294 * http://foo.example.com/templates/**). 17295 * - **RegExp** (*see caveat below*) 17296 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 17297 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 17298 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 17299 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 17300 * small number of cases. A `.` character in the regex used when matching the scheme or a 17301 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 17302 * is highly recommended to use the string patterns and only fall back to regular expressions 17303 * as a last resort. 17304 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 17305 * matched against the **entire** *normalized / absolute URL* of the resource being tested 17306 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 17307 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 17308 * - If you are generating your JavaScript from some other templating engine (not 17309 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 17310 * remember to escape your regular expression (and be aware that you might need more than 17311 * one level of escaping depending on your templating engine and the way you interpolated 17312 * the value.) Do make use of your platform's escaping mechanism as it might be good 17313 * enough before coding your own. E.g. Ruby has 17314 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 17315 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 17316 * Javascript lacks a similar built in function for escaping. Take a look at Google 17317 * Closure library's [goog.string.regExpEscape(s)]( 17318 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 17319 * 17320 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 17321 * 17322 * ## Show me an example using SCE. 17323 * 17324 * <example module="mySceApp" deps="angular-sanitize.js"> 17325 * <file name="index.html"> 17326 * <div ng-controller="AppController as myCtrl"> 17327 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 17328 * <b>User comments</b><br> 17329 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 17330 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 17331 * exploit. 17332 * <div class="well"> 17333 * <div ng-repeat="userComment in myCtrl.userComments"> 17334 * <b>{{userComment.name}}</b>: 17335 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 17336 * <br> 17337 * </div>
17338 * </div> 17339 * </div> 17340 * </file> 17341 * 17342 * <file name="script.js"> 17343 * angular.module('mySceApp', ['ngSanitize']) 17344 * .controller('AppController', ['$http', '$templateCache', '$sce', 17345 * function($http, $templateCache, $sce) { 17346 * var self = this; 17347 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 17348 * self.userComments = userComments; 17349 * }); 17350 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 17351 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 17352 * 'sanitization."">Hover over this text.</span>'); 17353 * }]); 17354 * </file> 17355 * 17356 * <file name="test_data.json"> 17357 * [ 17358 * { "name": "Alice", 17359 * "htmlComment": 17360 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 17361 * }, 17362 * { "name": "Bob", 17363 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 17364 * } 17365 * ] 17366 * </file> 17367 * 17368 * <file name="protractor.js" type="protractor"> 17369 * describe('SCE doc demo', function() { 17370 * it('should sanitize untrusted values', function() { 17371 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 17372 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 17373 * }); 17374 * 17375 * it('should NOT sanitize explicitly trusted values', function() { 17376 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 17377 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 17378 * 'sanitization."">Hover over this text.</span>'); 17379 * }); 17380 * }); 17381 * </file> 17382 * </example> 17383 * 17384 * 17385 * 17386 * ## Can I disable SCE completely? 17387 * 17388 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 17389 * for little coding overhead. It will be much harder to take an SCE disabled application and 17390 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 17391 * for cases where you have a lot of existing code that was written before SCE was introduced and 17392 * you're migrating them a module at a time. 17393 * 17394 * That said, here's how you can completely disable SCE: 17395 * 17396 * ``` 17397 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 17398 * // Completely disable SCE. For demonstration purposes only! 17399 * // Do not use in new projects. 17400 * $sceProvider.enabled(false); 17401 * }); 17402 * ``` 17403 * 17404 */ 17405/* jshint maxlen: 100 */ 17406 17407function $SceProvider() { 17408 var enabled = true; 17409 17410 /** 17411 * @ngdoc method 17412 * @name $sceProvider#enabled 17413 * @kind function 17414 * 17415 * @param {boolean=} value If provided, then enables/disables SCE. 17416 * @return {boolean} true if SCE is enabled, false otherwise. 17417 * 17418 * @description 17419 * Enables/disables SCE and returns the current value. 17420 */ 17421 this.enabled = function(value) { 17422 if (arguments.length) { 17423 enabled = !!value; 17424 } 17425 return enabled; 17426 }; 17427 17428 17429 /* Design notes on the default implementation for SCE. 17430 * 17431 * The API contract for the SCE delegate 17432 * ------------------------------------- 17433 * The SCE delegate object must provide the following 3 methods: 17434 * 17435 * - trustAs(contextEnum, value) 17436 * This method is used to tell the SCE service that the provided value is OK to use in the 17437 * contexts specified by contextEnum. It must return an object that will be accepted by 17438 * getTrusted() for a compatible contextEnum and return this value. 17439 * 17440 * - valueOf(value) 17441 * For values that were not produced by trustAs(), return them as is. For values that were 17442 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 17443 * trustAs is wrapping the given values into some type, this operation unwraps it when given 17444 * such a value. 17445 * 17446 * - getTrusted(contextEnum, value) 17447 * This function should return the a value that is safe to use in the context specified by 17448 * contextEnum or throw and exception otherwise. 17449 * 17450 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 17451 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 17452 * instance, an implementation could maintain a registry of all trusted objects by context. In 17453 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 17454 * return the same object passed in if it was found in the registry under a compatible context or 17455 * throw an exception otherwise. An implementation might only wrap values some of the time based 17456 * on some criteria. getTrusted() might return a value and not throw an exception for special 17457 * constants or objects even if not wrapped. All such implementations fulfill this contract. 17458 * 17459 * 17460 * A note on the inheritance model for SCE contexts 17461 * ------------------------------------------------ 17462 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 17463 * is purely an implementation details. 17464 * 17465 * The contract is simply this: 17466 * 17467 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 17468 * will also succeed. 17469 * 17470 * Inheritance happens to capture this in a natural way. In some future, we
17471 * may not use inheritance anymore. That is OK because no code outside of 17472 * sce.js and sceSpecs.js would need to be aware of this detail. 17473 */ 17474 17475 this.$get = ['$parse', '$sceDelegate', function( 17476 $parse, $sceDelegate) { 17477 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 17478 // the "expression(javascript expression)" syntax which is insecure. 17479 if (enabled && msie < 8) { 17480 throw $sceMinErr('iequirks', 17481 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 17482 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 17483 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 17484 } 17485 17486 var sce = shallowCopy(SCE_CONTEXTS); 17487 17488 /** 17489 * @ngdoc method 17490 * @name $sce#isEnabled 17491 * @kind function 17492 * 17493 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 17494 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 17495 * 17496 * @description 17497 * Returns a boolean indicating if SCE is enabled. 17498 */ 17499 sce.isEnabled = function() { 17500 return enabled; 17501 }; 17502 sce.trustAs = $sceDelegate.trustAs; 17503 sce.getTrusted = $sceDelegate.getTrusted; 17504 sce.valueOf = $sceDelegate.valueOf; 17505 17506 if (!enabled) { 17507 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 17508 sce.valueOf = identity; 17509 } 17510 17511 /** 17512 * @ngdoc method 17513 * @name $sce#parseAs 17514 * 17515 * @description 17516 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 17517 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 17518 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 17519 * *result*)} 17520 * 17521 * @param {string} type The kind of SCE context in which this result will be used. 17522 * @param {string} expression String expression to compile. 17523 * @returns {function(context, locals)} a function which represents the compiled expression: 17524 * 17525 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17526 * are evaluated against (typically a scope object). 17527 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17528 * `context`. 17529 */ 17530 sce.parseAs = function sceParseAs(type, expr) { 17531 var parsed = $parse(expr); 17532 if (parsed.literal && parsed.constant) { 17533 return parsed; 17534 } else { 17535 return $parse(expr, function(value) { 17536 return sce.getTrusted(type, value); 17537 }); 17538 } 17539 }; 17540 17541 /** 17542 * @ngdoc method 17543 * @name $sce#trustAs 17544 * 17545 * @description 17546 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 17547 * returns an object that is trusted by angular for use in specified strict contextual 17548 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 17549 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 17550 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 17551 * escaping. 17552 * 17553 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 17554 * resourceUrl, html, js and css. 17555 * @param {*} value The value that that should be considered trusted/safe. 17556 * @returns {*} A value that can be used to stand in for the provided `value` in places 17557 * where Angular expects a $sce.trustAs() return value. 17558 */ 17559 17560 /** 17561 * @ngdoc method 17562 * @name $sce#trustAsHtml 17563 * 17564 * @description 17565 * Shorthand method. `$sce.trustAsHtml(value)` â 17566 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 17567 * 17568 * @param {*} value The value to trustAs. 17569 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 17570 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 17571 * only accept expressions that are either literal constants or are the 17572 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17573 */ 17574 17575 /** 17576 * @ngdoc method 17577 * @name $sce#trustAsUrl 17578 *
17579 * @description 17580 * Shorthand method. `$sce.trustAsUrl(value)` â 17581 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 17582 * 17583 * @param {*} value The value to trustAs. 17584 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 17585 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 17586 * only accept expressions that are either literal constants or are the 17587 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17588 */ 17589 17590 /** 17591 * @ngdoc method 17592 * @name $sce#trustAsResourceUrl 17593 * 17594 * @description 17595 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 17596 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 17597 * 17598 * @param {*} value The value to trustAs. 17599 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 17600 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 17601 * only accept expressions that are either literal constants or are the return 17602 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 17603 */ 17604 17605 /** 17606 * @ngdoc method 17607 * @name $sce#trustAsJs 17608 * 17609 * @description 17610 * Shorthand method. `$sce.trustAsJs(value)` â 17611 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 17612 * 17613 * @param {*} value The value to trustAs. 17614 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 17615 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 17616 * only accept expressions that are either literal constants or are the 17617 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17618 */ 17619 17620 /** 17621 * @ngdoc method 17622 * @name $sce#getTrusted 17623 * 17624 * @description 17625 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 17626 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 17627 * originally supplied value if the queried context type is a supertype of the created type. 17628 * If this condition isn't satisfied, throws an exception. 17629 * 17630 * @param {string} type The kind of context in which this value is to be used. 17631 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 17632 * call. 17633 * @returns {*} The value the was originally provided to 17634 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 17635 * Otherwise, throws an exception. 17636 */ 17637 17638 /** 17639 * @ngdoc method 17640 * @name $sce#getTrustedHtml 17641 * 17642 * @description 17643 * Shorthand method. `$sce.getTrustedHtml(value)` â 17644 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 17645 * 17646 * @param {*} value The value to pass to `$sce.getTrusted`. 17647 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 17648 */ 17649 17650 /** 17651 * @ngdoc method 17652 * @name $sce#getTrustedCss 17653 * 17654 * @description 17655 * Shorthand method. `$sce.getTrustedCss(value)` â 17656 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 17657 * 17658 * @param {*} value The value to pass to `$sce.getTrusted`. 17659 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 17660 */ 17661 17662 /** 17663 * @ngdoc method 17664 * @name $sce#getTrustedUrl 17665 * 17666 * @description 17667 * Shorthand method. `$sce.getTrustedUrl(value)` â 17668 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 17669 * 17670 * @param {*} value The value to pass to `$sce.getTrusted`. 17671 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 17672 */ 17673 17674 /** 17675 * @ngdoc method 17676 * @name $sce#getTrustedResourceUrl 17677 * 17678 * @description 17679 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 17680 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 17681 * 17682 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 17683 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 17684 */ 17685 17686 /** 17687 * @ngdoc method 17688 * @name $sce#getTrustedJs 17689 *
17690 * @description 17691 * Shorthand method. `$sce.getTrustedJs(value)` â 17692 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 17693 * 17694 * @param {*} value The value to pass to `$sce.getTrusted`. 17695 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 17696 */ 17697 17698 /** 17699 * @ngdoc method 17700 * @name $sce#parseAsHtml 17701 * 17702 * @description 17703 * Shorthand method. `$sce.parseAsHtml(expression string)` â 17704 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 17705 * 17706 * @param {string} expression String expression to compile. 17707 * @returns {function(context, locals)} a function which represents the compiled expression: 17708 * 17709 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17710 * are evaluated against (typically a scope object). 17711 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17712 * `context`. 17713 */ 17714 17715 /** 17716 * @ngdoc method 17717 * @name $sce#parseAsCss 17718 * 17719 * @description 17720 * Shorthand method. `$sce.parseAsCss(value)` â 17721 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 17722 * 17723 * @param {string} expression String expression to compile. 17724 * @returns {function(context, locals)} a function which represents the compiled expression: 17725 * 17726 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17727 * are evaluated against (typically a scope object). 17728 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17729 * `context`. 17730 */ 17731 17732 /** 17733 * @ngdoc method 17734 * @name $sce#parseAsUrl 17735 * 17736 * @description 17737 * Shorthand method. `$sce.parseAsUrl(value)` â 17738 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 17739 * 17740 * @param {string} expression String expression to compile. 17741 * @returns {function(context, locals)} a function which represents the compiled expression: 17742 * 17743 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17744 * are evaluated against (typically a scope object). 17745 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17746 * `context`. 17747 */ 17748 17749 /** 17750 * @ngdoc method 17751 * @name $sce#parseAsResourceUrl 17752 * 17753 * @description 17754 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 17755 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 17756 * 17757 * @param {string} expression String expression to compile. 17758 * @returns {function(context, locals)} a function which represents the compiled expression: 17759 * 17760 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17761 * are evaluated against (typically a scope object). 17762 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17763 * `context`. 17764 */ 17765 17766 /** 17767 * @ngdoc method 17768 * @name $sce#parseAsJs 17769 * 17770 * @description 17771 * Shorthand method. `$sce.parseAsJs(value)` â 17772 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 17773 * 17774 * @param {string} expression String expression to compile. 17775 * @returns {function(context, locals)} a function which represents the compiled expression: 17776 * 17777 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17778 * are evaluated against (typically a scope object). 17779 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17780 * `context`. 17781 */ 17782 17783 // Shorthand delegations. 17784 var parse = sce.parseAs, 17785 getTrusted = sce.getTrusted, 17786 trustAs = sce.trustAs; 17787 17788 forEach(SCE_CONTEXTS, function(enumValue, name) { 17789 var lName = lowercase(name); 17790 sce[camelCase("parse_as_" + lName)] = function(expr) { 17791 return parse(enumValue, expr); 17792 }; 17793 sce[camelCase("get_trusted_" + lName)] = function(value) { 17794 return getTrusted(enumValue, value); 17795 }; 17796 sce[camelCase("trust_as_" + lName)] = function(value) { 17797 return trustAs(enumValue, value); 17798 }; 17799 }); 17800 17801 return sce; 17802 }]; 17803} 17804 17805/** 17806 * !!! This is an undocumented "private" service !!! 17807 * 17808 * @name $sniffer 17809 * @requires $window 17810 * @requires $document 17811 * 17812 * @property {boolean} history Does the browser support html5 history api ? 17813 * @property {boolean} transitions Does the browser support CSS transition events ? 17814 * @property {boolean} animations Does the browser support CSS animation events ? 17815 * 17816 * @description 17817 * This is very simple implementation of testing browser's features. 17818 */ 17819function $SnifferProvider() { 17820 this.$get = ['$window', '$document', function($window, $document) { 17821 var eventSupport = {}, 17822 android = 17823 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 17824 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 17825 document = $document[0] || {}, 17826 vendorPrefix, 17827 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 17828 bodyStyle = document.body && document.body.style, 17829 transitions = false, 17830 animations = false, 17831 match; 17832 17833 if (bodyStyle) { 17834 for (var prop in bodyStyle) {
17835 if (match = vendorRegex.exec(prop)) { 17836 vendorPrefix = match[0]; 17837 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 17838 break; 17839 } 17840 } 17841 17842 if (!vendorPrefix) { 17843 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 17844 } 17845 17846 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 17847 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 17848 17849 if (android && (!transitions || !animations)) { 17850 transitions = isString(bodyStyle.webkitTransition); 17851 animations = isString(bodyStyle.webkitAnimation); 17852 } 17853 } 17854 17855 17856 return { 17857 // Android has history.pushState, but it does not update location correctly 17858 // so let's not use the history API at all. 17859 // http://code.google.com/p/android/issues/detail?id=17471 17860 // https://github.com/angular/angular.js/issues/904 17861 17862 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 17863 // so let's not use the history API also 17864 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 17865 // jshint -W018 17866 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 17867 // jshint +W018 17868 hasEvent: function(event) { 17869 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 17870 // it. In particular the event is not fired when backspace or delete key are pressed or 17871 // when cut operation is performed. 17872 // IE10+ implements 'input' event but it erroneously fires under various situations, 17873 // e.g. when placeholder changes, or a form is focused. 17874 if (event === 'input' && msie <= 11) return false; 17875 17876 if (isUndefined(eventSupport[event])) { 17877 var divElm = document.createElement('div'); 17878 eventSupport[event] = 'on' + event in divElm; 17879 } 17880 17881 return eventSupport[event]; 17882 }, 17883 csp: csp(), 17884 vendorPrefix: vendorPrefix, 17885 transitions: transitions, 17886 animations: animations, 17887 android: android 17888 }; 17889 }]; 17890} 17891 17892var $compileMinErr = minErr('$compile'); 17893 17894/** 17895 * @ngdoc service 17896 * @name $templateRequest 17897 * 17898 * @description 17899 * The `$templateRequest` service runs security checks then downloads the provided template using 17900 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 17901 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 17902 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 17903 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 17904 * when `tpl` is of type string and `$templateCache` has the matching entry. 17905 * 17906 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 17907 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 17908 * 17909 * @return {Promise} a promise for the HTTP response data of the given URL. 17910 * 17911 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 17912 */ 17913function $TemplateRequestProvider() { 17914 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { 17915 function handleRequestFn(tpl, ignoreRequestError) { 17916 handleRequestFn.totalPendingRequests++; 17917 17918 // We consider the template cache holds only trusted templates, so 17919 // there's no need to go through whitelisting again for keys that already 17920 // are included in there. This also makes Angular accept any script 17921 // directive, no matter its name. However, we still need to unwrap trusted 17922 // types. 17923 if (!isString(tpl) || !$templateCache.get(tpl)) { 17924 tpl = $sce.getTrustedResourceUrl(tpl); 17925 } 17926 17927 var transformResponse = $http.defaults && $http.defaults.transformResponse; 17928 17929 if (isArray(transformResponse)) { 17930 transformResponse = transformResponse.filter(function(transformer) { 17931 return transformer !== defaultHttpResponseTransform; 17932 }); 17933 } else if (transformResponse === defaultHttpResponseTransform) { 17934 transformResponse = null; 17935 } 17936 17937 var httpOptions = { 17938 cache: $templateCache, 17939 transformResponse: transformResponse 17940 }; 17941 17942 return $http.get(tpl, httpOptions) 17943 ['finally'](function() { 17944 handleRequestFn.totalPendingRequests--; 17945 }) 17946 .then(function(response) { 17947 $templateCache.put(tpl, response.data); 17948 return response.data; 17949 }, handleError); 17950 17951 function handleError(resp) { 17952 if (!ignoreRequestError) { 17953 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 17954 tpl, resp.status, resp.statusText); 17955 } 17956 return $q.reject(resp); 17957 } 17958 } 17959 17960 handleRequestFn.totalPendingRequests = 0; 17961 17962 return handleRequestFn; 17963 }]; 17964} 17965
vendor: 5,426 bytes, lines 17966-18119
17966function $$TestabilityProvider() { 17967 this.$get = ['$rootScope', '$browser', '$location', 17968 function($rootScope, $browser, $location) { 17969 17970 /** 17971 * @name $testability 17972 * 17973 * @description 17974 * The private $$testability service provides a collection of methods for use when debugging 17975 * or by automated test and debugging tools. 17976 */ 17977 var testability = {}; 17978 17979 /** 17980 * @name $$testability#findBindings 17981 * 17982 * @description 17983 * Returns an array of elements that are bound (via ng-bind or {{}}) 17984 * to expressions matching the input. 17985 * 17986 * @param {Element} element The element root to search from. 17987 * @param {string} expression The binding expression to match. 17988 * @param {boolean} opt_exactMatch If true, only returns exact matches 17989 * for the expression. Filters and whitespace are ignored. 17990 */ 17991 testability.findBindings = function(element, expression, opt_exactMatch) { 17992 var bindings = element.getElementsByClassName('ng-binding'); 17993 var matches = []; 17994 forEach(bindings, function(binding) { 17995 var dataBinding = angular.element(binding).data('$binding'); 17996 if (dataBinding) { 17997 forEach(dataBinding, function(bindingName) { 17998 if (opt_exactMatch) { 17999 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 18000 if (matcher.test(bindingName)) { 18001 matches.push(binding); 18002 } 18003 } else { 18004 if (bindingName.indexOf(expression) != -1) { 18005 matches.push(binding); 18006 } 18007 } 18008 }); 18009 } 18010 }); 18011 return matches; 18012 }; 18013 18014 /** 18015 * @name $$testability#findModels 18016 * 18017 * @description 18018 * Returns an array of elements that are two-way found via ng-model to 18019 * expressions matching the input. 18020 * 18021 * @param {Element} element The element root to search from. 18022 * @param {string} expression The model expression to match. 18023 * @param {boolean} opt_exactMatch If true, only returns exact matches 18024 * for the expression. 18025 */ 18026 testability.findModels = function(element, expression, opt_exactMatch) { 18027 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 18028 for (var p = 0; p < prefixes.length; ++p) { 18029 var attributeEquals = opt_exactMatch ? '=' : '*='; 18030 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 18031 var elements = element.querySelectorAll(selector); 18032 if (elements.length) { 18033 return elements; 18034 } 18035 } 18036 }; 18037 18038 /** 18039 * @name $$testability#getLocation 18040 * 18041 * @description 18042 * Shortcut for getting the location in a browser agnostic way. Returns 18043 * the path, search, and hash. (e.g. /path?a=b#hash) 18044 */ 18045 testability.getLocation = function() { 18046 return $location.url(); 18047 }; 18048 18049 /** 18050 * @name $$testability#setLocation 18051 * 18052 * @description 18053 * Shortcut for navigating to a location without doing a full page reload. 18054 * 18055 * @param {string} url The location url (path, search and hash, 18056 * e.g. /path?a=b#hash) to go to. 18057 */ 18058 testability.setLocation = function(url) { 18059 if (url !== $location.url()) { 18060 $location.url(url); 18061 $rootScope.$digest(); 18062 } 18063 }; 18064 18065 /** 18066 * @name $$testability#whenStable 18067 * 18068 * @description 18069 * Calls the callback when $timeout and $http requests are completed. 18070 * 18071 * @param {function} callback 18072 */ 18073 testability.whenStable = function(callback) { 18074 $browser.notifyWhenNoOutstandingRequests(callback); 18075 }; 18076 18077 return testability; 18078 }]; 18079} 18080 18081function $TimeoutProvider() { 18082 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 18083 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 18084 18085 var deferreds = {}; 18086 18087 18088 /** 18089 * @ngdoc service 18090 * @name $timeout 18091 * 18092 * @description 18093 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 18094 * block and delegates any exceptions to 18095 * {@link ng.$exceptionHandler $exceptionHandler} service. 18096 * 18097 * The return value of calling `$timeout` is a promise, which will be resolved when 18098 * the delay has passed and the timeout function, if provided, is executed. 18099 * 18100 * To cancel a timeout request, call `$timeout.cancel(promise)`. 18101 * 18102 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 18103 * synchronously flush the queue of deferred functions. 18104 * 18105 * If you only want a promise that will be resolved after some specified delay 18106 * then you can call `$timeout` without the `fn` function. 18107 * 18108 * @param {function()=} fn A function, whose execution should be delayed. 18109 * @param {number=} [delay=0] Delay in milliseconds. 18110 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 18111 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 18112 * @param {...*=} Pass additional parameters to the executed function. 18113 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise 18114 * will be resolved with the return value of the `fn` function. 18115 * 18116 */ 18117 function timeout(fn, delay, invokeApply) { 18118 if (!isFunction(fn)) { 18119 invokeApply = delay;
18120 delay = fn; 18121 fn = noop; 18122 } 18123 18124 var args = sliceArgs(arguments, 3), 18125 skipApply = (isDefined(invokeApply) && !invokeApply), 18126 deferred = (skipApply ? $$q : $q).defer(), 18127 promise = deferred.promise, 18128 timeoutId; 18129 18130 timeoutId = $browser.defer(function() { 18131 try { 18132 deferred.resolve(fn.apply(null, args)); 18133 } catch (e) { 18134 deferred.reject(e); 18135 $exceptionHandler(e); 18136 } 18137 finally { 18138 delete deferreds[promise.$$timeoutId]; 18139 } 18140 18141 if (!skipApply) $rootScope.$apply(); 18142 }, delay); 18143 18144 promise.$$timeoutId = timeoutId; 18145 deferreds[timeoutId] = deferred; 18146 18147 return promise; 18148 } 18149 18150 18151 /** 18152 * @ngdoc method 18153 * @name $timeout#cancel 18154 * 18155 * @description 18156 * Cancels a task associated with the `promise`. As a result of this, the promise will be 18157 * resolved with a rejection. 18158 * 18159 * @param {Promise=} promise Promise returned by the `$timeout` function. 18160 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 18161 * canceled. 18162 */ 18163 timeout.cancel = function(promise) { 18164 if (promise && promise.$$timeoutId in deferreds) { 18165 deferreds[promise.$$timeoutId].reject('canceled'); 18166 delete deferreds[promise.$$timeoutId]; 18167 return $browser.defer.cancel(promise.$$timeoutId); 18168 } 18169 return false; 18170 }; 18171 18172 return timeout; 18173 }]; 18174} 18175 18176// NOTE: The usage of window and document instead of $window and $document here is 18177// deliberate. This service depends on the specific behavior of anchor nodes created by the 18178// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 18179// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 18180// doesn't know about mocked locations and resolves URLs to the real document - which is 18181// exactly the behavior needed here. There is little value is mocking these out for this 18182// service. 18183var urlParsingNode = document.createElement("a"); 18184var originUrl = urlResolve(window.location.href); 18185 18186 18187/** 18188 * 18189 * Implementation Notes for non-IE browsers 18190 * ---------------------------------------- 18191 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 18192 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 18193 * URL will be resolved into an absolute URL in the context of the application document. 18194 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 18195 * properties are all populated to reflect the normalized URL. This approach has wide 18196 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 18197 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 18198 * 18199 * Implementation Notes for IE 18200 * --------------------------- 18201 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 18202 * browsers. However, the parsed components will not be set if the URL assigned did not specify 18203 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 18204 * work around that by performing the parsing in a 2nd step by taking a previously normalized 18205 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 18206 * properties such as protocol, hostname, port, etc. 18207 * 18208 * References: 18209 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 18210 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 18211 * http://url.spec.whatwg.org/#urlutils 18212 * https://github.com/angular/angular.js/pull/2902 18213 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 18214 * 18215 * @kind function 18216 * @param {string} url The URL to be parsed. 18217 * @description Normalizes and parses a URL. 18218 * @returns {object} Returns the normalized URL as a dictionary. 18219 * 18220 * | member name | Description | 18221 * |---------------|----------------| 18222 * | href | A normalized version of the provided URL if it was not an absolute URL | 18223 * | protocol | The protocol including the trailing colon | 18224 * | host | The host and port (if the port is non-default) of the normalizedUrl | 18225 * | search | The search params, minus the question mark | 18226 * | hash | The hash string, minus the hash symbol 18227 * | hostname | The hostname 18228 * | port | The port, without ":" 18229 * | pathname | The pathname, beginning with "/" 18230 * 18231 */ 18232function urlResolve(url) { 18233 var href = url; 18234 18235 if (msie) { 18236 // Normalize before parse. Refer Implementation Notes on why this is 18237 // done in two steps on IE. 18238 urlParsingNode.setAttribute("href", href); 18239 href = urlParsingNode.href; 18240 } 18241 18242 urlParsingNode.setAttribute('href', href); 18243 18244 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 18245 return { 18246 href: urlParsingNode.href, 18247 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 18248 host: urlParsingNode.host, 18249 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 18250 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 18251 hostname: urlParsingNode.hostname, 18252 port: urlParsingNode.port, 18253 pathname: (urlParsingNode.pathname.charAt(0) === '/') 18254 ? urlParsingNode.pathname 18255 : '/' + urlParsingNode.pathname 18256 }; 18257} 18258 18259/** 18260 * Parse a request URL and determine whether this is a same-origin request as the application document. 18261 * 18262 * @param {string|object} requestUrl The url of the request as a string that will be resolved 18263 * or a parsed URL object. 18264 * @returns {boolean} Whether the request is for the same origin as the application document. 18265 */ 18266function urlIsSameOrigin(requestUrl) { 18267 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 18268 return (parsed.protocol === originUrl.protocol && 18269 parsed.host === originUrl.host); 18270} 18271 18272/** 18273 * @ngdoc service 18274 * @name $window 18275 * 18276 * @description 18277 * A reference to the browser's `window` object. While `window` 18278 * is globally available in JavaScript, it causes testability problems, because 18279 * it is a global variable. In angular we always refer to it through the 18280 * `$window` service, so it may be overridden, removed or mocked for testing. 18281 * 18282 * Expressions, like the one defined for the `ngClick` directive in the example 18283 * below, are evaluated with respect to the current scope. Therefore, there is 18284 * no risk of inadvertently coding in a dependency on a global value in such an 18285 * expression. 18286 * 18287 * @example 18288 <example module="windowExample"> 18289 <file name="index.html"> 18290 <script> 18291 angular.module('windowExample', [])
18292 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 18293 $scope.greeting = 'Hello, World!'; 18294 $scope.doGreeting = function(greeting) { 18295 $window.alert(greeting); 18296 }; 18297 }]); 18298 </script> 18299 <div ng-controller="ExampleController"> 18300 <input type="text" ng-model="greeting" aria-label="greeting" /> 18301 <button ng-click="doGreeting(greeting)">ALERT</button> 18302 </div> 18303 </file> 18304 <file name="protractor.js" type="protractor"> 18305 it('should display the greeting in the input box', function() { 18306 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 18307 // If we click the button it will block the test runner 18308 // element(':button').click(); 18309 }); 18310 </file> 18311 </example> 18312 */ 18313function $WindowProvider() { 18314 this.$get = valueFn(window); 18315} 18316 18317/** 18318 * @name $$cookieReader 18319 * @requires $document 18320 * 18321 * @description 18322 * This is a private service for reading cookies used by $http and ngCookies 18323 * 18324 * @return {Object} a key/value map of the current cookies 18325 */ 18326function $$CookieReader($document) { 18327 var rawDocument = $document[0] || {}; 18328 var lastCookies = {}; 18329 var lastCookieString = ''; 18330 18331 function safeDecodeURIComponent(str) { 18332 try { 18333 return decodeURIComponent(str); 18334 } catch (e) { 18335 return str; 18336 } 18337 } 18338 18339 return function() { 18340 var cookieArray, cookie, i, index, name; 18341 var currentCookieString = rawDocument.cookie || ''; 18342 18343 if (currentCookieString !== lastCookieString) { 18344 lastCookieString = currentCookieString; 18345 cookieArray = lastCookieString.split('; '); 18346 lastCookies = {}; 18347 18348 for (i = 0; i < cookieArray.length; i++) { 18349 cookie = cookieArray[i]; 18350 index = cookie.indexOf('='); 18351 if (index > 0) { //ignore nameless cookies 18352 name = safeDecodeURIComponent(cookie.substring(0, index)); 18353 // the first value that is seen for a cookie is the most 18354 // specific one. values for the same cookie name that 18355 // follow are for less specific paths. 18356 if (isUndefined(lastCookies[name])) { 18357 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 18358 } 18359 } 18360 } 18361 } 18362 return lastCookies; 18363 }; 18364} 18365 18366$$CookieReader.$inject = ['$document']; 18367 18368function $$CookieReaderProvider() { 18369 this.$get = $$CookieReader; 18370} 18371 18372/* global currencyFilter: true, 18373 dateFilter: true, 18374 filterFilter: true, 18375 jsonFilter: true, 18376 limitToFilter: true, 18377 lowercaseFilter: true, 18378 numberFilter: true, 18379 orderByFilter: true, 18380 uppercaseFilter: true, 18381 */ 18382 18383/** 18384 * @ngdoc provider 18385 * @name $filterProvider 18386 * @description 18387 * 18388 * Filters are just functions which transform input to an output. However filters need to be 18389 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 18390 * annotated with dependencies and is responsible for creating a filter function. 18391 * 18392 * <div class="alert alert-warning"> 18393 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18394 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18395 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18396 * (`myapp_subsection_filterx`). 18397 * </div> 18398 * 18399 * ```js 18400 * // Filter registration 18401 * function MyModule($provide, $filterProvider) { 18402 * // create a service to demonstrate injection (not always needed) 18403 * $provide.value('greet', function(name){ 18404 * return 'Hello ' + name + '!'; 18405 * }); 18406 * 18407 * // register a filter factory which uses the 18408 * // greet service to demonstrate DI. 18409 * $filterProvider.register('greet', function(greet){ 18410 * // return the filter function which uses the greet service 18411 * // to generate salutation 18412 * return function(text) { 18413 * // filters need to be forgiving so check input validity 18414 * return text && greet(text) || text; 18415 * }; 18416 * }); 18417 * } 18418 * ``` 18419 * 18420 * The filter function is registered with the `$injector` under the filter name suffix with 18421 * `Filter`. 18422 * 18423 * ```js 18424 * it('should be the same instance', inject( 18425 * function($filterProvider) { 18426 * $filterProvider.register('reverse', function(){ 18427 * return ...; 18428 * }); 18429 * }, 18430 * function($filter, reverseFilter) { 18431 * expect($filter('reverse')).toBe(reverseFilter); 18432 * }); 18433 * ``` 18434 * 18435 * 18436 * For more information about how angular filters work, and how to create your own filters, see 18437 * {@link guide/filter Filters} in the Angular Developer Guide. 18438 */ 18439 18440/** 18441 * @ngdoc service 18442 * @name $filter 18443 * @kind function 18444 * @description 18445 * Filters are used for formatting data displayed to the user. 18446 * 18447 * The general syntax in templates is as follows: 18448 * 18449 * {{ expression [| filter_name[:parameter_value] ... ] }} 18450 * 18451 * @param {String} name Name of the filter function to retrieve 18452 * @return {Function} the filter function 18453 * @example 18454 <example name="$filter" module="filterExample"> 18455 <file name="index.html"> 18456 <div ng-controller="MainCtrl"> 18457 <h3>{{ originalText }}</h3> 18458 <h3>{{ filteredText }}</h3> 18459 </div> 18460 </file> 18461 18462 <file name="script.js"> 18463 angular.module('filterExample', []) 18464 .controller('MainCtrl', function($scope, $filter) { 18465 $scope.originalText = 'hello'; 18466 $scope.filteredText = $filter('uppercase')($scope.originalText); 18467 }); 18468 </file> 18469 </example> 18470 */ 18471$FilterProvider.$inject = ['$provide']; 18472function $FilterProvider($provide) { 18473 var suffix = 'Filter'; 18474 18475 /** 18476 * @ngdoc method 18477 * @name $filterProvider#register 18478 * @param {string|Object} name Name of the filter function, or an object map of filters where 18479 * the keys are the filter names and the values are the filter factories. 18480 * 18481 * <div class="alert alert-warning"> 18482 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18483 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18484 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18485 * (`myapp_subsection_filterx`). 18486 * </div> 18487 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 18488 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 18489 * of the registered filter instances. 18490 */ 18491 function register(name, factory) { 18492 if (isObject(name)) { 18493 var filters = {};
18494 forEach(name, function(filter, key) { 18495 filters[key] = register(key, filter); 18496 }); 18497 return filters; 18498 } else { 18499 return $provide.factory(name + suffix, factory); 18500 } 18501 } 18502 this.register = register; 18503 18504 this.$get = ['$injector', function($injector) { 18505 return function(name) { 18506 return $injector.get(name + suffix); 18507 }; 18508 }]; 18509 18510 //////////////////////////////////////// 18511 18512 /* global 18513 currencyFilter: false, 18514 dateFilter: false, 18515 filterFilter: false, 18516 jsonFilter: false, 18517 limitToFilter: false, 18518 lowercaseFilter: false, 18519 numberFilter: false, 18520 orderByFilter: false, 18521 uppercaseFilter: false, 18522 */ 18523 18524 register('currency', currencyFilter); 18525 register('date', dateFilter); 18526 register('filter', filterFilter); 18527 register('json', jsonFilter); 18528 register('limitTo', limitToFilter); 18529 register('lowercase', lowercaseFilter); 18530 register('number', numberFilter); 18531 register('orderBy', orderByFilter); 18532 register('uppercase', uppercaseFilter); 18533} 18534 18535/** 18536 * @ngdoc filter 18537 * @name filter 18538 * @kind function 18539 * 18540 * @description 18541 * Selects a subset of items from `array` and returns it as a new array. 18542 * 18543 * @param {Array} array The source array. 18544 * @param {string|Object|function()} expression The predicate to be used for selecting items from 18545 * `array`. 18546 * 18547 * Can be one of: 18548 * 18549 * - `string`: The string is used for matching against the contents of the `array`. All strings or 18550 * objects with string properties in `array` that match this string will be returned. This also 18551 * applies to nested object properties. 18552 * The predicate can be negated by prefixing the string with `!`. 18553 * 18554 * - `Object`: A pattern object can be used to filter specific properties on objects contained 18555 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 18556 * which have property `name` containing "M" and property `phone` containing "1". A special 18557 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 18558 * property of the object or its nested object properties. That's equivalent to the simple 18559 * substring match with a `string` as described above. The predicate can be negated by prefixing 18560 * the string with `!`. 18561 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 18562 * not containing "M". 18563 * 18564 * Note that a named property will match properties on the same level only, while the special 18565 * `$` property will match properties on the same level or deeper. E.g. an array item like 18566 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 18567 * **will** be matched by `{$: 'John'}`. 18568 * 18569 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 18570 * The function is called for each element of the array, with the element, its index, and 18571 * the entire array itself as arguments. 18572 * 18573 * The final result is an array of those elements that the predicate returned true for. 18574 * 18575 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 18576 * determining if the expected value (from the filter expression) and actual value (from 18577 * the object in the array) should be considered a match. 18578 * 18579 * Can be one of: 18580 * 18581 * - `function(actual, expected)`: 18582 * The function will be given the object value and the predicate value to compare and 18583 * should return true if both values should be considered equal. 18584 * 18585 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 18586 * This is essentially strict comparison of expected and actual. 18587 * 18588 * - `false|undefined`: A short hand for a function which will look for a substring match in case 18589 * insensitive way. 18590 * 18591 * Primitive values are converted to strings. Objects are not compared against primitives, 18592 * unless they have a custom `toString` method (e.g. `Date` objects). 18593 * 18594 * @example 18595 <example> 18596 <file name="index.html"> 18597 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 18598 {name:'Mary', phone:'800-BIG-MARY'}, 18599 {name:'Mike', phone:'555-4321'}, 18600 {name:'Adam', phone:'555-5678'}, 18601 {name:'Julie', phone:'555-8765'}, 18602 {name:'Juliette', phone:'555-5678'}]"></div> 18603 18604 <label>Search: <input ng-model="searchText"></label> 18605 <table id="searchTextResults"> 18606 <tr><th>
18606Name</th><th>Phone</th></tr> 18607 <tr ng-repeat="friend in friends | filter:searchText"> 18608 <td>{{friend.name}}</td> 18609 <td>{{friend.phone}}</td> 18610 </tr> 18611 </table> 18612 <hr> 18613 <label>Any: <input ng-model="search.$"></label> <br> 18614 <label>Name only <input ng-model="search.name"></label><br> 18615 <label>Phone only <input ng-model="search.phone"></label><br> 18616 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 18617 <table id="searchObjResults"> 18618 <tr><th>Name</th><th>Phone</th></tr> 18619 <tr ng-repeat="friendObj in friends | filter:search:strict"> 18620 <td>{{friendObj.name}}</td> 18621 <td>{{friendObj.phone}}</td> 18622 </tr> 18623 </table> 18624 </file> 18625 <file name="protractor.js" type="protractor"> 18626 var expectFriendNames = function(expectedNames, key) { 18627 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 18628 arr.forEach(function(wd, i) { 18629 expect(wd.getText()).toMatch(expectedNames[i]); 18630 }); 18631 }); 18632 }; 18633 18634 it('should search across all fields when filtering with a string', function() { 18635 var searchText = element(by.model('searchText')); 18636 searchText.clear(); 18637 searchText.sendKeys('m'); 18638 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 18639 18640 searchText.clear(); 18641 searchText.sendKeys('76'); 18642 expectFriendNames(['John', 'Julie'], 'friend'); 18643 }); 18644 18645 it('should search in specific fields when filtering with a predicate object', function() { 18646 var searchAny = element(by.model('search.$')); 18647 searchAny.clear(); 18648 searchAny.sendKeys('i'); 18649 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 18650 }); 18651 it('should use a equal comparison when comparator is true', function() { 18652 var searchName = element(by.model('search.name')); 18653 var strict = element(by.model('strict')); 18654 searchName.clear(); 18655 searchName.sendKeys('Julie'); 18656 strict.click(); 18657 expectFriendNames(['Julie'], 'friendObj'); 18658 }); 18659 </file> 18660 </example> 18661 */ 18662function filterFilter() { 18663 return function(array, expression, comparator) { 18664 if (!isArrayLike(array)) { 18665 if (array == null) { 18666 return array; 18667 } else { 18668 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 18669 } 18670 } 18671 18672 var expressionType = getTypeForFilter(expression); 18673 var predicateFn; 18674 var matchAgainstAnyProp; 18675 18676 switch (expressionType) { 18677 case 'function': 18678 predicateFn = expression; 18679 break; 18680 case 'boolean': 18681 case 'null': 18682 case 'number': 18683 case 'string': 18684 matchAgainstAnyProp = true; 18685 //jshint -W086 18686 case 'object': 18687 //jshint +W086 18688 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 18689 break; 18690 default: 18691 return array; 18692 } 18693 18694 return Array.prototype.filter.call(array, predicateFn); 18695 }; 18696} 18697 18698// Helper functions for `filterFilter` 18699function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 18700 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 18701 var predicateFn; 18702 18703 if (comparator === true) { 18704 comparator = equals; 18705 } else if (!isFunction(comparator)) { 18706 comparator = function(actual, expected) { 18707 if (isUndefined(actual)) { 18708 // No substring matching against `undefined` 18709 return false; 18710 } 18711 if ((actual === null) || (expected === null)) { 18712 // No substring matching against `null`; only match against `null` 18713 return actual === expected; 18714 } 18715 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 18716 // Should not compare primitives against objects, unless they have custom `toString` method 18717 return false; 18718 } 18719 18720 actual = lowercase('' + actual); 18721 expected = lowercase('' + expected); 18722 return actual.indexOf(expected) !== -1; 18723 }; 18724 } 18725 18726 predicateFn = function(item) { 18727 if (shouldMatchPrimitives && !isObject(item)) { 18728 return deepCompare(item, expression.$, comparator, false); 18729 } 18730 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 18731 }; 18732 18733 return predicateFn; 18734} 18735 18736function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 18737 var actualType = getTypeForFilter(actual); 18738 var expectedType = getTypeForFilter(expected); 18739 18740 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 18741 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 18742 } else if (isArray(actual)) { 18743 // In case `actual` is an array, consider it a match 18744 // if ANY of it's items matches `expected` 18745 return actual.some(function(item) { 18746 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 18747 }); 18748 } 18749 18750 switch (actualType) { 18751 case 'object': 18752 var key; 18753 if (matchAgainstAnyProp) { 18754 for (key in actual) { 18755 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 18756 return true; 18757 } 18758 } 18759 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 18760 } else if (expectedType === 'object') { 18761 for (key in expected) { 18762 var expectedVal = expected[key]; 18763 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 18764 continue; 18765 } 18766 18767 var matchAnyProperty = key === '$'; 18768 var actualVal = matchAnyProperty ? actual : actual[key]; 18769 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 18770 return false;
18771 } 18772 } 18773 return true; 18774 } else { 18775 return comparator(actual, expected); 18776 } 18777 break; 18778 case 'function': 18779 return false; 18780 default: 18781 return comparator(actual, expected); 18782 } 18783} 18784 18785// Used for easily differentiating between `null` and actual `object` 18786function getTypeForFilter(val) { 18787 return (val === null) ? 'null' : typeof val; 18788} 18789 18790var MAX_DIGITS = 22; 18791var DECIMAL_SEP = '.'; 18792var ZERO_CHAR = '0'; 18793 18794/** 18795 * @ngdoc filter 18796 * @name currency 18797 * @kind function 18798 * 18799 * @description 18800 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 18801 * symbol for current locale is used. 18802 * 18803 * @param {number} amount Input to filter. 18804 * @param {string=} symbol Currency symbol or identifier to be displayed. 18805 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 18806 * @returns {string} Formatted number. 18807 * 18808 * 18809 * @example 18810 <example module="currencyExample"> 18811 <file name="index.html"> 18812 <script> 18813 angular.module('currencyExample', []) 18814 .controller('ExampleController', ['$scope', function($scope) { 18815 $scope.amount = 1234.56; 18816 }]); 18817 </script>
18818 <div ng-controller="ExampleController"> 18819 <input type="number" ng-model="amount" aria-label="amount"> <br> 18820 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 18821 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 18822 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 18823 </div> 18824 </file> 18825 <file name="protractor.js" type="protractor"> 18826 it('should init with 1234.56', function() { 18827 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 18828 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 18829 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 18830 }); 18831 it('should update', function() { 18832 if (browser.params.browser == 'safari') { 18833 // Safari does not understand the minus key. See 18834 // https://github.com/angular/protractor/issues/481 18835 return; 18836 } 18837 element(by.model('amount')).clear(); 18838 element(by.model('amount')).sendKeys('-1234'); 18839 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 18840 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 18841 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 18842 }); 18843 </file> 18844 </example> 18845 */ 18846currencyFilter.$inject = ['$locale']; 18847function currencyFilter($locale) { 18848 var formats = $locale.NUMBER_FORMATS; 18849 return function(amount, currencySymbol, fractionSize) { 18850 if (isUndefined(currencySymbol)) { 18851 currencySymbol = formats.CURRENCY_SYM; 18852 } 18853 18854 if (isUndefined(fractionSize)) { 18855 fractionSize = formats.PATTERNS[1].maxFrac; 18856 } 18857 18858 // if null or undefined pass it through 18859 return (amount == null) 18860 ? amount 18861 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 18862 replace(/\u00A4/g, currencySymbol); 18863 }; 18864} 18865 18866/** 18867 * @ngdoc filter 18868 * @name number 18869 * @kind function 18870 * 18871 * @description 18872 * Formats a number as text. 18873 * 18874 * If the input is null or undefined, it will just be returned. 18875 * If the input is infinite (Infinity/-Infinity) the Infinity symbol 'â' is returned. 18876 * If the input is not a number an empty string is returned. 18877 * 18878 * 18879 * @param {number|string} number Number to format. 18880 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 18881 * If this is not provided then the fraction size is computed from the current locale's number 18882 * formatting pattern. In the case of the default locale, it will be 3. 18883 * @returns {string} Number rounded to fractionSize and places a â,â after each third digit. 18884 * 18885 * @example 18886 <example module="numberFilterExample
vendor: 3,034 bytes, lines 18886-18965
18886"> 18887 <file name="index.html"> 18888 <script> 18889 angular.module('numberFilterExample', []) 18890 .controller('ExampleController', ['$scope', function($scope) { 18891 $scope.val = 1234.56789; 18892 }]); 18893 </script> 18894 <div ng-controller="ExampleController"> 18895 <label>Enter number: <input ng-model='val'></label><br> 18896 Default formatting: <span id='number-default'>{{val | number}}</span><br> 18897 No fractions: <span>{{val | number:0}}</span><br> 18898 Negative number: <span>{{-val | number:4}}</span> 18899 </div> 18900 </file> 18901 <file name="protractor.js" type="protractor"> 18902 it('should format numbers', function() { 18903 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 18904 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 18905 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 18906 }); 18907 18908 it('should update', function() { 18909 element(by.model('val')).clear(); 18910 element(by.model('val')).sendKeys('3374.333'); 18911 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 18912 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 18913 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 18914 }); 18915 </file> 18916 </example> 18917 */ 18918numberFilter.$inject = ['$locale']; 18919function numberFilter($locale) { 18920 var formats = $locale.NUMBER_FORMATS; 18921 return function(number, fractionSize) { 18922 18923 // if null or undefined pass it through 18924 return (number == null) 18925 ? number 18926 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 18927 fractionSize); 18928 }; 18929} 18930 18931/** 18932 * Parse a number (as a string) into three components that can be used 18933 * for formatting the number. 18934 * 18935 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) 18936 * 18937 * @param {string} numStr The number to parse 18938 * @return {object} An object describing this number, containing the following keys: 18939 * - d : an array of digits containing leading zeros as necessary 18940 * - i : the number of the digits in `d` that are to the left of the decimal point 18941 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` 18942 * 18943 */ 18944function parse(numStr) { 18945 var exponent = 0, digits, numberOfIntegerDigits; 18946 var i, j, zeros; 18947 18948 // Decimal point? 18949 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { 18950 numStr = numStr.replace(DECIMAL_SEP, ''); 18951 } 18952 18953 // Exponential form? 18954 if ((i = numStr.search(/e/i)) > 0) { 18955 // Work out the exponent. 18956 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; 18957 numberOfIntegerDigits += +numStr.slice(i + 1); 18958 numStr = numStr.substring(0, i); 18959 } else if (numberOfIntegerDigits < 0) { 18960 // There was no decimal point or exponent so it is an integer. 18961 numberOfIntegerDigits = numStr.length; 18962 } 18963 18964 // Count the number of leading zeros. 18965 for (i = 0; numStr.charAt(i) ==
18965 ZERO_CHAR; i++); 18966 18967 if (i == (zeros = numStr.length)) { 18968 // The digits are all zero. 18969 digits = [0]; 18970 numberOfIntegerDigits = 1; 18971 } else { 18972 // Count the number of trailing zeros 18973 zeros--; 18974 while (numStr.charAt(zeros) == ZERO_CHAR) zeros--; 18975 18976 // Trailing zeros are insignificant so ignore them 18977 numberOfIntegerDigits -= i; 18978 digits = []; 18979 // Convert string to array of digits without leading/trailing zeros. 18980 for (j = 0; i <= zeros; i++, j++) { 18981 digits[j] = +numStr.charAt(i); 18982 } 18983 } 18984 18985 // If the number overflows the maximum allowed digits then use an exponent. 18986 if (numberOfIntegerDigits > MAX_DIGITS) { 18987 digits = digits.splice(0, MAX_DIGITS - 1); 18988 exponent = numberOfIntegerDigits - 1; 18989 numberOfIntegerDigits = 1; 18990 } 18991 18992 return { d: digits, e: exponent, i: numberOfIntegerDigits }; 18993} 18994 18995/** 18996 * Round the parsed number to the specified number of decimal places 18997 * This function changed the parsedNumber in-place 18998 */ 18999function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { 19000 var digits = parsedNumber.d; 19001 var fractionLen = digits.length - parsedNumber.i; 19002 19003 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number 19004 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; 19005 19006 // The index of the digit to where rounding is to occur 19007 var roundAt = fractionSize + parsedNumber.i; 19008 var digit = digits[roundAt]; 19009 19010 if (roundAt > 0) { 19011 digits.splice(roundAt); 19012 } else { 19013 // We rounded to zero so reset the parsedNumber 19014 parsedNumber.i = 1; 19015 digits.length = roundAt = fractionSize + 1; 19016 for (var i=0; i < roundAt; i++) digits[i] = 0; 19017 } 19018 19019 if (digit >= 5) digits[roundAt - 1]++; 19020 19021 // Pad out with zeros to get the required fraction length 19022 for (; fractionLen < fractionSize; fractionLen++) digits.push(0); 19023 19024 19025 // Do any carrying, e.g. a digit was rounded up to 10 19026 var carry = digits.reduceRight(function(carry, d, i, digits) { 19027 d = d + carry; 19028 digits[i] = d % 10; 19029 return Math.floor(d / 10); 19030 }, 0); 19031 if (carry) { 19032 digits.unshift(carry); 19033 parsedNumber.i++; 19034 } 19035} 19036 19037/** 19038 * Format a number into a string 19039 * @param {number} number The number to format 19040 * @param {{ 19041 * minFrac, // the minimum number of digits required in the fraction part of the number 19042 * maxFrac, // the maximum number of digits required in the fraction part of the number 19043 * gSize, // number of digits in each group of separated digits 19044 * lgSize, // number of digits in the last group of digits before the decimal separator 19045 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) 19046 * posPre, // the string to go in front of a positive number 19047 * negSuf, // the string to go after a negative number (e.g. `)`) 19048 * posSuf // the string to go after a positive number 19049 * }} pattern 19050 * @param {string} groupSep The string to separate groups of number (e.g. `,`) 19051 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) 19052 * @param {[type]} fractionSize The size of the fractional part of the number 19053 * @return {string} The number formatted as a string 19054 */ 19055function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 19056 19057 if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; 19058 19059 var isInfinity = !isFinite(number); 19060 var isZero = false; 19061 var numStr = Math.abs(number) + '', 19062 formattedText = '', 19063 parsedNumber; 19064 19065 if (isInfinity) { 19066 formattedText = '\u221e'; 19067 } else { 19068 parsedNumber = parse(numStr); 19069 19070 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); 19071 19072 var digits = parsedNumber.d; 19073 var integerLen = parsedNumber.i; 19074 var exponent = parsedNumber.e; 19075 var decimals = []; 19076 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); 19077 19078 // pad zeros for small numbers 19079 while (integerLen < 0) { 19080 digits.unshift(0); 19081 integerLen++; 19082 } 19083 19084 // extract decimals digits 19085 if (integerLen > 0) { 19086 decimals = digits.splice(integerLen); 19087 } else { 19088 decimals = digits; 19089 digits = [0]; 19090 } 19091 19092 // format the integer digits with grouping separators 19093 var groups = []; 19094 if (digits.length > pattern.lgSize) {
vendor: 5,630 bytes, lines 19095-19274
19095 groups.unshift(digits.splice(-pattern.lgSize).join('')); 19096 } 19097 while (digits.length > pattern.gSize) { 19098 groups.unshift(digits.splice(-pattern.gSize).join('')); 19099 } 19100 if (digits.length) { 19101 groups.unshift(digits.join('')); 19102 } 19103 formattedText = groups.join(groupSep); 19104 19105 // append the decimal digits 19106 if (decimals.length) { 19107 formattedText += decimalSep + decimals.join(''); 19108 } 19109 19110 if (exponent) { 19111 formattedText += 'e+' + exponent; 19112 } 19113 } 19114 if (number < 0 && !isZero) { 19115 return pattern.negPre + formattedText + pattern.negSuf; 19116 } else { 19117 return pattern.posPre + formattedText + pattern.posSuf; 19118 } 19119} 19120 19121function padNumber(num, digits, trim) { 19122 var neg = ''; 19123 if (num < 0) { 19124 neg = '-'; 19125 num = -num; 19126 } 19127 num = '' + num; 19128 while (num.length < digits) num = ZERO_CHAR + num; 19129 if (trim) { 19130 num = num.substr(num.length - digits); 19131 } 19132 return neg + num; 19133} 19134 19135 19136function dateGetter(name, size, offset, trim) { 19137 offset = offset || 0; 19138 return function(date) { 19139 var value = date['get' + name](); 19140 if (offset > 0 || value > -offset) { 19141 value += offset; 19142 } 19143 if (value === 0 && offset == -12) value = 12; 19144 return padNumber(value, size, trim); 19145 }; 19146} 19147 19148function dateStrGetter(name, shortForm) { 19149 return function(date, formats) { 19150 var value = date['get' + name](); 19151 var get = uppercase(shortForm ? ('SHORT' + name) : name); 19152 19153 return formats[get][value]; 19154 }; 19155} 19156 19157function timeZoneGetter(date, formats, offset) { 19158 var zone = -1 * offset; 19159 var paddedZone = (zone >= 0) ? "+" : ""; 19160 19161 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 19162 padNumber(Math.abs(zone % 60), 2); 19163 19164 return paddedZone; 19165} 19166 19167function getFirstThursdayOfYear(year) { 19168 // 0 = index of January 19169 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 19170 // 4 = index of Thursday (+1 to account for 1st = 5) 19171 // 11 = index of *next* Thursday (+1 account for 1st = 12) 19172 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 19173} 19174 19175function getThursdayThisWeek(datetime) { 19176 return new Date(datetime.getFullYear(), datetime.getMonth(), 19177 // 4 = index of Thursday 19178 datetime.getDate() + (4 - datetime.getDay())); 19179} 19180 19181function weekGetter(size) { 19182 return function(date) { 19183 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 19184 thisThurs = getThursdayThisWeek(date); 19185 19186 var diff = +thisThurs - +firstThurs, 19187 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 19188 19189 return padNumber(result, size); 19190 }; 19191} 19192 19193function ampmGetter(date, formats) { 19194 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 19195} 19196 19197function eraGetter(date, formats) { 19198 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 19199} 19200 19201function longEraGetter(date, formats) { 19202 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 19203} 19204 19205var DATE_FORMATS = { 19206 yyyy: dateGetter('FullYear', 4), 19207 yy: dateGetter('FullYear', 2, 0, true), 19208 y: dateGetter('FullYear', 1), 19209 MMMM: dateStrGetter('Month'), 19210 MMM: dateStrGetter('Month', true), 19211 MM: dateGetter('Month', 2, 1), 19212 M: dateGetter('Month', 1, 1), 19213 dd: dateGetter('Date', 2), 19214 d: dateGetter('Date', 1), 19215 HH: dateGetter('Hours', 2), 19216 H: dateGetter('Hours', 1), 19217 hh: dateGetter('Hours', 2, -12), 19218 h: dateGetter('Hours', 1, -12), 19219 mm: dateGetter('Minutes', 2), 19220 m: dateGetter('Minutes', 1), 19221 ss: dateGetter('Seconds', 2), 19222 s: dateGetter('Seconds', 1), 19223 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 19224 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 19225 sss: dateGetter('Milliseconds', 3), 19226 EEEE: dateStrGetter('Day'), 19227 EEE: dateStrGetter('Day', true), 19228 a: ampmGetter, 19229 Z: timeZoneGetter, 19230 ww: weekGetter(2), 19231 w: weekGetter(1), 19232 G: eraGetter, 19233 GG: eraGetter, 19234 GGG: eraGetter, 19235 GGGG: longEraGetter 19236}; 19237 19238var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 19239 NUMBER_STRING = /^\-?\d+$/; 19240 19241/** 19242 * @ngdoc filter 19243 * @name date 19244 * @kind function 19245 * 19246 * @description 19247 * Formats `date` to a string based on the requested `format`. 19248 * 19249 * `format` string can be composed of the following elements: 19250 * 19251 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 19252 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 19253 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 19254 * * `'MMMM'`: Month in year (January-December) 19255 * * `'MMM'`: Month in year (Jan-Dec) 19256 * * `'MM'`: Month in year, padded (01-12) 19257 * * `'M'`: Month in year (1-12) 19258 * * `'dd'`: Day in month, padded (01-31) 19259 * * `'d'`: Day in month (1-31) 19260 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 19261 * * `'EEE'`: Day in Week, (Sun-Sat) 19262 * * `'HH'`: Hour in day, padded (00-23) 19263 * * `'H'`: Hour in day (0-23) 19264 * * `'hh'`: Hour in AM/PM, padded (01-12) 19265 * * `'h'`: Hour in AM/PM, (1-12) 19266 * * `'mm'`: Minute in hour, padded (00-59) 19267 * * `'m'`: Minute in hour (0-59) 19268 * * `'ss'`: Second in minute, padded (00-59) 19269 * * `'s'`: Second in minute (0-59) 19270 * * `'sss'`: Millisecond in second, padded (000-999) 19271 * * `'a'`: AM/PM marker 19272 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 19273 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 19274 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
19275 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 19276 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 19277 * 19278 * `format` string can also be one of the following predefined 19279 * {@link guide/i18n localizable formats}: 19280 * 19281 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 19282 * (e.g. Sep 3, 2010 12:05:08 PM) 19283 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 19284 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 19285 * (e.g. Friday, September 3, 2010) 19286 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 19287 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 19288 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 19289 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 19290 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 19291 * 19292 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 19293 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 19294 * (e.g. `"h 'o''clock'"`). 19295 * 19296 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 19297 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 19298 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 19299 * specified in the string input, the time is considered to be in the local timezone. 19300 * @param {string=} format Formatting rules (see Description). If not specified, 19301 * `mediumDate` is used. 19302 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 19303 * continental US time zone abbreviations, but for general use, use a time zone offset, for 19304 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 19305 * If not specified, the timezone of the browser will be used. 19306 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 19307 * 19308 * @example 19309 <example> 19310 <file name="index.html"> 19311 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 19312 <span>{{1288323623006 | date:'medium'}}</span><br> 19313 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 19314 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 19315 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 19316 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 19317 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 19318 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 19319 </file> 19320 <file name="protractor.js" type="protractor"> 19321 it('should format date', function() { 19322 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 19323 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 19324 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 19325 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 19326 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 19327 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 19328 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 19329 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 19330 }); 19331 </file> 19332 </example> 19333 */ 19334dateFilter.$inject = ['$locale']; 19335function dateFilter($locale) { 19336 19337 19338 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 19339 // 1 2 3 4 5 6 7 8 9 10 11 19340 function jsonStringToDate(string) { 19341 var match; 19342 if (match = string.match(R_ISO8601_STR)) { 19343 var date = new Date(0), 19344 tzHour = 0, 19345 tzMin = 0, 19346 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 19347 timeSetter = match[8] ? date.setUTCHours : date.setHours; 19348 19349 if (match[9]) { 19350 tzHour = toInt(match[9] + match[10]); 19351 tzMin = toInt(match[9] + match[11]); 19352 } 19353 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 19354 var h = toInt(match[4] || 0) - tzHour; 19355 var m = toInt(match[5] || 0) - tzMin; 19356 var s = toInt(match[6] || 0); 19357 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 19358 timeSetter.call(date, h, m, s, ms); 19359 return date; 19360 } 19361 return string; 19362 } 19363 19364 19365 return function(date, format, timezone) { 19366 var text = '', 19367 parts = [], 19368 fn, match; 19369 19370 format = format || 'mediumDate'; 19371 format = $locale.DATETIME_FORMATS[format] || format; 19372 if (isString(date)) { 19373 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 19374 } 19375 19376 if (isNumber(date)) { 19377 date = new Date(date); 19378 } 19379 19380 if (!isDate(date) || !isFinite(date.getTime())) { 19381 return date; 19382 } 19383 19384 while (format) { 19385 match = DATE_FORMATS_SPLIT.exec(format); 19386 if (match) { 19387 parts = concat(parts, match, 1); 19388 format = parts.pop(); 19389 } else { 19390 parts.push(format); 19391 format = null; 19392 } 19393 } 19394 19395 var dateTimezoneOffset = date.getTimezoneOffset(); 19396 if (timezone) { 19397 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 19398 date = convertTimezoneToLocal(date, timezone, true); 19399 }
19400 forEach(parts, function(value) { 19401 fn = DATE_FORMATS[value]; 19402 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 19403 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 19404 }); 19405 19406 return text; 19407 }; 19408} 19409 19410 19411/** 19412 * @ngdoc filter 19413 * @name json 19414 * @kind function 19415 * 19416 * @description 19417 * Allows you to convert a JavaScript object into JSON string. 19418 * 19419 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 19420 * the binding is automatically converted to JSON. 19421 * 19422 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 19423 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. 19424 * @returns {string} JSON string. 19425 * 19426 * 19427 * @example 19428 <example> 19429 <file name="index.html"> 19430 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 19431 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 19432 </file> 19433 <file name="protractor.js" type="protractor"> 19434 it('should jsonify filtered objects', function() { 19435 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 19436 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 19437 }); 19438 </file> 19439 </example> 19440 * 19441 */ 19442function jsonFilter() { 19443 return function(object, spacing) { 19444 if (isUndefined(spacing)) { 19445 spacing = 2; 19446 } 19447 return toJson(object, spacing); 19448 }; 19449} 19450 19451 19452/** 19453 * @ngdoc filter 19454 * @name lowercase 19455 * @kind function 19456 * @description 19457 * Converts string to lowercase. 19458 * @see angular.lowercase 19459 */ 19460var lowercaseFilter = valueFn(lowercase); 19461 19462 19463/** 19464 * @ngdoc filter 19465 * @name uppercase 19466 * @kind function 19467 * @description 19468 * Converts string to uppercase. 19469 * @see angular.uppercase 19470 */ 19471var uppercaseFilter = valueFn(uppercase); 19472 19473/** 19474 * @ngdoc filter 19475 * @name limitTo 19476 * @kind function 19477 * 19478 * @description 19479 * Creates a new array or string containing only a specified number of elements. The elements 19480 * are taken from either the beginning or the end of the source array, string or number, as specified by 19481 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 19482 * converted to a string. 19483 * 19484 * @param {Array|string|number} input Source array, string or number to be limited. 19485 * @param {string|number} limit The length of the returned array or string. If the `limit` number 19486 * is positive, `limit` number of items from the beginning of the source array/string are copied. 19487 * If the number is negative, `limit` number of items from the end of the source array/string 19488 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 19489 * the input will be returned unchanged. 19490 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 19491 * indicates an offset from the end of `input`. Defaults to `0`. 19492 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 19493 * had less than `limit` elements. 19494 * 19495 * @example 19496 <example module="limitToExample"> 19497 <file name="index.html"> 19498 <script> 19499 angular.module('limitToExample', []) 19500 .controller('ExampleController', ['$scope', function($scope) { 19501 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 19502 $scope.letters = "abcdefghi"; 19503 $scope.longNumber = 2345432342; 19504 $scope.numLimit = 3; 19505 $scope.letterLimit = 3; 19506 $scope.longNumberLimit = 3; 19507 }]); 19508 </script>
19509 <div ng-controller="ExampleController"> 19510 <label> 19511 Limit {{numbers}} to: 19512 <input type="number" step="1" ng-model="numLimit"> 19513 </label> 19514 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 19515 <label> 19516 Limit {{letters}} to: 19517 <input type="number" step="1" ng-model="letterLimit"> 19518 </label> 19519 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 19520 <label> 19521 Limit {{longNumber}} to: 19522 <input type="number" step="1" ng-model="longNumberLimit"> 19523 </label> 19524 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 19525 </div> 19526 </file> 19527 <file name="protractor.js" type="protractor"> 19528 var numLimitInput = element(by.model('numLimit')); 19529 var letterLimitInput = element(by.model('letterLimit')); 19530 var longNumberLimitInput = element(by.model('longNumberLimit')); 19531 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 19532 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 19533 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 19534 19535 it('should limit the number array to first three items', function() { 19536 expect(numLimitInput.getAttribute('value')).toBe('3'); 19537 expect(letterLimitInput.getAttribute('value')).toBe('3'); 19538 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 19539 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 19540 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 19541 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 19542 }); 19543 19544 // There is a bug in safari and protractor that doesn't like the minus key 19545 // it('should update the output when -3 is entered', function() { 19546 // numLimitInput.clear(); 19547 // numLimitInput.sendKeys('-3'); 19548 // letterLimitInput.clear(); 19549 // letterLimitInput.sendKeys('-3'); 19550 // longNumberLimitInput.clear(); 19551 // longNumberLimitInput.sendKeys('-3'); 19552 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 19553 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 19554 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 19555 // }); 19556 19557 it('should not exceed the maximum size of input array', function() { 19558 numLimitInput.clear(); 19559 numLimitInput.sendKeys('100'); 19560 letterLimitInput.clear(); 19561 letterLimitInput.sendKeys('100'); 19562 longNumberLimitInput.clear(); 19563 longNumberLimitInput.sendKeys('100'); 19564 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 19565 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 19566 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 19567 }); 19568 </file> 19569 </example> 19570*/ 19571function limitToFilter() { 19572 return function(input, limit, begin) { 19573 if (Math.abs(Number(limit)) === Infinity) { 19574 limit = Number(limit); 19575 } else { 19576 limit = toInt(limit); 19577 } 19578 if (isNaN(limit)) return input; 19579 19580 if (isNumber(input)) input = input.toString(); 19581 if (!isArray(input) && !isString(input)) return input; 19582 19583 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 19584 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; 19585 19586 if (limit >= 0) { 19587 return input.slice(begin, begin + limit); 19588 } else { 19589 if (begin === 0) { 19590 return input.slice(limit, input.length); 19591 } else { 19592 return input.slice(Math.max(0, begin + limit), begin); 19593 } 19594 } 19595 }; 19596} 19597 19598/** 19599 * @ngdoc filter 19600 * @name orderBy 19601 * @kind function 19602 * 19603 * @description 19604 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 19605 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 19606 * as expected, make sure they are actually being saved as numbers and not strings. 19607 * 19608 * @param {Array} array The array to sort. 19609 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 19610 * used by the comparator to determine the order of elements. 19611 * 19612 * Can be one of: 19613 * 19614 * - `function`: Getter function. The result of this function will be sorted using the 19615 * `<`, `===`, `>` operator. 19616 * - `string`: An Angular expression. The result of this expression is used to compare elements 19617 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 19618 * 3 first characters of a property called `name`). The result of a constant expression 19619 * is interpreted as a property name to be used in comparisons (for example `"special name"` 19620 * to sort object by the value of their `special name` property). An expression can be 19621 * optionally prefixed with `+` or `-` to control ascending or descending sort order 19622 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 19623 * element itself is used to compare where sorting. 19624 * - `Array`: An array of function or string predicates. The first predicate in the array 19625 * is used for sorting, but when two items are equivalent, the next predicate is used. 19626 * 19627 * If the predicate is missing or empty then it defaults to `'+'`. 19628 * 19629 * @param {boolean=} reverse Reverse the order of the array. 19630 * @returns {Array} Sorted copy of the source array. 19631 * 19632 * 19633 * @example 19634 * The example below demonstrates a simple ngRepeat, where the data is sorted 19635 * by age in descending order (predicate is set to `'-age'`). 19636 * `reverse` is not set, which means it defaults to `false`. 19637 <example module="orderByExample"> 19638 <file name="index.html">
19639 <div ng-controller="ExampleController"> 19640 <table class="friend"> 19641 <tr> 19642 <th>Name</th> 19643 <th>Phone Number</th> 19644 <th>Age</th> 19645 </tr> 19646 <tr ng-repeat="friend in friends | orderBy:'-age'"> 19647 <td>{{friend.name}}</td> 19648 <td>{{friend.phone}}</td> 19649 <td>{{friend.age}}</td> 19650 </tr> 19651 </table> 19652 </div> 19653 </file> 19654 <file name="script.js"> 19655 angular.module('orderByExample', []) 19656 .controller('ExampleController', ['$scope', function($scope) { 19657 $scope.friends = 19658 [{name:'John', phone:'555-1212', age:10}, 19659 {name:'Mary', phone:'555-9876', age:19}, 19660 {name:'Mike', phone:'555-4321', age:21}, 19661 {name:'Adam', phone:'555-5678', age:35}, 19662 {name:'Julie', phone:'555-8765', age:29}]; 19663 }]); 19664 </file> 19665 </example> 19666 * 19667 * The predicate and reverse parameters can be controlled dynamically through scope properties, 19668 * as shown in the next example. 19669 * @example 19670 <example module="orderByExample"> 19671 <file name="index.html">
19672 <div ng-controller="ExampleController"> 19673 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 19674 <hr/> 19675 <button ng-click="predicate=''">Set to unsorted</button> 19676 <table class="friend"> 19677 <tr> 19678 <th> 19679 <button ng-click="order('name')">Name</button> 19680 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 19681 </th> 19682 <th> 19683 <button ng-click="order('phone')">Phone Number</button> 19684 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 19685 </th> 19686 <th> 19687 <button ng-click="order('age')">Age</button> 19688 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 19689 </th> 19690 </tr> 19691 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 19692 <td>{{friend.name}}</td> 19693 <td>{{friend.phone}}</td> 19694 <td>{{friend.age}}</td> 19695 </tr> 19696 </table> 19697 </div> 19698 </file> 19699 <file name="script.js"> 19700 angular.module('orderByExample', []) 19701 .controller('ExampleController', ['$scope', function($scope) { 19702 $scope.friends = 19703 [{name:'John', phone:'555-1212', age:10}, 19704 {name:'Mary', phone:'555-9876', age:19}, 19705 {name:'Mike', phone:'555-4321', age:21}, 19706 {name:'Adam', phone:'555-5678', age:35}, 19707 {name:'Julie', phone:'555-8765', age:29}]; 19708 $scope.predicate = 'age'; 19709 $scope.reverse = true; 19710 $scope.order = function(predicate) { 19711 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 19712 $scope.predicate = predicate; 19713 }; 19714 }]); 19715 </file> 19716 <file name="style.css"> 19717 .sortorder:after { 19718 content: '\25b2'; 19719 } 19720 .sortorder.reverse:after { 19721 content: '\25bc'; 19722 } 19723 </file> 19724 </example> 19725 * 19726 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 19727 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 19728 * desired parameters. 19729 * 19730 * Example: 19731 * 19732 * @example 19733 <example module="orderByExample"> 19734 <file name="index.html">
19735 <div ng-controller="ExampleController"> 19736 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 19737 <table class="friend"> 19738 <tr> 19739 <th> 19740 <button ng-click="order('name')">Name</button> 19741 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 19742 </th> 19743 <th> 19744 <button ng-click="order('phone')">Phone Number</button> 19745 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 19746 </th> 19747 <th> 19748 <button ng-click="order('age')">Age</button> 19749 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 19750 </th> 19751 </tr> 19752 <tr ng-repeat="friend in friends"> 19753 <td>{{friend.name}}</td> 19754 <td>{{friend.phone}}</td> 19755 <td>{{friend.age}}</td> 19756 </tr> 19757 </table> 19758 </div> 19759 </file> 19760 19761 <file name="script.js"> 19762 angular.module('orderByExample', []) 19763 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 19764 var orderBy = $filter('orderBy'); 19765 $scope.friends = [ 19766 { name: 'John', phone: '555-1212', age: 10 }, 19767 { name: 'Mary', phone: '555-9876', age: 19 }, 19768 { name: 'Mike', phone: '555-4321', age: 21 }, 19769 { name: 'Adam', phone: '555-5678', age: 35 }, 19770 { name: 'Julie', phone: '555-8765', age: 29 } 19771 ]; 19772 $scope.order = function(predicate) { 19773 $scope.predicate = predicate; 19774 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 19775 $scope.friends = orderBy($scope.friends, predicate, $scope.reverse); 19776 }; 19777 $scope.order('age', true); 19778 }]); 19779 </file> 19780 19781 <file name="style.css"> 19782 .sortorder:after { 19783 content: '\25b2'; 19784 } 19785 .sortorder.reverse:after { 19786 content: '\25bc'; 19787 } 19788 </file> 19789</example> 19790 */ 19791orderByFilter.$inject = ['$parse']; 19792function orderByFilter($parse) { 19793 return function(array, sortPredicate, reverseOrder) { 19794 19795 if (!(isArrayLike(array))) return array; 19796 19797 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 19798 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 19799 19800 var predicates = processPredicates(sortPredicate, reverseOrder); 19801 // Add a predicate at the end that evaluates to the element index. This makes the 19802 // sort stable as it works as a tie-breaker when all the input predicates cannot 19803 // distinguish between two elements. 19804 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1}); 19805 19806 // The next three lines are a version of a Swartzian Transform idiom from Perl 19807 // (sometimes called the Decorate-Sort-Undecorate idiom) 19808 // See https://en.wikipedia.org/wiki/Schwartzian_transform 19809 var compareValues = Array.prototype.map.call(array, getComparisonObject); 19810 compareValues.sort(doComparison); 19811 array = compareValues.map(function(item) { return item.value; }); 19812 19813 return array; 19814 19815 function getComparisonObject(value, index) { 19816 return { 19817 value: value, 19818 predicateValues: predicates.map(function(predicate) { 19819 return getPredicateValue(predicate.get(value), index); 19820 }) 19821 }; 19822 } 19823 19824 function doComparison(v1, v2) { 19825 var result = 0; 19826 for (var index=0, length = predicates.length; index < length; ++index) { 19827 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; 19828 if (result) break; 19829 } 19830 return result; 19831 } 19832 }; 19833 19834 function processPredicates(sortPredicate, reverseOrder) { 19835 reverseOrder = reverseOrder ? -1 : 1; 19836 return sortPredicate.map(function(predicate) { 19837 var descending = 1, get = identity; 19838 19839 if (isFunction(predicate)) { 19840 get = predicate; 19841 } else if (isString(predicate)) { 19842 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 19843 descending = predicate.charAt(0) == '-' ? -1 : 1; 19844 predicate = predicate.substring(1); 19845 } 19846 if (predicate !== '') { 19847 get = $parse(predicate); 19848 if (get.constant) { 19849 var key = get(); 19850 get = function(value) { return value[key]; }; 19851 } 19852 } 19853 }
19854 return { get: get, descending: descending * reverseOrder }; 19855 }); 19856 } 19857 19858 function isPrimitive(value) { 19859 switch (typeof value) { 19860 case 'number': /* falls through */ 19861 case 'boolean': /* falls through */ 19862 case 'string': 19863 return true; 19864 default: 19865 return false; 19866 } 19867 } 19868 19869 function objectValue(value, index) { 19870 // If `valueOf` is a valid function use that 19871 if (typeof value.valueOf === 'function') { 19872 value = value.valueOf(); 19873 if (isPrimitive(value)) return value; 19874 } 19875 // If `toString` is a valid function and not the one from `Object.prototype` use that 19876 if (hasCustomToString(value)) { 19877 value = value.toString(); 19878 if (isPrimitive(value)) return value; 19879 } 19880 // We have a basic object so we use the position of the object in the collection 19881 return index; 19882 } 19883 19884 function getPredicateValue(value, index) { 19885 var type = typeof value; 19886 if (value === null) { 19887 type = 'string'; 19888 value = 'null'; 19889 } else if (type === 'string') { 19890 value = value.toLowerCase(); 19891 } else if (type === 'object') { 19892 value = objectValue(value, index); 19893 } 19894 return { value: value, type: type }; 19895 } 19896 19897 function compare(v1, v2) { 19898 var result = 0; 19899 if (v1.type === v2.type) { 19900 if (v1.value !== v2.value) { 19901 result = v1.value < v2.value ? -1 : 1; 19902 } 19903 } else { 19904 result = v1.type < v2.type ? -1 : 1; 19905 } 19906 return result; 19907 } 19908} 19909 19910function ngDirective(directive) { 19911 if (isFunction(directive)) { 19912 directive = { 19913 link: directive 19914 }; 19915 } 19916 directive.restrict = directive.restrict || 'AC'; 19917 return valueFn(directive); 19918} 19919 19920/** 19921 * @ngdoc directive 19922 * @name a 19923 * @restrict E 19924 * 19925 * @description 19926 * Modifies the default behavior of the html A tag so that the default action is prevented when 19927 * the href attribute is empty. 19928 * 19929 * This change permits the easy creation of action links with the `ngClick` directive 19930 * without changing the location or causing page reloads, e.g.: 19931 * `<a href="" ng-click="list.addItem()">Add Item</a>` 19932 */ 19933var htmlAnchorDirective = valueFn({ 19934 restrict: 'E', 19935 compile: function(element, attr) { 19936 if (!attr.href && !attr.xlinkHref) { 19937 return function(scope, element) { 19938 // If the linked element is not an anchor tag anymore, do nothing 19939 if (element[0].nodeName.toLowerCase() !== 'a') return; 19940 19941 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 19942 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 19943 'xlink:href' : 'href'; 19944 element.on('click', function(event) { 19945 // if we have no href url, then don't navigate anywhere. 19946 if (!element.attr(href)) { 19947 event.preventDefault(); 19948 } 19949 }); 19950 }; 19951 } 19952 } 19953}); 19954 19955/** 19956 * @ngdoc directive 19957 * @name ngHref 19958 * @restrict A 19959 * @priority 99 19960 * 19961 * @description 19962 * Using Angular markup like `{{hash}}` in an href attribute will 19963 * make the link go to the wrong URL if the user clicks it before 19964 * Angular has a chance to replace the `{{hash}}` markup with its 19965 * value. Until Angular replaces the markup the link will be broken 19966 * and will most likely return a 404 error. The `ngHref` directive 19967 * solves this problem. 19968 * 19969 * The wrong way to write it: 19970 * ```html 19971 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19972 * ``` 19973 * 19974 * The correct way to write it: 19975 * ```html 19976 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19977 * ``` 19978 * 19979 * @element A 19980 * @param {template} ngHref any string which can contain `{{}}` markup. 19981 * 19982 * @example 19983 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 19984 * in links and their different behaviors: 19985 <example> 19986 <file name="index.html"> 19987 <input ng-model="value" /><br /> 19988 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 19989 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 19990 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 19991 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 19992 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 19993 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 19994 </file> 19995 <file name="protractor.js" type="protractor"> 19996 it('should execute ng-click but not reload when href without value', function() {
19997 element(by.id('link-1')).click(); 19998 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 19999 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 20000 }); 20001 20002 it('should execute ng-click but not reload when href empty string', function() { 20003 element(by.id('link-2')).click(); 20004 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 20005 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 20006 }); 20007 20008 it('should execute ng-click and change url when ng-href specified', function() { 20009 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 20010 20011 element(by.id('link-3')).click(); 20012 20013 // At this point, we navigate away from an Angular page, so we need 20014 // to use browser.driver to get the base webdriver. 20015 20016 browser.wait(function() { 20017 return browser.driver.getCurrentUrl().then(function(url) { 20018 return url.match(/\/123$/); 20019 }); 20020 }, 5000, 'page should navigate to /123'); 20021 }); 20022 20023 it('should execute ng-click but not reload when href empty string and name specified', function() { 20024 element(by.id('link-4')).click(); 20025 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 20026 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 20027 }); 20028 20029 it('should execute ng-click but not reload when no href but name specified', function() { 20030 element(by.id('link-5')).click(); 20031 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 20032 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 20033 }); 20034 20035 it('should only change url when only ng-href', function() { 20036 element(by.model('value')).clear(); 20037 element(by.model('value')).sendKeys('6'); 20038 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 20039 20040 element(by.id('link-6')).click(); 20041 20042 // At this point, we navigate away from an Angular page, so we need 20043 // to use browser.driver to get the base webdriver. 20044 browser.wait(function() { 20045 return browser.driver.getCurrentUrl().then(function(url) { 20046 return url.match(/\/6$/); 20047 }); 20048 }, 5000, 'page should navigate to /6'); 20049 }); 20050 </file> 20051 </example> 20052 */ 20053 20054/** 20055 * @ngdoc directive 20056 * @name ngSrc 20057 * @restrict A 20058 * @priority 99 20059 * 20060 * @description 20061 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 20062 * work right: The browser will fetch from the URL with the literal 20063 * text `{{hash}}` until Angular replaces the expression inside 20064 * `{{hash}}`. The `ngSrc` directive solves this problem. 20065 * 20066 * The buggy way to write it: 20067 * ```html 20068 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 20069 * ``` 20070 * 20071 * The correct way to write it: 20072 * ```html 20073 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 20074 * ``` 20075 * 20076 * @element IMG 20077 * @param {template} ngSrc any string which can contain `{{}}` markup. 20078 */ 20079 20080/** 20081 * @ngdoc directive 20082 * @name ngSrcset 20083 * @restrict A 20084 * @priority 99 20085 * 20086 * @description 20087 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 20088 * work right: The browser will fetch from the URL with the literal 20089 * text `{{hash}}` until Angular replaces the expression inside 20090 * `{{hash}}`. The `ngSrcset` directive solves this problem. 20091 * 20092 * The buggy way to write it: 20093 * ```html 20094 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 20095 * ``` 20096 * 20097 * The correct way to write it: 20098 * ```html 20099 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 20100 * ``` 20101 * 20102 * @element IMG 20103 * @param {template} ngSrcset any string which can contain `{{}}` markup. 20104 */ 20105 20106/** 20107 * @ngdoc directive 20108 * @name ngDisabled 20109 * @restrict A 20110 * @priority 100 20111 * 20112 * @description 20113 * 20114 * This directive sets the `disabled` attribute on the element if the 20115 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 20116 * 20117 * A special directive is necessary because we cannot use interpolation inside the `disabled` 20118 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20119 * 20120 * @example 20121 <example> 20122 <file name="index.html"> 20123 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 20124 <button ng-model="button" ng-disabled="checked">Button</button> 20125 </file> 20126 <file name="protractor.js" type="protractor"> 20127 it('should toggle button', function() { 20128 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
20129 element(by.model('checked')).click(); 20130 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 20131 }); 20132 </file> 20133 </example> 20134 * 20135 * @element INPUT 20136 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 20137 * then the `disabled` attribute will be set on the element 20138 */ 20139 20140 20141/** 20142 * @ngdoc directive 20143 * @name ngChecked 20144 * @restrict A 20145 * @priority 100 20146 * 20147 * @description 20148 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 20149 * 20150 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 20151 * as this can lead to unexpected behavior. 20152 * 20153 * A special directive is necessary because we cannot use interpolation inside the `checked` 20154 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20155 * 20156 * @example 20157 <example> 20158 <file name="index.html"> 20159 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 20160 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 20161 </file> 20162 <file name="protractor.js" type="protractor"> 20163 it('should check both checkBoxes', function() { 20164 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 20165 element(by.model('master')).click(); 20166 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 20167 }); 20168 </file> 20169 </example> 20170 * 20171 * @element INPUT 20172 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 20173 * then the `checked` attribute will be set on the element 20174 */ 20175 20176 20177/** 20178 * @ngdoc directive 20179 * @name ngReadonly 20180 * @restrict A 20181 * @priority 100 20182 * 20183 * @description 20184 * 20185 * Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy. 20186 * 20187 * A special directive is necessary because we cannot use interpolation inside the `readOnly` 20188 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20189 * 20190 * @example 20191 <example> 20192 <file name="index.html"> 20193 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 20194 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 20195 </file> 20196 <file name="protractor.js" type="protractor"> 20197 it('should toggle readonly attr', function() { 20198 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 20199 element(by.model('checked')).click(); 20200 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 20201 }); 20202 </file> 20203 </example> 20204 * 20205 * @element INPUT 20206 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 20207 * then special attribute "readonly" will be set on the element 20208 */ 20209 20210 20211/** 20212 * @ngdoc directive 20213 * @name ngSelected 20214 * @restrict A 20215 * @priority 100 20216 * 20217 * @description 20218 * 20219 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. 20220 * 20221 * A special directive is necessary because we cannot use interpolation inside the `selected` 20222 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20223 * 20224 * @example 20225 <example> 20226 <file name="index.html"> 20227 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 20228 <select aria-label="ngSelected demo"> 20229 <option>Hello!</option> 20230 <option id="greet" ng-selected="selected">Greetings!</option> 20231 </select> 20232 </file> 20233 <file name="protractor.js" type="protractor"> 20234 it('should select Greetings!', function() { 20235 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 20236 element(by.model('selected')).click(); 20237 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 20238 }); 20239 </file> 20240 </example> 20241 * 20242 * @element OPTION 20243 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 20244 * then special attribute "selected" will be set on the element 20245 */ 20246 20247/** 20248 * @ngdoc directive 20249 * @name ngOpen 20250 * @restrict A 20251 * @priority 100 20252 * 20253 * @description 20254 * 20255 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. 20256 * 20257 * A special directive is necessary because we cannot use interpolation inside the `open` 20258 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 20259 * 20260 * @example 20261 <example> 20262 <file name="index.html"> 20263 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 20264 <details id="details" ng-open="open"> 20265 <summary>Show/Hide me</summary> 20266 </details> 20267 </file> 20268 <file name="protractor.js" type="protractor"> 20269 it('should toggle open', function() { 20270 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
20271 element(by.model('open')).click(); 20272 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 20273 }); 20274 </file> 20275 </example> 20276 * 20277 * @element DETAILS 20278 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 20279 * then special attribute "open" will be set on the element 20280 */ 20281 20282var ngAttributeAliasDirectives = {}; 20283 20284// boolean attrs are evaluated 20285forEach(BOOLEAN_ATTR, function(propName, attrName) { 20286 // binding to multiple is not supported 20287 if (propName == "multiple") return; 20288 20289 function defaultLinkFn(scope, element, attr) { 20290 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 20291 attr.$set(attrName, !!value); 20292 }); 20293 } 20294 20295 var normalized = directiveNormalize('ng-' + attrName); 20296 var linkFn = defaultLinkFn; 20297 20298 if (propName === 'checked') { 20299 linkFn = function(scope, element, attr) { 20300 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 20301 if (attr.ngModel !== attr[normalized]) { 20302 defaultLinkFn(scope, element, attr); 20303 } 20304 }; 20305 } 20306 20307 ngAttributeAliasDirectives[normalized] = function() { 20308 return { 20309 restrict: 'A', 20310 priority: 100, 20311 link: linkFn 20312 }; 20313 }; 20314}); 20315 20316// aliased input attrs are evaluated 20317forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 20318 ngAttributeAliasDirectives[ngAttr] = function() { 20319 return { 20320 priority: 100, 20321 link: function(scope, element, attr) { 20322 //special case ngPattern when a literal regular expression value 20323 //is used as the expression (this way we don't have to watch anything). 20324 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 20325 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 20326 if (match) { 20327 attr.$set("ngPattern", new RegExp(match[1], match[2])); 20328 return; 20329 } 20330 } 20331 20332 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 20333 attr.$set(ngAttr, value); 20334 }); 20335 } 20336 }; 20337 }; 20338}); 20339 20340// ng-src, ng-srcset, ng-href are interpolated 20341forEach(['src', 'srcset', 'href'], function(attrName) { 20342 var normalized = directiveNormalize('ng-' + attrName); 20343 ngAttributeAliasDirectives[normalized] = function() { 20344 return { 20345 priority: 99, // it needs to run after the attributes are interpolated 20346 link: function(scope, element, attr) { 20347 var propName = attrName, 20348 name = attrName; 20349 20350 if (attrName === 'href' && 20351 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 20352 name = 'xlinkHref'; 20353 attr.$attr[name] = 'xlink:href'; 20354 propName = null; 20355 } 20356 20357 attr.$observe(normalized, function(value) { 20358 if (!value) { 20359 if (attrName === 'href') { 20360 attr.$set(name, null); 20361 } 20362 return; 20363 } 20364 20365 attr.$set(name, value); 20366 20367 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 20368 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 20369 // to set the property as well to achieve the desired effect. 20370 // we use attr[attrName] value since $set can sanitize the url. 20371 if (msie && propName) element.prop(propName, attr[name]); 20372 }); 20373 } 20374 }; 20375 }; 20376}); 20377 20378/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 20379 */ 20380var nullFormCtrl = { 20381 $addControl: noop, 20382 $$renameControl: nullFormRenameControl, 20383 $removeControl: noop, 20384 $setValidity: noop, 20385 $setDirty: noop, 20386 $setPristine: noop, 20387 $setSubmitted: noop 20388}, 20389SUBMITTED_CLASS = 'ng-submitted'; 20390 20391function nullFormRenameControl(control, name) { 20392 control.$name = name; 20393} 20394 20395/** 20396 * @ngdoc type 20397 * @name form.FormController 20398 * 20399 * @property {boolean} $pristine True if user has not interacted with the form yet. 20400 * @property {boolean} $dirty True if user has already interacted with the form. 20401 * @property {boolean} $valid True if all of the containing forms and controls are valid. 20402 * @property {boolean} $invalid True if at least one containing control or form is invali
20402d. 20403 * @property {boolean} $pending True if at least one containing control or form is pending. 20404 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 20405 * 20406 * @property {Object} $error Is an object hash, containing references to controls or 20407 * forms with failing validators, where: 20408 * 20409 * - keys are validation tokens (error names), 20410 * - values are arrays of controls or forms that have a failing validator for given error name. 20411 * 20412 * Built-in validation tokens: 20413 * 20414 * - `email` 20415 * - `max` 20416 * - `maxlength` 20417 * - `min` 20418 * - `minlength` 20419 * - `number` 20420 * - `pattern` 20421 * - `required` 20422 * - `url` 20423 * - `date` 20424 * - `datetimelocal` 20425 * - `time` 20426 * - `week` 20427 * - `month` 20428 * 20429 * @description 20430 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 20431 * such as being valid/invalid or dirty/pristine. 20432 * 20433 * Each {@link ng.directive:form form} directive creates an instance 20434 * of `FormController`. 20435 * 20436 */ 20437//asks for $scope to fool the BC controller module 20438FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 20439function FormController(element, attrs, $scope, $animate, $interpolate) { 20440 var form = this, 20441 controls = []; 20442 20443 // init state 20444 form.$error = {}; 20445 form.$$success = {}; 20446 form.$pending = undefined; 20447 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 20448 form.$dirty = false; 20449 form.$pristine = true; 20450 form.$valid = true; 20451 form.$invalid = false; 20452 form.$submitted = false; 20453 form.$$parentForm = nullFormCtrl; 20454 20455 /** 20456 * @ngdoc method 20457 * @name form.FormController#$rollbackViewValue 20458 * 20459 * @description 20460 * Rollback all form controls pending updates to the `$modelValue`. 20461 * 20462 * Updates may be pending by a debounced event or because the input is waiting for a some future 20463 * event defined in `ng-model-options`. This method is typically needed by the reset button of 20464 * a form that uses `ng-model-options` to pend updates. 20465 */ 20466 form.$rollbackViewValue = function() { 20467 forEach(controls, function(control) { 20468 control.$rollbackViewValue(); 20469 }); 20470 }; 20471 20472 /** 20473 * @ngdoc method 20474 * @name form.FormController#$commitViewValue 20475 * 20476 * @description 20477 * Commit all form controls pending updates to the `$modelValue`. 20478 * 20479 * Updates may be pending by a debounced event or because the input is waiting for a some future 20480 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 20481 * usually handles calling this in response to input events. 20482 */ 20483 form.$commitViewValue = function() { 20484 forEach(controls, function(control) { 20485 control.$commitViewValue(); 20486 }); 20487 }; 20488 20489 /** 20490 * @ngdoc method 20491 * @name form.FormController#$addControl 20492 * @param {object} control control object, either a {@link form.FormController} or an 20493 * {@link ngModel.NgModelController} 20494 * 20495 * @description 20496 * Register a control with the form. Input elements using ngModelController do this automatically 20497 * when they are linked. 20498 * 20499 * Note that the current state of the control will not be reflected on the new parent form. This 20500 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 20501 * state. 20502 * 20503 * However, if the method is used programmatically, for example by adding dynamically created controls, 20504 * or controls that have been previously removed without destroying their corresponding DOM element, 20505 * it's the developers responsiblity to make sure the current state propagates to the parent form. 20506 * 20507 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 20508 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 20509 */ 20510 form.$addControl = function(control) { 20511 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 20512 // and not added to the scope. Now we throw an error. 20513 assertNotHasOwnProperty(control.$name, 'input'); 20514 controls.push(control); 20515 20516 if (control.$name) { 20517 form[control.$name] = control; 20518 } 20519 20520 control.$$parentForm = form; 20521 }; 20522 20523 // Private API: rename a form control 20524 form.$$renameControl = function(control, newName) { 20525 var oldName = control.$name; 20526 20527 if (form[oldName] === control) { 20528 delete form[oldName]; 20529 } 20530 form[newName] = control; 20531 control.$name = newName; 20532 }; 20533 20534 /** 20535 * @ngdoc method
20536 * @name form.FormController#$removeControl 20537 * @param {object} control control object, either a {@link form.FormController} or an 20538 * {@link ngModel.NgModelController} 20539 * 20540 * @description 20541 * Deregister a control from the form. 20542 * 20543 * Input elements using ngModelController do this automatically when they are destroyed. 20544 * 20545 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 20546 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 20547 * different from case to case. For example, removing the only `$dirty` control from a form may or 20548 * may not mean that the form is still `$dirty`. 20549 */ 20550 form.$removeControl = function(control) { 20551 if (control.$name && form[control.$name] === control) { 20552 delete form[control.$name]; 20553 } 20554 forEach(form.$pending, function(value, name) { 20555 form.$setValidity(name, null, control); 20556 }); 20557 forEach(form.$error, function(value, name) { 20558 form.$setValidity(name, null, control); 20559 }); 20560 forEach(form.$$success, function(value, name) { 20561 form.$setValidity(name, null, control); 20562 }); 20563 20564 arrayRemove(controls, control); 20565 control.$$parentForm = nullFormCtrl; 20566 }; 20567 20568 20569 /** 20570 * @ngdoc method 20571 * @name form.FormController#$setValidity 20572 * 20573 * @description 20574 * Sets the validity of a form control. 20575 * 20576 * This method will also propagate to parent forms. 20577 */ 20578 addSetValidityMethod({ 20579 ctrl: this, 20580 $element: element, 20581 set: function(object, property, controller) { 20582 var list = object[property]; 20583 if (!list) { 20584 object[property] = [controller]; 20585 } else { 20586 var index = list.indexOf(controller); 20587 if (index === -1) { 20588 list.push(controller); 20589 } 20590 } 20591 }, 20592 unset: function(object, property, controller) { 20593 var list = object[property]; 20594 if (!list) { 20595 return; 20596 } 20597 arrayRemove(list, controller); 20598 if (list.length === 0) { 20599 delete object[property]; 20600 } 20601 }, 20602 $animate: $animate 20603 }); 20604 20605 /** 20606 * @ngdoc method 20607 * @name form.FormController#$setDirty 20608 * 20609 * @description 20610 * Sets the form to a dirty state. 20611 * 20612 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 20613 * state (ng-dirty class). This method will also propagate to parent forms. 20614 */ 20615 form.$setDirty = function() { 20616 $animate.removeClass(element, PRISTINE_CLASS); 20617 $animate.addClass(element, DIRTY_CLASS); 20618 form.$dirty = true; 20619 form.$pristine = false; 20620 form.$$parentForm.$setDirty(); 20621 }; 20622 20623 /** 20624 * @ngdoc method 20625 * @name form.FormController#$setPristine 20626 * 20627 * @description 20628 * Sets the form to its pristine state. 20629 * 20630 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 20631 * state (ng-pristine class). This method will also propagate to all the controls contained 20632 * in this form. 20633 * 20634 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 20635 * saving or resetting it. 20636 */ 20637 form.$setPristine = function() { 20638 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 20639 form.$dirty = false; 20640 form.$pristine = true; 20641 form.$submitted = false; 20642 forEach(controls, function(control) { 20643 control.$setPristine(); 20644 }); 20645 }; 20646 20647 /** 20648 * @ngdoc method 20649 * @name form.FormController#$setUntouched 20650 * 20651 * @description 20652 * Sets the form to its untouched state. 20653 * 20654 * This method can be called to remove the 'ng-touched' class and set the form controls to their 20655 * untouched state (ng-untouched class). 20656 * 20657 * Setting a form controls back to their untouched state is often useful when setting the form 20658 * back to its pristine state. 20659 */ 20660 form.$setUntouched = function() { 20661 forEach(controls, function(control) { 20662 control.$setUntouched(); 20663 }); 20664 }; 20665 20666 /** 20667 * @ngdoc method 20668 * @name form.FormController#$setSubmitted 20669 * 20670 * @description 20671 * Sets the form to its submitted state. 20672 */ 20673 form.$setSubmitted = function() {
20674 $animate.addClass(element, SUBMITTED_CLASS); 20675 form.$submitted = true; 20676 form.$$parentForm.$setSubmitted(); 20677 }; 20678} 20679 20680/** 20681 * @ngdoc directive 20682 * @name ngForm 20683 * @restrict EAC 20684 * 20685 * @description 20686 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 20687 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 20688 * sub-group of controls needs to be determined. 20689 * 20690 * Note: the purpose of `ngForm` is to group controls, 20691 * but not to be a replacement for the `<form>` tag with all of its capabilities 20692 * (e.g. posting to the server, ...). 20693 * 20694 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 20695 * related scope, under this name. 20696 * 20697 */ 20698 20699 /** 20700 * @ngdoc directive 20701 * @name form 20702 * @restrict E 20703 * 20704 * @description 20705 * Directive that instantiates 20706 * {@link form.FormController FormController}. 20707 * 20708 * If the `name` attribute is specified, the form controller is published onto the current scope under 20709 * this name. 20710 * 20711 * # Alias: {@link ng.directive:ngForm `ngForm`} 20712 * 20713 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 20714 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 20715 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to 20716 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group 20717 * of controls needs to be determined. 20718 * 20719 * # CSS classes 20720 * - `ng-valid` is set if the form is valid. 20721 * - `ng-invalid` is set if the form is invalid. 20722 * - `ng-pending` is set if the form is pending. 20723 * - `ng-pristine` is set if the form is pristine. 20724 * - `ng-dirty` is set if the form is dirty. 20725 * - `ng-submitted` is set if the form was submitted. 20726 * 20727 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 20728 * 20729 * 20730 * # Submitting a form and preventing the default action 20731 * 20732 * Since the role of forms in client-side Angular applications is different than in classical 20733 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 20734 * page reload that sends the data to the server. Instead some javascript logic should be triggered 20735 * to handle the form submission in an application-specific way. 20736 * 20737 * For this reason, Angular prevents the default action (form submission to the server) unless the 20738 * `<form>` element has an `action` attribute specified. 20739 * 20740 * You can use one of the following two ways to specify what javascript method should be called when 20741 * a form is submitted: 20742 * 20743 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 20744 * - {@link ng.directive:ngClick ngClick} directive on the first 20745 * button or input field of type submit (input[type=submit]) 20746 * 20747 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 20748 * or {@link ng.directive:ngClick ngClick} directives. 20749 * This is because of the following form submission rules in the HTML specification: 20750 * 20751 * - If a form has only one input field then hitting enter in this field triggers form submit 20752 * (`ngSubmit`) 20753 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 20754 * doesn't trigger submit 20755 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 20756 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 20757 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 20758 * 20759 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 20760 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 20761 * to have access to the updated model. 20762 * 20763 * ## Animation Hooks 20764 * 20765 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 20766 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 20767 * other validations that are performed within the form. Animations in ngForm are similar to how 20768 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 20769 * as JS animations. 20770 * 20771 * The following example shows a simple way to utilize CSS transitions to style a form element
20772 * that has been rendered as invalid after it has been validated: 20773 * 20774 * <pre> 20775 * //be sure to include ngAnimate as a module to hook into more 20776 * //advanced animations 20777 * .my-form { 20778 * transition:0.5s linear all; 20779 * background: white; 20780 * } 20781 * .my-form.ng-invalid { 20782 * background: red; 20783 * color:white; 20784 * } 20785 * </pre> 20786 * 20787 * @example 20788 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 20789 <file name="index.html"> 20790 <script> 20791 angular.module('formExample', []) 20792 .controller('FormController', ['$scope', function($scope) { 20793 $scope.userType = 'guest'; 20794 }]); 20795 </script> 20796 <style> 20797 .my-form { 20798 transition:all linear 0.5s; 20799 background: transparent; 20800 } 20801 .my-form.ng-invalid { 20802 background: red; 20803 } 20804 </style> 20805 <form name="myForm" ng-controller="FormController" class="my-form"> 20806 userType: <input name="input" ng-model="userType" required> 20807 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 20808 <code>userType = {{userType}}</code><br> 20809 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 20810 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 20811 <code>myForm.$valid = {{myForm.$valid}}</code><br> 20812 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 20813 </form> 20814 </file> 20815 <file name="protractor.js" type="protractor"> 20816 it('should initialize to model', function() { 20817 var userType = element(by.binding('userType')); 20818 var valid = element(by.binding('myForm.input.$valid')); 20819 20820 expect(userType.getText()).toContain('guest'); 20821 expect(valid.getText()).toContain('true'); 20822 }); 20823 20824 it('should be invalid if empty', function() { 20825 var userType = element(by.binding('userType')); 20826 var valid = element(by.binding('myForm.input.$valid')); 20827 var userInput = element(by.model('userType')); 20828 20829 userInput.clear(); 20830 userInput.sendKeys(''); 20831 20832 expect(userType.getText()).toEqual('userType ='); 20833 expect(valid.getText()).toContain('false'); 20834 }); 20835 </file> 20836 </example> 20837 * 20838 * @param {string=} name Name of the form. If specified, the form controller will be published into 20839 * related scope, under this name. 20840 */ 20841var formDirectiveFactory = function(isNgForm) { 20842 return ['$timeout', '$parse', function($timeout, $parse) { 20843 var formDirective = { 20844 name: 'form', 20845 restrict: isNgForm ? 'EAC' : 'E', 20846 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 20847 controller: FormController, 20848 compile: function ngFormCompile(formElement, attr) { 20849 // Setup initial state of the control 20850 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 20851 20852 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 20853 20854 return { 20855 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 20856 var controller = ctrls[0]; 20857 20858 // if `action` attr is not present on the form, prevent the default action (submission) 20859 if (!('action' in attr)) { 20860 // we can't use jq events because if a form is destroyed during submission the default 20861 // action is not prevented. see #1238 20862 // 20863 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 20864 // page reload if the form was destroyed by submission of the form via a click handler 20865 // on a button in the form. Looks like an IE9 specific bug. 20866 var handleFormSubmission = function(event) { 20867 scope.$apply(function() { 20868 controller.$commitViewValue(); 20869 controller.$setSubmitted(); 20870 }); 20871 20872 event.preventDefault(); 20873 }; 20874 20875 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20876 20877 // unregister the preventDefault listener so that we don't not leak memory but in a 20878 // way that will achieve the prevention of the default action. 20879 formElement.on('$destroy', function() { 20880 $timeout(function() { 20881 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20882 }, 0, false); 20883 }); 20884 } 20885 20886 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 20887 parentFormCtrl.$addControl(controller); 20888 20889 var setter = nameAttr ? getSetter(controller.$name) : noop; 20890 20891 if (nameAttr) { 20892 setter(scope, controller); 20893 attr.$observe(nameAttr, function(newValue) { 20894 if (controller.$name === newValue) return; 20895 setter(scope, undefined); 20896 controller.$$parentForm.$$renameControl(controller, newValue); 20897 setter = getSetter(controller.$name); 20898 setter(scope, controller); 20899 }); 20900 } 20901 formElement.on('$destroy', function() { 20902 controller.$$parentForm.$removeControl(controller); 20903 setter(scope, undefined); 20904 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 20905 }); 20906 } 20907 }; 20908 } 20909 }; 20910 20911 return formDirective; 20912 20913 function getSetter(expression) { 20914 if (expression === '') { 20915 //create an assignable expression, so forms with an empty name can be renamed later 20916 return $parse('this[""]').assign; 20917 } 20918 return $parse(expression).assign || noop; 20919 } 20920 }]; 20921}; 20922 20923var formDirective = formDirectiveFactory(); 20924var ngFormDirective = formDirectiveFactory(true); 20925 20926/* global VALID_CLASS: false, 20927 INVALID_CLASS: false, 20928 PRISTINE_CLASS: false, 20929 DIRTY_CLASS: false, 20930 UNTOUCHED_CLASS: false, 20931 TOUCHED_CLASS: false, 20932 ngModelMinErr: false, 20933*/ 20934 20935// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 20936var 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)/;
20937// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) 20938// Note: We are being more lenient, because browsers are too. 20939// 1. Scheme 20940// 2. Slashes 20941// 3. Username 20942// 4. Password 20943// 5. Hostname 20944// 6. Port 20945// 7. Path 20946// 8. Query 20947// 9. Fragment 20948// 1111111111111111 222 333333 44444 555555555555555555555555 666 77777777 8888888 999 20949var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; 20950var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 20951var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 20952var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; 20953var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20954var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; 20955var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; 20956var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20957 20958var inputType = { 20959 20960 /** 20961 * @ngdoc input 20962 * @name input[text] 20963 * 20964 * @description 20965 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 20966 * 20967 * 20968 * @param {string} ngModel Assignable angular expression to data-bind to. 20969 * @param {string=} name Property name of the form under which the control is published. 20970 * @param {string=} required Adds `required` validation error key if the value is not entered. 20971 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20972 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20973 * `required` when you want to data-bind to the `required` attribute. 20974 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20975 * minlength. 20976 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20977 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 20978 * any length. 20979 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 20980 * that contains the regular expression body that will be converted to a regular expression 20981 * as in the ngPattern directive. 20982 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 20983 * a RegExp found by evaluating the Angular expression given in the attribute value. 20984 * If the expression evaluates to a RegExp object, then this is used directly. 20985 * If the expression evaluates to a string, then it will be converted to a RegExp 20986 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 20987 * `new RegExp('^abc$')`.<br /> 20988 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 20989 * start at the index of the last search's match, thus not taking the whole input value into 20990 * account. 20991 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20992 * interaction with the input element. 20993 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 20994 * This parameter is ignored for input[type=password] controls, which will never trim the 20995 * input. 20996 * 20997 * @example 20998 <example name="text-input-directive" module="textInputExample"> 20999 <file name="index.html"> 21000 <script> 21001 angular.module('textInputExample', []) 21002 .controller('ExampleController', ['$scope', function($scope) { 21003 $scope.example = { 21004 text: 'guest', 21005 word: /^\s*\w*\s*$/ 21006 }; 21007 }]); 21008 </script>
21009 <form name="myForm" ng-controller="ExampleController"> 21010 <label>Single word: 21011 <input type="text" name="input" ng-model="example.text" 21012 ng-pattern="example.word" required ng-trim="false"> 21013 </label> 21014 <div role="alert"> 21015 <span class="error" ng-show="myForm.input.$error.required"> 21016 Required!</span> 21017 <span class="error" ng-show="myForm.input.$error.pattern"> 21018 Single word only!</span> 21019 </div> 21020 <tt>text = {{example.text}}</tt><br/> 21021 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21022 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21023 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21024 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21025 </form> 21026 </file> 21027 <file name="protractor.js" type="protractor"> 21028 var text = element(by.binding('example.text')); 21029 var valid = element(by.binding('myForm.input.$valid')); 21030 var input = element(by.model('example.text')); 21031 21032 it('should initialize to model', function() { 21033 expect(text.getText()).toContain('guest'); 21034 expect(valid.getText()).toContain('true'); 21035 }); 21036 21037 it('should be invalid if empty', function() { 21038 input.clear(); 21039 input.sendKeys(''); 21040 21041 expect(text.getText()).toEqual('text ='); 21042 expect(valid.getText()).toContain('false'); 21043 }); 21044 21045 it('should be invalid if multi word', function() { 21046 input.clear(); 21047 input.sendKeys('hello world'); 21048 21049 expect(valid.getText()).toContain('false'); 21050 }); 21051 </file> 21052 </example> 21053 */ 21054 'text': textInputType, 21055 21056 /** 21057 * @ngdoc input 21058 * @name input[date] 21059 * 21060 * @description 21061 * Input with date validation and transformation. In browsers that do not yet support 21062 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 21063 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 21064 * modern browsers do not yet support this input type, it is important to provide cues to users on the 21065 * expected input format via a placeholder or label. 21066 * 21067 * The model must always be a Date object, otherwise Angular will throw an error. 21068 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21069 * 21070 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21071 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21072 * 21073 * @param {string} ngModel Assignable angular expression to data-bind to. 21074 * @param {string=} name Property name of the form under which the control is published. 21075 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 21076 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 21077 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 21078 * constraint validation. 21079 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 21080 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 21081 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 21082 * constraint validation. 21083 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 21084 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21085 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 21086 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21087 * @param {string=} required Sets `required` validation error key if the value is not entered. 21088 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21089 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21090 * `required` when you want to data-bind to the `required` attribute. 21091 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21092 * interaction with the input element. 21093 * 21094 * @example 21095 <example name="date-input-directive" module="dateInputExample"> 21096 <file name="index.html"> 21097 <script> 21098 angular.module('dateInputExample', []) 21099 .controller('DateController', ['$scope', function($scope) { 21100 $scope.example = { 21101 value: new Date(2013, 9, 22) 21102 }; 21103 }]); 21104 </script> 21105 <form name="myForm" ng-controller="DateController as dateCtrl"> 21106 <label for="exampleInput">Pick a date in 2013:</label> 21107 <input type="date" id="exampleInput" name="input" ng-model="example.value" 21108 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 21109 <div role="alert">
21110 <span class="error" ng-show="myForm.input.$error.required"> 21111 Required!</span> 21112 <span class="error" ng-show="myForm.input.$error.date"> 21113 Not a valid date!</span> 21114 </div> 21115 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 21116 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21117 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21118 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21119 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21120 </form> 21121 </file> 21122 <file name="protractor.js" type="protractor"> 21123 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 21124 var valid = element(by.binding('myForm.input.$valid')); 21125 var input = element(by.model('example.value')); 21126 21127 // currently protractor/webdriver does not support 21128 // sending keys to all known HTML5 input controls 21129 // for various browsers (see https://github.com/angular/protractor/issues/562). 21130 function setInput(val) { 21131 // set the value of the element and force validation. 21132 var scr = "var ipt = document.getElementById('exampleInput'); " + 21133 "ipt.value = '" + val + "';" + 21134 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21135 browser.executeScript(scr); 21136 } 21137 21138 it('should initialize to model', function() { 21139 expect(value.getText()).toContain('2013-10-22'); 21140 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21141 }); 21142 21143 it('should be invalid if empty', function() { 21144 setInput(''); 21145 expect(value.getText()).toEqual('value ='); 21146 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21147 }); 21148 21149 it('should be invalid if over max', function() { 21150 setInput('2015-01-01'); 21151 expect(value.getText()).toContain(''); 21152 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21153 }); 21154 </file> 21155 </example> 21156 */ 21157 'date': createDateInputType('date', DATE_REGEXP, 21158 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 21159 'yyyy-MM-dd'), 21160 21161 /** 21162 * @ngdoc input 21163 * @name input[datetime-local] 21164 * 21165 * @description 21166 * Input with datetime validation and transformation. In browsers that do not yet support 21167 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21168 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 21169 * 21170 * The model must always be a Date object, otherwise Angular will throw an error. 21171 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21172 * 21173 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21174 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21175 * 21176 * @param {string} ngModel Assignable angular expression to data-bind to. 21177 * @param {string=} name Property name of the form under which the control is published. 21178 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21179 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 21180 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 21181 * Note that `min` will also add native HTML5 constraint validation. 21182 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21183 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 21184 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 21185 * Note that `max` will also add native HTML5 constraint validation. 21186 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 21187 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21188 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 21189 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21190 * @param {string=} required Sets `required` validation error key if the value is not entered. 21191 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21192 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21193 * `required` when you want to data-bind to the `required` attribute. 21194 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21195 * interaction with the input element. 21196 * 21197 * @example 21198 <example name="datetimelocal-input-directive" module="dateExample"> 21199 <file name="index.html"> 21200 <script> 21201 angular.module('dateExample', []) 21202 .controller('DateController', ['$scope', function($scope) { 21203 $scope.example = { 21204 value: new Date(2010, 11, 28, 14, 57) 21205 }; 21206 }]); 21207 </script> 21208 <form name="myForm" ng-controller="DateController as dateCtrl"> 21209 <label for="exampleInput">Pick a date between in 2013:</label> 21210 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 21211 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 21212 <div role="alert">
21213 <span class="error" ng-show="myForm.input.$error.required"> 21214 Required!</span> 21215 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 21216 Not a valid date!</span> 21217 </div> 21218 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 21219 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21220 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21221 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21222 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21223 </form> 21224 </file> 21225 <file name="protractor.js" type="protractor"> 21226 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 21227 var valid = element(by.binding('myForm.input.$valid')); 21228 var input = element(by.model('example.value')); 21229 21230 // currently protractor/webdriver does not support 21231 // sending keys to all known HTML5 input controls 21232 // for various browsers (https://github.com/angular/protractor/issues/562). 21233 function setInput(val) { 21234 // set the value of the element and force validation. 21235 var scr = "var ipt = document.getElementById('exampleInput'); " + 21236 "ipt.value = '" + val + "';" + 21237 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21238 browser.executeScript(scr); 21239 } 21240 21241 it('should initialize to model', function() { 21242 expect(value.getText()).toContain('2010-12-28T14:57:00'); 21243 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21244 }); 21245 21246 it('should be invalid if empty', function() { 21247 setInput(''); 21248 expect(value.getText()).toEqual('value ='); 21249 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21250 }); 21251 21252 it('should be invalid if over max', function() { 21253 setInput('2015-01-01T23:59:00'); 21254 expect(value.getText()).toContain(''); 21255 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21256 }); 21257 </file> 21258 </example> 21259 */ 21260 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 21261 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 21262 'yyyy-MM-ddTHH:mm:ss.sss'), 21263 21264 /** 21265 * @ngdoc input 21266 * @name input[time] 21267 * 21268 * @description 21269 * Input with time validation and transformation. In browsers that do not yet support 21270 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21271 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 21272 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 21273 * 21274 * The model must always be a Date object, otherwise Angular will throw an error. 21275 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21276 * 21277 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21278 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21279 * 21280 * @param {string} ngModel Assignable angular expression to data-bind to. 21281 * @param {string=} name Property name of the form under which the control is published. 21282 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21283 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 21284 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 21285 * native HTML5 constraint validation. 21286 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21287 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 21288 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 21289 * native HTML5 constraint validation. 21290 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 21291 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21292 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 21293 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21294 * @param {string=} required Sets `required` validation error key if the value is not entered. 21295 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21296 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21297 * `required` when you want to data-bind to the `required` attribute. 21298 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21299 * interaction with the input element. 21300 * 21301 * @example 21302 <example name="time-input-directive" module="timeExample"> 21303 <file name="index.html"> 21304 <script> 21305 angular.module('timeExample', []) 21306 .controller('DateController', ['$scope', function($scope) { 21307 $scope.example = { 21308 value: new Date(1970, 0, 1, 14, 57, 0) 21309 }; 21310 }]); 21311 </script> 21312 <form name="myForm" ng-controller="DateController as dateCtrl"> 21313 <label for="exampleInput">Pick a between 8am and 5pm:</label> 21314 <input type="time" id="exampleInput" name="input" ng-model="example.value" 21315 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 21316 <div role="alert">
21317 <span class="error" ng-show="myForm.input.$error.required"> 21318 Required!</span> 21319 <span class="error" ng-show="myForm.input.$error.time"> 21320 Not a valid date!</span> 21321 </div> 21322 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 21323 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21324 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21325 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21326 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21327 </form> 21328 </file> 21329 <file name="protractor.js" type="protractor"> 21330 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 21331 var valid = element(by.binding('myForm.input.$valid')); 21332 var input = element(by.model('example.value')); 21333 21334 // currently protractor/webdriver does not support 21335 // sending keys to all known HTML5 input controls 21336 // for various browsers (https://github.com/angular/protractor/issues/562). 21337 function setInput(val) { 21338 // set the value of the element and force validation. 21339 var scr = "var ipt = document.getElementById('exampleInput'); " + 21340 "ipt.value = '" + val + "';" + 21341 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21342 browser.executeScript(scr); 21343 } 21344 21345 it('should initialize to model', function() { 21346 expect(value.getText()).toContain('14:57:00'); 21347 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21348 }); 21349 21350 it('should be invalid if empty', function() { 21351 setInput(''); 21352 expect(value.getText()).toEqual('value ='); 21353 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21354 }); 21355 21356 it('should be invalid if over max', function() { 21357 setInput('23:59:00'); 21358 expect(value.getText()).toContain(''); 21359 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21360 }); 21361 </file> 21362 </example> 21363 */ 21364 'time': createDateInputType('time', TIME_REGEXP, 21365 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 21366 'HH:mm:ss.sss'), 21367 21368 /** 21369 * @ngdoc input 21370 * @name input[week] 21371 * 21372 * @description 21373 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 21374 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21375 * week format (yyyy-W##), for example: `2013-W02`. 21376 * 21377 * The model must always be a Date object, otherwise Angular will throw an error. 21378 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21379 * 21380 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21381 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21382 * 21383 * @param {string} ngModel Assignable angular expression to data-bind to. 21384 * @param {string=} name Property name of the form under which the control is published. 21385 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21386 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 21387 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 21388 * native HTML5 constraint validation. 21389 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21390 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 21391 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 21392 * native HTML5 constraint validation. 21393 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 21394 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21395 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 21396 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21397 * @param {string=} required Sets `required` validation error key if the value is not entered. 21398 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21399 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21400 * `required` when you want to data-bind to the `required` attribute. 21401 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21402 * interaction with the input element. 21403 * 21404 * @example 21405 <example name="week-input-directive" module="weekExample"> 21406 <file name="index.html"> 21407 <script> 21408 angular.module('weekExample', []) 21409 .controller('DateController', ['$scope', function($scope) { 21410 $scope.example = { 21411 value: new Date(2013, 0, 3) 21412 }; 21413 }]); 21414 </script> 21415 <form name="myForm" ng-controller="DateController as dateCtrl"> 21416 <label>Pick a date between in 2013: 21417 <input id="exampleInput" type="week" name="input" ng-model="example.value" 21418 placeholder="YYYY-W##" min="2012-W32" 21419 max="2013-W52" required /> 21420 </label> 21421 <div role="alert">
21422 <span class="error" ng-show="myForm.input.$error.required"> 21423 Required!</span> 21424 <span class="error" ng-show="myForm.input.$error.week"> 21425 Not a valid date!</span> 21426 </div> 21427 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 21428 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21429 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21430 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21431 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21432 </form> 21433 </file> 21434 <file name="protractor.js" type="protractor"> 21435 var value = element(by.binding('example.value | date: "yyyy-Www"')); 21436 var valid = element(by.binding('myForm.input.$valid')); 21437 var input = element(by.model('example.value')); 21438 21439 // currently protractor/webdriver does not support 21440 // sending keys to all known HTML5 input controls 21441 // for various browsers (https://github.com/angular/protractor/issues/562). 21442 function setInput(val) { 21443 // set the value of the element and force validation. 21444 var scr = "var ipt = document.getElementById('exampleInput'); " + 21445 "ipt.value = '" + val + "';" + 21446 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21447 browser.executeScript(scr); 21448 } 21449 21450 it('should initialize to model', function() { 21451 expect(value.getText()).toContain('2013-W01'); 21452 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21453 }); 21454 21455 it('should be invalid if empty', function() { 21456 setInput(''); 21457 expect(value.getText()).toEqual('value ='); 21458 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21459 }); 21460 21461 it('should be invalid if over max', function() { 21462 setInput('2015-W01'); 21463 expect(value.getText()).toContain(''); 21464 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21465 }); 21466 </file> 21467 </example> 21468 */ 21469 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 21470 21471 /** 21472 * @ngdoc input 21473 * @name input[month] 21474 * 21475 * @description 21476 * Input with month validation and transformation. In browsers that do not yet support 21477 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21478 * month format (yyyy-MM), for example: `2009-01`. 21479 * 21480 * The model must always be a Date object, otherwise Angular will throw an error. 21481 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21482 * If the model is not set to the first of the month, the next view to model update will set it 21483 * to the first of the month. 21484 * 21485 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21486 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21487 * 21488 * @param {string} ngModel Assignable angular expression to data-bind to. 21489 * @param {string=} name Property name of the form under which the control is published. 21490 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21491 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 21492 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 21493 * native HTML5 constraint validation. 21494 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21495 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 21496 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 21497 * native HTML5 constraint validation. 21498 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 21499 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21500 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 21501 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21502 21503 * @param {string=} required Sets `required` validation error key if the value is not entered. 21504 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21505 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21506 * `required` when you want to data-bind to the `required` attribute. 21507 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21508 * interaction with the input element. 21509 * 21510 * @example 21511 <example name="month-input-directive" module="monthExample"> 21512 <file name="index.html"> 21513 <script> 21514 angular.module('monthExample', []) 21515 .controller('DateController', ['$scope', function($scope) { 21516 $scope.example = { 21517 value: new Date(2013, 9, 1) 21518 }; 21519 }]); 21520 </script> 21521 <form name="myForm" ng-controller="DateController as dateCtrl"> 21522 <label for="exampleInput">Pick a month in 2013:</label> 21523 <input id="exampleInput" type="month" name="input" ng-model="example.value" 21524 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 21525 <div role="alert">
21526 <span class="error" ng-show="myForm.input.$error.required"> 21527 Required!</span> 21528 <span class="error" ng-show="myForm.input.$error.month"> 21529 Not a valid month!</span> 21530 </div> 21531 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 21532 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21533 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21534 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21535 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21536 </form> 21537 </file> 21538 <file name="protractor.js" type="protractor"> 21539 var value = element(by.binding('example.value | date: "yyyy-MM"')); 21540 var valid = element(by.binding('myForm.input.$valid')); 21541 var input = element(by.model('example.value')); 21542 21543 // currently protractor/webdriver does not support 21544 // sending keys to all known HTML5 input controls 21545 // for various browsers (https://github.com/angular/protractor/issues/562). 21546 function setInput(val) { 21547 // set the value of the element and force validation. 21548 var scr = "var ipt = document.getElementById('exampleInput'); " + 21549 "ipt.value = '" + val + "';" + 21550 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21551 browser.executeScript(scr); 21552 } 21553 21554 it('should initialize to model', function() { 21555 expect(value.getText()).toContain('2013-10'); 21556 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21557 }); 21558 21559 it('should be invalid if empty', function() { 21560 setInput(''); 21561 expect(value.getText()).toEqual('value ='); 21562 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21563 }); 21564 21565 it('should be invalid if over max', function() { 21566 setInput('2015-01'); 21567 expect(value.getText()).toContain(''); 21568 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21569 }); 21570 </file> 21571 </example> 21572 */ 21573 'month': createDateInputType('month', MONTH_REGEXP, 21574 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 21575 'yyyy-MM'), 21576 21577 /** 21578 * @ngdoc input 21579 * @name input[number] 21580 * 21581 * @description 21582 * Text input with number validation and transformation. Sets the `number` validation 21583 * error if not a valid number. 21584 * 21585 * <div class="alert alert-warning"> 21586 * The model must always be of type `number` otherwise Angular will throw an error. 21587 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 21588 * error docs for more information and an example of how to convert your model if necessary. 21589 * </div> 21590 * 21591 * ## Issues with HTML5 constraint validation 21592 * 21593 * In browsers that follow the 21594 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 21595 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 21596 * If a non-number is entered in the input, the browser will report the value as an empty string, 21597 * which means the view / model values in `ngModel` and subsequently the scope value 21598 * will also be an empty string. 21599 * 21600 * 21601 * @param {string} ngModel Assignable angular expression to data-bind to. 21602 * @param {string=} name Property name of the form under which the control is published. 21603 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21604 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21605 * @param {string=} required Sets `required` validation error key if the value is not entered. 21606 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21607 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21608 * `required` when you want to data-bind to the `required` attribute. 21609 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21610 * minlength. 21611 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21612 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21613 * any length. 21614 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21615 * that contains the regular expression body that will be converted to a regular expression 21616 * as in the ngPattern directive. 21617 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21618 * a RegExp found by evaluating the Angular expression given in the attribute value. 21619 * If the expression evaluates to a RegExp object, then this is used directly. 21620 * If the expression evaluates to a string, then it will be converted to a RegExp 21621 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21622 * `new RegExp('^abc$')`.<br /> 21623 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21624 * start at the index of the last search's match, thus not taking the whole input value into 21625 * account. 21626 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21627 * interaction with the input element. 21628 * 21629 * @example 21630 <example name="number-input-directive" module="numberExample"> 21631 <file name="index.html"> 21632 <script> 21633 angular.module('numberExample', [])
21634 .controller('ExampleController', ['$scope', function($scope) { 21635 $scope.example = { 21636 value: 12 21637 }; 21638 }]); 21639 </script> 21640 <form name="myForm" ng-controller="ExampleController"> 21641 <label>Number: 21642 <input type="number" name="input" ng-model="example.value" 21643 min="0" max="99" required> 21644 </label> 21645 <div role="alert"> 21646 <span class="error" ng-show="myForm.input.$error.required"> 21647 Required!</span> 21648 <span class="error" ng-show="myForm.input.$error.number"> 21649 Not valid number!</span> 21650 </div> 21651 <tt>value = {{example.value}}</tt><br/> 21652 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21653 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21654 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21655 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21656 </form> 21657 </file> 21658 <file name="protractor.js" type="protractor"> 21659 var value = element(by.binding('example.value')); 21660 var valid = element(by.binding('myForm.input.$valid')); 21661 var input = element(by.model('example.value')); 21662 21663 it('should initialize to model', function() { 21664 expect(value.getText()).toContain('12'); 21665 expect(valid.getText()).toContain('true'); 21666 }); 21667 21668 it('should be invalid if empty', function() { 21669 input.clear(); 21670 input.sendKeys(''); 21671 expect(value.getText()).toEqual('value ='); 21672 expect(valid.getText()).toContain('false'); 21673 }); 21674 21675 it('should be invalid if over max', function() { 21676 input.clear(); 21677 input.sendKeys('123'); 21678 expect(value.getText()).toEqual('value ='); 21679 expect(valid.getText()).toContain('false'); 21680 }); 21681 </file> 21682 </example> 21683 */ 21684 'number': numberInputType, 21685 21686 21687 /** 21688 * @ngdoc input 21689 * @name input[url] 21690 * 21691 * @description 21692 * Text input with URL validation. Sets the `url` validation error key if the content is not a 21693 * valid URL. 21694 * 21695 * <div class="alert alert-warning"> 21696 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 21697 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 21698 * the built-in validators (see the {@link guide/forms Forms guide}) 21699 * </div> 21700 * 21701 * @param {string} ngModel Assignable angular expression to data-bind to. 21702 * @param {string=} name Property name of the form under which the control is published. 21703 * @param {string=} required Sets `required` validation error key if the value is not entered. 21704 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21705 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21706 * `required` when you want to data-bind to the `required` attribute. 21707 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21708 * minlength. 21709 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21710 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21711 * any length. 21712 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21713 * that contains the regular expression body that will be converted to a regular expression 21714 * as in the ngPattern directive. 21715 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21716 * a RegExp found by evaluating the Angular expression given in the attribute value. 21717 * If the expression evaluates to a RegExp object, then this is used directly. 21718 * If the expression evaluates to a string, then it will be converted to a RegExp 21719 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21720 * `new RegExp('^abc$')`.<br /> 21721 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21722 * start at the index of the last search's match, thus not taking the whole input value into 21723 * account. 21724 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21725 * interaction with the input element. 21726 * 21727 * @example 21728 <example name="url-input-directive" module="urlExample"> 21729 <file name="index.html"> 21730 <script> 21731 angular.module('urlExample', [])
21732 .controller('ExampleController', ['$scope', function($scope) { 21733 $scope.url = { 21734 text: 'http://google.com' 21735 }; 21736 }]); 21737 </script> 21738 <form name="myForm" ng-controller="ExampleController"> 21739 <label>URL: 21740 <input type="url" name="input" ng-model="url.text" required> 21741 <label> 21742 <div role="alert"> 21743 <span class="error" ng-show="myForm.input.$error.required"> 21744 Required!</span> 21745 <span class="error" ng-show="myForm.input.$error.url"> 21746 Not valid url!</span> 21747 </div> 21748 <tt>text = {{url.text}}</tt><br/> 21749 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21750 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21751 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21752 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21753 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 21754 </form> 21755 </file> 21756 <file name="protractor.js" type="protractor"> 21757 var text = element(by.binding('url.text')); 21758 var valid = element(by.binding('myForm.input.$valid')); 21759 var input = element(by.model('url.text')); 21760 21761 it('should initialize to model', function() { 21762 expect(text.getText()).toContain('http://google.com'); 21763 expect(valid.getText()).toContain('true'); 21764 }); 21765 21766 it('should be invalid if empty', function() { 21767 input.clear(); 21768 input.sendKeys(''); 21769 21770 expect(text.getText()).toEqual('text ='); 21771 expect(valid.getText()).toContain('false'); 21772 }); 21773 21774 it('should be invalid if not url', function() { 21775 input.clear(); 21776 input.sendKeys('box'); 21777 21778 expect(valid.getText()).toContain('false'); 21779 }); 21780 </file> 21781 </example> 21782 */ 21783 'url': urlInputType, 21784 21785 21786 /** 21787 * @ngdoc input 21788 * @name input[email] 21789 * 21790 * @description 21791 * Text input with email validation. Sets the `email` validation error key if not a valid email 21792 * address. 21793 * 21794 * <div class="alert alert-warning"> 21795 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 21796 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 21797 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 21798 * </div> 21799 * 21800 * @param {string} ngModel Assignable angular expression to data-bind to. 21801 * @param {string=} name Property name of the form under which the control is published. 21802 * @param {string=} required Sets `required` validation error key if the value is not entered. 21803 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21804 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21805 * `required` when you want to data-bind to the `required` attribute. 21806 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21807 * minlength. 21808 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21809 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21810 * any length. 21811 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21812 * that contains the regular expression body that will be converted to a regular expression 21813 * as in the ngPattern directive. 21814 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21815 * a RegExp found by evaluating the Angular expression given in the attribute value. 21816 * If the expression evaluates to a RegExp object, then this is used directly. 21817 * If the expression evaluates to a string, then it will be converted to a RegExp 21818 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21819 * `new RegExp('^abc$')`.<br /> 21820 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21821 * start at the index of the last search's match, thus not taking the whole input value into 21822 * account. 21823 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21824 * interaction with the input element. 21825 * 21826 * @example 21827 <example name="email-input-directive" module="emailExample"> 21828 <file name="index.html"> 21829 <script> 21830 angular.module('emailExample', [])
21831 .controller('ExampleController', ['$scope', function($scope) { 21832 $scope.email = { 21833 text: '[email protected]' 21834 }; 21835 }]); 21836 </script> 21837 <form name="myForm" ng-controller="ExampleController"> 21838 <label>Email: 21839 <input type="email" name="input" ng-model="email.text" required> 21840 </label> 21841 <div role="alert"> 21842 <span class="error" ng-show="myForm.input.$error.required"> 21843 Required!</span> 21844 <span class="error" ng-show="myForm.input.$error.email"> 21845 Not valid email!</span> 21846 </div> 21847 <tt>text = {{email.text}}</tt><br/> 21848 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21849 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21850 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21851 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21852 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 21853 </form> 21854 </file> 21855 <file name="protractor.js" type="protractor"> 21856 var text = element(by.binding('email.text')); 21857 var valid = element(by.binding('myForm.input.$valid')); 21858 var input = element(by.model('email.text')); 21859 21860 it('should initialize to model', function() { 21861 expect(text.getText()).toContain('[email protected]'); 21862 expect(valid.getText()).toContain('true'); 21863 }); 21864 21865 it('should be invalid if empty', function() { 21866 input.clear(); 21867 input.sendKeys(''); 21868 expect(text.getText()).toEqual('text ='); 21869 expect(valid.getText()).toContain('false'); 21870 }); 21871 21872 it('should be invalid if not email', function() { 21873 input.clear(); 21874 input.sendKeys('xxx'); 21875 21876 expect(valid.getText()).toContain('false'); 21877 }); 21878 </file> 21879 </example> 21880 */ 21881 'email': emailInputType, 21882 21883 21884 /** 21885 * @ngdoc input 21886 * @name input[radio] 21887 * 21888 * @description 21889 * HTML radio button. 21890 * 21891 * @param {string} ngModel Assignable angular expression to data-bind to. 21892 * @param {string} value The value to which the `ngModel` expression should be set when selected. 21893 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 21894 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 21895 * @param {string=} name Property name of the form under which the control is published. 21896 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21897 * interaction with the input element. 21898 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio 21899 * is selected. Should be used instead of the `value` attribute if you need 21900 * a non-string `ngModel` (`boolean`, `array`, ...). 21901 * 21902 * @example 21903 <example name="radio-input-directive" module="radioExample"> 21904 <file name="index.html"> 21905 <script> 21906 angular.module('radioExample', []) 21907 .controller('ExampleController', ['$scope', function($scope) { 21908 $scope.color = { 21909 name: 'blue' 21910 }; 21911 $scope.specialValue = { 21912 "id": "12345", 21913 "value": "green" 21914 }; 21915 }]); 21916 </script>
21917 <form name="myForm" ng-controller="ExampleController"> 21918 <label> 21919 <input type="radio" ng-model="color.name" value="red"> 21920 Red 21921 </label><br/> 21922 <label> 21923 <input type="radio" ng-model="color.name" ng-value="specialValue"> 21924 Green 21925 </label><br/> 21926 <label> 21927 <input type="radio" ng-model="color.name" value="blue"> 21928 Blue 21929 </label><br/> 21930 <tt>color = {{color.name | json}}</tt><br/> 21931 </form> 21932 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 21933 </file> 21934 <file name="protractor.js" type="protractor"> 21935 it('should change state', function() { 21936 var color = element(by.binding('color.name')); 21937 21938 expect(color.getText()).toContain('blue'); 21939 21940 element.all(by.model('color.name')).get(0).click(); 21941 21942 expect(color.getText()).toContain('red'); 21943 }); 21944 </file> 21945 </example> 21946 */ 21947 'radio': radioInputType, 21948 21949 21950 /** 21951 * @ngdoc input 21952 * @name input[checkbox] 21953 * 21954 * @description 21955 * HTML checkbox. 21956 * 21957 * @param {string} ngModel Assignable angular expression to data-bind to. 21958 * @param {string=} name Property name of the form under which the control is published. 21959 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 21960 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 21961 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21962 * interaction with the input element. 21963 * 21964 * @example 21965 <example name="checkbox-input-directive" module="checkboxExample"> 21966 <file name="index.html"> 21967 <script> 21968 angular.module('checkboxExample', []) 21969 .controller('ExampleController', ['$scope', function($scope) {
21970 $scope.checkboxModel = { 21971 value1 : true, 21972 value2 : 'YES' 21973 }; 21974 }]); 21975 </script> 21976 <form name="myForm" ng-controller="ExampleController"> 21977 <label>Value1: 21978 <input type="checkbox" ng-model="checkboxModel.value1"> 21979 </label><br/> 21980 <label>Value2: 21981 <input type="checkbox" ng-model="checkboxModel.value2" 21982 ng-true-value="'YES'" ng-false-value="'NO'"> 21983 </label><br/> 21984 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 21985 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 21986 </form> 21987 </file> 21988 <file name="protractor.js" type="protractor"> 21989 it('should change state', function() { 21990 var value1 = element(by.binding('checkboxModel.value1')); 21991 var value2 = element(by.binding('checkboxModel.value2')); 21992 21993 expect(value1.getText()).toContain('true'); 21994 expect(value2.getText()).toContain('YES'); 21995 21996 element(by.model('checkboxModel.value1')).click(); 21997 element(by.model('checkboxModel.value2')).click(); 21998 21999 expect(value1.getText()).toContain('false'); 22000 expect(value2.getText()).toContain('NO'); 22001 }); 22002 </file> 22003 </example> 22004 */ 22005 'checkbox': checkboxInputType, 22006 22007 'hidden': noop, 22008 'button': noop, 22009 'submit': noop, 22010 'reset': noop, 22011 'file': noop 22012}; 22013 22014function stringBasedInputType(ctrl) { 22015 ctrl.$formatters.push(function(value) { 22016 return ctrl.$isEmpty(value) ? value : value.toString(); 22017 }); 22018} 22019 22020function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22021 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22022 stringBasedInputType(ctrl); 22023} 22024 22025function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22026 var type = lowercase(element[0].type); 22027 22028 // In composition mode, users are still inputing intermediate text buffer, 22029 // hold the listener until composition is done. 22030 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 22031 if (!$sniffer.android) { 22032 var composing = false; 22033 22034 element.on('compositionstart', function(data) { 22035 composing = true; 22036 }); 22037 22038 element.on('compositionend', function() { 22039 composing = false; 22040 listener(); 22041 }); 22042 } 22043 22044 var listener = function(ev) { 22045 if (timeout) { 22046 $browser.defer.cancel(timeout); 22047 timeout = null; 22048 } 22049 if (composing) return; 22050 var value = element.val(), 22051 event = ev && ev.type; 22052 22053 // By default we will trim the value 22054 // If the attribute ng-trim exists we will avoid trimming 22055 // If input type is 'password', the value is never trimmed 22056 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 22057 value = trim(value); 22058 } 22059 22060 // If a control is suffering from bad input (due to native validators), browsers discard its 22061 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 22062 // control's value is the same empty value twice in a row. 22063 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 22064 ctrl.$setViewValue(value, event); 22065 } 22066 }; 22067 22068 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 22069 // input event on backspace, delete or cut 22070 if ($sniffer.hasEvent('input')) { 22071 element.on('input', listener); 22072 } else { 22073 var timeout; 22074 22075 var deferListener = function(ev, input, origValue) { 22076 if (!timeout) { 22077 timeout = $browser.defer(function() { 22078 timeout = null; 22079 if (!input || input.value !== origValue) { 22080 listener(ev); 22081 } 22082 }); 22083 } 22084 }; 22085 22086 element.on('keydown', function(event) { 22087 var key = event.keyCode; 22088 22089 // ignore 22090 // command modifiers arrows 22091 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 22092 22093 deferListener(event, this, this.value); 22094 }); 22095 22096 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 22097 if ($sniffer.hasEvent('paste')) { 22098 element.on('paste cut', deferListener); 22099 } 22100 } 22101 22102 // if user paste into input using mouse on older browser 22103 // or form autocomplete on newer browser, we need "change" event to catch it 22104 element.on('change', listener); 22105 22106 ctrl.$render = function() { 22107 // Workaround for Firefox validation #12102. 22108 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 22109 if (element.val() !== value) { 22110 element.val(value); 22111 } 22112 }; 22113} 22114 22115function weekParser(isoWeek, existingDate) { 22116 if (isDate(isoWeek)) { 22117 return isoWeek; 22118 } 22119 22120 if (isString(isoWeek)) { 22121 WEEK_REGEXP.lastIndex = 0; 22122 var parts = WEEK_REGEXP.exec(isoWeek); 22123 if (parts) { 22124 var year = +parts[1], 22125 week = +parts[2], 22126 hours = 0, 22127 minutes = 0, 22128 seconds = 0, 22129 milliseconds = 0, 22130 firstThurs = getFirstThursdayOfYear(year), 22131 addDays = (week - 1) * 7; 22132 22133 if (existingDate) { 22134 hours = existingDate.getHours(); 22135 minutes = existingDate.getMinutes(); 22136 seconds = existingDate.getSeconds(); 22137 milliseconds = existingDate.getMilliseconds(); 22138 } 22139 22140 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 22141 } 22142 } 22143 22144 return NaN; 22145} 22146 22147function createDateParser(regexp, mapping) { 22148 return function(iso, date) { 22149 var parts, map; 22150 22151 if (isDate(iso)) { 22152 return iso; 22153 } 22154 22155 if (isString(iso)) { 22156 // When a date is JSON'ified to wraps itself inside of an extra 22157 // set of double quotes. This makes the date parsing code unable 22158 // to match the date string and parse it as a date. 22159 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 22160 iso = iso.substring(1, iso.length - 1); 22161 }
22162 if (ISO_DATE_REGEXP.test(iso)) { 22163 return new Date(iso); 22164 } 22165 regexp.lastIndex = 0; 22166 parts = regexp.exec(iso); 22167 22168 if (parts) { 22169 parts.shift(); 22170 if (date) { 22171 map = { 22172 yyyy: date.getFullYear(), 22173 MM: date.getMonth() + 1, 22174 dd: date.getDate(), 22175 HH: date.getHours(), 22176 mm: date.getMinutes(), 22177 ss: date.getSeconds(), 22178 sss: date.getMilliseconds() / 1000 22179 }; 22180 } else { 22181 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 22182 } 22183 22184 forEach(parts, function(part, index) { 22185 if (index < mapping.length) { 22186 map[mapping[index]] = +part; 22187 } 22188 }); 22189 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 22190 } 22191 } 22192 22193 return NaN; 22194 }; 22195} 22196 22197function createDateInputType(type, regexp, parseDate, format) { 22198 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 22199 badInputChecker(scope, element, attr, ctrl); 22200 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22201 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 22202 var previousDate; 22203 22204 ctrl.$$parserName = type; 22205 ctrl.$parsers.push(function(value) { 22206 if (ctrl.$isEmpty(value)) return null; 22207 if (regexp.test(value)) { 22208 // Note: We cannot read ctrl.$modelValue, as there might be a different 22209 // parser/formatter in the processing chain so that the model 22210 // contains some different data format! 22211 var parsedDate = parseDate(value, previousDate); 22212 if (timezone) { 22213 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 22214 } 22215 return parsedDate; 22216 } 22217 return undefined; 22218 }); 22219 22220 ctrl.$formatters.push(function(value) { 22221 if (value && !isDate(value)) { 22222 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 22223 } 22224 if (isValidDate(value)) { 22225 previousDate = value; 22226 if (previousDate && timezone) { 22227 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 22228 } 22229 return $filter('date')(value, format, timezone); 22230 } else { 22231 previousDate = null; 22232 return ''; 22233 } 22234 }); 22235 22236 if (isDefined(attr.min) || attr.ngMin) { 22237 var minVal; 22238 ctrl.$validators.min = function(value) { 22239 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 22240 }; 22241 attr.$observe('min', function(val) { 22242 minVal = parseObservedDateValue(val); 22243 ctrl.$validate(); 22244 }); 22245 } 22246 22247 if (isDefined(attr.max) || attr.ngMax) { 22248 var maxVal; 22249 ctrl.$validators.max = function(value) { 22250 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 22251 }; 22252 attr.$observe('max', function(val) { 22253 maxVal = parseObservedDateValue(val); 22254 ctrl.$validate(); 22255 }); 22256 } 22257 22258 function isValidDate(value) { 22259 // Invalid Date: getTime() returns NaN 22260 return value && !(value.getTime && value.getTime() !== value.getTime()); 22261 } 22262 22263 function parseObservedDateValue(val) { 22264 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; 22265 } 22266 }; 22267} 22268 22269function badInputChecker(scope, element, attr, ctrl) { 22270 var node = element[0]; 22271 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 22272 if (nativeValidation) { 22273 ctrl.$parsers.push(function(value) { 22274 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 22275 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): 22276 // - also sets validity.badInput (should only be validity.typeMismatch). 22277 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) 22278 // - can ignore this case as we can still read out the erroneous email... 22279 return validity.badInput && !validity.typeMismatch ? undefined : value; 22280 }); 22281 } 22282} 22283 22284function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22285 badInputChecker(scope, element, attr, ctrl); 22286 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22287 22288 ctrl.$$parserName = 'number'; 22289 ctrl.$parsers.push(function(value) { 22290 if (ctrl.$isEmpty(value)) return null; 22291 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 22292 return undefined; 22293 }); 22294 22295 ctrl.$formatters.push(function(value) { 22296 if (!ctrl.$isEmpty(value)) { 22297 if (!isNumber(value)) { 22298 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 22299 } 22300 value = value.toString(); 22301 } 22302 return value; 22303 }); 22304 22305 if (isDefined(attr.min) || attr.ngMin) { 22306 var minVal; 22307 ctrl.$validators.min = function(value) { 22308 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 22309 }; 22310 22311 attr.$observe('min', function(val) { 22312 if (isDefined(val) && !isNumber(val)) { 22313 val = parseFloat(val, 10); 22314 } 22315 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 22316 // TODO(matsko): implement validateLater to reduce number of validations 22317 ctrl.$validate(); 22318 }); 22319 } 22320 22321 if (isDefined(attr.max) || attr.ngMax) { 22322 var maxVal; 22323 ctrl.$validators.max = function(value) { 22324 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 22325 }; 22326 22327 attr.$observe('max', function(val) { 22328 if (isDefined(val) && !isNumber(val)) { 22329 val = parseFloat(val, 10); 22330 } 22331 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 22332 // TODO(matsko): implement validateLater to reduce number of validations 22333 ctrl.$validate(); 22334 }); 22335 } 22336} 22337 22338function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22339 // Note: no badInputChecker here by purpose as `url` is only a validation 22340 // in browsers, i.e. we can always read out input.value even if it is not val
22340id! 22341 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22342 stringBasedInputType(ctrl); 22343 22344 ctrl.$$parserName = 'url'; 22345 ctrl.$validators.url = function(modelValue, viewValue) { 22346 var value = modelValue || viewValue; 22347 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 22348 }; 22349} 22350 22351function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22352 // Note: no badInputChecker here by purpose as `url` is only a validation 22353 // in browsers, i.e. we can always read out input.value even if it is not valid! 22354 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22355 stringBasedInputType(ctrl); 22356 22357 ctrl.$$parserName = 'email'; 22358 ctrl.$validators.email = function(modelValue, viewValue) { 22359 var value = modelValue || viewValue; 22360 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 22361 }; 22362} 22363 22364function radioInputType(scope, element, attr, ctrl) { 22365 // make the name unique, if not defined 22366 if (isUndefined(attr.name)) { 22367 element.attr('name', nextUid()); 22368 } 22369 22370 var listener = function(ev) { 22371 if (element[0].checked) { 22372 ctrl.$setViewValue(attr.value, ev && ev.type); 22373 } 22374 }; 22375 22376 element.on('click', listener); 22377 22378 ctrl.$render = function() { 22379 var value = attr.value; 22380 element[0].checked = (value == ctrl.$viewValue); 22381 }; 22382 22383 attr.$observe('value', ctrl.$render); 22384} 22385 22386function parseConstantExpr($parse, context, name, expression, fallback) { 22387 var parseFn; 22388 if (isDefined(expression)) { 22389 parseFn = $parse(expression); 22390 if (!parseFn.constant) { 22391 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 22392 '`{1}`.', name, expression); 22393 } 22394 return parseFn(context); 22395 } 22396 return fallback; 22397} 22398 22399function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 22400 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 22401 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 22402 22403 var listener = function(ev) { 22404 ctrl.$setViewValue(element[0].checked, ev && ev.type); 22405 }; 22406 22407 element.on('click', listener); 22408 22409 ctrl.$render = function() { 22410 element[0].checked = ctrl.$viewValue; 22411 }; 22412 22413 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 22414 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 22415 // it to a boolean. 22416 ctrl.$isEmpty = function(value) { 22417 return value === false; 22418 }; 22419 22420 ctrl.$formatters.push(function(value) { 22421 return equals(value, trueValue); 22422 }); 22423 22424 ctrl.$parsers.push(function(value) { 22425 return value ? trueValue : falseValue; 22426 }); 22427} 22428 22429 22430/** 22431 * @ngdoc directive 22432 * @name textarea 22433 * @restrict E 22434 * 22435 * @description 22436 * HTML textarea element control with angular data-binding. The data-binding and validation 22437 * properties of this element are exactly the same as those of the 22438 * {@link ng.directive:input input element}. 22439 * 22440 * @param {string} ngModel Assignable angular expression to data-bind to. 22441 * @param {string=} name Property name of the form under which the control is published. 22442 * @param {string=} required Sets `required` validation error key if the value is not entered. 22443 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22444 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22445 * `required` when you want to data-bind to the `required` attribute. 22446 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22447 * minlength. 22448 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22449 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 22450 * length. 22451 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 22452 * a RegExp found by evaluating the Angular expression given in the attribute value. 22453 * If the expression evaluates to a RegExp object, then this is used directly. 22454 * If the expression evaluates to a string, then it will be converted to a RegExp 22455 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22456 * `new RegExp('^abc$')`.<br /> 22457 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22458 * start at the index of the last search's match, thus not taking the whole input value into 22459 * account. 22460 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22461 * interaction with the input element. 22462 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 22463 */ 22464 22465 22466/** 22467 * @ngdoc directive 22468 * @name input 22469 * @restrict E 22470 * 22471 * @description 22472 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 22473 * input state control, and validation. 22474 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 22475 * 22476 * <div class="alert alert-warning"> 22477 * **Note:** Not every feature offered is available for all input types. 22478 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 22479 * </div> 22480 * 22481 * @param {string} ngModel Assignable angular expression to data-bind to. 22482 * @param {string=} name Property name of the form under which the control is published. 22483 * @param {string=} required Sets `required` validation error key if the value is not entered. 22484 * @param {boolean=} ngRequired Sets `required` attribute if set to true 22485 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22486 * minlength. 22487 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22488 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 22489 * length. 22490 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 22491 * a RegExp found by evaluating the Angular expression given in the attribute value. 22492 * If the expression evaluates to a RegExp object, then this is used directly. 22493 * If the expression evaluates to a string, then it will be converted to a RegExp 22494 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22495 * `new RegExp('^abc$')`.<br /> 22496 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22497 * start at the index of the last search's match, thus not taking the whole input value into 22498 * account. 22499 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22500 * interaction with the input element. 22501 * @param {boolean=} [ngTrim=true] If set to false Angular
vendor: 4,842 bytes, lines 22501-22619
22501 will not automatically trim the input. 22502 * This parameter is ignored for input[type=password] controls, which will never trim the 22503 * input. 22504 * 22505 * @example 22506 <example name="input-directive" module="inputExample"> 22507 <file name="index.html"> 22508 <script> 22509 angular.module('inputExample', []) 22510 .controller('ExampleController', ['$scope', function($scope) { 22511 $scope.user = {name: 'guest', last: 'visitor'}; 22512 }]); 22513 </script> 22514 <div ng-controller="ExampleController"> 22515 <form name="myForm"> 22516 <label> 22517 User name: 22518 <input type="text" name="userName" ng-model="user.name" required> 22519 </label> 22520 <div role="alert"> 22521 <span class="error" ng-show="myForm.userName.$error.required"> 22522 Required!</span> 22523 </div> 22524 <label> 22525 Last name: 22526 <input type="text" name="lastName" ng-model="user.last" 22527 ng-minlength="3" ng-maxlength="10"> 22528 </label> 22529 <div role="alert"> 22530 <span class="error" ng-show="myForm.lastName.$error.minlength"> 22531 Too short!</span> 22532 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 22533 Too long!</span> 22534 </div> 22535 </form> 22536 <hr> 22537 <tt>user = {{user}}</tt><br/> 22538 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 22539 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 22540 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 22541 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 22542 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22543 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22544 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 22545 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 22546 </div> 22547 </file> 22548 <file name="protractor.js" type="protractor"> 22549 var user = element(by.exactBinding('user')); 22550 var userNameValid = element(by.binding('myForm.userName.$valid')); 22551 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 22552 var lastNameError = element(by.binding('myForm.lastName.$error')); 22553 var formValid = element(by.binding('myForm.$valid')); 22554 var userNameInput = element(by.model('user.name')); 22555 var userLastInput = element(by.model('user.last')); 22556 22557 it('should initialize to model', function() { 22558 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 22559 expect(userNameValid.getText()).toContain('true'); 22560 expect(formValid.getText()).toContain('true'); 22561 }); 22562 22563 it('should be invalid if empty when required', function() { 22564 userNameInput.clear(); 22565 userNameInput.sendKeys(''); 22566 22567 expect(user.getText()).toContain('{"last":"visitor"}'); 22568 expect(userNameValid.getText()).toContain('false'); 22569 expect(formValid.getText()).toContain('false'); 22570 }); 22571 22572 it('should be valid if empty when min length is set', function() { 22573 userLastInput.clear(); 22574 userLastInput.sendKeys(''); 22575 22576 expect(user.getText()).toContain('{"name":"guest","last":""}'); 22577 expect(lastNameValid.getText()).toContain('true'); 22578 expect(formValid.getText()).toContain('true'); 22579 }); 22580 22581 it('should be invalid if less than required min length', function() { 22582 userLastInput.clear(); 22583 userLastInput.sendKeys('xx'); 22584 22585 expect(user.getText()).toContain('{"name":"guest"}'); 22586 expect(lastNameValid.getText()).toContain('false'); 22587 expect(lastNameError.getText()).toContain('minlength'); 22588 expect(formValid.getText()).toContain('false'); 22589 }); 22590 22591 it('should be invalid if longer than max length', function() { 22592 userLastInput.clear(); 22593 userLastInput.sendKeys('some ridiculously long name'); 22594 22595 expect(user.getText()).toContain('{"name":"guest"}'); 22596 expect(lastNameValid.getText()).toContain('false'); 22597 expect(lastNameError.getText()).toContain('maxlength'); 22598 expect(formValid.getText()).toContain('false'); 22599 }); 22600 </file> 22601 </example> 22602 */ 22603var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 22604 function($browser, $sniffer, $filter, $parse) { 22605 return { 22606 restrict: 'E', 22607 require: ['?ngModel'], 22608 link: { 22609 pre: function(scope, element, attr, ctrls) { 22610 if (ctrls[0]) { 22611 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 22612 $browser, $filter, $parse); 22613 } 22614 } 22615 } 22616 }; 22617}]; 22618 22619
22620 22621var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 22622/** 22623 * @ngdoc directive 22624 * @name ngValue 22625 * 22626 * @description 22627 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 22628 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 22629 * the bound value. 22630 * 22631 * `ngValue` is useful when dynamically generating lists of radio buttons using 22632 * {@link ngRepeat `ngRepeat`}, as shown below. 22633 * 22634 * Likewise, `ngValue` can be used to generate `<option>` elements for 22635 * the {@link select `select`} element. In that case however, only strings are supported 22636 * for the `value `attribute, so the resulting `ngModel` will always be a string. 22637 * Support for `select` models with non-string values is available via `ngOptions`. 22638 * 22639 * @element input 22640 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 22641 * of the `input` element 22642 * 22643 * @example 22644 <example name="ngValue-directive" module="valueExample"> 22645 <file name="index.html"> 22646 <script> 22647 angular.module('valueExample', []) 22648 .controller('ExampleController', ['$scope', function($scope) { 22649 $scope.names = ['pizza', 'unicorns', 'robots']; 22650 $scope.my = { favorite: 'unicorns' }; 22651 }]); 22652 </script>
22653 <form ng-controller="ExampleController"> 22654 <h2>Which is your favorite?</h2> 22655 <label ng-repeat="name in names" for="{{name}}"> 22656 {{name}} 22657 <input type="radio" 22658 ng-model="my.favorite" 22659 ng-value="name" 22660 id="{{name}}" 22661 name="favorite"> 22662 </label> 22663 <div>You chose {{my.favorite}}</div> 22664 </form> 22665 </file> 22666 <file name="protractor.js" type="protractor"> 22667 var favorite = element(by.binding('my.favorite')); 22668 22669 it('should initialize to model', function() { 22670 expect(favorite.getText()).toContain('unicorns'); 22671 }); 22672 it('should bind the values to the inputs', function() { 22673 element.all(by.model('my.favorite')).get(0).click(); 22674 expect(favorite.getText()).toContain('pizza'); 22675 }); 22676 </file> 22677 </example> 22678 */ 22679var ngValueDirective = function() { 22680 return { 22681 restrict: 'A', 22682 priority: 100, 22683 compile: function(tpl, tplAttr) { 22684 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 22685 return function ngValueConstantLink(scope, elm, attr) { 22686 attr.$set('value', scope.$eval(attr.ngValue)); 22687 }; 22688 } else { 22689 return function ngValueLink(scope, elm, attr) { 22690 scope.$watch(attr.ngValue, function valueWatchAction(value) { 22691 attr.$set('value', value); 22692 }); 22693 }; 22694 } 22695 } 22696 }; 22697}; 22698 22699/** 22700 * @ngdoc directive 22701 * @name ngBind 22702 * @restrict AC 22703 * 22704 * @description 22705 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 22706 * with the value of a given expression, and to update the text content when the value of that 22707 * expression changes. 22708 * 22709 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 22710 * `{{ expression }}` which is similar but less verbose. 22711 * 22712 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 22713 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 22714 * element attribute, it makes the bindings invisible to the user while the page is loading. 22715 * 22716 * An alternative solution to this problem would be using the 22717 * {@link ng.directive:ngCloak ngCloak} directive. 22718 * 22719 * 22720 * @element ANY 22721 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 22722 * 22723 * @example 22724 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 22725 <example module="bindExample"> 22726 <file name="index.html"> 22727 <script> 22728 angular.module('bindExample', []) 22729 .controller('ExampleController', ['$scope', function($scope) { 22730 $scope.name = 'Whirled'; 22731 }]); 22732 </script>
22733 <div ng-controller="ExampleController"> 22734 <label>Enter name: <input type="text" ng-model="name"></label><br> 22735 Hello <span ng-bind="name"></span>! 22736 </div> 22737 </file> 22738 <file name="protractor.js" type="protractor"> 22739 it('should check ng-bind', function() { 22740 var nameInput = element(by.model('name')); 22741 22742 expect(element(by.binding('name')).getText()).toBe('Whirled'); 22743 nameInput.clear(); 22744 nameInput.sendKeys('world'); 22745 expect(element(by.binding('name')).getText()).toBe('world'); 22746 }); 22747 </file> 22748 </example> 22749 */ 22750var ngBindDirective = ['$compile', function($compile) { 22751 return { 22752 restrict: 'AC', 22753 compile: function ngBindCompile(templateElement) { 22754 $compile.$$addBindingClass(templateElement); 22755 return function ngBindLink(scope, element, attr) { 22756 $compile.$$addBindingInfo(element, attr.ngBind); 22757 element = element[0]; 22758 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 22759 element.textContent = isUndefined(value) ? '' : value; 22760 }); 22761 }; 22762 } 22763 }; 22764}]; 22765 22766 22767/** 22768 * @ngdoc directive 22769 * @name ngBindTemplate 22770 * 22771 * @description 22772 * The `ngBindTemplate` directive specifies that the element 22773 * text content should be replaced with the interpolation of the template 22774 * in the `ngBindTemplate` attribute. 22775 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 22776 * expressions. This directive is needed since some HTML elements 22777 * (such as TITLE and OPTION) cannot contain SPAN elements. 22778 * 22779 * @element ANY 22780 * @param {string} ngBindTemplate template of form 22781 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 22782 * 22783 * @example 22784 * Try it here: enter text in text box and watch the greeting change. 22785 <example module="bindExample"> 22786 <file name="index.html"> 22787 <script> 22788 angular.module('bindExample', []) 22789 .controller('ExampleController', ['$scope', function($scope) { 22790 $scope.salutation = 'Hello'; 22791 $scope.name = 'World'; 22792 }]); 22793 </script>
22794 <div ng-controller="ExampleController"> 22795 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 22796 <label>Name: <input type="text" ng-model="name"></label><br> 22797 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 22798 </div> 22799 </file> 22800 <file name="protractor.js" type="protractor"> 22801 it('should check ng-bind', function() { 22802 var salutationElem = element(by.binding('salutation')); 22803 var salutationInput = element(by.model('salutation')); 22804 var nameInput = element(by.model('name')); 22805 22806 expect(salutationElem.getText()).toBe('Hello World!'); 22807 22808 salutationInput.clear(); 22809 salutationInput.sendKeys('Greetings'); 22810 nameInput.clear(); 22811 nameInput.sendKeys('user'); 22812 22813 expect(salutationElem.getText()).toBe('Greetings user!'); 22814 }); 22815 </file> 22816 </example> 22817 */ 22818var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 22819 return { 22820 compile: function ngBindTemplateCompile(templateElement) { 22821 $compile.$$addBindingClass(templateElement); 22822 return function ngBindTemplateLink(scope, element, attr) { 22823 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 22824 $compile.$$addBindingInfo(element, interpolateFn.expressions); 22825 element = element[0]; 22826 attr.$observe('ngBindTemplate', function(value) { 22827 element.textContent = isUndefined(value) ? '' : value; 22828 }); 22829 }; 22830 } 22831 }; 22832}]; 22833 22834 22835/** 22836 * @ngdoc directive 22837 * @name ngBindHtml 22838 * 22839 * @description 22840 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 22841 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 22842 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 22843 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 22844 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 22845 * 22846 * You may also bypass sanitization for values you know are safe. To do so, bind to 22847 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 22848 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 22849 * 22850 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 22851 * will have an exception (instead of an exploit.) 22852 * 22853 * @element ANY 22854 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 22855 * 22856 * @example 22857 22858 <example module="bindHtmlExample" deps="angular-sanitize.js"> 22859 <file name="index.html">
22860 <div ng-controller="ExampleController"> 22861 <p ng-bind-html="myHTML"></p> 22862 </div> 22863 </file> 22864 22865 <file name="script.js"> 22866 angular.module('bindHtmlExample', ['ngSanitize']) 22867 .controller('ExampleController', ['$scope', function($scope) { 22868 $scope.myHTML = 22869 'I am an <code>HTML</code>string with ' + 22870 '<a href="#">links!</a> and other <em>stuff</em>'; 22871 }]); 22872 </file> 22873 22874 <file name="protractor.js" type="protractor"> 22875 it('should check ng-bind-html', function() { 22876 expect(element(by.binding('myHTML')).getText()).toBe( 22877 'I am an HTMLstring with links! and other stuff'); 22878 }); 22879 </file> 22880 </example> 22881 */ 22882var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 22883 return { 22884 restrict: 'A', 22885 compile: function ngBindHtmlCompile(tElement, tAttrs) { 22886 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 22887 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 22888 return (value || '').toString(); 22889 }); 22890 $compile.$$addBindingClass(tElement); 22891 22892 return function ngBindHtmlLink(scope, element, attr) { 22893 $compile.$$addBindingInfo(element, attr.ngBindHtml); 22894 22895 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 22896 // we re-evaluate the expr because we want a TrustedValueHolderType 22897 // for $sce, not a string 22898 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 22899 }); 22900 }; 22901 } 22902 }; 22903}]; 22904 22905/** 22906 * @ngdoc directive 22907 * @name ngChange 22908 * 22909 * @description 22910 * Evaluate the given expression when the user changes the input. 22911 * The expression is evaluated immediately, unlike the JavaScript onchange event 22912 * which only triggers at the end of a change (usually, when the user leaves the 22913 * form element or presses the return key). 22914 * 22915 * The `ngChange` expression is only evaluated when a change in the input value causes 22916 * a new value to be committed to the model. 22917 * 22918 * It will not be evaluated: 22919 * * if the value returned from the `$parsers` transformation pipeline has not changed 22920 * * if the input has continued to be invalid since the model will stay `null` 22921 * * if the model is changed programmatically and not by a change to the input value 22922 * 22923 * 22924 * Note, this directive requires `ngModel` to be present. 22925 * 22926 * @element input 22927 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 22928 * in input value. 22929 * 22930 * @example 22931 * <example name="ngChange-directive" module="changeExample"> 22932 * <file name="index.html"> 22933 * <script> 22934 * angular.module('changeExample', []) 22935 * .controller('ExampleController', ['$scope', function($scope) { 22936 * $scope.counter = 0; 22937 * $scope.change = function() { 22938 * $scope.counter++; 22939 * }; 22940 * }]); 22941 * </script>
22942 * <div ng-controller="ExampleController"> 22943 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 22944 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 22945 * <label for="ng-change-example2">Confirmed</label><br /> 22946 * <tt>debug = {{confirmed}}</tt><br/> 22947 * <tt>counter = {{counter}}</tt><br/> 22948 * </div> 22949 * </file> 22950 * <file name="protractor.js" type="protractor"> 22951 * var counter = element(by.binding('counter')); 22952 * var debug = element(by.binding('confirmed')); 22953 * 22954 * it('should evaluate the expression if changing from view', function() { 22955 * expect(counter.getText()).toContain('0'); 22956 * 22957 * element(by.id('ng-change-example1')).click(); 22958 * 22959 * expect(counter.getText()).toContain('1'); 22960 * expect(debug.getText()).toContain('true'); 22961 * }); 22962 * 22963 * it('should not evaluate the expression if changing from model', function() { 22964 * element(by.id('ng-change-example2')).click(); 22965 22966 * expect(counter.getText()).toContain('0'); 22967 * expect(debug.getText()).toContain('true'); 22968 * }); 22969 * </file> 22970 * </example> 22971 */ 22972var ngChangeDirective = valueFn({ 22973 restrict: 'A', 22974 require: 'ngModel', 22975 link: function(scope, element, attr, ctrl) { 22976 ctrl.$viewChangeListeners.push(function() { 22977 scope.$eval(attr.ngChange); 22978 }); 22979 } 22980}); 22981 22982function classDirective(name, selector) { 22983 name = 'ngClass' + name; 22984 return ['$animate', function($animate) { 22985 return { 22986 restrict: 'AC', 22987 link: function(scope, element, attr) { 22988 var oldVal; 22989 22990 scope.$watch(attr[name], ngClassWatchAction, true); 22991 22992 attr.$observe('class', function(value) { 22993 ngClassWatchAction(scope.$eval(attr[name])); 22994 }); 22995 22996 22997 if (name !== 'ngClass') { 22998 scope.$watch('$index', function($index, old$index) { 22999 // jshint bitwise: false 23000 var mod = $index & 1; 23001 if (mod !== (old$index & 1)) { 23002 var classes = arrayClasses(scope.$eval(attr[name])); 23003 mod === selector ? 23004 addClasses(classes) : 23005 removeClasses(classes); 23006 } 23007 }); 23008 } 23009 23010 function addClasses(classes) { 23011 var newClasses = digestClassCounts(classes, 1); 23012 attr.$addClass(newClasses); 23013 } 23014 23015 function removeClasses(classes) { 23016 var newClasses = digestClassCounts(classes, -1); 23017 attr.$removeClass(newClasses); 23018 } 23019 23020 function digestClassCounts(classes, count) { 23021 // Use createMap() to prevent class assumptions involving property 23022 // names in Object.prototype 23023 var classCounts = element.data('$classCounts') || createMap(); 23024 var classesToUpdate = []; 23025 forEach(classes, function(className) { 23026 if (count > 0 || classCounts[className]) { 23027 classCounts[className] = (classCounts[className] || 0) + count; 23028 if (classCounts[className] === +(count > 0)) { 23029 classesToUpdate.push(className); 23030 } 23031 } 23032 }); 23033 element.data('$classCounts', classCounts); 23034 return classesToUpdate.join(' '); 23035 } 23036 23037 function updateClasses(oldClasses, newClasses) { 23038 var toAdd = arrayDifference(newClasses, oldClasses); 23039 var toRemove = arrayDifference(oldClasses, newClasses); 23040 toAdd = digestClassCounts(toAdd, 1); 23041 toRemove = digestClassCounts(toRemove, -1); 23042 if (toAdd && toAdd.length) { 23043 $animate.addClass(element, toAdd); 23044 } 23045 if (toRemove && toRemove.length) { 23046 $animate.removeClass(element, toRemove); 23047 } 23048 } 23049 23050 function ngClassWatchAction(newVal) { 23051 if (selector === true || scope.$index % 2 === selector) { 23052 var newClasses = arrayClasses(newVal || []); 23053 if (!oldVal) { 23054 addClasses(newClasses); 23055 } else if (!equals(newVal,oldVal)) { 23056 var oldClasses = arrayClasses(oldVal);
23057 updateClasses(oldClasses, newClasses); 23058 } 23059 } 23060 oldVal = shallowCopy(newVal); 23061 } 23062 } 23063 }; 23064 23065 function arrayDifference(tokens1, tokens2) { 23066 var values = []; 23067 23068 outer: 23069 for (var i = 0; i < tokens1.length; i++) { 23070 var token = tokens1[i]; 23071 for (var j = 0; j < tokens2.length; j++) { 23072 if (token == tokens2[j]) continue outer; 23073 } 23074 values.push(token); 23075 } 23076 return values; 23077 } 23078 23079 function arrayClasses(classVal) { 23080 var classes = []; 23081 if (isArray(classVal)) { 23082 forEach(classVal, function(v) { 23083 classes = classes.concat(arrayClasses(v)); 23084 }); 23085 return classes; 23086 } else if (isString(classVal)) { 23087 return classVal.split(' '); 23088 } else if (isObject(classVal)) { 23089 forEach(classVal, function(v, k) { 23090 if (v) { 23091 classes = classes.concat(k.split(' ')); 23092 } 23093 }); 23094 return classes; 23095 } 23096 return classVal; 23097 } 23098 }]; 23099} 23100 23101/** 23102 * @ngdoc directive 23103 * @name ngClass 23104 * @restrict AC 23105 * 23106 * @description 23107 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 23108 * an expression that represents all classes to be added. 23109 * 23110 * The directive operates in three different ways, depending on which of three types the expression 23111 * evaluates to: 23112 * 23113 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 23114 * names. 23115 * 23116 * 2. If the expression evaluates to an object, then for each key-value pair of the 23117 * object with a truthy value the corresponding key is used as a class name. 23118 * 23119 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 23120 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 23121 * to give you more control over what CSS classes appear. See the code below for an example of this. 23122 * 23123 * 23124 * The directive won't add duplicate classes if a particular class was already set. 23125 * 23126 * When the expression changes, the previously added classes are removed and only then are the 23127 * new classes added. 23128 * 23129 * @animations 23130 * **add** - happens just before the class is applied to the elements 23131 * 23132 * **remove** - happens just before the class is removed from the element 23133 * 23134 * @element ANY 23135 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 23136 * of the evaluation can be a string representing space delimited class 23137 * names, an array, or a map of class names to boolean values. In the case of a map, the 23138 * names of the properties whose values are truthy will be added as css classes to the 23139 * element. 23140 * 23141 * @example Example that demonstrates basic bindings via ngClass directive. 23142 <example> 23143 <file name="index.html"> 23144 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 23145 <label> 23146 <input type="checkbox" ng-model="deleted"> 23147 deleted (apply "strike" class) 23148 </label><br> 23149 <label> 23150 <input type="checkbox" ng-model="important"> 23151 important (apply "bold" class) 23152 </label><br> 23153 <label> 23154 <input type="checkbox" ng-model="error"> 23155 error (apply "has-error" class) 23156 </label> 23157 <hr> 23158 <p ng-class="style">Using String Syntax</p> 23159 <input type="text" ng-model="style" 23160 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 23161 <hr> 23162 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 23163 <input ng-model="style1" 23164 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 23165 <input ng-model="style2" 23166 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 23167 <input ng-model="style3" 23168 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 23169 <hr> 23170 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 23171 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 23172 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 23173 </file> 23174 <file name="style.css"> 23175 .strike { 23176 text-decoration: line-through; 23177 } 23178 .bold { 23179 font-weight: bold; 23180 } 23181 .red { 23182 color: red; 23183 } 23184 .has-error { 23185 color: red; 23186 background-color: yellow; 23187 } 23188 .orange { 23189 color: orange; 23190 } 23191 </file> 23192 <file name="protractor.js" type="protractor"> 23193 var ps = element.all(by.css('p')); 23194 23195 it('should let you toggle the class', function() { 23196 23197 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 23198 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 23199
23200 element(by.model('important')).click(); 23201 expect(ps.first().getAttribute('class')).toMatch(/bold/); 23202 23203 element(by.model('error')).click(); 23204 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 23205 }); 23206 23207 it('should let you toggle string example', function() { 23208 expect(ps.get(1).getAttribute('class')).toBe(''); 23209 element(by.model('style')).clear(); 23210 element(by.model('style')).sendKeys('red'); 23211 expect(ps.get(1).getAttribute('class')).toBe('red'); 23212 }); 23213 23214 it('array example should have 3 classes', function() { 23215 expect(ps.get(2).getAttribute('class')).toBe(''); 23216 element(by.model('style1')).sendKeys('bold'); 23217 element(by.model('style2')).sendKeys('strike'); 23218 element(by.model('style3')).sendKeys('red'); 23219 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 23220 }); 23221 23222 it('array with map example should have 2 classes', function() { 23223 expect(ps.last().getAttribute('class')).toBe(''); 23224 element(by.model('style4')).sendKeys('bold'); 23225 element(by.model('warning')).click(); 23226 expect(ps.last().getAttribute('class')).toBe('bold orange'); 23227 }); 23228 </file> 23229 </example> 23230 23231 ## Animations 23232 23233 The example below demonstrates how to perform animations using ngClass. 23234 23235 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 23236 <file name="index.html"> 23237 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 23238 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 23239 <br> 23240 <span class="base-class" ng-class="myVar">Sample Text</span> 23241 </file> 23242 <file name="style.css"> 23243 .base-class { 23244 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23245 } 23246 23247 .base-class.my-class { 23248 color: red; 23249 font-size:3em; 23250 } 23251 </file> 23252 <file name="protractor.js" type="protractor"> 23253 it('should check ng-class', function() { 23254 expect(element(by.css('.base-class')).getAttribute('class')).not. 23255 toMatch(/my-class/); 23256 23257 element(by.id('setbtn')).click(); 23258 23259 expect(element(by.css('.base-class')).getAttribute('class')). 23260 toMatch(/my-class/); 23261 23262 element(by.id('clearbtn')).click(); 23263 23264 expect(element(by.css('.base-class')).getAttribute('class')).not. 23265 toMatch(/my-class/); 23266 }); 23267 </file> 23268 </example> 23269 23270 23271 ## ngClass and pre-existing CSS3 Transitions/Animations 23272 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 23273 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 23274 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 23275 to view the step by step details of {@link $animate#addClass $animate.addClass} and 23276 {@link $animate#removeClass $animate.removeClass}. 23277 */ 23278var ngClassDirective = classDirective('', true); 23279 23280/** 23281 * @ngdoc directive 23282 * @name ngClassOdd 23283 * @restrict AC 23284 * 23285 * @description 23286 * The `ngClassOdd` and `ngClassEven` directives work exactly as 23287 * {@link ng.directive:ngClass ngClass}, except they work in 23288 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 23289 * 23290 * This directive can be applied only within the scope of an 23291 * {@link ng.directive:ngRepeat ngRepeat}. 23292 * 23293 * @element ANY 23294 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 23295 * of the evaluation can be a string representing space delimited class names or an array. 23296 * 23297 * @example 23298 <example> 23299 <file name="index.html"> 23300 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 23301 <li ng-repeat="name in names"> 23302 <span ng-class-odd="'odd'" ng-class-even="'even'"> 23303 {{name}} 23304 </span> 23305 </li> 23306 </ol> 23307 </file> 23308 <file name="style.css"> 23309 .odd { 23310 color: red; 23311 } 23312 .even { 23313 color: blue; 23314 } 23315 </file> 23316 <file name="protractor.js" type="protractor"> 23317 it('should check ng-class-odd and ng-class-even', function() { 23318 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 23319 toMatch(/odd/); 23320 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 23321 toMatch(/even/); 23322 }); 23323 </file> 23324 </example> 23325 */ 23326var ngClassOddDirective = classDirective('Odd', 0); 23327 23328/** 23329 * @ngdoc directive 23330 * @name ngClassEven 23331 * @restrict AC 23332 * 23333 * @description 23334 * The `ngClassOdd` and `ngClassEven` directives work exactly as 23335 * {@link ng.directive:ngClass ngClass}, except they work in 23336 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 23337 * 23338 * This directive can be applied only within the scope of an 23339 * {@link ng.directive:ngRepeat ngRepeat}. 23340 * 23341 * @element ANY 23342 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 23343 * result of the evaluation can be a string representing space delimited class names or an array.
23344 * 23345 * @example 23346 <example> 23347 <file name="index.html"> 23348 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 23349 <li ng-repeat="name in names"> 23350 <span ng-class-odd="'odd'" ng-class-even="'even'"> 23351 {{name}} 23352 </span> 23353 </li> 23354 </ol> 23355 </file> 23356 <file name="style.css"> 23357 .odd { 23358 color: red; 23359 } 23360 .even { 23361 color: blue; 23362 } 23363 </file> 23364 <file name="protractor.js" type="protractor"> 23365 it('should check ng-class-odd and ng-class-even', function() { 23366 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 23367 toMatch(/odd/); 23368 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 23369 toMatch(/even/); 23370 }); 23371 </file> 23372 </example> 23373 */ 23374var ngClassEvenDirective = classDirective('Even', 1); 23375 23376/** 23377 * @ngdoc directive 23378 * @name ngCloak 23379 * @restrict AC 23380 * 23381 * @description 23382 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 23383 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 23384 * directive to avoid the undesirable flicker effect caused by the html template display. 23385 * 23386 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 23387 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 23388 * of the browser view. 23389 * 23390 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 23391 * `angular.min.js`. 23392 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 23393 * 23394 * ```css 23395 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 23396 * display: none !important; 23397 * } 23398 * ``` 23399 * 23400 * When this css rule is loaded by the browser, all html elements (including their children) that 23401 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 23402 * during the compilation of the template it deletes the `ngCloak` element attribute, making 23403 * the compiled element visible. 23404 * 23405 * For the best result, the `angular.js` script must be loaded in the head section of the html 23406 * document; alternatively, the css rule above must be included in the external stylesheet of the 23407 * application. 23408 * 23409 * @element ANY 23410 * 23411 * @example 23412 <example> 23413 <file name="index.html"> 23414 <div id="template1" ng-cloak>{{ 'hello' }}</div> 23415 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 23416 </file> 23417 <file name="protractor.js" type="protractor"> 23418 it('should remove the template directive and css class', function() { 23419 expect($('#template1').getAttribute('ng-cloak')). 23420 toBeNull(); 23421 expect($('#template2').getAttribute('ng-cloak')). 23422 toBeNull(); 23423 }); 23424 </file> 23425 </example> 23426 * 23427 */ 23428var ngCloakDirective = ngDirective({ 23429 compile: function(element, attr) { 23430 attr.$set('ngCloak', undefined); 23431 element.removeClass('ng-cloak'); 23432 } 23433}); 23434 23435/** 23436 * @ngdoc directive 23437 * @name ngController 23438 * 23439 * @description 23440 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 23441 * supports the principles behind the Model-View-Controller design pattern. 23442 * 23443 * MVC components in angular: 23444 * 23445 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 23446 * are accessed through bindings. 23447 * * View â The template (HTML with data bindings) that is rendered into the View. 23448 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 23449 * logic behind the application to decorate the scope with functions and values 23450 * 23451 * Note that you can also attach controllers to the DOM by declaring it in a route definition 23452 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 23453 * again using `ng-controller` in the template itself. This will cause the controller to be attached 23454 * and executed twice. 23455 * 23456 * @element ANY 23457 * @scope 23458 * @priority 500 23459 * @param {expression} ngController Name of a constructor function registered with the current 23460 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 23461 * that on the current scope evaluates to a constructor function. 23462 * 23463 * The controller instance can be published into a scope property by specifying 23464 * `ng-controller="as propertyName"`. 23465 * 23466 * If the current `$controllerProvider` is configured to use globals (via 23467 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 23468 * also be the name of a globally accessible constructor function (not recommended). 23469 * 23470 * @example 23471 * Here is a simple form for editing user contact information. Adding, remov
23471ing, clearing, and 23472 * greeting are methods declared on the controller (see source tab). These methods can 23473 * easily be called from the angular markup. Any changes to the data are automatically reflected 23474 * in the View without the need for a manual update. 23475 * 23476 * Two different declaration styles are included below: 23477 * 23478 * * one binds methods and properties directly onto the controller using `this`: 23479 * `ng-controller="SettingsController1 as settings"` 23480 * * one injects `$scope` into the controller: 23481 * `ng-controller="SettingsController2"` 23482 * 23483 * The second option is more common in the Angular community, and is generally used in boilerplates 23484 * and in this guide. However, there are advantages to binding properties directly to the controller 23485 * and avoiding scope. 23486 * 23487 * * Using `controller as` makes it obvious which controller you are accessing in the template when 23488 * multiple controllers apply to an element. 23489 * * If you are writing your controllers as classes you have easier access to the properties and 23490 * methods, which will appear on the scope, from inside the controller code. 23491 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 23492 * inheritance masking primitives. 23493 * 23494 * This example demonstrates the `controller as` syntax. 23495 * 23496 * <example name="ngControllerAs" module="controllerAsExample"> 23497 * <file name="index.html"> 23498 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 23499 * <label>Name: <input type="text" ng-model="settings.name"/></label> 23500 * <button ng-click="settings.greet()">greet</button><br/> 23501 * Contact: 23502 * <ul> 23503 * <li ng-repeat="contact in settings.contacts"> 23504 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 23505 * <option>phone</option> 23506 * <option>email</option> 23507 * </select> 23508 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 23509 * <button ng-click="settings.clearContact(contact)">clear</button> 23510 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 23511 * </li> 23512 * <li><button ng-click="settings.addContact()">add</button></li> 23513 * </ul> 23514 * </div> 23515 * </file> 23516 * <file name="app.js"> 23517 * angular.module('controllerAsExample', []) 23518 * .controller('SettingsController1', SettingsController1); 23519 * 23520 * function SettingsController1() { 23521 * this.name = "John Smith"; 23522 * this.contacts = [ 23523 * {type: 'phone', value: '408 555 1212'}, 23524 * {type: 'email', value: '[email protected]'} ]; 23525 * } 23526 * 23527 * SettingsController1.prototype.greet = function() { 23528 * alert(this.name); 23529 * }; 23530 * 23531 * SettingsController1.prototype.addContact = function() { 23532 * this.contacts.push({type: 'email', value: '[email protected]'}); 23533 * }; 23534 * 23535 * SettingsController1.prototype.removeContact = function(contactToRemove) { 23536 * var index = this.contacts.indexOf(contactToRemove); 23537 * this.contacts.splice(index, 1); 23538 * }; 23539 * 23540 * SettingsController1.prototype.clearContact = function(contact) { 23541 * contact.type = 'phone'; 23542 * contact.value = ''; 23543 * }; 23544 * </file> 23545 * <file name="protractor.js" type="protractor"> 23546 * it('should check controller as', function() { 23547 * var container = element(by.id('ctrl-as-exmpl')); 23548 * expect(container.element(by.model('settings.name')) 23549 * .getAttribute('value')).toBe('John Smith'); 23550 * 23551 * var firstRepeat = 23552 * container.element(by.repeater('contact in settings.contacts').row(0)); 23553 * var secondRepeat = 23554 * container.element(by.repeater('contact in settings.contacts').row(1)); 23555 * 23556 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23557 * .toBe('408 555 1212'); 23558 * 23559 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 23560 * .toBe('[email protected]'); 23561 * 23562 * firstRepeat.element(by.buttonText('clear')).click(); 23563 * 23564 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23565 * .toBe(''); 23566 * 23567 * container.element(by.buttonText('add')).click(); 23568 * 23569 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 23570 * .element(by.model('contact.value')) 23571 * .getAttribute('value')) 23572 * .toBe('[email protected]'); 23573 * }); 23574 * </file> 23575 * </example> 23576 * 23577 * This example demonstrates the "attach to `$scope`" style of controller. 23578 * 23579 * <example name="ngController" module="controllerExample"> 23580 * <file name="index.html"> 23581 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 23582 * <label>Name: <input type="text" ng-model="name"/></label> 23583 * <button ng-click="greet()">greet</button><br/> 23584 * Contact: 23585 * <ul> 23586 * <li ng-repeat="contact in contacts"> 23587 * <select ng-model="contact.type" id="select_{{$index}}"> 23588 * <option>phone</option> 23589 * <option>email</option> 23590 * </select> 23591 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 23592 * <button ng-click="clearContact(contact)">clear</button> 23593 * <button ng-click="removeContact(contact)">X</button> 23594 * </li> 23595 * <li>[ <button ng-click="addContact()">add</button> ]</li> 23596 * </ul> 23597 * </div> 23598 * </file> 23599 * <file name="app.js">
23600 * angular.module('controllerExample', []) 23601 * .controller('SettingsController2', ['$scope', SettingsController2]); 23602 * 23603 * function SettingsController2($scope) { 23604 * $scope.name = "John Smith"; 23605 * $scope.contacts = [ 23606 * {type:'phone', value:'408 555 1212'}, 23607 * {type:'email', value:'[email protected]'} ]; 23608 * 23609 * $scope.greet = function() { 23610 * alert($scope.name); 23611 * }; 23612 * 23613 * $scope.addContact = function() { 23614 * $scope.contacts.push({type:'email', value:'[email protected]'}); 23615 * }; 23616 * 23617 * $scope.removeContact = function(contactToRemove) { 23618 * var index = $scope.contacts.indexOf(contactToRemove); 23619 * $scope.contacts.splice(index, 1); 23620 * }; 23621 * 23622 * $scope.clearContact = function(contact) { 23623 * contact.type = 'phone'; 23624 * contact.value = ''; 23625 * }; 23626 * } 23627 * </file> 23628 * <file name="protractor.js" type="protractor"> 23629 * it('should check controller', function() { 23630 * var container = element(by.id('ctrl-exmpl')); 23631 * 23632 * expect(container.element(by.model('name')) 23633 * .getAttribute('value')).toBe('John Smith'); 23634 * 23635 * var firstRepeat = 23636 * container.element(by.repeater('contact in contacts').row(0)); 23637 * var secondRepeat = 23638 * container.element(by.repeater('contact in contacts').row(1)); 23639 * 23640 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23641 * .toBe('408 555 1212'); 23642 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 23643 * .toBe('[email protected]'); 23644 * 23645 * firstRepeat.element(by.buttonText('clear')).click(); 23646 * 23647 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23648 * .toBe(''); 23649 * 23650 * container.element(by.buttonText('add')).click(); 23651 * 23652 * expect(container.element(by.repeater('contact in contacts').row(2)) 23653 * .element(by.model('contact.value')) 23654 * .getAttribute('value')) 23655 * .toBe('[email protected]'); 23656 * }); 23657 * </file> 23658 *</example> 23659 23660 */ 23661var ngControllerDirective = [function() { 23662 return { 23663 restrict: 'A', 23664 scope: true, 23665 controller: '@', 23666 priority: 500 23667 }; 23668}]; 23669 23670/** 23671 * @ngdoc directive 23672 * @name ngCsp 23673 * 23674 * @element html 23675 * @description 23676 * 23677 * Angular has some features that can break certain 23678 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 23679 * 23680 * If you intend to implement these rules then you must tell Angular not to use these features. 23681 * 23682 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 23683 * 23684 * 23685 * The following rules affect Angular: 23686 * 23687 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions 23688 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30% 23689 * increase in the speed of evaluating Angular expressions. 23690 * 23691 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular 23692 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). 23693 * To make these directives work when a CSP rule is blocking inline styles, you must link to the 23694 * `angular-csp.css` in your HTML manually. 23695 * 23696 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval 23697 * and automatically deactivates this feature in the {@link $parse} service. This autodetection, 23698 * however, triggers a CSP error to be logged in the console: 23699 * 23700 * ``` 23701 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 23702 * script in the following Content Security Policy directive: "default-src 'self'". Note that 23703 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 23704 * ``` 23705 * 23706 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 23707 * directive on an element of the HTML document that appears before the `<script>` tag that loads 23708 * the `angular.js` file. 23709 * 23710 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 23711 * 23712 * You can specify which of the CSP related Angular features should be deactivated by providing 23713 * a value for the `ng-csp` attribute. The options are as follows: 23714 * 23715 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM 23716 *
23717 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings 23718 * 23719 * You can use these values in the following combinations: 23720 * 23721 * 23722 * * No declaration means that Angular will assume that you can do inline styles, but it will do 23723 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions 23724 * of Angular. 23725 * 23726 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline 23727 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions 23728 * of Angular. 23729 * 23730 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject 23731 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 23732 * 23733 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can 23734 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 23735 * 23736 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject 23737 * styles nor use eval, which is the same as an empty: ng-csp. 23738 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 23739 * 23740 * @example 23741 * This example shows how to apply the `ngCsp` directive to the `html` tag. 23742 ```html 23743 <!doctype html> 23744 <html ng-app ng-csp> 23745 ... 23746 ... 23747 </html> 23748 ``` 23749 * @example 23750 // Note: the suffix `.csp` in the example name triggers 23751 // csp mode in our http server! 23752 <example name="example.csp" module="cspExample" ng-csp="true"> 23753 <file name="index.html"> 23754 <div ng-controller="MainController as ctrl"> 23755 <div> 23756 <button ng-click="ctrl.inc()" id="inc">Increment</button> 23757 <span id="counter"> 23758 {{ctrl.counter}} 23759 </span> 23760 </div> 23761 23762 <div> 23763 <button ng-click="ctrl.evil()" id="evil">Evil</button> 23764 <span id="evilError"> 23765 {{ctrl.evilError}} 23766 </span> 23767 </div> 23768 </div> 23769 </file> 23770 <file name="script.js"> 23771 angular.module('cspExample', []) 23772 .controller('MainController', function() { 23773 this.counter = 0; 23774 this.inc = function() { 23775 this.counter++; 23776 }; 23777 this.evil = function() { 23778 // jshint evil:true 23779 try { 23780 eval('1+2'); 23781 } catch (e) { 23782 this.evilError = e.message; 23783 } 23784 }; 23785 }); 23786 </file> 23787 <file name="protractor.js" type="protractor"> 23788 var util, webdriver; 23789 23790 var incBtn = element(by.id('inc')); 23791 var counter = element(by.id('counter')); 23792 var evilBtn = element(by.id('evil')); 23793 var evilError = element(by.id('evilError')); 23794 23795 function getAndClearSevereErrors() { 23796 return browser.manage().logs().get('browser').then(function(browserLog) { 23797 return browserLog.filter(function(logEntry) { 23798 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 23799 }); 23800 }); 23801 } 23802 23803 function clearErrors() { 23804 getAndClearSevereErrors(); 23805 } 23806 23807 function expectNoErrors() { 23808 getAndClearSevereErrors().then(function(filteredLog) { 23809 expect(filteredLog.length).toEqual(0); 23810 if (filteredLog.length) { 23811 console.log('browser console errors: ' + util.inspect(filteredLog)); 23812 } 23813 }); 23814 } 23815 23816 function expectError(regex) { 23817 getAndClearSevereErrors().then(function(filteredLog) { 23818 var found = false; 23819 filteredLog.forEach(function(log) { 23820 if (log.message.match(regex)) { 23821 found = true; 23822 } 23823 }); 23824 if (!found) { 23825 throw new Error('expected an error that matches ' + regex); 23826 } 23827 }); 23828 } 23829 23830 beforeEach(function() { 23831 util = require('util'); 23832 webdriver = require('protractor/node_modules/selenium-webdriver'); 23833 }); 23834 23835 // For now, we only test on Chrome, 23836 // as Safari does not load the page with Protractor's injected scripts, 23837 // and Firefox webdriver always disables content security policy (#6358) 23838 if (browser.params.browser !== 'chrome') { 23839 return; 23840 } 23841 23842 it('should not report errors when the page is loaded', function() { 23843 // clear errors so we are not dependent on previous tests 23844 clearErrors(); 23845 // Need to reload the page as the page is already loaded when 23846 // we come here 23847 browser.driver.getCurrentUrl().then(function(url) { 23848 browser.get(url); 23849 }); 23850 expectNoErrors(); 23851 }); 23852 23853 it('should evaluate expressions', function() { 23854 expect(counter.getText()).toEqual('0'); 23855 incBtn.click(); 23856 expect(counter.getText()).toEqual('1'); 23857 expectNoErrors(); 23858 }); 23859 23860 it('should throw and report an error when using "eval"', function() { 23861 evilBtn.click(); 23862 expect(evilError.getText()).toMatch(/Content Security Policy/); 23863 expectError(/Content Security Policy/); 23864 }); 23865 </file> 23866 </example> 23867 */ 23868 23869// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23870// bootstrap the system (before $parse is instantiated), for this reason we just have 23871// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc 23872 23873/** 23874 * @ngdoc directive 23875 * @name ngClick 23876 * 23877 * @description 23878 * The ngClick directive allows you to specify custom behavior when 23879 * an element is clicked. 23880 * 23881 * @element ANY 23882 * @priority 0 23883 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 23884 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 23885 * 23886 * @example 23887 <example> 23888 <file name="index.html"> 23889 <button ng-click="count = count + 1" ng-init="count=0"> 23890 Increment 23891 </button> 23892 <span> 23893 count: {{count}} 23894 </span> 23895 </file> 23896 <file name="protractor.js" type="protractor"> 23897 it('should check ng-click', function() { 23898 expect(element(by.binding('count')).getText()).toMatch('0'); 23899 element(by.css('button')).click(); 23900 expect(element(by.binding('count')).getText()).toMatch('1'); 23901 }); 23902 </file> 23903 </example> 23904 */ 23905/* 23906 * A collection of directives that allows creation of custom event handlers that are defined as 23907 * angular expressions and are compiled and executed within the current scope. 23908 */ 23909var ngEventDirectives = {}; 23910 23911// For events that might fire synchronously during DOM manipulation 23912// we need to execute their event handlers asynchronously using $evalAsync, 23913// so that they are not executed in an inconsistent state. 23914var forceAsyncEvents = { 23915 'blur': true, 23916 'focus': true 23917}; 23918forEach( 23919 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 23920 function(eventName) { 23921 var directiveName = directiveNormalize('ng-' + eventName); 23922 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 23923 return { 23924 restrict: 'A', 23925 compile: function($element, attr) { 23926 // We expose the powerful $event object on the scope that provides access to the Window, 23927 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 23928 // checks at the cost of speed since event handler expressions are not executed as 23929 // frequently as regular change detection. 23930 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 23931 return function ngEventHandler(scope, element) { 23932 element.on(eventName, function(event) { 23933 var callback = function() { 23934 fn(scope, {$event:event}); 23935 }; 23936 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 23937 scope.$evalAsync(callback); 23938 } else { 23939 scope.$apply(callback); 23940 } 23941 }); 23942 }; 23943 } 23944 }; 23945 }]; 23946 } 23947); 23948 23949/** 23950 * @ngdoc directive 23951 * @name ngDblclick 23952 * 23953 * @description 23954 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 23955 * 23956 * @element ANY 23957 * @priority 0 23958 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 23959 * a dblclick. (The Event object is available as `$event`) 23960 * 23961 * @example 23962 <example> 23963 <file name="index.html"> 23964 <button ng-dblclick="count = count + 1" ng-init="count=0"> 23965 Increment (on double click) 23966 </button> 23967 count: {{count}} 23968 </file> 23969 </example> 23970 */ 23971 23972 23973/** 23974 * @ngdoc directive 23975 * @name ngMousedown 23976 * 23977 * @description 23978 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 23979 * 23980 * @element ANY 23981 * @priority 0 23982 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 23983 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 23984 * 23985 * @example 23986 <example> 23987 <file name="index.html"> 23988 <button ng-mousedown="count = count + 1" ng-init="count=0"> 23989 Increment (on mouse down) 23990 </button> 23991 count: {{count}} 23992 </file> 23993 </example> 23994 */ 23995 23996 23997/** 23998 * @ngdoc directive 23999 * @name ngMouseup 24000 * 24001 * @description 24002 * Specify custom behavior on mouseup event. 24003 * 24004 * @element ANY 24005 * @priority 0 24006 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 24007 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 24008 * 24009 * @example 24010 <example> 24011 <file name="index.html"> 24012 <button ng-mouseup="count = count + 1" ng-init="count=0"> 24013 Increment (on mouse up) 24014 </button> 24015 count: {{count}} 24016 </file> 24017 </example> 24018 */ 24019 24020/** 24021 * @ngdoc directive 24022 * @name ngMouseover 24023 * 24024 * @description 24025 * Specify custom behavior on mouseover event. 24026 * 24027 * @element ANY 24028 * @priority 0 24029 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 24030 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 24031 * 24032 * @example 24033 <example> 24034 <file name="index.html"> 24035 <button ng-mouseover="count = count + 1" ng-init="count=0"> 24036 Increment (when mouse is over) 24037 </button> 24038 count: {{count}} 24039 </file> 24040 </example> 24041 */ 24042 24043 24044/** 24045 * @ngdoc directive 24046 * @name ngMouseenter 24047 * 24048 * @description 24049 * Specify custom behavior on mouseenter event. 24050 * 24051 * @element ANY 24052 * @priority 0 24053 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 24054 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 24055 * 24056 * @example 24057 <example> 24058 <file name="index.html"> 24059 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 24060 Increment (when mouse enters) 24061 </button> 24062 count: {{count}} 24063 </file> 24064 </example> 24065 */ 24066 24067 24068/** 24069 * @ngdoc directive 24070 * @name ngMouseleave 24071 * 24072 * @description 24073 * Specify custom behavior on mouseleave event. 24074 * 24075 * @element ANY 24076 * @priority 0 24077 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 24078 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 24079 * 24080 * @example 24081 <example> 24082 <file name="index.html"> 24083 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 24084 Increment (when mouse leaves) 24085 </button> 24086 count: {{count}} 24087 </file> 24088 </example> 24089 */ 24090 24091 24092/** 24093 * @ngdoc directive 24094 * @name ngMousemove 24095 * 24096 * @description 24097 * Specify custom behavior on mousemove event. 24098 * 24099 * @element ANY 24100 * @priority 0 24101 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 24102 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 24103 * 24104 * @example 24105 <example> 24106 <file name="index.html"> 24107 <button ng-mousemove="count = count + 1" ng-init="count=0"> 24108 Increment (when mouse moves) 24109 </button> 24110 count: {{count}} 24111 </file> 24112 </example> 24113 */ 24114 24115 24116/** 24117 * @ngdoc directive 24118 * @name ngKeydown 24119 * 24120 * @description 24121 * Specify custom behavior on keydown event. 24122 * 24123 * @element ANY 24124 * @priority 0 24125 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 24126 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 24127 * 24128 * @example 24129 <example> 24130 <file name="index.html"> 24131 <input ng-keydown="count = count + 1" ng-init="count=0"> 24132 key down count: {{count}} 24133 </file> 24134 </example> 24135 */ 24136 24137 24138/** 24139 * @ngdoc directive 24140 * @name ngKeyup 24141 * 24142 * @description 24143 * Specify custom behavior on keyup event. 24144 * 24145 * @element ANY 24146 * @priority 0 24147 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 24148 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 24149 * 24150 * @example 24151 <example> 24152 <file name="index.html"> 24153 <p>Typing in the input box below updates the key count</p> 24154 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 24155 24156 <p>Typing in the input box below updates the keycode</p> 24157 <input ng-keyup="event=$event"> 24158 <p>event keyCode: {{ event.keyCode }}</p> 24159 <p>event altKey: {{ event.altKey }}</p> 24160 </file> 24161 </example> 24162 */ 24163 24164 24165/** 24166 * @ngdoc directive 24167 * @name ngKeypress 24168 * 24169 * @description 24170 * Specify custom behavior on keypress event. 24171 * 24172 * @element ANY 24173 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 24174 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 24175 * and can be interrogated for keyCode, altKey, etc.) 24176 * 24177 * @example 24178 <example> 24179 <file name="index.html"> 24180 <input ng-keypress="count = count + 1" ng-init="count=0"> 24181 key press count: {{count}} 24182 </file> 24183 </example> 24184 */ 24185 24186 24187/** 24188 * @ngdoc directive 24189 * @name ngSubmit 24190 * 24191 * @description 24192 * Enables binding angular expressions to onsubmit events. 24193 * 24194 * Additionally it prevents the default action (which for form means
24194sending the request to the 24195 * server and reloading the current page), but only if the form does not contain `action`, 24196 * `data-action`, or `x-action` attributes. 24197 * 24198 * <div class="alert alert-warning"> 24199 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 24200 * `ngSubmit` handlers together. See the 24201 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 24202 * for a detailed discussion of when `ngSubmit` may be triggered. 24203 * </div> 24204 * 24205 * @element form 24206 * @priority 0 24207 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 24208 * ({@link guide/expression#-event- Event object is available as `$event`}) 24209 * 24210 * @example 24211 <example module="submitExample"> 24212 <file name="index.html"> 24213 <script> 24214 angular.module('submitExample', []) 24215 .controller('ExampleController', ['$scope', function($scope) { 24216 $scope.list = []; 24217 $scope.text = 'hello'; 24218 $scope.submit = function() { 24219 if ($scope.text) { 24220 $scope.list.push(this.text); 24221 $scope.text = ''; 24222 } 24223 }; 24224 }]); 24225 </script>
24226 <form ng-submit="submit()" ng-controller="ExampleController"> 24227 Enter text and hit enter: 24228 <input type="text" ng-model="text" name="text" /> 24229 <input type="submit" id="submit" value="Submit" /> 24230 <pre>list={{list}}</pre> 24231 </form> 24232 </file> 24233 <file name="protractor.js" type="protractor"> 24234 it('should check ng-submit', function() { 24235 expect(element(by.binding('list')).getText()).toBe('list=[]'); 24236 element(by.css('#submit')).click(); 24237 expect(element(by.binding('list')).getText()).toContain('hello'); 24238 expect(element(by.model('text')).getAttribute('value')).toBe(''); 24239 }); 24240 it('should ignore empty strings', function() { 24241 expect(element(by.binding('list')).getText()).toBe('list=[]'); 24242 element(by.css('#submit')).click(); 24243 element(by.css('#submit')).click(); 24244 expect(element(by.binding('list')).getText()).toContain('hello'); 24245 }); 24246 </file> 24247 </example> 24248 */ 24249 24250/** 24251 * @ngdoc directive 24252 * @name ngFocus 24253 * 24254 * @description 24255 * Specify custom behavior on focus event. 24256 * 24257 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 24258 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 24259 * during an `$apply` to ensure a consistent state. 24260 * 24261 * @element window, input, select, textarea, a 24262 * @priority 0 24263 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 24264 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 24265 * 24266 * @example 24267 * See {@link ng.directive:ngClick ngClick} 24268 */ 24269 24270/** 24271 * @ngdoc directive 24272 * @name ngBlur 24273 * 24274 * @description 24275 * Specify custom behavior on blur event. 24276 * 24277 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 24278 * an element has lost focus. 24279 * 24280 * Note: As the `blur` event is executed synchronously also during DOM manipulations 24281 * (e.g. removing a focussed input), 24282 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 24283 * during an `$apply` to ensure a consistent state. 24284 * 24285 * @element window, input, select, textarea, a 24286 * @priority 0 24287 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 24288 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 24289 * 24290 * @example 24291 * See {@link ng.directive:ngClick ngClick} 24292 */ 24293 24294/** 24295 * @ngdoc directive 24296 * @name ngCopy 24297 * 24298 * @description 24299 * Specify custom behavior on copy event. 24300 * 24301 * @element window, input, select, textarea, a 24302 * @priority 0 24303 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 24304 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 24305 * 24306 * @example 24307 <example> 24308 <file name="index.html"> 24309 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 24310 copied: {{copied}} 24311 </file> 24312 </example> 24313 */ 24314 24315/** 24316 * @ngdoc directive 24317 * @name ngCut 24318 * 24319 * @description 24320 * Specify custom behavior on cut event. 24321 * 24322 * @element window, input, select, textarea, a 24323 * @priority 0 24324 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 24325 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 24326 * 24327 * @example 24328 <example> 24329 <file name="index.html"> 24330 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 24331 cut: {{cut}} 24332 </file> 24333 </example> 24334 */ 24335 24336/** 24337 * @ngdoc directive 24338 * @name ngPaste 24339 * 24340 * @description 24341 * Specify custom behavior on paste event. 24342 * 24343 * @element window, input, select, textarea, a 24344 * @priority 0 24345 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 24346 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 24347 * 24348 * @example 24349 <example> 24350 <file name="index.html"> 24351 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 24352 pasted: {{paste}} 24353 </file> 24354 </example> 24355 */ 24356 24357/** 24358 * @ngdoc directive 24359 * @name ngIf 24360 * @restrict A 24361 * @multiElement 24362 * 24363 * @description 24364 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 24365 * {expression}. If the expression assigned to `ngIf` evaluates to a false 24366 * value then the element is removed from the DOM, otherwise a clone of the 24367 * element is reinserted into the DOM. 24368 * 24369 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 24370 * element in the DOM rather than changing its visibility via the `display` css property. A common 24371 * case when this difference is significant is when using css selectors that rely on an element's 24372 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
24372s. 24373 * 24374 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 24375 * is created when the element is restored. The scope created within `ngIf` inherits from 24376 * its parent scope using 24377 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 24378 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 24379 * a javascript primitive defined in the parent scope. In this case any modifications made to the 24380 * variable within the child scope will override (hide) the value in the parent scope. 24381 * 24382 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 24383 * is if an element's class attribute is directly modified after it's compiled, using something like 24384 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 24385 * the added class will be lost because the original compiled state is used to regenerate the element. 24386 * 24387 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 24388 * and `leave` effects. 24389 * 24390 * @animations 24391 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container 24392 * leave - happens just before the `ngIf` contents are removed from the DOM 24393 * 24394 * @element ANY 24395 * @scope 24396 * @priority 600 24397 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 24398 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 24399 * element is added to the DOM tree. 24400 * 24401 * @example 24402 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 24403 <file name="index.html"> 24404 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 24405 Show when checked: 24406 <span ng-if="checked" class="animate-if"> 24407 This is removed when the checkbox is unchecked. 24408 </span> 24409 </file> 24410 <file name="animations.css"> 24411 .animate-if { 24412 background:white; 24413 border:1px solid black; 24414 padding:10px; 24415 } 24416 24417 .animate-if.ng-enter, .animate-if.ng-leave { 24418 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 24419 } 24420 24421 .animate-if.ng-enter, 24422 .animate-if.ng-leave.ng-leave-active { 24423 opacity:0; 24424 } 24425 24426 .animate-if.ng-leave, 24427 .animate-if.ng-enter.ng-enter-active { 24428 opacity:1; 24429 } 24430 </file> 24431 </example> 24432 */ 24433var ngIfDirective = ['$animate', function($animate) { 24434 return { 24435 multiElement: true, 24436 transclude: 'element', 24437 priority: 600, 24438 terminal: true, 24439 restrict: 'A', 24440 $$tlb: true, 24441 link: function($scope, $element, $attr, ctrl, $transclude) { 24442 var block, childScope, previousElements; 24443 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 24444 24445 if (value) { 24446 if (!childScope) { 24447 $transclude(function(clone, newScope) { 24448 childScope = newScope; 24449 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 24450 // Note: We only need the first/last node of the cloned nodes. 24451 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 24452 // by a directive with templateUrl when its template arrives. 24453 block = { 24454 clone: clone 24455 }; 24456 $animate.enter(clone, $element.parent(), $element); 24457 }); 24458 } 24459 } else { 24460 if (previousElements) { 24461 previousElements.remove(); 24462 previousElements = null; 24463 } 24464 if (childScope) { 24465 childScope.$destroy(); 24466 childScope = null; 24467 } 24468 if (block) { 24469 previousElements = getBlockNodes(block.clone); 24470 $animate.leave(previousElements).then(function() { 24471 previousElements = null; 24472 }); 24473 block = null; 24474 } 24475 } 24476 }); 24477 } 24478 }; 24479}]; 24480 24481/** 24482 * @ngdoc directive 24483 * @name ngInclude 24484 * @restrict ECA 24485 * 24486 * @description 24487 * Fetches, compiles and includes an external HTML fragment. 24488 * 24489 * By default, the template URL is restricted to the same domain and protocol as the 24490 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 24491 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 24492 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 24493 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 24494 * ng.$sce Strict Contextual Escaping}. 24495 * 24496 * In addition, the browser's 24497 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 24498 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 24499 * policy may further restrict whether the template is successfully loaded. 24500 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 24501 * access on some browsers. 24502 * 24503 * @animations 24504 * enter - animation is used to bring new content into the browser. 24505 * leave - animation is used to animate existing content away. 24506 * 24507 * The enter and leave animation occur concurrently. 24508 * 24509 * @scope 24510 * @priority 400 24511 * 24512 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 24513 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 24514 * @param {string=} onload Expression to evaluate when a new partial is loaded. 24515 * <div class="alert alert-warning"> 24516 * **Note:** When using onload on SVG elements in IE11, the browser will try to call 24517 * a function with the name on the window element, which will usually throw a 24518 * "function is undefined" error. To fix this, you can instead use `data-onload` or a 24519 * different form that {@link guide/directive#normalization matches} `onload`. 24520 * </div> 24521 * 24522 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 24523 * $anchorScroll} to scroll the viewport after the content is loaded. 24524 * 24525 * - If the attribute is not set, disable scrolling. 24526 * - If the attribute is set without value, enable scrolling. 24527 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 24528 * 24529 * @example 24530 <example module="includeExample" deps="angular-animate.js" animations="true"> 24531 <file name="index.html">
24532 <div ng-controller="ExampleController"> 24533 <select ng-model="template" ng-options="t.name for t in templates"> 24534 <option value="">(blank)</option> 24535 </select> 24536 url of the template: <code>{{template.url}}</code> 24537 <hr/> 24538 <div class="slide-animate-container"> 24539 <div class="slide-animate" ng-include="template.url"></div> 24540 </div> 24541 </div> 24542 </file> 24543 <file name="script.js"> 24544 angular.module('includeExample', ['ngAnimate']) 24545 .controller('ExampleController', ['$scope', function($scope) { 24546 $scope.templates = 24547 [ { name: 'template1.html', url: 'template1.html'}, 24548 { name: 'template2.html', url: 'template2.html'} ]; 24549 $scope.template = $scope.templates[0]; 24550 }]); 24551 </file> 24552 <file name="template1.html"> 24553 Content of template1.html 24554 </file> 24555 <file name="template2.html"> 24556 Content of template2.html 24557 </file> 24558 <file name="animations.css"> 24559 .slide-animate-container { 24560 position:relative; 24561 background:white; 24562 border:1px solid black; 24563 height:40px; 24564 overflow:hidden; 24565 } 24566 24567 .slide-animate { 24568 padding:10px; 24569 } 24570 24571 .slide-animate.ng-enter, .slide-animate.ng-leave { 24572 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 24573 24574 position:absolute; 24575 top:0; 24576 left:0; 24577 right:0; 24578 bottom:0; 24579 display:block; 24580 padding:10px; 24581 } 24582 24583 .slide-animate.ng-enter { 24584 top:-50px; 24585 } 24586 .slide-animate.ng-enter.ng-enter-active { 24587 top:0; 24588 } 24589 24590 .slide-animate.ng-leave { 24591 top:0; 24592 } 24593 .slide-animate.ng-leave.ng-leave-active { 24594 top:50px; 24595 } 24596 </file> 24597 <file name="protractor.js" type="protractor"> 24598 var templateSelect = element(by.model('template')); 24599 var includeElem = element(by.css('[ng-include]')); 24600 24601 it('should load template1.html', function() { 24602 expect(includeElem.getText()).toMatch(/Content of template1.html/); 24603 }); 24604 24605 it('should load template2.html', function() { 24606 if (browser.params.browser == 'firefox') { 24607 // Firefox can't handle using selects 24608 // See https://github.com/angular/protractor/issues/480 24609 return; 24610 } 24611 templateSelect.click(); 24612 templateSelect.all(by.css('option')).get(2).click(); 24613 expect(includeElem.getText()).toMatch(/Content of template2.html/); 24614 }); 24615 24616 it('should change to blank', function() { 24617 if (browser.params.browser == 'firefox') { 24618 // Firefox can't handle using selects 24619 return; 24620 } 24621 templateSelect.click(); 24622 templateSelect.all(by.css('option')).get(0).click(); 24623 expect(includeElem.isPresent()).toBe(false); 24624 }); 24625 </file> 24626 </example> 24627 */ 24628 24629 24630/** 24631 * @ngdoc event 24632 * @name ngInclude#$includeContentRequested 24633 * @eventType emit on the scope ngInclude was declared in 24634 * @description 24635 * Emitted every time the ngInclude content is requested. 24636 * 24637 * @param {Object} angularEvent Synthetic event object. 24638 * @param {String} src URL of content to load. 24639 */ 24640 24641 24642/** 24643 * @ngdoc event 24644 * @name ngInclude#$includeContentLoaded 24645 * @eventType emit on the current ngInclude scope 24646 * @description 24647 * Emitted every time the ngInclude content is reloaded. 24648 * 24649 * @param {Object} angularEvent Synthetic event object. 24650 * @param {String} src URL of content to load. 24651 */ 24652 24653 24654/** 24655 * @ngdoc event 24656 * @name ngInclude#$includeContentError 24657 * @eventType emit on the scope ngInclude was declared in 24658 * @description 24659 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 24660 * 24661 * @param {Object} angularEvent Synthetic event object. 24662 * @param {String} src URL of content to load. 24663 */ 24664var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 24665 function($templateRequest, $anchorScroll, $animate) { 24666 return { 24667 restrict: 'ECA', 24668 priority: 400, 24669 terminal: true, 24670 transclude: 'element', 24671 controller: angular.noop, 24672 compile: function(element, attr) { 24673 var srcExp = attr.ngInclude || attr.src, 24674 onloadExp = attr.onload || '', 24675 autoScrollExp = attr.autoscroll; 24676 24677 return function(scope, $element, $attr, ctrl, $transclude) { 24678 var changeCounter = 0, 24679 currentScope, 24680 previousElement, 24681 currentElement; 24682 24683 var cleanupLastIncludeContent = function() { 24684 if (previousElement) { 24685 previousElement.remove(); 24686 previousElement = null; 24687 } 24688 if (currentScope) { 24689 currentScope.$destroy(); 24690 currentScope = null; 24691 } 24692 if (currentElement) { 24693 $animate.leave(currentElement).then(function() { 24694 previousElement = null; 24695 }); 24696 previousElement = currentElement; 24697 currentElement = null; 24698 } 24699 }; 24700 24701 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 24702 var afterAnimation = function() { 24703 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 24704 $anchorScroll(); 24705 } 24706 }; 24707 var thisChangeId = ++changeCounter; 24708 24709 if (src) { 24710 //set the 2nd param to true to ignore the template request error so that the inner 24711 //contents and scope can be cleaned up. 24712 $templateRequest(src, true).then(function(response) { 24713 if (scope.$$destroyed) return; 24714 24715 if (thisChangeId !== changeCounter) return; 24716 var newScope = scope.$new(); 24717 ctrl.template = response; 24718 24719 // Note: This will also link all children of ng-include that were contained in the original 24720 // html. If that content contains controllers, ... they could pollute/change the scope. 24721 // However, using ng-include on an element with additional content does not make sense... 24722 // Note: We can't remove them in the cloneAttchFn of $transclude as that 24723 // function is called before linking the content, which would apply child 24724 // directives to non existing elements. 24725 var clone = $transclude(newScope, function(clone) { 24726 cleanupLastIncludeContent(); 24727 $animate.enter(clone, null, $element).then(afterAnimation); 24728 }); 24729 24730 currentScope = newScope; 24731 currentElement = clone; 24732 24733 currentScope.$emit('$includeContentLoaded', src); 24734 scope.$eval(onloadExp); 24735 }, function() { 24736 if (scope.$$destroyed) return; 24737 24738 if (thisChangeId === changeCounter) { 24739 cleanupLastIncludeContent();
24740 scope.$emit('$includeContentError', src); 24741 } 24742 }); 24743 scope.$emit('$includeContentRequested', src); 24744 } else { 24745 cleanupLastIncludeContent(); 24746 ctrl.template = null; 24747 } 24748 }); 24749 }; 24750 } 24751 }; 24752}]; 24753 24754// This directive is called during the $transclude call of the first `ngInclude` directive. 24755// It will replace and compile the content of the element with the loaded template. 24756// We need this directive so that the element content is already filled when 24757// the link function of another directive on the same element as ngInclude 24758// is called. 24759var ngIncludeFillContentDirective = ['$compile', 24760 function($compile) { 24761 return { 24762 restrict: 'ECA', 24763 priority: -400, 24764 require: 'ngInclude', 24765 link: function(scope, $element, $attr, ctrl) { 24766 if (/SVG/.test($element[0].toString())) { 24767 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 24768 // support innerHTML, so detect this here and try to generate the contents 24769 // specially. 24770 $element.empty(); 24771 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, 24772 function namespaceAdaptedClone(clone) { 24773 $element.append(clone); 24774 }, {futureParentElement: $element}); 24775 return; 24776 } 24777 24778 $element.html(ctrl.template); 24779 $compile($element.contents())(scope); 24780 } 24781 }; 24782 }]; 24783 24784/** 24785 * @ngdoc directive 24786 * @name ngInit 24787 * @restrict AC 24788 * 24789 * @description 24790 * The `ngInit` directive allows you to evaluate an expression in the 24791 * current scope. 24792 * 24793 * <div class="alert alert-danger"> 24794 * This directive can be abused to add unnecessary amounts of logic into your templates. 24795 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of 24796 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via 24797 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} 24798 * rather than `ngInit` to initialize values on a scope. 24799 * </div> 24800 * 24801 * <div class="alert alert-warning"> 24802 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 24803 * sure you have parentheses to ensure correct operator precedence: 24804 * <pre class="prettyprint"> 24805 * `<div ng-init="test1 = ($index | toString)"></div>` 24806 * </pre> 24807 * </div> 24808 * 24809 * @priority 450 24810 * 24811 * @element ANY 24812 * @param {expression} ngInit {@link guide/expression Expression} to eval. 24813 * 24814 * @example 24815 <example module="initExample"> 24816 <file name="index.html"> 24817 <script> 24818 angular.module('initExample', []) 24819 .controller('ExampleController', ['$scope', function($scope) { 24820 $scope.list = [['a', 'b'], ['c', 'd']]; 24821 }]); 24822 </script>
24823 <div ng-controller="ExampleController"> 24824 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 24825 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 24826 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 24827 </div> 24828 </div> 24829 </div> 24830 </file> 24831 <file name="protractor.js" type="protractor"> 24832 it('should alias index positions', function() { 24833 var elements = element.all(by.css('.example-init')); 24834 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 24835 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 24836 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 24837 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 24838 }); 24839 </file> 24840 </example> 24841 */ 24842var ngInitDirective = ngDirective({ 24843 priority: 450, 24844 compile: function() { 24845 return { 24846 pre: function(scope, element, attrs) { 24847 scope.$eval(attrs.ngInit); 24848 } 24849 }; 24850 } 24851}); 24852 24853/** 24854 * @ngdoc directive 24855 * @name ngList 24856 * 24857 * @description 24858 * Text input that converts between a delimited string and an array of strings. The default 24859 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 24860 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 24861 * 24862 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 24863 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 24864 * list item is respected. This implies that the user of the directive is responsible for 24865 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 24866 * tab or newline character. 24867 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 24868 * when joining the list items back together) and whitespace around each list item is stripped 24869 * before it is added to the model. 24870 * 24871 * ### Example with Validation 24872 * 24873 * <example name="ngList-directive" module="listExample"> 24874 * <file name="app.js"> 24875 * angular.module('listExample', []) 24876 * .controller('ExampleController', ['$scope', function($scope) { 24877 * $scope.names = ['morpheus', 'neo', 'trinity']; 24878 * }]); 24879 * </file> 24880 * <file name="index.html">
24881 * <form name="myForm" ng-controller="ExampleController"> 24882 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 24883 * <span role="alert"> 24884 * <span class="error" ng-show="myForm.namesInput.$error.required"> 24885 * Required!</span> 24886 * </span> 24887 * <br> 24888 * <tt>names = {{names}}</tt><br/> 24889 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 24890 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 24891 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24892 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24893 * </form> 24894 * </file> 24895 * <file name="protractor.js" type="protractor"> 24896 * var listInput = element(by.model('names')); 24897 * var names = element(by.exactBinding('names')); 24898 * var valid = element(by.binding('myForm.namesInput.$valid')); 24899 * var error = element(by.css('span.error')); 24900 * 24901 * it('should initialize to model', function() { 24902 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 24903 * expect(valid.getText()).toContain('true'); 24904 * expect(error.getCssValue('display')).toBe('none'); 24905 * }); 24906 * 24907 * it('should be invalid if empty', function() { 24908 * listInput.clear(); 24909 * listInput.sendKeys(''); 24910 * 24911 * expect(names.getText()).toContain(''); 24912 * expect(valid.getText()).toContain('false'); 24913 * expect(error.getCssValue('display')).not.toBe('none'); 24914 * }); 24915 * </file> 24916 * </example> 24917 * 24918 * ### Example - splitting on newline 24919 * <example name="ngList-directive-newlines"> 24920 * <file name="index.html"> 24921 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 24922 * <pre>{{ list | json }}</pre> 24923 * </file> 24924 * <file name="protractor.js" type="protractor"> 24925 * it("should split the text by newlines", function() { 24926 * var listInput = element(by.model('list')); 24927 * var output = element(by.binding('list | json')); 24928 * listInput.sendKeys('abc\ndef\nghi'); 24929 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 24930 * }); 24931 * </file> 24932 * </example> 24933 * 24934 * @element input 24935 * @param {string=} ngList optional delimiter that should be used to split the value. 24936 */ 24937var ngListDirective = function() { 24938 return { 24939 restrict: 'A', 24940 priority: 100, 24941 require: 'ngModel', 24942 link: function(scope, element, attr, ctrl) { 24943 // We want to control whitespace trimming so we use this convoluted approach 24944 // to access the ngList attribute, which doesn't pre-trim the attribute 24945 var ngList = element.attr(attr.$attr.ngList) || ', '; 24946 var trimValues = attr.ngTrim !== 'false'; 24947 var separator = trimValues ? trim(ngList) : ngList; 24948 24949 var parse = function(viewValue) { 24950 // If the viewValue is invalid (say required but empty) it will be `undefined` 24951 if (isUndefined(viewValue)) return; 24952 24953 var list = []; 24954 24955 if (viewValue) { 24956 forEach(viewValue.split(separator), function(value) { 24957 if (value) list.push(trimValues ? trim(value) : value); 24958 }); 24959 } 24960 24961 return list; 24962 }; 24963 24964 ctrl.$parsers.push(parse); 24965 ctrl.$formatters.push(function(value) { 24966 if (isArray(value)) { 24967 return value.join(ngList); 24968 } 24969 24970 return undefined; 24971 }); 24972 24973 // Override the standard $isEmpty because an empty array means the input is empty. 24974 ctrl.$isEmpty = function(value) { 24975 return !value || !value.length; 24976 }; 24977 } 24978 }; 24979}; 24980 24981/* global VALID_CLASS: true, 24982 INVALID_CLASS: true, 24983 PRISTINE_CLASS: true, 24984 DIRTY_CLASS: true, 24985 UNTOUCHED_CLASS: true, 24986 TOUCHED_CLASS: true, 24987*/ 24988 24989var VALID_CLASS = 'ng-valid', 24990 INVALID_CLASS = 'ng-invalid', 24991 PRISTINE_CLASS = 'ng-pristine', 24992 DIRTY_CLASS = 'ng-dirty', 24993 UNTOUCHED_CLASS = 'ng-untouched', 24994 TOUCHED_CLASS = 'ng-touched', 24995 PENDING_CLASS = 'ng-pending'; 24996 24997var ngModelMinErr = minErr('ngModel'); 24998 24999/** 25000 * @ngdoc type 25001 * @name ngModel.NgModelController 25002 * 25003 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 25004 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 25005 * is set. 25006 * @property {*} $modelValue The value in the model that the control is bound to. 25007 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 25008 the control reads value from the DOM. The functions are called in array order, each passing 25009 its return value through to the next. The last return value is forwarded to the 25010 {@link ngModel.NgModelController#$validators `$validators`} collection. 25011 25012Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 25013`$viewValue`}. 25014 25015Returning `undefined` from a parser means a parse error occurred. In that case, 25016no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 25017will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 25018is set to `true`. The parse error is stored in `ngModel.$error.parse`. 25019 25020 * 25021 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 25022 the model value changes. The functions are called in reverse array order, each passing the value through to the 25023 next. The last return value is used as the actual DOM value. 25024 Used to format / convert values for display in the control. 25025 * ```js 25026 * function formatter(value) { 25027 * if (value) { 25028 * return value.toUpperCase(); 25029 * } 25030 * } 25031 * ngModel.$formatters.push(formatter); 25032 * ``` 25033 * 25034 * @property {Object.<string, function>} $validators A collection of validators that are applied 25035 * whenever the model value changes. The key value within the object refers to the name of the 25036 * validator while the function refers to the validation operation. The validation operation is 25037 * provided with the model value as an argument and must return a true or false value depending 25038 * on the response of that validation. 25039 * 25040 * ```js 25041 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 25042 * var value = modelValue || viewValue; 25043 * return /[0-9]+/.test(value) && 25044 * /[a-z]+/.test(value) && 25045 * /[A-Z]+/.test(value) && 25046 * /\W+/.test(value); 25047 * }; 25048 * ``` 25049 * 25050 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 25051 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 25052 * is expected to return a promise when it is run during the model validation process. Once the promise 25053 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 25054 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 25055 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 25056 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 25057 * will only run once all synchronous validators have passed. 25058 * 25059 * Please note that if $http is used then it is important that the server returns a success HTTP response code 25060 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 25061 * 25062 * ```js 25063 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 25064 * var value = modelValue || viewValue; 25065 * 25066 * // Lookup user by username 25067 * return $http.get('/api/users/' + value). 25068 * then(function resolved() { 25069 * //username exists, this means validation fails 25070 * return $q.reject('exists'); 25071 * }, function rejected() { 25072 * //username does not exist, therefore this validation passes 25073 * return true; 25074 * }); 25075 * }; 25076 * ``` 25077 * 25078 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 25079 * view value has changed. It is called with no arguments, and its return value is ignored. 25080 * This can be used in place of additional $watches against the model value. 25081 * 25082 * @property {Object} $error An object hash with all failing validator ids as keys. 25083 * @property {Object} $pending An object hash with all pending validator ids as keys. 25084 * 25085 * @property {boolean} $untouched True if control has not lost focus yet. 25086 * @property {boolean} $touched True if control has lost focus. 25087 * @property {boolean} $pristine True if user has not interacted with the control yet. 25088 * @property {boolean} $dirty True if user has already interacted with the control. 25089 * @property {boolean} $valid True if there is no error. 25090 * @property {boolean}
25090 $invalid True if at least one error on the control. 25091 * @property {string} $name The name attribute of the control. 25092 * 25093 * @description 25094 * 25095 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 25096 * The controller contains services for data-binding, validation, CSS updates, and value formatting 25097 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 25098 * listening to DOM events. 25099 * Such DOM related logic should be provided by other directives which make use of 25100 * `NgModelController` for data-binding to control elements. 25101 * Angular provides this DOM logic for most {@link input `input`} elements. 25102 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 25103 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 25104 * 25105 * @example 25106 * ### Custom Control Example 25107 * This example shows how to use `NgModelController` with a custom control to achieve 25108 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 25109 * collaborate together to achieve the desired result. 25110 * 25111 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 25112 * contents be edited in place by the user. 25113 * 25114 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 25115 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 25116 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 25117 * that content using the `$sce` service. 25118 * 25119 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 25120 <file name="style.css"> 25121 [contenteditable] { 25122 border: 1px solid black; 25123 background-color: white; 25124 min-height: 20px; 25125 } 25126 25127 .ng-invalid { 25128 border: 1px solid red; 25129 } 25130 25131 </file> 25132 <file name="script.js"> 25133 angular.module('customControl', ['ngSanitize']). 25134 directive('contenteditable', ['$sce', function($sce) { 25135 return { 25136 restrict: 'A', // only activate on element attribute 25137 require: '?ngModel', // get a hold of NgModelController 25138 link: function(scope, element, attrs, ngModel) { 25139 if (!ngModel) return; // do nothing if no ng-model 25140 25141 // Specify how UI should be updated 25142 ngModel.$render = function() { 25143 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 25144 }; 25145 25146 // Listen for change events to enable binding 25147 element.on('blur keyup change', function() { 25148 scope.$evalAsync(read); 25149 }); 25150 read(); // initialize 25151 25152 // Write data to the model 25153 function read() { 25154 var html = element.html(); 25155 // When we clear the content editable the browser leaves a <br> behind 25156 // If strip-br attribute is provided then we strip this out 25157 if ( attrs.stripBr && html == '<br>' ) { 25158 html = ''; 25159 } 25160 ngModel.$setViewValue(html); 25161 } 25162 } 25163 }; 25164 }]); 25165 </file> 25166 <file name="index.html"> 25167 <form name="myForm"> 25168 <div contenteditable 25169 name="myWidget" ng-model="userContent" 25170 strip-br="true" 25171 required>Change me!</div> 25172 <span ng-show="myForm.myWidget.$error.required">Required!</span> 25173 <hr> 25174 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 25175 </form> 25176 </file> 25177 <file name="protractor.js" type="protractor"> 25178 it('should data-bind and become invalid', function() { 25179 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 25180 // SafariDriver can't handle contenteditable 25181 // and Firefox driver can't clear contenteditables very well 25182 return; 25183 } 25184 var contentEditable = element(by.css('[contenteditable]')); 25185 var content = 'Change me!'; 25186 25187 expect(contentEditable.getText()).toEqual(content); 25188 25189 contentEditable.clear(); 25190 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 25191 expect(contentEditable.getText()).toEqual(''); 25192 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 25193 }); 25194 </file> 25195 * </example> 25196 * 25197 * 25198 */ 25199var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 25200 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 25201 this.$viewValue = Number.NaN; 25202 this.$modelValue = Number.NaN; 25203 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 25204 this.$validators = {}; 25205 this.$asyncValidators = {}; 25206 this.$parsers = []; 25207 this.$formatters = []; 25208 this.$viewChangeListeners = []; 25209 this.$untouched = true; 25210 this.$touched = false;
25211 this.$pristine = true; 25212 this.$dirty = false; 25213 this.$valid = true; 25214 this.$invalid = false; 25215 this.$error = {}; // keep invalid keys here 25216 this.$$success = {}; // keep valid keys here 25217 this.$pending = undefined; // keep pending keys here 25218 this.$name = $interpolate($attr.name || '', false)($scope); 25219 this.$$parentForm = nullFormCtrl; 25220 25221 var parsedNgModel = $parse($attr.ngModel), 25222 parsedNgModelAssign = parsedNgModel.assign, 25223 ngModelGet = parsedNgModel, 25224 ngModelSet = parsedNgModelAssign, 25225 pendingDebounce = null, 25226 parserValid, 25227 ctrl = this; 25228 25229 this.$$setOptions = function(options) { 25230 ctrl.$options = options; 25231 if (options && options.getterSetter) { 25232 var invokeModelGetter = $parse($attr.ngModel + '()'), 25233 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 25234 25235 ngModelGet = function($scope) { 25236 var modelValue = parsedNgModel($scope); 25237 if (isFunction(modelValue)) { 25238 modelValue = invokeModelGetter($scope); 25239 } 25240 return modelValue; 25241 }; 25242 ngModelSet = function($scope, newValue) { 25243 if (isFunction(parsedNgModel($scope))) { 25244 invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); 25245 } else { 25246 parsedNgModelAssign($scope, ctrl.$modelValue); 25247 } 25248 }; 25249 } else if (!parsedNgModel.assign) { 25250 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 25251 $attr.ngModel, startingTag($element)); 25252 } 25253 }; 25254 25255 /** 25256 * @ngdoc method 25257 * @name ngModel.NgModelController#$render 25258 * 25259 * @description 25260 * Called when the view needs to be updated. It is expected that the user of the ng-model 25261 * directive will implement this method. 25262 * 25263 * The `$render()` method is invoked in the following situations: 25264 * 25265 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 25266 * committed value then `$render()` is called to update the input control. 25267 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 25268 * the `$viewValue` are different from last time. 25269 * 25270 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 25271 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue` 25272 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 25273 * invoked if you only change a property on the objects. 25274 */ 25275 this.$render = noop; 25276 25277 /** 25278 * @ngdoc method 25279 * @name ngModel.NgModelController#$isEmpty 25280 * 25281 * @description 25282 * This is called when we need to determine if the value of an input is empty. 25283 * 25284 * For instance, the required directive does this to work out if the input has data or not. 25285 * 25286 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 25287 * 25288 * You can override this for input directives whose concept of being empty is different from the 25289 * default. The `checkboxInputType` directive does this because in its case a value of `false` 25290 * implies empty. 25291 * 25292 * @param {*} value The value of the input to check for emptiness. 25293 * @returns {boolean} True if `value` is "empty". 25294 */ 25295 this.$isEmpty = function(value) { 25296 return isUndefined(value) || value === '' || value === null || value !== value; 25297 }; 25298 25299 var currentValidationRunId = 0; 25300 25301 /** 25302 * @ngdoc method 25303 * @name ngModel.NgModelController#$setValidity 25304 * 25305 * @description 25306 * Change the validity state, and notify the form. 25307 * 25308 * This method can be called within $parsers/$formatters or a custom validation implementation. 25309 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 25310 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 25311 * 25312 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 25313 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 25314 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
25315 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 25316 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 25317 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 25318 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 25319 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 25320 * Skipped is used by Angular when validators do not run because of parse errors and 25321 * when `$asyncValidators` do not run because any of the `$validators` failed. 25322 */ 25323 addSetValidityMethod({ 25324 ctrl: this, 25325 $element: $element, 25326 set: function(object, property) { 25327 object[property] = true; 25328 }, 25329 unset: function(object, property) { 25330 delete object[property]; 25331 }, 25332 $animate: $animate 25333 }); 25334 25335 /** 25336 * @ngdoc method 25337 * @name ngModel.NgModelController#$setPristine 25338 * 25339 * @description 25340 * Sets the control to its pristine state. 25341 * 25342 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 25343 * state (`ng-pristine` class). A model is considered to be pristine when the control 25344 * has not been changed from when first compiled. 25345 */ 25346 this.$setPristine = function() { 25347 ctrl.$dirty = false; 25348 ctrl.$pristine = true; 25349 $animate.removeClass($element, DIRTY_CLASS); 25350 $animate.addClass($element, PRISTINE_CLASS); 25351 }; 25352 25353 /** 25354 * @ngdoc method 25355 * @name ngModel.NgModelController#$setDirty 25356 * 25357 * @description 25358 * Sets the control to its dirty state. 25359 * 25360 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 25361 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 25362 * from when first compiled. 25363 */ 25364 this.$setDirty = function() { 25365 ctrl.$dirty = true; 25366 ctrl.$pristine = false; 25367 $animate.removeClass($element, PRISTINE_CLASS); 25368 $animate.addClass($element, DIRTY_CLASS); 25369 ctrl.$$parentForm.$setDirty(); 25370 }; 25371 25372 /** 25373 * @ngdoc method 25374 * @name ngModel.NgModelController#$setUntouched 25375 * 25376 * @description 25377 * Sets the control to its untouched state. 25378 * 25379 * This method can be called to remove the `ng-touched` class and set the control to its 25380 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 25381 * by default, however this function can be used to restore that state if the model has 25382 * already been touched by the user. 25383 */ 25384 this.$setUntouched = function() { 25385 ctrl.$touched = false; 25386 ctrl.$untouched = true; 25387 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 25388 }; 25389 25390 /** 25391 * @ngdoc method 25392 * @name ngModel.NgModelController#$setTouched 25393 * 25394 * @description 25395 * Sets the control to its touched state. 25396 * 25397 * This method can be called to remove the `ng-untouched` class and set the control to its 25398 * touched state (`ng-touched` class). A model is considered to be touched when the user has 25399 * first focused the control element and then shifted focus away from the control (blur event). 25400 */ 25401 this.$setTouched = function() { 25402 ctrl.$touched = true; 25403 ctrl.$untouched = false; 25404 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 25405 }; 25406 25407 /** 25408 * @ngdoc method 25409 * @name ngModel.NgModelController#$rollbackViewValue 25410 * 25411 * @description 25412 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 25413 * which may be caused by a pending debounced event or because the input is waiting for a some 25414 * future event. 25415 * 25416 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that 25417 * depend on special events such as blur, you can have a situation where there is a period when 25418 * the `$viewValue` is out of sync with the ngModel's `$modelValue`. 25419 * 25420 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update 25421 * and reset the input to the last committed view value. 25422 * 25423 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` 25424 * programmatically before these debounced/future events have resolved/occurred, because Angular's 25425 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 25426 * 25427 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 25428 * input which may have such events pending. This is important in order to make sure that the 25429 * input field will be updated with the new model value and any pending operations are cancelled. 25430 * 25431 * <example name="ng-model-cancel-update" module="cancel-update-example"> 25432 * <file name="app.js">
25433 * angular.module('cancel-update-example', []) 25434 * 25435 * .controller('CancelUpdateController', ['$scope', function($scope) { 25436 * $scope.model = {}; 25437 * 25438 * $scope.setEmpty = function(e, value, rollback) { 25439 * if (e.keyCode == 27) { 25440 * e.preventDefault(); 25441 * if (rollback) { 25442 * $scope.myForm[value].$rollbackViewValue(); 25443 * } 25444 * $scope.model[value] = ''; 25445 * } 25446 * }; 25447 * }]); 25448 * </file> 25449 * <file name="index.html"> 25450 * <div ng-controller="CancelUpdateController"> 25451 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should 25452 * empty them. Follow these steps and observe the difference:</p> 25453 * <ol> 25454 * <li>Type something in the input. You will see that the model is not yet updated</li> 25455 * <li>Press the Escape key. 25456 * <ol> 25457 * <li> In the first example, nothing happens, because the model is already '', and no 25458 * update is detected. If you blur the input, the model will be set to the current view. 25459 * </li> 25460 * <li> In the second example, the pending update is cancelled, and the input is set back 25461 * to the last committed view value (''). Blurring the input does nothing. 25462 * </li> 25463 * </ol> 25464 * </li> 25465 * </ol> 25466 * 25467 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 25468 * <div> 25469 * <p id="inputDescription1">Without $rollbackViewValue():</p> 25470 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" 25471 * ng-keydown="setEmpty($event, 'value1')"> 25472 * value1: "{{ model.value1 }}" 25473 * </div> 25474 * 25475 * <div> 25476 * <p id="inputDescription2">With $rollbackViewValue():</p> 25477 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" 25478 * ng-keydown="setEmpty($event, 'value2', true)"> 25479 * value2: "{{ model.value2 }}" 25480 * </div> 25481 * </form> 25482 * </div> 25483 * </file> 25484 <file name="style.css"> 25485 div { 25486 display: table-cell; 25487 } 25488 div:nth-child(1) { 25489 padding-right: 30px; 25490 } 25491 25492 </file> 25493 * </example> 25494 */ 25495 this.$rollbackViewValue = function() { 25496 $timeout.cancel(pendingDebounce); 25497 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 25498 ctrl.$render(); 25499 }; 25500 25501 /** 25502 * @ngdoc method 25503 * @name ngModel.NgModelController#$validate 25504 * 25505 * @description 25506 * Runs each of the registered validators (first synchronous validators and then 25507 * asynchronous validators). 25508 * If the validity changes to invalid, the model will be set to `undefined`, 25509 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 25510 * If the validity changes to valid, it will set the model to the last available valid 25511 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 25512 */ 25513 this.$validate = function() { 25514 // ignore $validate before model is initialized 25515 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 25516 return; 25517 } 25518 25519 var viewValue = ctrl.$$lastCommittedViewValue; 25520 // Note: we use the $$rawModelValue as $modelValue might have been 25521 // set to undefined during a view -> model update that found validation 25522 // errors. We can't parse the view here, since that could change 25523 // the model although neither viewValue nor the model on the scope changed 25524 var modelValue = ctrl.$$rawModelValue; 25525 25526 var prevValid = ctrl.$valid; 25527 var prevModelValue = ctrl.$modelValue; 25528 25529 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 25530 25531 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 25532 // If there was no change in validity, don't update the model 25533 // This prevents changing an invalid modelValue to undefined 25534 if (!allowInvalid && prevValid !== allValid) { 25535 // Note: Don't check ctrl.$valid here, as we could have 25536 // external validators (e.g. calculated on the server), 25537 // that just call $setValidity and need the model value 25538 // to calculate their validity. 25539 ctrl.$modelValue = allValid ? modelValue : undefined; 25540 25541 if (ctrl.$modelValue !== prevModelValue) { 25542 ctrl.$$writeModelToScope(); 25543 } 25544 } 25545 }); 25546 25547 }; 25548 25549 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 25550 currentValidationRunId++; 25551 var localValidationRunId = currentValidationRunId; 25552 25553 // check parser error 25554 if (!processParseErrors()) { 25555 validationDone(false); 25556 return; 25557 } 25558 if (!processSyncValidators()) { 25559 validationDone(false); 25560 return; 25561 } 25562 processAsyncValidators(); 25563 25564 function processParseErrors() { 25565 var errorKey = ctrl.$$parserName || 'parse'; 25566 if (isUndefined(parserValid)) {
25567 setValidity(errorKey, null); 25568 } else { 25569 if (!parserValid) { 25570 forEach(ctrl.$validators, function(v, name) { 25571 setValidity(name, null); 25572 }); 25573 forEach(ctrl.$asyncValidators, function(v, name) { 25574 setValidity(name, null); 25575 }); 25576 } 25577 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 25578 setValidity(errorKey, parserValid); 25579 return parserValid; 25580 } 25581 return true; 25582 } 25583 25584 function processSyncValidators() { 25585 var syncValidatorsValid = true; 25586 forEach(ctrl.$validators, function(validator, name) { 25587 var result = validator(modelValue, viewValue); 25588 syncValidatorsValid = syncValidatorsValid && result; 25589 setValidity(name, result); 25590 }); 25591 if (!syncValidatorsValid) { 25592 forEach(ctrl.$asyncValidators, function(v, name) { 25593 setValidity(name, null); 25594 }); 25595 return false; 25596 } 25597 return true; 25598 } 25599 25600 function processAsyncValidators() { 25601 var validatorPromises = []; 25602 var allValid = true; 25603 forEach(ctrl.$asyncValidators, function(validator, name) { 25604 var promise = validator(modelValue, viewValue); 25605 if (!isPromiseLike(promise)) { 25606 throw ngModelMinErr('nopromise', 25607 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 25608 } 25609 setValidity(name, undefined); 25610 validatorPromises.push(promise.then(function() { 25611 setValidity(name, true); 25612 }, function(error) { 25613 allValid = false; 25614 setValidity(name, false); 25615 })); 25616 }); 25617 if (!validatorPromises.length) { 25618 validationDone(true); 25619 } else { 25620 $q.all(validatorPromises).then(function() { 25621 validationDone(allValid); 25622 }, noop); 25623 } 25624 } 25625 25626 function setValidity(name, isValid) { 25627 if (localValidationRunId === currentValidationRunId) { 25628 ctrl.$setValidity(name, isValid); 25629 } 25630 } 25631 25632 function validationDone(allValid) { 25633 if (localValidationRunId === currentValidationRunId) { 25634 25635 doneCallback(allValid); 25636 } 25637 } 25638 }; 25639 25640 /** 25641 * @ngdoc method 25642 * @name ngModel.NgModelController#$commitViewValue 25643 * 25644 * @description 25645 * Commit a pending update to the `$modelValue`. 25646 * 25647 * Updates may be pending by a debounced event or because the input is waiting for a some future 25648 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 25649 * usually handles calling this in response to input events. 25650 */ 25651 this.$commitViewValue = function() { 25652 var viewValue = ctrl.$viewValue; 25653 25654 $timeout.cancel(pendingDebounce); 25655 25656 // If the view value has not changed then we should just exit, except in the case where there is 25657 // a native validator on the element. In this case the validation state may have changed even though 25658 // the viewValue has stayed empty. 25659 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 25660 return; 25661 } 25662 ctrl.$$lastCommittedViewValue = viewValue; 25663 25664 // change to dirty 25665 if (ctrl.$pristine) { 25666 this.$setDirty(); 25667 } 25668 this.$$parseAndValidate(); 25669 }; 25670 25671 this.$$parseAndValidate = function() { 25672 var viewValue = ctrl.$$lastCommittedViewValue; 25673 var modelValue = viewValue; 25674 parserValid = isUndefined(modelValue) ? undefined : true; 25675 25676 if (parserValid) { 25677 for (var i = 0; i < ctrl.$parsers.length; i++) { 25678 modelValue = ctrl.$parsers[i](modelValue); 25679 if (isUndefined(modelValue)) { 25680 parserValid = false; 25681 break; 25682 } 25683 } 25684 } 25685 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 25686 // ctrl.$modelValue has not been touched yet... 25687 ctrl.$modelValue = ngModelGet($scope); 25688 } 25689 var prevModelValue = ctrl.$modelValue; 25690 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 25691 ctrl.$$rawModelValue = modelValue; 25692 25693 if (allowInvalid) { 25694 ctrl.$modelValue = modelValue; 25695 writeToModelIfNeeded(); 25696 } 25697 25698 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 25699 // This can happen if e.g. $setViewValue is called from inside a parser 25700 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 25701 if (!allowInvalid) { 25702 // Note: Don't check ctrl.$valid here, as we could have 25703 // external validators (e.g. calculated on the server), 25704 // that just call $setValidity and need the model value 25705 // to calculate their validity. 25706 ctrl.$modelValue = allValid ? modelValue : undefined; 25707 writeToModelIfNeeded(); 25708 } 25709 }); 25710 25711 function writeToModelIfNeeded() { 25712 if (ctrl.$modelValue !== prevModelValue) { 25713 ctrl.$$writeModelToScope(); 25714 } 25715 } 25716 }; 25717 25718 this.$$writeModelToScope = function() { 25719 ngModelSet($scope, ctrl.$modelValue);
25720 forEach(ctrl.$viewChangeListeners, function(listener) { 25721 try { 25722 listener(); 25723 } catch (e) { 25724 $exceptionHandler(e); 25725 } 25726 }); 25727 }; 25728 25729 /** 25730 * @ngdoc method 25731 * @name ngModel.NgModelController#$setViewValue 25732 * 25733 * @description 25734 * Update the view value. 25735 * 25736 * This method should be called when a control wants to change the view value; typically, 25737 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 25738 * directive calls it when the value of the input changes and {@link ng.directive:select select} 25739 * calls it when an option is selected. 25740 * 25741 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 25742 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 25743 * value sent directly for processing, finally to be applied to `$modelValue` and then the 25744 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, 25745 * in the `$viewChangeListeners` list, are called. 25746 * 25747 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 25748 * and the `default` trigger is not listed, all those actions will remain pending until one of the 25749 * `updateOn` events is triggered on the DOM element. 25750 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 25751 * directive is used with a custom debounce for this particular event. 25752 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 25753 * is specified, once the timer runs out. 25754 * 25755 * When used with standard inputs, the view value will always be a string (which is in some cases 25756 * parsed into another type, such as a `Date` object for `input[date]`.) 25757 * However, custom controls might also pass objects to this method. In this case, we should make 25758 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 25759 * perform a deep watch of objects, it only looks for a change of identity. If you only change 25760 * the property of the object then ngModel will not realise that the object has changed and 25761 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 25762 * not change properties of the copy once it has been passed to `$setViewValue`. 25763 * Otherwise you may cause the model value on the scope to change incorrectly. 25764 * 25765 * <div class="alert alert-info"> 25766 * In any case, the value passed to the method should always reflect the current value 25767 * of the control. For example, if you are calling `$setViewValue` for an input element, 25768 * you should pass the input DOM value. Otherwise, the control and the scope model become 25769 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 25770 * the control's DOM value in any way. If we want to change the control's DOM value 25771 * programmatically, we should update the `ngModel` scope expression. Its new value will be 25772 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 25773 * to update the DOM, and finally call `$validate` on it. 25774 * </div> 25775 * 25776 * @param {*} value value from the view. 25777 * @param {string} trigger Event that triggered the update. 25778 */ 25779 this.$setViewValue = function(value, trigger) { 25780 ctrl.$viewValue = value; 25781 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 25782 ctrl.$$debounceViewValueCommit(trigger); 25783 } 25784 }; 25785 25786 this.$$debounceViewValueCommit = function(trigger) { 25787 var debounceDelay = 0, 25788 options = ctrl.$options, 25789 debounce; 25790 25791 if (options && isDefined(options.debounce)) { 25792 debounce = options.debounce; 25793 if (isNumber(debounce)) { 25794 debounceDelay = debounce; 25795 } else if (isNumber(debounce[trigger])) { 25796 debounceDelay = debounce[trigger]; 25797 } else if (isNumber(debounce['default'])) { 25798 debounceDelay = debounce['default']; 25799 } 25800 } 25801 25802 $timeout.cancel(pendingDebounce); 25803 if (debounceDelay) { 25804 pendingDebounce = $timeout(function() { 25805 ctrl.$commitViewValue(); 25806 }, debounceDelay); 25807 } else if ($rootScope.$$phase) { 25808 ctrl.$commitViewValue(); 25809 } else { 25810 $scope.$apply(function() { 25811 ctrl.$commitViewValue(); 25812 }); 25813 } 25814 }; 25815 25816 // model -> value 25817 // Note: we cannot use a normal scope.$watch as we want to detect the following: 25818 // 1. scope value is 'a' 25819 // 2. user enters 'b' 25820 // 3. ng-change kicks in and reverts scope value to 'a' 25821 // ->
25821 scope value did not change since the last digest as 25822 // ng-change executes in apply phase 25823 // 4. view should be changed back to 'a' 25824 $scope.$watch(function ngModelWatch() { 25825 var modelValue = ngModelGet($scope); 25826 25827 // if scope model value and ngModel value are out of sync 25828 // TODO(perf): why not move this to the action fn? 25829 if (modelValue !== ctrl.$modelValue && 25830 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 25831 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 25832 ) { 25833 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 25834 parserValid = undefined; 25835 25836 var formatters = ctrl.$formatters, 25837 idx = formatters.length; 25838 25839 var viewValue = modelValue; 25840 while (idx--) { 25841 viewValue = formatters[idx](viewValue); 25842 } 25843 if (ctrl.$viewValue !== viewValue) { 25844 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 25845 ctrl.$render(); 25846 25847 ctrl.$$runValidators(modelValue, viewValue, noop); 25848 } 25849 } 25850 25851 return modelValue; 25852 }); 25853}]; 25854 25855 25856/** 25857 * @ngdoc directive 25858 * @name ngModel 25859 * 25860 * @element input 25861 * @priority 1 25862 * 25863 * @description 25864 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 25865 * property on the scope using {@link ngModel.NgModelController NgModelController}, 25866 * which is created and exposed by this directive. 25867 * 25868 * `ngModel` is responsible for: 25869 * 25870 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 25871 * require. 25872 * - Providing validation behavior (i.e. required, number, email, url). 25873 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 25874 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. 25875 * - Registering the control with its parent {@link ng.directive:form form}. 25876 * 25877 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 25878 * current scope. If the property doesn't already exist on this scope, it will be created 25879 * implicitly and added to the scope. 25880 * 25881 * For best practices on using `ngModel`, see: 25882 * 25883 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 25884 * 25885 * For basic examples, how to use `ngModel`, see: 25886 * 25887 * - {@link ng.directive:input input} 25888 * - {@link input[text] text} 25889 * - {@link input[checkbox] checkbox} 25890 * - {@link input[radio] radio} 25891 * - {@link input[number] number} 25892 * - {@link input[email] email} 25893 * - {@link input[url] url} 25894 * - {@link input[date] date} 25895 * - {@link input[datetime-local] datetime-local} 25896 * - {@link input[time] time} 25897 * - {@link input[month] month} 25898 * - {@link input[week] week} 25899 * - {@link ng.directive:select select} 25900 * - {@link ng.directive:textarea textarea} 25901 * 25902 * # CSS classes 25903 * The following CSS classes are added and removed on the associated input/select/textarea element 25904 * depending on the validity of the model. 25905 * 25906 * - `ng-valid`: the model is valid 25907 * - `ng-invalid`: the model is invalid 25908 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 25909 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 25910 * - `ng-pristine`: the control hasn't been interacted with yet 25911 * - `ng-dirty`: the control has been interacted with 25912 * - `ng-touched`: the control has been blurred 25913 * - `ng-untouched`: the control hasn't been blurred 25914 * - `ng-pending`: any `$asyncValidators` are unfulfilled 25915 * 25916 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 25917 * 25918 * ## Animation Hooks 25919 * 25920 * Animations within models are triggered when any of the associated CSS classes are added and removed 25921 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 25922 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 25923 * The animations that are triggered within ngModel are similar to how they work in ngClass and 25924 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 25925 * 25926 * The following example shows a simple way to utilize CSS transitions to style an input element
25927 * that has been rendered as invalid after it has been validated: 25928 * 25929 * <pre> 25930 * //be sure to include ngAnimate as a module to hook into more 25931 * //advanced animations 25932 * .my-input { 25933 * transition:0.5s linear all; 25934 * background: white; 25935 * } 25936 * .my-input.ng-invalid { 25937 * background: red; 25938 * color:white; 25939 * } 25940 * </pre> 25941 * 25942 * @example 25943 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 25944 <file name="index.html"> 25945 <script> 25946 angular.module('inputExample', []) 25947 .controller('ExampleController', ['$scope', function($scope) { 25948 $scope.val = '1'; 25949 }]); 25950 </script> 25951 <style> 25952 .my-input { 25953 transition:all linear 0.5s; 25954 background: transparent; 25955 } 25956 .my-input.ng-invalid { 25957 color:white; 25958 background: red; 25959 } 25960 </style> 25961 <p id="inputDescription"> 25962 Update input to see transitions when valid/invalid. 25963 Integer is a valid value. 25964 </p>
25965 <form name="testForm" ng-controller="ExampleController"> 25966 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 25967 aria-describedby="inputDescription" /> 25968 </form> 25969 </file> 25970 * </example> 25971 * 25972 * ## Binding to a getter/setter 25973 * 25974 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 25975 * function that returns a representation of the model when called with zero arguments, and sets 25976 * the internal state of a model when called with an argument. It's sometimes useful to use this 25977 * for models that have an internal representation that's different from what the model exposes 25978 * to the view. 25979 * 25980 * <div class="alert alert-success"> 25981 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 25982 * frequently than other parts of your code. 25983 * </div> 25984 * 25985 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 25986 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 25987 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 25988 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 25989 * 25990 * The following example shows how to use `ngModel` with a getter/setter: 25991 * 25992 * @example 25993 * <example name="ngModel-getter-setter" module="getterSetterExample"> 25994 <file name="index.html">
25995 <div ng-controller="ExampleController"> 25996 <form name="userForm"> 25997 <label>Name: 25998 <input type="text" name="userName" 25999 ng-model="user.name" 26000 ng-model-options="{ getterSetter: true }" /> 26001 </label> 26002 </form> 26003 <pre>user.name = <span ng-bind="user.name()"></span></pre> 26004 </div> 26005 </file> 26006 <file name="app.js"> 26007 angular.module('getterSetterExample', []) 26008 .controller('ExampleController', ['$scope', function($scope) { 26009 var _name = 'Brian'; 26010 $scope.user = { 26011 name: function(newName) { 26012 // Note that newName can be undefined for two reasons: 26013 // 1. Because it is called as a getter and thus called with no arguments 26014 // 2. Because the property should actually be set to undefined. This happens e.g. if the 26015 // input is invalid 26016 return arguments.length ? (_name = newName) : _name; 26017 } 26018 }; 26019 }]); 26020 </file> 26021 * </example> 26022 */ 26023var ngModelDirective = ['$rootScope', function($rootScope) { 26024 return { 26025 restrict: 'A', 26026 require: ['ngModel', '^?form', '^?ngModelOptions'], 26027 controller: NgModelController, 26028 // Prelink needs to run before any input directive 26029 // so that we can set the NgModelOptions in NgModelController 26030 // before anyone else uses it. 26031 priority: 1, 26032 compile: function ngModelCompile(element) { 26033 // Setup initial state of the control 26034 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 26035 26036 return { 26037 pre: function ngModelPreLink(scope, element, attr, ctrls) { 26038 var modelCtrl = ctrls[0], 26039 formCtrl = ctrls[1] || modelCtrl.$$parentForm; 26040 26041 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 26042 26043 // notify others, especially parent forms 26044 formCtrl.$addControl(modelCtrl); 26045 26046 attr.$observe('name', function(newValue) { 26047 if (modelCtrl.$name !== newValue) { 26048 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 26049 } 26050 }); 26051 26052 scope.$on('$destroy', function() { 26053 modelCtrl.$$parentForm.$removeControl(modelCtrl); 26054 }); 26055 }, 26056 post: function ngModelPostLink(scope, element, attr, ctrls) { 26057 var modelCtrl = ctrls[0]; 26058 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 26059 element.on(modelCtrl.$options.updateOn, function(ev) { 26060 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 26061 }); 26062 } 26063 26064 element.on('blur', function(ev) { 26065 if (modelCtrl.$touched) return; 26066 26067 if ($rootScope.$$phase) { 26068 scope.$evalAsync(modelCtrl.$setTouched); 26069 } else { 26070 scope.$apply(modelCtrl.$setTouched); 26071 } 26072 }); 26073 } 26074 }; 26075 } 26076 }; 26077}]; 26078 26079var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 26080 26081/** 26082 * @ngdoc directive 26083 * @name ngModelOptions 26084 * 26085 * @description 26086 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 26087 * events that will trigger a model update and/or a debouncing delay so that the actual update only 26088 * takes place when a timer expires; this timer will be reset after another change takes place. 26089 * 26090 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 26091 * be different from the value in the actual model. This means that if you update the model you 26092 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 26093 * order to make sure it is synchronized with the model and that any debounced action is canceled. 26094 * 26095 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 26096 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 26097 * important because `form` controllers are published to the related scope under the name in their 26098 * `name` attribute. 26099 * 26100 * Any pending changes will take place immediately when an enclosing form is submitted via the 26101 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 26102 * to have access to the updated model. 26103 * 26104 * `ngModelOptions` has an effect on the element it's declared on and its desce
26104ndants. 26105 * 26106 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 26107 * - `updateOn`: string specifying which event should the input be bound to. You can set several 26108 * events using an space delimited list. There is a special event called `default` that 26109 * matches the default events belonging of the control. 26110 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 26111 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 26112 * custom value for each event. For example: 26113 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 26114 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 26115 * not validate correctly instead of the default behavior of setting the model to undefined. 26116 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 26117 `ngModel` as getters/setters. 26118 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 26119 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 26120 * continental US time zone abbreviations, but for general use, use a time zone offset, for 26121 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 26122 * If not specified, the timezone of the browser will be used. 26123 * 26124 * @example 26125 26126 The following example shows how to override immediate updates. Changes on the inputs within the 26127 form will update the model only when the control loses focus (blur event). If `escape` key is 26128 pressed while the input field is focused, the value is reset to the value in the current model. 26129 26130 <example name="ngModelOptions-directive-blur" module="optionsExample"> 26131 <file name="index.html">
26132 <div ng-controller="ExampleController"> 26133 <form name="userForm"> 26134 <label>Name: 26135 <input type="text" name="userName" 26136 ng-model="user.name" 26137 ng-model-options="{ updateOn: 'blur' }" 26138 ng-keyup="cancel($event)" /> 26139 </label><br /> 26140 <label>Other data: 26141 <input type="text" ng-model="user.data" /> 26142 </label><br /> 26143 </form> 26144 <pre>user.name = <span ng-bind="user.name"></span></pre> 26145 <pre>user.data = <span ng-bind="user.data"></span></pre> 26146 </div> 26147 </file> 26148 <file name="app.js"> 26149 angular.module('optionsExample', []) 26150 .controller('ExampleController', ['$scope', function($scope) { 26151 $scope.user = { name: 'John', data: '' }; 26152 26153 $scope.cancel = function(e) { 26154 if (e.keyCode == 27) { 26155 $scope.userForm.userName.$rollbackViewValue(); 26156 } 26157 }; 26158 }]); 26159 </file> 26160 <file name="protractor.js" type="protractor"> 26161 var model = element(by.binding('user.name')); 26162 var input = element(by.model('user.name')); 26163 var other = element(by.model('user.data')); 26164 26165 it('should allow custom events', function() { 26166 input.sendKeys(' Doe'); 26167 input.click(); 26168 expect(model.getText()).toEqual('John'); 26169 other.click(); 26170 expect(model.getText()).toEqual('John Doe'); 26171 }); 26172 26173 it('should $rollbackViewValue when model changes', function() { 26174 input.sendKeys(' Doe'); 26175 expect(input.getAttribute('value')).toEqual('John Doe'); 26176 input.sendKeys(protractor.Key.ESCAPE); 26177 expect(input.getAttribute('value')).toEqual('John'); 26178 other.click(); 26179 expect(model.getText()).toEqual('John'); 26180 }); 26181 </file> 26182 </example> 26183 26184 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 26185 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 26186 26187 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 26188 <file name="index.html">
26189 <div ng-controller="ExampleController"> 26190 <form name="userForm"> 26191 <label>Name: 26192 <input type="text" name="userName" 26193 ng-model="user.name" 26194 ng-model-options="{ debounce: 1000 }" /> 26195 </label> 26196 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 26197 <br /> 26198 </form> 26199 <pre>user.name = <span ng-bind="user.name"></span></pre> 26200 </div> 26201 </file> 26202 <file name="app.js"> 26203 angular.module('optionsExample', []) 26204 .controller('ExampleController', ['$scope', function($scope) { 26205 $scope.user = { name: 'Igor' }; 26206 }]); 26207 </file> 26208 </example> 26209 26210 This one shows how to bind to getter/setters: 26211 26212 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 26213 <file name="index.html"> 26214 <div ng-controller="ExampleController"> 26215 <form name="userForm"> 26216 <label>Name: 26217 <input type="text" name="userName" 26218 ng-model="user.name" 26219 ng-model-options="{ getterSetter: true }" /> 26220 </label> 26221 </form> 26222 <pre>user.name = <span ng-bind="user.name()"></span></pre> 26223 </div> 26224 </file> 26225 <file name="app.js"> 26226 angular.module('getterSetterExample', []) 26227 .controller('ExampleController', ['$scope', function($scope) { 26228 var _name = 'Brian'; 26229 $scope.user = { 26230 name: function(newName) { 26231 // Note that newName can be undefined for two reasons: 26232 // 1. Because it is called as a getter and thus called with no arguments 26233 // 2. Because the property should actually be set to undefined. This happens e.g. if the 26234 // input is invalid 26235 return arguments.length ? (_name = newName) : _name; 26236 } 26237 }; 26238 }]); 26239 </file> 26240 </example> 26241 */ 26242var ngModelOptionsDirective = function() { 26243 return { 26244 restrict: 'A', 26245 controller: ['$scope', '$attrs', function($scope, $attrs) { 26246 var that = this; 26247 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 26248 // Allow adding/overriding bound events 26249 if (isDefined(this.$options.updateOn)) { 26250 this.$options.updateOnDefault = false;
26251 // extract "default" pseudo-event from list of events that can trigger a model update 26252 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 26253 that.$options.updateOnDefault = true; 26254 return ' '; 26255 })); 26256 } else { 26257 this.$options.updateOnDefault = true; 26258 } 26259 }] 26260 }; 26261}; 26262 26263 26264 26265// helper methods 26266function addSetValidityMethod(context) { 26267 var ctrl = context.ctrl, 26268 $element = context.$element, 26269 classCache = {}, 26270 set = context.set, 26271 unset = context.unset, 26272 $animate = context.$animate; 26273 26274 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 26275 26276 ctrl.$setValidity = setValidity; 26277 26278 function setValidity(validationErrorKey, state, controller) { 26279 if (isUndefined(state)) { 26280 createAndSet('$pending', validationErrorKey, controller); 26281 } else { 26282 unsetAndCleanup('$pending', validationErrorKey, controller); 26283 } 26284 if (!isBoolean(state)) { 26285 unset(ctrl.$error, validationErrorKey, controller); 26286 unset(ctrl.$$success, validationErrorKey, controller); 26287 } else { 26288 if (state) { 26289 unset(ctrl.$error, validationErrorKey, controller); 26290 set(ctrl.$$success, validationErrorKey, controller); 26291 } else { 26292 set(ctrl.$error, validationErrorKey, controller); 26293 unset(ctrl.$$success, validationErrorKey, controller); 26294 } 26295 } 26296 if (ctrl.$pending) { 26297 cachedToggleClass(PENDING_CLASS, true); 26298 ctrl.$valid = ctrl.$invalid = undefined; 26299 toggleValidationCss('', null); 26300 } else { 26301 cachedToggleClass(PENDING_CLASS, false); 26302 ctrl.$valid = isObjectEmpty(ctrl.$error); 26303 ctrl.$invalid = !ctrl.$valid; 26304 toggleValidationCss('', ctrl.$valid); 26305 } 26306 26307 // re-read the state as the set/unset methods could have 26308 // combined state in ctrl.$error[validationError] (used for forms), 26309 // where setting/unsetting only increments/decrements the value, 26310 // and does not replace it. 26311 var combinedState; 26312 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 26313 combinedState = undefined; 26314 } else if (ctrl.$error[validationErrorKey]) { 26315 combinedState = false; 26316 } else if (ctrl.$$success[validationErrorKey]) { 26317 combinedState = true; 26318 } else { 26319 combinedState = null; 26320 } 26321
26322 toggleValidationCss(validationErrorKey, combinedState); 26323 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 26324 } 26325 26326 function createAndSet(name, value, controller) { 26327 if (!ctrl[name]) { 26328 ctrl[name] = {}; 26329 } 26330 set(ctrl[name], value, controller); 26331 } 26332 26333 function unsetAndCleanup(name, value, controller) { 26334 if (ctrl[name]) { 26335 unset(ctrl[name], value, controller); 26336 } 26337 if (isObjectEmpty(ctrl[name])) { 26338 ctrl[name] = undefined; 26339 } 26340 } 26341 26342 function cachedToggleClass(className, switchValue) { 26343 if (switchValue && !classCache[className]) { 26344 $animate.addClass($element, className); 26345 classCache[className] = true; 26346 } else if (!switchValue && classCache[className]) { 26347 $animate.removeClass($element, className); 26348 classCache[className] = false; 26349 } 26350 } 26351 26352 function toggleValidationCss(validationErrorKey, isValid) { 26353 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 26354 26355 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 26356 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 26357 } 26358} 26359 26360function isObjectEmpty(obj) { 26361 if (obj) { 26362 for (var prop in obj) { 26363 if (obj.hasOwnProperty(prop)) { 26364 return false; 26365 } 26366 } 26367 } 26368 return true; 26369} 26370 26371/** 26372 * @ngdoc directive 26373 * @name ngNonBindable 26374 * @restrict AC 26375 * @priority 1000 26376 * 26377 * @description 26378 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 26379 * DOM element. This is useful if the element contains what appears to be Angular directives and 26380 * bindings but which should be ignored by Angular. This could be the case if you have a site that 26381 * displays snippets of code, for instance. 26382 * 26383 * @element ANY 26384 * 26385 * @example 26386 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 26387 * but the one wrapped in `ngNonBindable` is left alone. 26388 * 26389 * @example 26390 <example> 26391 <file name="index.html"> 26392 <div>Normal: {{1 + 2}}</div> 26393 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 26394 </file> 26395 <file name="protractor.js" type="protractor"> 26396 it('should check ng-non-bindable', function() { 26397 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 26398 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 26399 }); 26400 </file> 26401 </example> 26402 */ 26403var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 26404 26405/* global jqLiteRemove */ 26406 26407var ngOptionsMinErr = minErr('ngOptions'); 26408 26409/** 26410 * @ngdoc directive 26411 * @name ngOptions 26412 * @restrict A 26413 * 26414 * @description 26415 * 26416 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 26417 * elements for the `<select>` element using the array or object obtained by evaluating the 26418 * `ngOptions` comprehension expression. 26419 * 26420 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 26421 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 26422 * increasing speed by not creating a new scope for each repeated instance, as well as providing 26423 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 26424 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 26425 * to a non-string value. This is because an option element can only be bound to string values at 26426 * present. 26427 * 26428 * When an item in the `<select>` menu is selected, the array element or object property 26429 * represented by the selected option will be bound to the model identified by the `ngModel` 26430 * directive. 26431 * 26432 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 26433 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 26434 * option. See example below for demonstration. 26435 * 26436 * ## Complex Models (objects or collections) 26437 * 26438 * By default, `ngModel` watches the model by reference, not value. This is important to know when 26439 * binding the select to a model that is an object or a collection. 26440 * 26441 * One issue occurs if you want to preselect an option. For example, if you set 26442 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, 26443 * because the objects are not identical. So by default, you should always reference the item in y
26443our collection 26444 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`. 26445 * 26446 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity 26447 * of the item not by reference, but by the result of the `track by` expression. For example, if your 26448 * collection items have an id property, you would `track by item.id`. 26449 * 26450 * A different issue with objects or collections is that ngModel won't detect if an object property or 26451 * a collection item changes. For that reason, `ngOptions` additionally watches the model using 26452 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. 26453 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection 26454 * has not changed identity, but only a property on the object or an item in the collection changes. 26455 * 26456 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 26457 * if the model is an array). This means that changing a property deeper than the first level inside the 26458 * object/collection will not trigger a re-rendering. 26459 * 26460 * ## `select` **`as`** 26461 * 26462 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 26463 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 26464 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 26465 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 26466 * 26467 * 26468 * ### `select` **`as`** and **`track by`** 26469 * 26470 * <div class="alert alert-warning"> 26471 * Be careful when using `select` **`as`** and **`track by`** in the same expression. 26472 * </div> 26473 * 26474 * Given this array of items on the $scope: 26475 * 26476 * ```js 26477 * $scope.items = [{ 26478 * id: 1, 26479 * label: 'aLabel', 26480 * subItem: { name: 'aSubItem' } 26481 * }, { 26482 * id: 2, 26483 * label: 'bLabel', 26484 * subItem: { name: 'bSubItem' } 26485 * }]; 26486 * ``` 26487 * 26488 * This will work: 26489 * 26490 * ```html 26491 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select> 26492 * ``` 26493 * ```js 26494 * $scope.selected = $scope.items[0]; 26495 * ``` 26496 * 26497 * but this will not work: 26498 * 26499 * ```html 26500 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select> 26501 * ``` 26502 * ```js 26503 * $scope.selected = $scope.items[0].subItem; 26504 * ``` 26505 * 26506 * In both examples, the **`track by`** expression is applied successfully to each `item` in the 26507 * `items` array. Because the selected option has been set programmatically in the controller, the 26508 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the 26509 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with 26510 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`** 26511 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value 26512 * is not matched against any `<option>` and the `<select>` appears as having no selected value. 26513 * 26514 * 26515 * @param {string} ngModel Assignable angular expression to data-bind to. 26516 * @param {string=} name Property name of the form under which the control is published. 26517 * @param {string=} required The control is considered valid only if value is entered. 26518 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26519 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26520 * `required` when you want to data-bind to the `required` attribute. 26521 * @param {comprehension_expression=} ngOptions in one of the following forms: 26522 * 26523 * * for array data sources: 26524 * * `label` **`for`** `value` **`in`** `array` 26525 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 26526 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 26527 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 26528 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 26529 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 26530 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 26531 * (for including a filter with `track by`) 26532 * * for object data sources:
26533 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 26534 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 26535 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 26536 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 26537 * * `select` **`as`** `label` **`group by`** `group` 26538 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 26539 * * `select` **`as`** `label` **`disable when`** `disable` 26540 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 26541 * 26542 * Where: 26543 * 26544 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 26545 * * `value`: local variable which will refer to each item in the `array` or each property value 26546 * of `object` during iteration. 26547 * * `key`: local variable which will refer to a property name in `object` during iteration. 26548 * * `label`: The result of this expression will be the label for `<option>` element. The 26549 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 26550 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 26551 * element. If not specified, `select` expression will default to `value`. 26552 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 26553 * DOM element. 26554 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 26555 * element. Return `true` to disable. 26556 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 26557 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 26558 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 26559 * even when the options are recreated (e.g. reloaded from the server). 26560 * 26561 * @example 26562 <example module="selectExample"> 26563 <file name="index.html"> 26564 <script> 26565 angular.module('selectExample', []) 26566 .controller('ExampleController', ['$scope', function($scope) { 26567 $scope.colors = [ 26568 {name:'black', shade:'dark'}, 26569 {name:'white', shade:'light', notAnOption: true}, 26570 {name:'red', shade:'dark'}, 26571 {name:'blue', shade:'dark', notAnOption: true}, 26572 {name:'yellow', shade:'light', notAnOption: false} 26573 ]; 26574 $scope.myColor = $scope.colors[2]; // red 26575 }]); 26576 </script>
26577 <div ng-controller="ExampleController"> 26578 <ul> 26579 <li ng-repeat="color in colors"> 26580 <label>Name: <input ng-model="color.name"></label> 26581 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 26582 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 26583 </li> 26584 <li> 26585 <button ng-click="colors.push({})">add</button> 26586 </li> 26587 </ul> 26588 <hr/> 26589 <label>Color (null not allowed): 26590 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 26591 </label><br/> 26592 <label>Color (null allowed): 26593 <span class="nullable"> 26594 <select ng-model="myColor" ng-options="color.name for color in colors"> 26595 <option value="">-- choose color --</option> 26596 </select> 26597 </span></label><br/> 26598 26599 <label>Color grouped by shade: 26600 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 26601 </select> 26602 </label><br/> 26603 26604 <label>Color grouped by shade, with some disabled: 26605 <select ng-model="myColor" 26606 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 26607 </select> 26608 </label><br/> 26609 26610 26611 26612 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 26613 <br/> 26614 <hr/> 26615 Currently selected: {{ {selected_color:myColor} }} 26616 <div style="border:solid 1px black; height:20px" 26617 ng-style="{'background-color':myColor.name}"> 26618 </div> 26619 </div> 26620 </file> 26621 <file name="protractor.js" type="protractor"> 26622 it('should check ng-options', function() { 26623 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 26624 element.all(by.model('myColor')).first().click(); 26625 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 26626 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 26627 element(by.css('.nullable select[ng-model="myColor"]')).click(); 26628 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 26629 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 26630 }); 26631 </file> 26632 </example> 26633 */ 26634 26635// jshint maxlen: false 26636// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 26637var 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]+?))?$/; 26638 // 1: value expression (valueFn) 26639 // 2: label expression (displayFn) 26640 // 3: group by expression (groupByFn) 26641 // 4: disable when expression (disableWhenFn) 26642 // 5: array item variable name 26643 // 6: object item key variable name 26644 // 7: object item value variable name 26645 // 8: collection expression 26646 // 9: track by expression 26647// jshint maxlen: 100 26648 26649 26650var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { 26651 26652 function parseOptionsExpression(optionsExp, selectElement, scope) { 26653 26654 var match = optionsExp.match(NG_OPTIONS_REGEXP); 26655 if (!(match)) { 26656 throw ngOptionsMinErr('iexp', 26657 "Expected expression in form of " + 26658 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 26659 " but got '{0}'. Element: {1}", 26660 optionsExp, startingTag(selectElement)); 26661 } 26662 26663 // Extract the parts from the ngOptions expression 26664 26665 // The variable name for the value of the item in the collection 26666 var valueName = match[5] || match[7]; 26667 // The variable name for the key of the item in the collection 26668 var keyName = match[6]; 26669 26670 // An expression that generates the viewValue for an option if there is a label expression 26671 var selectAs = / as /.test(match[0]) && match[1]; 26672 // An expression that is used to track the id of each object in the options collection 26673 var trackBy = match[9]; 26674 // An expression that generates the viewValue for an option if there is no label expression 26675 var valueFn = $parse(match[2] ? match[1] : valueName); 26676 var selectAsFn = selectAs && $parse(selectAs); 26677 var viewValueFn = selectAsFn || valueFn; 26678 var trackByFn = trackBy && $parse(trackBy); 26679 26680 // Get the value by which we are going to track the option 26681 // if we have a trackFn then use that (passing scope and locals) 26682 // otherwise just hash the given viewValue 26683 var getTrackByValueFn = trackBy ? 26684 function(value, locals) { return trackByFn(scope, locals); } : 26685 function getHashOfValue(value) { return hashKey(value); }; 26686 var getTrackByValue = function(value, key) {
26687 return getTrackByValueFn(value, getLocals(value, key)); 26688 }; 26689 26690 var displayFn = $parse(match[2] || match[1]); 26691 var groupByFn = $parse(match[3] || ''); 26692 var disableWhenFn = $parse(match[4] || ''); 26693 var valuesFn = $parse(match[8]); 26694 26695 var locals = {}; 26696 var getLocals = keyName ? function(value, key) { 26697 locals[keyName] = key; 26698 locals[valueName] = value; 26699 return locals; 26700 } : function(value) { 26701 locals[valueName] = value; 26702 return locals; 26703 }; 26704 26705 26706 function Option(selectValue, viewValue, label, group, disabled) { 26707 this.selectValue = selectValue; 26708 this.viewValue = viewValue; 26709 this.label = label; 26710 this.group = group; 26711 this.disabled = disabled; 26712 } 26713 26714 function getOptionValuesKeys(optionValues) { 26715 var optionValuesKeys; 26716 26717 if (!keyName && isArrayLike(optionValues)) { 26718 optionValuesKeys = optionValues; 26719 } else { 26720 // if object, extract keys, in enumeration order, unsorted 26721 optionValuesKeys = []; 26722 for (var itemKey in optionValues) { 26723 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 26724 optionValuesKeys.push(itemKey); 26725 } 26726 } 26727 } 26728 return optionValuesKeys; 26729 } 26730 26731 return { 26732 trackBy: trackBy, 26733 getTrackByValue: getTrackByValue, 26734 getWatchables: $parse(valuesFn, function(optionValues) { 26735 // Create a collection of things that we would like to watch (watchedArray) 26736 // so that they can all be watched using a single $watchCollection 26737 // that only runs the handler once if anything changes 26738 var watchedArray = []; 26739 optionValues = optionValues || []; 26740 26741 var optionValuesKeys = getOptionValuesKeys(optionValues); 26742 var optionValuesLength = optionValuesKeys.length; 26743 for (var index = 0; index < optionValuesLength; index++) { 26744 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 26745 var value = optionValues[key]; 26746 26747 var locals = getLocals(optionValues[key], key); 26748 var selectValue = getTrackByValueFn(optionValues[key], locals); 26749 watchedArray.push(selectValue); 26750 26751 // Only need to watch the displayFn if there is a specific label expression 26752 if (match[2] || match[1]) { 26753 var label = displayFn(scope, locals); 26754 watchedArray.push(label); 26755 } 26756 26757 // Only need to watch the disableWhenFn if there is a specific disable expression 26758 if (match[4]) { 26759 var disableWhen = disableWhenFn(scope, locals); 26760 watchedArray.push(disableWhen); 26761 } 26762 } 26763 return watchedArray; 26764 }), 26765 26766 getOptions: function() { 26767 26768 var optionItems = []; 26769 var selectValueMap = {}; 26770 26771 // The option values were already computed in the `getWatchables` fn, 26772 // which must have been called to trigger `getOptions` 26773 var optionValues = valuesFn(scope) || []; 26774 var optionValuesKeys = getOptionValuesKeys(optionValues); 26775 var optionValuesLength = optionValuesKeys.length; 26776 26777 for (var index = 0; index < optionValuesLength; index++) { 26778 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 26779 var value = optionValues[key]; 26780 var locals = getLocals(value, key); 26781 var viewValue = viewValueFn(scope, locals); 26782 var selectValue = getTrackByValueFn(viewValue, locals); 26783 var label = displayFn(scope, locals); 26784 var group = groupByFn(scope, locals); 26785 var disabled = disableWhenFn(scope, locals); 26786 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 26787 26788 optionItems.push(optionItem); 26789 selectValueMap[selectValue] = optionItem; 26790 } 26791 26792 return { 26793 items: optionItems, 26794 selectValueMap: selectValueMap, 26795 getOptionFromViewValue: function(value) { 26796 return selectValueMap[getTrackByValue(value)]; 26797 }, 26798 getViewValueFromOption: function(option) { 26799 // If the viewValue could be an object that may be mutated by the application, 26800 // we need to make a copy and not return the reference to the value on the option. 26801 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 26802 } 26803 }; 26804 } 26805 }; 26806 } 26807 26808 26809 // we can't just jqLite('<option>') since jqLite is not smart enough 26810 // to create it in <select> and IE barfs otherwise. 26811 var optionTemplate = document.createElement('option'), 26812 optGroupTemplate = document.createElement('optgroup'); 26813 26814 26815 function ngOptionsPostLink(scope, selectElement, attr, ctrls) { 26816 26817 // if ngModel is not defined, we don't need to do anything 26818 var ngModelCtrl = ctrls[1]; 26819 if (!ngModelCtrl) return; 26820 26821 var selectCtrl = ctrls[0]; 26822 var multiple = attr.multiple; 26823 26824 // The emptyOption allows the application developer to provide their own custom "empty" 26825 // option when the viewValue does not match any of the option values. 26826 var emptyOption; 26827 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 26828 if (children[i].value === '') { 26829 emptyOption = children.eq(i); 26830 break; 26831 } 26832 } 26833 26834 var providedEmptyOption = !!emptyOption; 26835 26836 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 26837 unknownOption.val('?'); 26838 26839 var options; 26840 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 26841 26842 26843 var renderEmptyOption = function() { 26844 if (!providedEmptyOption) {
26845 selectElement.prepend(emptyOption); 26846 } 26847 selectElement.val(''); 26848 emptyOption.prop('selected', true); // needed for IE 26849 emptyOption.attr('selected', true); 26850 }; 26851 26852 var removeEmptyOption = function() { 26853 if (!providedEmptyOption) { 26854 emptyOption.remove(); 26855 } 26856 }; 26857 26858 26859 var renderUnknownOption = function() { 26860 selectElement.prepend(unknownOption); 26861 selectElement.val('?'); 26862 unknownOption.prop('selected', true); // needed for IE 26863 unknownOption.attr('selected', true); 26864 }; 26865 26866 var removeUnknownOption = function() { 26867 unknownOption.remove(); 26868 }; 26869 26870 // Update the controller methods for multiple selectable options 26871 if (!multiple) { 26872 26873 selectCtrl.writeValue = function writeNgOptionsValue(value) { 26874 var option = options.getOptionFromViewValue(value); 26875 26876 if (option && !option.disabled) { 26877 if (selectElement[0].value !== option.selectValue) { 26878 removeUnknownOption(); 26879 removeEmptyOption(); 26880 26881 selectElement[0].value = option.selectValue; 26882 option.element.selected = true; 26883 option.element.setAttribute('selected', 'selected'); 26884 } 26885 } else { 26886 if (value === null || providedEmptyOption) { 26887 removeUnknownOption(); 26888 renderEmptyOption(); 26889 } else { 26890 removeEmptyOption(); 26891 renderUnknownOption(); 26892 } 26893 } 26894 }; 26895 26896 selectCtrl.readValue = function readNgOptionsValue() { 26897 26898 var selectedOption = options.selectValueMap[selectElement.val()]; 26899 26900 if (selectedOption && !selectedOption.disabled) { 26901 removeEmptyOption(); 26902 removeUnknownOption(); 26903 return options.getViewValueFromOption(selectedOption); 26904 } 26905 return null; 26906 }; 26907 26908 // If we are using `track by` then we must watch the tracked value on the model 26909 // since ngModel only watches for object identity change 26910 if (ngOptions.trackBy) { 26911 scope.$watch( 26912 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 26913 function() { ngModelCtrl.$render(); } 26914 ); 26915 } 26916 26917 } else { 26918 26919 ngModelCtrl.$isEmpty = function(value) { 26920 return !value || value.length === 0; 26921 }; 26922 26923 26924 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 26925 options.items.forEach(function(option) { 26926 option.element.selected = false; 26927 }); 26928 26929 if (value) { 26930 value.forEach(function(item) { 26931 var option = options.getOptionFromViewValue(item); 26932 if (option && !option.disabled) option.element.selected = true; 26933 }); 26934 } 26935 }; 26936 26937 26938 selectCtrl.readValue = function readNgOptionsMultiple() { 26939 var selectedValues = selectElement.val() || [], 26940 selections = []; 26941 26942 forEach(selectedValues, function(value) { 26943 var option = options.selectValueMap[value]; 26944 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 26945 }); 26946 26947 return selections; 26948 }; 26949 26950 // If we are using `track by` then we must watch these tracked values on the model 26951 // since ngModel only watches for object identity change 26952 if (ngOptions.trackBy) { 26953 26954 scope.$watchCollection(function() { 26955 if (isArray(ngModelCtrl.$viewValue)) { 26956 return ngModelCtrl.$viewValue.map(function(value) { 26957 return ngOptions.getTrackByValue(value); 26958 }); 26959 } 26960 }, function() { 26961 ngModelCtrl.$render(); 26962 }); 26963 26964 } 26965 } 26966 26967 26968 if (providedEmptyOption) { 26969 26970 // we need to remove it before calling selectElement.empty() because otherwise IE will 26971 // remove the label from the element. wtf? 26972 emptyOption.remove(); 26973 26974 // compile the element since there might be bindings in it 26975 $compile(emptyOption)(scope); 26976 26977 // remove the class, which is added automatically because we recompile the element and it 26978 // becomes the compilation root 26979 emptyOption.removeClass('ng-scope'); 26980 } else { 26981 emptyOption = jqLite(optionTemplate.cloneNode(false)); 26982 } 26983 26984 // We need to do this here to ensure that the options object is defined 26985 // when we first hit it in writeNgOptionsValue 26986 updateOptions(); 26987 26988 // We will re-render the option elements if the option values or labels change 26989 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 26990 26991 // ------------------------------------------------------------------ // 26992 26993 26994 function updateOptionElement(option, element) { 26995 option.element = element; 26996 element.disabled = option.disabled; 26997 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive 26998 // selects in certain circumstances when multiple selects are next to each other and display 26999 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 27000 // See https://github.com/angular/angular.js/issues/11314 for more info. 27001 // This is unfortunately untestable with unit / e2e tests 27002 if (option.label !== element.label) { 27003 element.label = option.label; 27004 element.textContent = option.label; 27005 } 27006 if (option.value !== element.value) element.value = option.selectValue; 27007 } 27008 27009 function addOrReuseElement(parent, current, type, templateElement) { 27010 var element; 27011 // Check whether we can reuse the next element 27012 if (current && lowercase(current.nodeName) === type) { 27013 // The next element is the right type so reuse it 27014 element = current; 27015 } else { 27016 // The next element is not the right type so create a new one 27017 element = templateElement.cloneNode(false); 27018 if (!current) { 27019 // There are no more elements so just append it to the select 27020 parent.appendChild(element); 27021 } else { 27022 // The next element is not a group so insert the new one 27023 parent.insertBefore(element, current); 27024 } 27025 } 27026 return element; 27027 } 27028 27029 27030 function removeExcessElements(current) { 27031 var next; 27032 while (current) { 27033 next = current.nextSibling; 27034 jqLiteRemove(current); 27035 current = next; 27036 } 27037 } 27038 27039 27040 function skipEmptyAndUnknownOptions(current) { 27041 var emptyOption_ = emptyOption && emptyOption[0]; 27042 var unknownOption_ = unknownOption && unknownOption[0]; 27043 27044 // We cannot rely on the extracted empty option being the same as the compiled empty option, 27045 // because the compiled empty option might have been replaced by a comment because 27046 // it had an "element" transclusion directive on it (such as ngIf) 27047 if (emptyOption_ || unknownOption_) { 27048 while (current && 27049 (current === emptyOption_ || 27050 current === unknownOption_ || 27051 current.nodeType === NODE_TYPE_COMMENT || 27052 (nodeName_(current) === 'option' && current.value === ''))) { 27053 current = current.nextSibling; 27054 } 27055 } 27056 return current; 27057 } 27058 27059 27060 function updateOptions() { 27061 27062 var previousValue = options && selectCtrl.readValue(); 27063 27064 options = ngOptions.getOptions(); 27065 27066 var groupMap = {}; 27067 var currentElement = selectElement[0].firstChild; 27068 27069 // Ensure that the empty option is always there if it was explicitly provided 27070 if (providedEmptyOption) {
27071 selectElement.prepend(emptyOption); 27072 } 27073 27074 currentElement = skipEmptyAndUnknownOptions(currentElement); 27075 27076 options.items.forEach(function updateOption(option) { 27077 var group; 27078 var groupElement; 27079 var optionElement; 27080 27081 if (option.group) { 27082 27083 // This option is to live in a group 27084 // See if we have already created this group 27085 group = groupMap[option.group]; 27086 27087 if (!group) { 27088 27089 // We have not already created this group 27090 groupElement = addOrReuseElement(selectElement[0], 27091 currentElement, 27092 'optgroup', 27093 optGroupTemplate); 27094 // Move to the next element 27095 currentElement = groupElement.nextSibling; 27096 27097 // Update the label on the group element 27098 groupElement.label = option.group; 27099 27100 // Store it for use later 27101 group = groupMap[option.group] = { 27102 groupElement: groupElement, 27103 currentOptionElement: groupElement.firstChild 27104 }; 27105 27106 } 27107 27108 // So now we have a group for this option we add the option to the group 27109 optionElement = addOrReuseElement(group.groupElement, 27110 group.currentOptionElement, 27111 'option', 27112 optionTemplate); 27113 updateOptionElement(option, optionElement); 27114 // Move to the next element 27115 group.currentOptionElement = optionElement.nextSibling; 27116 27117 } else { 27118 27119 // This option is not in a group 27120 optionElement = addOrReuseElement(selectElement[0], 27121 currentElement, 27122 'option', 27123 optionTemplate); 27124 updateOptionElement(option, optionElement); 27125 // Move to the next element 27126 currentElement = optionElement.nextSibling; 27127 } 27128 }); 27129 27130 27131 // Now remove all excess options and group 27132 Object.keys(groupMap).forEach(function(key) { 27133 removeExcessElements(groupMap[key].currentOptionElement); 27134 }); 27135 removeExcessElements(currentElement); 27136 27137 ngModelCtrl.$render(); 27138 27139 // Check to see if the value has changed due to the update to the options 27140 if (!ngModelCtrl.$isEmpty(previousValue)) { 27141 var nextValue = selectCtrl.readValue(); 27142 var isNotPrimitive = ngOptions.trackBy || multiple; 27143 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 27144 ngModelCtrl.$setViewValue(nextValue); 27145 ngModelCtrl.$render(); 27146 } 27147 } 27148 27149 } 27150 } 27151 27152 return { 27153 restrict: 'A', 27154 terminal: true, 27155 require: ['select', '?ngModel'], 27156 link: { 27157 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { 27158 // Deactivate the SelectController.register method to prevent 27159 // option directives from accidentally registering themselves 27160 // (and unwanted $destroy handlers etc.) 27161 ctrls[0].registerOption = noop; 27162 }, 27163 post: ngOptionsPostLink 27164 } 27165 }; 27166}]; 27167 27168/** 27169 * @ngdoc directive 27170 * @name ngPluralize 27171 * @restrict EA 27172 * 27173 * @description 27174 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 27175 * These rules are bundled with angular.js, but can be overridden 27176 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 27177 * by specifying the mappings between 27178 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 27179 * and the strings to be displayed. 27180 * 27181 * # Plural categories and explicit number rules 27182 * There are two 27183 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 27184 * in Angular's default en-US locale: "one" and "other". 27185 * 27186 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 27187 * any number that is not 1), an explicit number rule can only match one number. For example, the 27188 * explicit number rule for "3" matches the number 3. There are examples of plural categories 27189 * and explicit number rules throughout the rest of this documentation. 27190 * 27191 * # Configuring ngPluralize 27192 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 27193 * You can also provide an optional attribute, `offset`. 27194 * 27195 * The value of the `count` attribute can be either a string or an {@link guide/expression 27196 * Angular expression}; these are evaluated on the current scope for its bound value. 27197 * 27198 * The `when` attribute specifies the mappings between plural categories and the actual 27199 * string to be displayed. The value of the attribute should be a JSON object. 27200 * 27201 * The following example shows how to configure ngPluralize: 27202 * 27203 * ```html 27204 * <ng-pluralize count="personCount"
27205 when="{'0': 'Nobody is viewing.', 27206 * 'one': '1 person is viewing.', 27207 * 'other': '{} people are viewing.'}"> 27208 * </ng-pluralize> 27209 *``` 27210 * 27211 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 27212 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 27213 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 27214 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 27215 * show "a dozen people are viewing". 27216 * 27217 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 27218 * into pluralized strings. In the previous example, Angular will replace `{}` with 27219 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 27220 * for <span ng-non-bindable>{{numberExpression}}</span>. 27221 * 27222 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 27223 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 27224 * 27225 * # Configuring ngPluralize with offset 27226 * The `offset` attribute allows further customization of pluralized text, which can result in 27227 * a better user experience. For example, instead of the message "4 people are viewing this document", 27228 * you might display "John, Kate and 2 others are viewing this document". 27229 * The offset attribute allows you to offset a number by any desired value. 27230 * Let's take a look at an example: 27231 * 27232 * ```html 27233 * <ng-pluralize count="personCount" offset=2 27234 * when="{'0': 'Nobody is viewing.', 27235 * '1': '{{person1}} is viewing.', 27236 * '2': '{{person1}} and {{person2}} are viewing.', 27237 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 27238 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 27239 * </ng-pluralize> 27240 * ``` 27241 * 27242 * Notice that we are still using two plural categories(one, other), but we added 27243 * three explicit number rules 0, 1 and 2. 27244 * When one person, perhaps John, views the document, "John is viewing" will be shown. 27245 * When three people view the document, no explicit number rule is found, so 27246 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 27247 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 27248 * is shown. 27249 * 27250 * Note that when you specify offsets, you must provide explicit number rules for 27251 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 27252 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 27253 * plural categories "one" and "other". 27254 * 27255 * @param {string|expression} count The variable to be bound to. 27256 * @param {string} when The mapping between plural category to its corresponding strings. 27257 * @param {number=} offset Offset to deduct from the total number. 27258 * 27259 * @example 27260 <example module="pluralizeExampl
vendor: 4,913 bytes, lines 27260-27374
27260e"> 27261 <file name="index.html"> 27262 <script> 27263 angular.module('pluralizeExample', []) 27264 .controller('ExampleController', ['$scope', function($scope) { 27265 $scope.person1 = 'Igor'; 27266 $scope.person2 = 'Misko'; 27267 $scope.personCount = 1; 27268 }]); 27269 </script> 27270 <div ng-controller="ExampleController"> 27271 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 27272 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 27273 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 27274 27275 <!--- Example with simple pluralization rules for en locale ---> 27276 Without Offset: 27277 <ng-pluralize count="personCount" 27278 when="{'0': 'Nobody is viewing.', 27279 'one': '1 person is viewing.', 27280 'other': '{} people are viewing.'}"> 27281 </ng-pluralize><br> 27282 27283 <!--- Example with offset ---> 27284 With Offset(2): 27285 <ng-pluralize count="personCount" offset=2 27286 when="{'0': 'Nobody is viewing.', 27287 '1': '{{person1}} is viewing.', 27288 '2': '{{person1}} and {{person2}} are viewing.', 27289 'one': '{{person1}}, {{person2}} and one other person are viewing.', 27290 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 27291 </ng-pluralize> 27292 </div> 27293 </file> 27294 <file name="protractor.js" type="protractor"> 27295 it('should show correct pluralized string', function() { 27296 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 27297 var withOffset = element.all(by.css('ng-pluralize')).get(1); 27298 var countInput = element(by.model('personCount')); 27299 27300 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 27301 expect(withOffset.getText()).toEqual('Igor is viewing.'); 27302 27303 countInput.clear(); 27304 countInput.sendKeys('0'); 27305 27306 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 27307 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 27308 27309 countInput.clear(); 27310 countInput.sendKeys('2'); 27311 27312 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 27313 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 27314 27315 countInput.clear(); 27316 countInput.sendKeys('3'); 27317 27318 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 27319 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 27320 27321 countInput.clear(); 27322 countInput.sendKeys('4'); 27323 27324 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 27325 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 27326 }); 27327 it('should show data-bound names', function() { 27328 var withOffset = element.all(by.css('ng-pluralize')).get(1); 27329 var personCount = element(by.model('personCount')); 27330 var person1 = element(by.model('person1')); 27331 var person2 = element(by.model('person2')); 27332 personCount.clear(); 27333 personCount.sendKeys('4'); 27334 person1.clear(); 27335 person1.sendKeys('Di'); 27336 person2.clear(); 27337 person2.sendKeys('Vojta'); 27338 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 27339 }); 27340 </file> 27341 </example> 27342 */ 27343var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 27344 var BRACE = /{}/g, 27345 IS_WHEN = /^when(Minus)?(.+)$/; 27346 27347 return { 27348 link: function(scope, element, attr) { 27349 var numberExp = attr.count, 27350 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 27351 offset = attr.offset || 0, 27352 whens = scope.$eval(whenExp) || {}, 27353 whensExpFns = {}, 27354 startSymbol = $interpolate.startSymbol(), 27355 endSymbol = $interpolate.endSymbol(), 27356 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 27357 watchRemover = angular.noop, 27358 lastCount; 27359 27360 forEach(attr, function(expression, attributeName) { 27361 var tmpMatch = IS_WHEN.exec(attributeName); 27362 if (tmpMatch) { 27363 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 27364 whens[whenKey] = element.attr(attr.$attr[attributeName]); 27365 } 27366 }); 27367 forEach(whens, function(expression, key) { 27368 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 27369 27370 }); 27371 27372 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 27373 var count = parseFloat(newVal); 27374 var countIsNaN = isN
vendor: 11,505 bytes, lines 27374-27623
27374aN(count); 27375 27376 if (!countIsNaN && !(count in whens)) { 27377 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 27378 // Otherwise, check it against pluralization rules in $locale service. 27379 count = $locale.pluralCat(count - offset); 27380 } 27381 27382 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 27383 // In JS `NaN !== NaN`, so we have to exlicitly check. 27384 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 27385 watchRemover(); 27386 var whenExpFn = whensExpFns[count]; 27387 if (isUndefined(whenExpFn)) { 27388 if (newVal != null) { 27389 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 27390 } 27391 watchRemover = noop; 27392 updateElementText(); 27393 } else { 27394 watchRemover = scope.$watch(whenExpFn, updateElementText); 27395 } 27396 lastCount = count; 27397 } 27398 }); 27399 27400 function updateElementText(newText) { 27401 element.text(newText || ''); 27402 } 27403 } 27404 }; 27405}]; 27406 27407/** 27408 * @ngdoc directive 27409 * @name ngRepeat 27410 * @multiElement 27411 * 27412 * @description 27413 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 27414 * instance gets its own scope, where the given loop variable is set to the current collection item, 27415 * and `$index` is set to the item index or key. 27416 * 27417 * Special properties are exposed on the local scope of each template instance, including: 27418 * 27419 * | Variable | Type | Details | 27420 * |-----------|-----------------|-----------------------------------------------------------------------------| 27421 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 27422 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 27423 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 27424 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 27425 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 27426 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 27427 * 27428 * <div class="alert alert-info"> 27429 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 27430 * This may be useful when, for instance, nesting ngRepeats. 27431 * </div> 27432 * 27433 * 27434 * # Iterating over object properties 27435 * 27436 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 27437 * syntax: 27438 * 27439 * ```js 27440 * <div ng-repeat="(key, value) in myObj"> ... </div> 27441 * ``` 27442 * 27443 * You need to be aware that the JavaScript specification does not define the order of keys 27444 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive 27445 * used to sort the keys alphabetically.) 27446 * 27447 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser 27448 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing 27449 * keys in the order in which they were defined, although there are exceptions when keys are deleted 27450 * and reinstated. See the [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). 27451 * 27452 * If this is not desired, the recommended workaround is to convert your object into an array 27453 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 27454 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 27455 * or implement a `$watch` on the object yourself. 27456 * 27457 * 27458 * # Tracking and Duplicates 27459 * 27460 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in 27461 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM: 27462 * 27463 * * When an item is added, a new instance of the template is added to the DOM. 27464 * * When an item is removed, its template instance is removed from the DOM. 27465 * * When items are reordered, their respective templates are reordered in the DOM. 27466 * 27467 * To minimize creation of DOM elements, `ngRepeat` uses a function 27468 * to "keep track" of all items in the collection and their corresponding DOM elements. 27469 * For example, if an item is added to the collection, ngRepeat will know that all other items 27470 * already have DOM elements, and will not re-render them. 27471 * 27472 * The default tracking function (which tracks items by their identity) does not allow 27473 * duplicate items in arrays. This is because when there are duplicates, it is not possible 27474 * to maintain a one-to-one mapping between collection items and DOM elements. 27475 * 27476 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 27477 * with your own using the `track by` expression. 27478 * 27479 * For example, you may track items by the index of each item in the collection, using the 27480 * special scope property `$index`: 27481 * ```html 27482 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 27483 * {{n}} 27484 * </div> 27485 * ``` 27486 * 27487 * You may also use arbitrary expressions in `track by`, including references to custom functions 27488 * on the scope: 27489 * ```html 27490 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 27491 * {{n}} 27492 * </div> 27493 * ``` 27494 * 27495 * <div class="alert alert-success"> 27496 * If you are working with objects that have an identifier property, you should track 27497 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 27498 * will not have to rebuild the DOM elements for items it has already rendered, even if the 27499 * JavaScript objects in the collection have been substituted for new ones. For large collections, 27500 * this signifincantly improves rendering performance. If you don't have a unique identifier, 27501 * `track by $index` can also provide a performance boost. 27502 * </div> 27503 * ```html 27504 * <div ng-repeat="model in collection track by model.id"> 27505 * {{model.name}} 27506 * </div> 27507 * ``` 27508 * 27509 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 27510 * `$id` function, which tracks items by their identity: 27511 * ```html 27512 * <div ng-repeat="obj in collection track by $id(obj)"> 27513 * {{obj.prop}} 27514 * </div> 27515 * ``` 27516 * 27517 * <div class="alert alert-warning"> 27518 * **Note:** `track by` must always be the last expression: 27519 * </div> 27520 * ``` 27521 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 27522 * {{model.name}} 27523 * </div> 27524 * ``` 27525 * 27526 * # Special repeat start and end points 27527 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 27528 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 27529 * 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) 27530 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 27531 * 27532 * The example below makes use of this feature: 27533 * ```html 27534 * <header ng-repeat-start="item in items"> 27535 * Header {{ item }} 27536 * </header> 27537 * <div class="body"> 27538 * Body {{ item }} 27539 * </div> 27540 * <footer ng-repeat-end> 27541 * Footer {{ item }} 27542 * </footer> 27543 * ``` 27544 * 27545 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 27546 * ```html 27547 * <header> 27548 * Header A 27549 * </header> 27550 * <div class="body"> 27551 * Body A 27552 * </div> 27553 * <footer> 27554 * Footer A 27555 * </footer> 27556 * <header> 27557 * Header B 27558 * </header> 27559 * <div class="body"> 27560 * Body B 27561 * </div> 27562 * <footer> 27563 * Footer B 27564 * </footer> 27565 * ``` 27566 * 27567 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 27568 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 27569 * 27570 * @animations 27571 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 27572 * 27573 * **.leave** - when an item is removed from the list or when an item is filtered out 27574 * 27575 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 27576 * 27577 * @element ANY 27578 * @scope 27579 * @priority 1000 27580 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 27581 * formats are currently supported: 27582 * 27583 * * `variable in expression` â where variable is the user defined loop variable and `expression` 27584 * is a scope expression giving the collection to enumerate. 27585 * 27586 * For example: `album in artist.albums`. 27587 * 27588 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 27589 * and `expression` is the scope expression giving the collection to enumerate. 27590 * 27591 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 27592 * 27593 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 27594 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 27595 * is specified, ng-repeat associates elements by identity. It is an error to have 27596 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 27597 * mapped to the same DOM element, which is not possible.) 27598 * 27599 * Note that the tracking expression must come last, after any filters, and the alias expression. 27600 * 27601 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 27602 * will be associated by item identity in the array. 27603 * 27604 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 27605 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 27606 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 27607 * element in the same way in the DOM. 27608 * 27609 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 27610 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 27611 * property is same. 27612 * 27613 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 27614 * to items in conjunction with a tracking expression. 27615 * 27616 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 27617 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 27618 * when a filter is active on the repeater, but the filtered result set is empty. 27619 * 27620 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 27621 * the items have been processed through the filter. 27622 * 27623 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax s
27623o it can be used only at the end 27624 * (and not as operator, inside an expression). 27625 * 27626 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 27627 * 27628 * @example 27629 * This example initializes the scope to a list of names and 27630 * then uses `ngRepeat` to display every person: 27631 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27632 <file name="index.html"> 27633 <div ng-init="friends = [ 27634 {name:'John', age:25, gender:'boy'}, 27635 {name:'Jessie', age:30, gender:'girl'}, 27636 {name:'Johanna', age:28, gender:'girl'}, 27637 {name:'Joy', age:15, gender:'girl'}, 27638 {name:'Mary', age:28, gender:'girl'}, 27639 {name:'Peter', age:95, gender:'boy'}, 27640 {name:'Sebastian', age:50, gender:'boy'}, 27641 {name:'Erika', age:27, gender:'girl'}, 27642 {name:'Patrick', age:40, gender:'boy'}, 27643 {name:'Samantha', age:60, gender:'girl'} 27644 ]"> 27645 I have {{friends.length}} friends. They are: 27646 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 27647 <ul class="example-animate-container"> 27648 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 27649 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 27650 </li> 27651 <li class="animate-repeat" ng-if="results.length == 0"> 27652 <strong>No results found...</strong> 27653 </li> 27654 </ul> 27655 </div> 27656 </file> 27657 <file name="animations.css"> 27658 .example-animate-container { 27659 background:white; 27660 border:1px solid black; 27661 list-style:none; 27662 margin:0; 27663 padding:0 10px; 27664 } 27665 27666 .animate-repeat { 27667 line-height:40px; 27668 list-style:none; 27669 box-sizing:border-box; 27670 } 27671 27672 .animate-repeat.ng-move, 27673 .animate-repeat.ng-enter, 27674 .animate-repeat.ng-leave { 27675 transition:all linear 0.5s; 27676 } 27677 27678 .animate-repeat.ng-leave.ng-leave-active, 27679 .animate-repeat.ng-move, 27680 .animate-repeat.ng-enter { 27681 opacity:0; 27682 max-height:0; 27683 } 27684 27685 .animate-repeat.ng-leave, 27686 .animate-repeat.ng-move.ng-move-active, 27687 .animate-repeat.ng-enter.ng-enter-active { 27688 opacity:1; 27689 max-height:40px; 27690 } 27691 </file> 27692 <file name="protractor.js" type="protractor"> 27693 var friends = element.all(by.repeater('friend in friends')); 27694 27695 it('should render initial data set', function() { 27696 expect(friends.count()).toBe(10); 27697 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 27698 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 27699 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 27700 expect(element(by.binding('friends.length')).getText()) 27701 .toMatch("I have 10 friends. They are:"); 27702 }); 27703 27704 it('should update repeater when filter predicate changes', function() { 27705 expect(friends.count()).toBe(10); 27706 27707 element(by.model('q')).sendKeys('ma'); 27708 27709 expect(friends.count()).toBe(2); 27710 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 27711 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 27712 }); 27713 </file> 27714 </example> 27715 */ 27716var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 27717 var NG_REMOVED = '$$NG_REMOVED'; 27718 var ngRepeatMinErr = minErr('ngRepeat'); 27719 27720 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 27721 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 27722 scope[valueIdentifier] = value; 27723 if (keyIdentifier) scope[keyIdentifier] = key; 27724 scope.$index = index; 27725 scope.$first = (index === 0); 27726 scope.$last = (index === (arrayLength - 1)); 27727 scope.$middle = !(scope.$first || scope.$last); 27728 // jshint bitwise: false 27729 scope.$odd = !(scope.$even = (index&1) === 0); 27730 // jshint bitwise: true 27731 }; 27732 27733 var getBlockStart = function(block) { 27734 return block.clone[0]; 27735 }; 27736 27737 var getBlockEnd = function(block) { 27738 return block.clone[block.clone.length - 1]; 27739 }; 27740 27741 27742 return { 27743 restrict: 'A', 27744 multiElement: true, 27745 transclude: 'element', 27746 priority: 1000, 27747 terminal: true, 27748 $$tlb: true, 27749 compile: function ngRepeatCompile($element, $attr) { 27750 var expression = $attr.ngRepeat; 27751 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); 27752 27753 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*$/); 27754 27755 if (!match) { 27756 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 27757 expression); 27758 } 27759 27760 var lhs = match[1]; 27761 var rhs = match[2]; 27762 var aliasAs = match[3]; 27763 var trackByExp = match[4]; 27764 27765 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 27766 27767 if (!match) { 27768 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 27769 lhs); 27770 } 27771 var valueIdentifier = match[3] || match[1]; 27772 var keyIdentifier = match[2]; 27773 27774 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 27775 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 27776 throw ngRepeatMinErr('badident', "alias '{0}
27776' is invalid --- must be a valid JS identifier which is not a reserved name.", 27777 aliasAs); 27778 } 27779 27780 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 27781 var hashFnLocals = {$id: hashKey}; 27782 27783 if (trackByExp) { 27784 trackByExpGetter = $parse(trackByExp); 27785 } else { 27786 trackByIdArrayFn = function(key, value) { 27787 return hashKey(value); 27788 }; 27789 trackByIdObjFn = function(key) { 27790 return key; 27791 }; 27792 } 27793 27794 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 27795 27796 if (trackByExpGetter) { 27797 trackByIdExpFn = function(key, value, index) { 27798 // assign key, value, and $index to the locals so that they can be used in hash functions 27799 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 27800 hashFnLocals[valueIdentifier] = value; 27801 hashFnLocals.$index = index; 27802 return trackByExpGetter($scope, hashFnLocals); 27803 }; 27804 } 27805 27806 // Store a list of elements from previous run. This is a hash where key is the item from the 27807 // iterator, and the value is objects with following properties. 27808 // - scope: bound scope 27809 // - element: previous element. 27810 // - index: position 27811 // 27812 // We are using no-proto object so that we don't need to guard against inherited props via 27813 // hasOwnProperty. 27814 var lastBlockMap = createMap(); 27815 27816 //watch props 27817 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 27818 var index, length, 27819 previousNode = $element[0], // node that cloned nodes should be inserted after 27820 // initialized to the comment node anchor 27821 nextNode, 27822 // Same as lastBlockMap but it has the current state. It will become the 27823 // lastBlockMap on the next iteration. 27824 nextBlockMap = createMap(), 27825 collectionLength, 27826 key, value, // key/value of iteration 27827 trackById, 27828 trackByIdFn, 27829 collectionKeys, 27830 block, // last object information {scope, element, id} 27831 nextBlockOrder, 27832 elementsToRemove; 27833 27834 if (aliasAs) { 27835 $scope[aliasAs] = collection; 27836 } 27837 27838 if (isArrayLike(collection)) { 27839 collectionKeys = collection; 27840 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 27841 } else { 27842 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 27843 // if object, extract keys, in enumeration order, unsorted 27844 collectionKeys = []; 27845 for (var itemKey in collection) { 27846 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 27847 collectionKeys.push(itemKey); 27848 } 27849 } 27850 } 27851 27852 collectionLength = collectionKeys.length; 27853 nextBlockOrder = new Array(collectionLength); 27854 27855 // locate existing items 27856 for (index = 0; index < collectionLength; index++) { 27857 key = (collection === collectionKeys) ? index : collectionKeys[index]; 27858 value = collection[key]; 27859 trackById = trackByIdFn(key, value, index); 27860 if (lastBlockMap[trackById]) { 27861 // found previously seen block 27862 block = lastBlockMap[trackById]; 27863 delete lastBlockMap[trackById]; 27864 nextBlockMap[trackById] = block; 27865 nextBlockOrder[index] = block; 27866 } else if (nextBlockMap[trackById]) { 27867 // if collision detected. restore lastBlockMap and throw an error 27868 forEach(nextBlockOrder, function(block) { 27869 if (block && block.scope) lastBlockMap[block.id] = block; 27870 }); 27871 throw ngRepeatMinErr('dupes', 27872 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 27873 expression, trackById, value); 27874 } else { 27875 // new never before seen block 27876 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 27877 nextBlockMap[trackById] = true; 27878 } 27879 } 27880 27881 // remove leftover items 27882 for (var blockKey in lastBlockMap) { 27883 block = lastBlockMap[blockKey]; 27884 elementsToRemove = getBlockNodes(block.clone); 27885 $animate.leave(elementsToRemove); 27886 if (elementsToRemove[0].parentNode) { 27887 // if the element was not removed yet because of pending animation, mark it as deleted 27888 // so that we can ignore it later 27889 for (index = 0, length = elementsToRemove.length; index < length; index++) { 27890 elementsToRemove[index][NG_REMOVED] = true; 27891 } 27892 } 27893 block.scope.$destroy(); 27894 } 27895
vendor: 6,929 bytes, lines 27896-28048
27896 // we are not using forEach for perf reasons (trying to avoid #call) 27897 for (index = 0; index < collectionLength; index++) { 27898 key = (collection === collectionKeys) ? index : collectionKeys[index]; 27899 value = collection[key]; 27900 block = nextBlockOrder[index]; 27901 27902 if (block.scope) { 27903 // if we have already seen this object, then we need to reuse the 27904 // associated scope/element 27905 27906 nextNode = previousNode; 27907 27908 // skip nodes that are already pending removal via leave animation 27909 do { 27910 nextNode = nextNode.nextSibling; 27911 } while (nextNode && nextNode[NG_REMOVED]); 27912 27913 if (getBlockStart(block) != nextNode) { 27914 // existing item which got moved 27915 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); 27916 } 27917 previousNode = getBlockEnd(block); 27918 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 27919 } else { 27920 // new item which we don't know about 27921 $transclude(function ngRepeatTransclude(clone, scope) { 27922 block.scope = scope; 27923 // http://jsperf.com/clone-vs-createcomment 27924 var endNode = ngRepeatEndComment.cloneNode(false); 27925 clone[clone.length++] = endNode; 27926 27927 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? 27928 $animate.enter(clone, null, jqLite(previousNode)); 27929 previousNode = endNode; 27930 // Note: We only need the first/last node of the cloned nodes. 27931 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 27932 // by a directive with templateUrl when its template arrives. 27933 block.clone = clone; 27934 nextBlockMap[block.id] = block; 27935 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 27936 }); 27937 } 27938 } 27939 lastBlockMap = nextBlockMap; 27940 }); 27941 }; 27942 } 27943 }; 27944}]; 27945 27946var NG_HIDE_CLASS = 'ng-hide'; 27947var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 27948/** 27949 * @ngdoc directive 27950 * @name ngShow 27951 * @multiElement 27952 * 27953 * @description 27954 * The `ngShow` directive shows or hides the given HTML element based on the expression 27955 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 27956 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 27957 * in AngularJS and sets the display style to none (using an !important flag). 27958 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27959 * 27960 * ```html 27961 * <!-- when $scope.myValue is truthy (element is visible) --> 27962 * <div ng-show="myValue"></div> 27963 * 27964 * <!-- when $scope.myValue is falsy (element is hidden) --> 27965 * <div ng-show="myValue" class="ng-hide"></div> 27966 * ``` 27967 * 27968 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 27969 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 27970 * from the element causing the element not to appear hidden. 27971 * 27972 * ## Why is !important used? 27973 * 27974 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27975 * can be easily overridden by heavier selectors. For example, something as simple 27976 * as changing the display style on a HTML list item would make hidden elements appear visible. 27977 * This also becomes a bigger issue when dealing with CSS frameworks. 27978 * 27979 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27980 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27981 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27982 * 27983 * ### Overriding `.ng-hide` 27984 * 27985 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27986 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27987 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 27988 * with extra animation classes that can be added. 27989 * 27990 * ```css 27991 * .ng-hide:not(.ng-hide-animate) { 27992 * /* this is just another form of hiding an element */ 27993 * display: block!important; 27994 * position: absolute; 27995 * top: -9999px; 27996 * left: -9999px; 27997 * } 27998 * ``` 27999 * 28000 * By default you don't need to override in CSS anything and the animations will work around the display style. 28001 * 28002 * ## A note about animations with `ngShow` 28003 * 28004 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 28005 * is true and false. This system works like the animation system present with ngClass except that 28006 * you must also include the !important flag to override the display property 28007 * so that you can perform an animation when the element is hidden during the time of the animation. 28008 * 28009 * ```css 28010 * // 28011 * //a working example can be found at the bottom of this page 28012 * // 28013 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 28014 * /* this is required as of 1.3x to properly 28015 * apply all styling in a show/hide animation */ 28016 * transition: 0s linear all; 28017 * } 28018 * 28019 * .my-element.ng-hide-add-active, 28020 * .my-element.ng-hide-remove-active { 28021 * /* the transition is defined in the active class */ 28022 * transition: 1s linear all; 28023 * } 28024 * 28025 * .my-element.ng-hide-add { ... } 28026 * .my-element.ng-hide-add.ng-hide-add-active { ... } 28027 * .my-element.ng-hide-remove { ... } 28028 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 28029 * ``` 28030 * 28031 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 28032 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 28033 * 28034 * @animations 28035 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible 28036 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden 28037 * 28038 * @element ANY 28039 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 28040 * then the element is shown or hidden respectively. 28041 * 28042 * @example 28043 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 28044 <file name="index.html"> 28045 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 28046 <div> 28047 Show: 28048 <div class="check-element animate-show" ng-show="checked">
28049 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 28050 </div> 28051 </div> 28052 <div> 28053 Hide: 28054 <div class="check-element animate-show" ng-hide="checked"> 28055 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 28056 </div> 28057 </div> 28058 </file> 28059 <file name="glyphicons.css"> 28060 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 28061 </file> 28062 <file name="animations.css"> 28063 .animate-show { 28064 line-height: 20px; 28065 opacity: 1; 28066 padding: 10px; 28067 border: 1px solid black; 28068 background: white; 28069 } 28070 28071 .animate-show.ng-hide-add, .animate-show.ng-hide-remove { 28072 transition: all linear 0.5s; 28073 } 28074 28075 .animate-show.ng-hide { 28076 line-height: 0; 28077 opacity: 0; 28078 padding: 0 10px; 28079 } 28080 28081 .check-element { 28082 padding: 10px; 28083 border: 1px solid black; 28084 background: white; 28085 } 28086 </file> 28087 <file name="protractor.js" type="protractor"> 28088 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 28089 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 28090 28091 it('should check ng-show / ng-hide', function() { 28092 expect(thumbsUp.isDisplayed()).toBeFalsy(); 28093 expect(thumbsDown.isDisplayed()).toBeTruthy(); 28094 28095 element(by.model('checked')).click(); 28096 28097 expect(thumbsUp.isDisplayed()).toBeTruthy(); 28098 expect(thumbsDown.isDisplayed()).toBeFalsy(); 28099 }); 28100 </file> 28101 </example> 28102 */ 28103var ngShowDirective = ['$animate', function($animate) { 28104 return { 28105 restrict: 'A', 28106 multiElement: true, 28107 link: function(scope, element, attr) { 28108 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 28109 // we're adding a temporary, animation-specific class for ng-hide since this way 28110 // we can control when the element is actually displayed on screen without having 28111 // to have a global/greedy CSS selector that breaks when other animations are run. 28112 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 28113 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 28114 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 28115 }); 28116 }); 28117 } 28118 }; 28119}]; 28120 28121 28122/** 28123 * @ngdoc directive 28124 * @name ngHide 28125 * @multiElement 28126 * 28127 * @description 28128 * The `ngHide` directive shows or hides the given HTML element based on the expression 28129 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 28130 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 28131 * in AngularJS and sets the display style to none (using an !important flag). 28132 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 28133 * 28134 * ```html 28135 * <!-- when $scope.myValue is truthy (element is hidden) --> 28136 * <div ng-hide="myValue" class="ng-hide"></div> 28137 * 28138 * <!-- when $scope.myValue is falsy (element is visible) --> 28139 * <div ng-hide="myValue"></div> 28140 * ``` 28141 * 28142 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 28143 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 28144 * from the element causing the element not to appear hidden. 28145 * 28146 * ## Why is !important used? 28147 * 28148 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 28149 * can be easily overridden by heavier selectors. For example, something as simple 28150 * as changing the display style on a HTML list item would make hidden elements appear visible. 28151 * This also becomes a bigger issue when dealing with CSS frameworks. 28152 * 28153 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 28154 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 28155 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 28156 * 28157 * ### Overriding `.ng-hide` 28158 * 28159 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 28160 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 28161 * class in CSS: 28162 * 28163 * ```css 28164 * .ng-hide { 28165 * /* this is just another form of hiding an element */ 28166 * display: block!important; 28167 * position: absolute; 28168 * top: -9999px; 28169 * left: -9999px; 28170 * } 28171 * ``` 28172 * 28173 * By default you don't need to override in CSS anything and the animations will work around the display style. 28174 * 28175 * ## A note about animations with `ngHide` 28176 * 28177 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 28178 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 28179 * CSS class is added and removed for you instead of your own CSS class. 28180 * 28181 * ```css 28182 * // 28183 * //a working example can be found at the bottom of this page 28184 * // 28185 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 28186 * transition: 0.5s linear all; 28187 * } 28188 * 28189 * .my-element.ng-hide-add { ... } 28190 * .my-element.ng-hide-add.ng-hide-add-active { ... } 28191 * .my-element.ng-hide-remove { ... } 28192 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 28193 * ``` 28194 * 28195 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 28196 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 28197 * 28198 * @animations 28199 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden 28200 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible 28201 * 28202 * @element ANY 28203 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 28204 * the element is shown or hidden respectively. 28205 * 28206 * @example 28207 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 28208 <file name="index.html"> 28209 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 28210 <div> 28211 Show: 28212 <div class="check-element animate-hide" ng-show="checked">
28213 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 28214 </div> 28215 </div> 28216 <div> 28217 Hide: 28218 <div class="check-element animate-hide" ng-hide="checked"> 28219 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 28220 </div> 28221 </div> 28222 </file> 28223 <file name="glyphicons.css"> 28224 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 28225 </file> 28226 <file name="animations.css"> 28227 .animate-hide { 28228 transition: all linear 0.5s; 28229 line-height: 20px; 28230 opacity: 1; 28231 padding: 10px; 28232 border: 1px solid black; 28233 background: white; 28234 } 28235 28236 .animate-hide.ng-hide { 28237 line-height: 0; 28238 opacity: 0; 28239 padding: 0 10px; 28240 } 28241 28242 .check-element { 28243 padding: 10px; 28244 border: 1px solid black; 28245 background: white; 28246 } 28247 </file> 28248 <file name="protractor.js" type="protractor"> 28249 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 28250 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 28251 28252 it('should check ng-show / ng-hide', function() { 28253 expect(thumbsUp.isDisplayed()).toBeFalsy(); 28254 expect(thumbsDown.isDisplayed()).toBeTruthy(); 28255 28256 element(by.model('checked')).click(); 28257 28258 expect(thumbsUp.isDisplayed()).toBeTruthy(); 28259 expect(thumbsDown.isDisplayed()).toBeFalsy(); 28260 }); 28261 </file> 28262 </example> 28263 */ 28264var ngHideDirective = ['$animate', function($animate) { 28265 return { 28266 restrict: 'A', 28267 multiElement: true, 28268 link: function(scope, element, attr) { 28269 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 28270 // The comment inside of the ngShowDirective explains why we add and 28271 // remove a temporary class for the show/hide animation 28272 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 28273 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 28274 }); 28275 }); 28276 } 28277 }; 28278}]; 28279 28280/** 28281 * @ngdoc directive 28282 * @name ngStyle 28283 * @restrict AC 28284 * 28285 * @description 28286 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 28287 * 28288 * @element ANY 28289 * @param {expression} ngStyle 28290 * 28291 * {@link guide/expression Expression} which evals to an 28292 * object whose keys are CSS style names and values are corresponding values for those CSS 28293 * keys. 28294 * 28295 * Since some CSS style names are not valid keys for an object, they must be quoted. 28296 * See the 'background-color' style in the example below. 28297 * 28298 * @example 28299 <example> 28300 <file name="index.html"> 28301 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 28302 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 28303 <input type="button" value="clear" ng-click="myStyle={}"> 28304 <br/> 28305 <span ng-style="myStyle">Sample Text</span> 28306 <pre>myStyle={{myStyle}}</pre> 28307 </file> 28308 <file name="style.css"> 28309 span { 28310 color: black; 28311 } 28312 </file> 28313 <file name="protractor.js" type="protractor"> 28314 var colorSpan = element(by.css('span')); 28315 28316 it('should check ng-style', function() { 28317 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 28318 element(by.css('input[value=\'set color\']')).click(); 28319 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 28320 element(by.css('input[value=clear]')).click(); 28321 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 28322 }); 28323 </file> 28324 </example> 28325 */ 28326var ngStyleDirective = ngDirective(function(scope, element, attr) { 28327 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 28328 if (oldStyles && (newStyles !== oldStyles)) { 28329 forEach(oldStyles, function(val, style) { element.css(style, '');}); 28330 } 28331 if (newStyles) element.css(newStyles); 28332 }, true); 28333}); 28334 28335/** 28336 * @ngdoc directive 28337 * @name ngSwitch 28338 * @restrict EA 28339 * 28340 * @description 28341 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 28342 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 28343 * as specified in the template. 28344 * 28345 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 28346 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 28347 * matches the value obtained from the evaluated expression. In other words, you define a container element 28348 * (where you place the directive), place an expression on the **`on="..."` attribute** 28349 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 28350 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 28351 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 28352 * attribute is displayed. 28353 * 28354 * <div class="alert alert-info"> 28355 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 28356 * as literal string values to match against. 28357 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
28357omeVal"` not against the 28358 * value of the expression `$scope.someVal`. 28359 * </div> 28360 28361 * @animations 28362 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 28363 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 28364 * 28365 * @usage 28366 * 28367 * ``` 28368 * <ANY ng-switch="expression"> 28369 * <ANY ng-switch-when="matchValue1">...</ANY> 28370 * <ANY ng-switch-when="matchValue2">...</ANY> 28371 * <ANY ng-switch-default>...</ANY> 28372 * </ANY> 28373 * ``` 28374 * 28375 * 28376 * @scope 28377 * @priority 1200 28378 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 28379 * On child elements add: 28380 * 28381 * * `ngSwitchWhen`: the case statement to match against. If match then this 28382 * case will be displayed. If the same match appears multiple times, all the 28383 * elements will be displayed. 28384 * * `ngSwitchDefault`: the default case when no other case match. If there 28385 * are multiple default cases, all of them will be displayed when no other 28386 * case match. 28387 * 28388 * 28389 * @example 28390 <example module="switchExample" deps="angular-animate.js" animations="true"> 28391 <file name="index.html">
28392 <div ng-controller="ExampleController"> 28393 <select ng-model="selection" ng-options="item for item in items"> 28394 </select> 28395 <code>selection={{selection}}</code> 28396 <hr/> 28397 <div class="animate-switch-container" 28398 ng-switch on="selection"> 28399 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 28400 <div class="animate-switch" ng-switch-when="home">Home Span</div> 28401 <div class="animate-switch" ng-switch-default>default</div> 28402 </div> 28403 </div> 28404 </file> 28405 <file name="script.js"> 28406 angular.module('switchExample', ['ngAnimate']) 28407 .controller('ExampleController', ['$scope', function($scope) { 28408 $scope.items = ['settings', 'home', 'other']; 28409 $scope.selection = $scope.items[0]; 28410 }]); 28411 </file> 28412 <file name="animations.css"> 28413 .animate-switch-container { 28414 position:relative; 28415 background:white; 28416 border:1px solid black; 28417 height:40px; 28418 overflow:hidden; 28419 } 28420 28421 .animate-switch { 28422 padding:10px; 28423 } 28424 28425 .animate-switch.ng-animate { 28426 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 28427 28428 position:absolute; 28429 top:0; 28430 left:0; 28431 right:0; 28432 bottom:0; 28433 } 28434 28435 .animate-switch.ng-leave.ng-leave-active, 28436 .animate-switch.ng-enter { 28437 top:-50px; 28438 } 28439 .animate-switch.ng-leave, 28440 .animate-switch.ng-enter.ng-enter-active { 28441 top:0; 28442 } 28443 </file> 28444 <file name="protractor.js" type="protractor"> 28445 var switchElem = element(by.css('[ng-switch]')); 28446 var select = element(by.model('selection')); 28447 28448 it('should start in settings', function() { 28449 expect(switchElem.getText()).toMatch(/Settings Div/); 28450 }); 28451 it('should change to home', function() { 28452 select.all(by.css('option')).get(1).click(); 28453 expect(switchElem.getText()).toMatch(/Home Span/); 28454 }); 28455 it('should select default', function() { 28456 select.all(by.css('option')).get(2).click(); 28457 expect(switchElem.getText()).toMatch(/default/); 28458 }); 28459 </file> 28460 </example> 28461 */ 28462var ngSwitchDirective = ['$animate', function($animate) { 28463 return { 28464 require: 'ngSwitch', 28465 28466 // asks for $scope to fool the BC controller module 28467 controller: ['$scope', function ngSwitchController() { 28468 this.cases = {}; 28469 }], 28470 link: function(scope, element, attr, ngSwitchController) { 28471 var watchExpr = attr.ngSwitch || attr.on, 28472 selectedTranscludes = [], 28473 selectedElements = [], 28474 previousLeaveAnimations = [], 28475 selectedScopes = []; 28476 28477 var spliceFactory = function(array, index) { 28478 return function() { array.splice(index, 1); }; 28479 }; 28480 28481 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 28482 var i, ii; 28483 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 28484 $animate.cancel(previousLeaveAnimations[i]); 28485 } 28486 previousLeaveAnimations.length = 0; 28487 28488 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 28489 var selected = getBlockNodes(selectedElements[i].clone); 28490 selectedScopes[i].$destroy(); 28491 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 28492 promise.then(spliceFactory(previousLeaveAnimations, i)); 28493 } 28494 28495 selectedElements.length = 0; 28496 selectedScopes.length = 0; 28497 28498 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 28499 forEach(selectedTranscludes, function(selectedTransclude) { 28500 selectedTransclude.transclude(function(caseElement, selectedScope) { 28501 selectedScopes.push(selectedScope); 28502 var anchor = selectedTransclude.element; 28503 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); 28504 var block = { clone: caseElement }; 28505 28506 selectedElements.push(block); 28507 $animate.enter(caseElement, anchor.parent(), anchor); 28508 }); 28509 }); 28510 } 28511 }); 28512 } 28513 }; 28514}]; 28515 28516var ngSwitchWhenDirective = ngDirective({ 28517 transclude: 'element', 28518 priority: 1200, 28519 require: '^ngSwitch', 28520 multiElement: true, 28521 link: function(scope, element, attrs, ctrl, $transclude) { 28522 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 28523 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 28524 } 28525}); 28526 28527var ngSwitchDefaultDirective = ngDirective({ 28528 transclude: 'element', 28529 priority: 1200, 28530 require: '^ngSwitch', 28531 multiElement: true, 28532 link: function(scope, element, attr, ctrl, $transclude) { 28533 ctrl.cases['?'] = (ctrl.cases['?'] || []); 28534 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 28535 } 28536}); 28537 28538/** 28539 * @ngdoc directive 28540 * @name ngTransclude 28541 * @restrict EAC 28542 * 28543 * @description 28544 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 28545 * 28546 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 28547 * 28548 * @element ANY 28549 * 28550 * @example 28551 <example module="transcludeExample"> 28552 <file name="index.html"> 28553 <script> 28554 angular.module('transcludeExample', []) 28555 .directive('pane', function(){ 28556 return { 28557 restrict: 'E', 28558 transclude: true, 28559 scope: { title:'@' }, 28560 template: '<div style="border: 1px solid black;">
28560' + 28561 '<div style="background-color: gray">{{title}}</div>' + 28562 '<ng-transclude></ng-transclude>' + 28563 '</div>' 28564 }; 28565 }) 28566 .controller('ExampleController', ['$scope', function($scope) { 28567 $scope.title = 'Lorem Ipsum'; 28568 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 28569 }]); 28570 </script> 28571 <div ng-controller="ExampleController"> 28572 <input ng-model="title" aria-label="title"> <br/> 28573 <textarea ng-model="text" aria-label="text"></textarea> <br/> 28574 <pane title="{{title}}">{{text}}</pane> 28575 </div> 28576 </file> 28577 <file name="protractor.js" type="protractor"> 28578 it('should have transcluded', function() { 28579 var titleElement = element(by.model('title')); 28580 titleElement.clear(); 28581 titleElement.sendKeys('TITLE'); 28582 var textElement = element(by.model('text')); 28583 textElement.clear(); 28584 textElement.sendKeys('TEXT'); 28585 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 28586 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 28587 }); 28588 </file> 28589 </example> 28590 * 28591 */ 28592var ngTranscludeDirective = ngDirective({ 28593 restrict: 'EAC', 28594 link: function($scope, $element, $attrs, controller, $transclude) { 28595 if (!$transclude) { 28596 throw minErr('ngTransclude')('orphan', 28597 'Illegal use of ngTransclude directive in the template! ' + 28598 'No parent directive that requires a transclusion found. ' + 28599 'Element: {0}', 28600 startingTag($element)); 28601 } 28602 28603 $transclude(function(clone) { 28604 $element.empty(); 28605 $element.append(clone); 28606 }); 28607 } 28608}); 28609 28610/** 28611 * @ngdoc directive 28612 * @name script 28613 * @restrict E 28614 * 28615 * @description 28616 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 28617 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 28618 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 28619 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 28620 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 28621 * 28622 * @param {string} type Must be set to `'text/ng-template'`. 28623 * @param {string} id Cache name of the template. 28624 * 28625 * @example 28626 <example> 28627 <file name="index.html"> 28628 <script type="text/ng-template" id="/tpl.html"> 28629 Content of the template. 28630 </script> 28631 28632 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 28633 <div id="tpl-content" ng-include src="currentTpl"></div> 28634 </file> 28635 <file name="protractor.js" type="protractor"> 28636 it('should load template defined inside script tag', function() { 28637 element(by.css('#tpl-link')).click(); 28638 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 28639 }); 28640 </file> 28641 </example> 28642 */ 28643var scriptDirective = ['$templateCache', function($templateCache) { 28644 return { 28645 restrict: 'E', 28646 terminal: true, 28647 compile: function(element, attr) { 28648 if (attr.type == 'text/ng-template') { 28649 var templateUrl = attr.id, 28650 text = element[0].text; 28651 28652 $templateCache.put(templateUrl, text); 28653 } 28654 } 28655 }; 28656}]; 28657 28658var noopNgModelController = { $setViewValue: noop, $render: noop }; 28659 28660function chromeHack(optionElement) { 28661 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 28662 // Adding an <option selected="selected"> element to a <select required="required"> should 28663 // automatically select the new element 28664 if (optionElement[0].hasAttribute('selected')) { 28665 optionElement[0].selected = true; 28666 } 28667} 28668 28669/** 28670 * @ngdoc type 28671 * @name select.SelectController 28672 * @description 28673 * The controller for the `<select>` directive. This provides support for reading 28674 * and writing the selected value(s) of the control and also coordinates dynamically 28675 * added `<option>` elements, perhaps by an `ngRepeat` directive. 28676 */ 28677var SelectController = 28678 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 28679 28680 var self = this, 28681 optionsMap = new HashMap(); 28682 28683 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 28684 self.ngModelCtrl = noopNgModelController; 28685 28686 // The "unknown" option is one that is prepended to the list if the viewValue 28687 // does not match any of the options. When it is rendered the value of the unknown 28688 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 28689 // 28690 // We can't just jqLite('<option>') since jqLite is not smart enough 28691 // to create it in <select> and IE barfs otherwise. 28692 self.unknownOption = jqLite(document.createElement('option')); 28693 self.renderUnknownOption = function(val) { 28694 var unknownVal = '? ' + hashKey(val) + ' ?'; 28695 self.unknownOption.val(unknownVal);
28696 $element.prepend(self.unknownOption); 28697 $element.val(unknownVal); 28698 }; 28699 28700 $scope.$on('$destroy', function() { 28701 // disable unknown option so that we don't do work when the whole select is being destroyed 28702 self.renderUnknownOption = noop; 28703 }); 28704 28705 self.removeUnknownOption = function() { 28706 if (self.unknownOption.parent()) self.unknownOption.remove(); 28707 }; 28708 28709 28710 // Read the value of the select control, the implementation of this changes depending 28711 // upon whether the select can have multiple values and whether ngOptions is at work. 28712 self.readValue = function readSingleValue() { 28713 self.removeUnknownOption(); 28714 return $element.val(); 28715 }; 28716 28717 28718 // Write the value to the select control, the implementation of this changes depending 28719 // upon whether the select can have multiple values and whether ngOptions is at work. 28720 self.writeValue = function writeSingleValue(value) { 28721 if (self.hasOption(value)) { 28722 self.removeUnknownOption(); 28723 $element.val(value); 28724 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 28725 } else { 28726 if (value == null && self.emptyOption) { 28727 self.removeUnknownOption(); 28728 $element.val(''); 28729 } else { 28730 self.renderUnknownOption(value); 28731 } 28732 } 28733 }; 28734 28735 28736 // Tell the select control that an option, with the given value, has been added 28737 self.addOption = function(value, element) { 28738 assertNotHasOwnProperty(value, '"option value"'); 28739 if (value === '') { 28740 self.emptyOption = element; 28741 } 28742 var count = optionsMap.get(value) || 0; 28743 optionsMap.put(value, count + 1); 28744 self.ngModelCtrl.$render(); 28745 chromeHack(element); 28746 }; 28747 28748 // Tell the select control that an option, with the given value, has been removed 28749 self.removeOption = function(value) { 28750 var count = optionsMap.get(value); 28751 if (count) { 28752 if (count === 1) { 28753 optionsMap.remove(value); 28754 if (value === '') { 28755 self.emptyOption = undefined; 28756 } 28757 } else { 28758 optionsMap.put(value, count - 1); 28759 } 28760 } 28761 }; 28762 28763 // Check whether the select control has an option matching the given value 28764 self.hasOption = function(value) { 28765 return !!optionsMap.get(value); 28766 }; 28767 28768 28769 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) { 28770 28771 if (interpolateValueFn) { 28772 // The value attribute is interpolated 28773 var oldVal; 28774 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 28775 if (isDefined(oldVal)) { 28776 self.removeOption(oldVal); 28777 } 28778 oldVal = newVal; 28779 self.addOption(newVal, optionElement); 28780 }); 28781 } else if (interpolateTextFn) { 28782 // The text content is interpolated 28783 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) { 28784 optionAttrs.$set('value', newVal); 28785 if (oldVal !== newVal) { 28786 self.removeOption(oldVal); 28787 } 28788 self.addOption(newVal, optionElement); 28789 }); 28790 } else { 28791 // The value attribute is static 28792 self.addOption(optionAttrs.value, optionElement); 28793 } 28794 28795 optionElement.on('$destroy', function() { 28796 self.removeOption(optionAttrs.value); 28797 self.ngModelCtrl.$render(); 28798 }); 28799 }; 28800}]; 28801 28802/** 28803 * @ngdoc directive 28804 * @name select 28805 * @restrict E 28806 * 28807 * @description 28808 * HTML `SELECT` element with angular data-binding. 28809 * 28810 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 28811 * between the scope and the `<select>` control (including setting default values). 28812 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 28813 * {@link ngOptions `ngOptions`} directives. 28814 * 28815 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 28816 * to the model identified by the `ngModel` directive. With static or repeated options, this is 28817 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 28818 * If you want dynamic value attributes, you can use interpolation inside the value attribute. 28819 * 28820 * <div class="alert alert-warning"> 28821 * Note that the value of a `select` directive used without `ngOptions` is always a string. 28822 * When the model needs to be bound to a non-string value, you must either explictly convert it 28823 * using a directive (see example below) or use `ngOptions` to specify the set of options. 28824 * This is because an option element can only be bound to string values at present. 28825 * </div> 28826 * 28827 * If the viewValue of `ngModel` does not match any of the options, then the control 28828 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 28829 * 28830 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 28831 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 28832 * option. See example below for demonstration. 28833 * 28834 * <div class="alert alert-info"> 28835 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 28836 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benef
28836its, such as 28837 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 28838 * comprehension expression, and additionally in reducing memory and increasing speed by not creating 28839 * a new scope for each repeated instance. 28840 * </div> 28841 * 28842 * 28843 * @param {string} ngModel Assignable angular expression to data-bind to. 28844 * @param {string=} name Property name of the form under which the control is published. 28845 * @param {string=} multiple Allows multiple options to be selected. The selected values will be 28846 * bound to the model as an array. 28847 * @param {string=} required Sets `required` validation error key if the value is not entered. 28848 * @param {string=} ngRequired Adds required attribute and required validation constraint to 28849 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 28850 * when you want to data-bind to the required attribute. 28851 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user 28852 * interaction with the select element. 28853 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 28854 * set on the model on selection. See {@link ngOptions `ngOptions`}. 28855 * 28856 * @example 28857 * ### Simple `select` elements with static options 28858 * 28859 * <example name="static-select" module="staticSelect"> 28860 * <file name="index.html">
28861 * <div ng-controller="ExampleController"> 28862 * <form name="myForm"> 28863 * <label for="singleSelect"> Single select: </label><br> 28864 * <select name="singleSelect" ng-model="data.singleSelect"> 28865 * <option value="option-1">Option 1</option> 28866 * <option value="option-2">Option 2</option> 28867 * </select><br> 28868 * 28869 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 28870 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 28871 * <option value="">---Please select---</option> <!-- not selected / blank option --> 28872 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 28873 * <option value="option-2">Option 2</option> 28874 * </select><br> 28875 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 28876 * <tt>singleSelect = {{data.singleSelect}}</tt> 28877 * 28878 * <hr> 28879 * <label for="multipleSelect"> Multiple select: </label><br> 28880 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 28881 * <option value="option-1">Option 1</option> 28882 * <option value="option-2">Option 2</option> 28883 * <option value="option-3">Option 3</option> 28884 * </select><br> 28885 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 28886 * </form> 28887 * </div> 28888 * </file> 28889 * <file name="app.js"> 28890 * angular.module('staticSelect', []) 28891 * .controller('ExampleController', ['$scope', function($scope) { 28892 * $scope.data = { 28893 * singleSelect: null, 28894 * multipleSelect: [], 28895 * option1: 'option-1', 28896 * }; 28897 * 28898 * $scope.forceUnknownOption = function() { 28899 * $scope.data.singleSelect = 'nonsense'; 28900 * }; 28901 * }]); 28902 * </file> 28903 *</example> 28904 * 28905 * ### Using `ngRepeat` to generate `select` options 28906 * <example name="ngrepeat-select" module="ngrepeatSelect"> 28907 * <file name="index.html">
28908 * <div ng-controller="ExampleController"> 28909 * <form name="myForm"> 28910 * <label for="repeatSelect"> Repeat select: </label> 28911 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> 28912 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 28913 * </select> 28914 * </form> 28915 * <hr> 28916 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> 28917 * </div> 28918 * </file> 28919 * <file name="app.js"> 28920 * angular.module('ngrepeatSelect', []) 28921 * .controller('ExampleController', ['$scope', function($scope) { 28922 * $scope.data = { 28923 * repeatSelect: null, 28924 * availableOptions: [ 28925 * {id: '1', name: 'Option A'}, 28926 * {id: '2', name: 'Option B'}, 28927 * {id: '3', name: 'Option C'} 28928 * ], 28929 * }; 28930 * }]); 28931 * </file> 28932 *</example> 28933 * 28934 * 28935 * ### Using `select` with `ngOptions` and setting a default value 28936 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 28937 * 28938 * <example name="select-with-default-values" module="defaultValueSelect"> 28939 * <file name="index.html">
28940 * <div ng-controller="ExampleController"> 28941 * <form name="myForm"> 28942 * <label for="mySelect">Make a choice:</label> 28943 * <select name="mySelect" id="mySelect" 28944 * ng-options="option.name for option in data.availableOptions track by option.id" 28945 * ng-model="data.selectedOption"></select> 28946 * </form> 28947 * <hr> 28948 * <tt>option = {{data.selectedOption}}</tt><br/> 28949 * </div> 28950 * </file> 28951 * <file name="app.js"> 28952 * angular.module('defaultValueSelect', []) 28953 * .controller('ExampleController', ['$scope', function($scope) { 28954 * $scope.data = { 28955 * availableOptions: [ 28956 * {id: '1', name: 'Option A'}, 28957 * {id: '2', name: 'Option B'}, 28958 * {id: '3', name: 'Option C'} 28959 * ], 28960 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 28961 * }; 28962 * }]); 28963 * </file> 28964 *</example> 28965 * 28966 * 28967 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 28968 * 28969 * <example name="select-with-non-string-options" module="nonStringSelect"> 28970 * <file name="index.html"> 28971 * <select ng-model="model.id" convert-to-number> 28972 * <option value="0">Zero</option> 28973 * <option value="1">One</option> 28974 * <option value="2">Two</option> 28975 * </select> 28976 * {{ model }} 28977 * </file> 28978 * <file name="app.js"> 28979 * angular.module('nonStringSelect', []) 28980 * .run(function($rootScope) { 28981 * $rootScope.model = { id: 2 }; 28982 * }) 28983 * .directive('convertToNumber', function() { 28984 * return { 28985 * require: 'ngModel', 28986 * link: function(scope, element, attrs, ngModel) { 28987 * ngModel.$parsers.push(function(val) { 28988 * return parseInt(val, 10); 28989 * }); 28990 * ngModel.$formatters.push(function(val) { 28991 * return '' + val; 28992 * }); 28993 * } 28994 * }; 28995 * }); 28996 * </file> 28997 * <file name="protractor.js" type="protractor"> 28998 * it('should initialize to model', function() { 28999 * var select = element(by.css('select')); 29000 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 29001 * }); 29002 * </file> 29003 * </example> 29004 * 29005 */ 29006var selectDirective = function() { 29007 29008 return { 29009 restrict: 'E', 29010 require: ['select', '?ngModel'], 29011 controller: SelectController, 29012 priority: 1, 29013 link: { 29014 pre: selectPreLink, 29015 post: selectPostLink 29016 } 29017 }; 29018 29019 function selectPreLink(scope, element, attr, ctrls) { 29020 29021 // if ngModel is not defined, we don't need to do anything 29022 var ngModelCtrl = ctrls[1]; 29023 if (!ngModelCtrl) return; 29024 29025 var selectCtrl = ctrls[0]; 29026 29027 selectCtrl.ngModelCtrl = ngModelCtrl; 29028 29029 // When the selected item(s) changes we delegate getting the value of the select control 29030 // to the `readValue` method, which can be changed if the select can have multiple 29031 // selected values or if the options are being generated by `ngOptions` 29032 element.on('change', function() { 29033 scope.$apply(function() { 29034 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 29035 }); 29036 }); 29037 29038 // If the select allows multiple values then we need to modify how we read and write 29039 // values from and to the control; also what it means for the value to be empty and 29040 // we have to add an extra watch since ngModel doesn't work well with arrays - it 29041 // doesn't trigger rendering if only an item in the array changes. 29042 if (attr.multiple) { 29043 29044 // Read value now needs to check each option to see if it is selected 29045 selectCtrl.readValue = function readMultipleValue() { 29046 var array = []; 29047 forEach(element.find('option'), function(option) { 29048 if (option.selected) { 29049 array.push(option.value); 29050 } 29051 }); 29052 return array; 29053 }; 29054 29055 // Write value now needs to set the selected property of each matching option 29056 selectCtrl.writeValue = function writeMultipleValue(value) { 29057 var items = new HashMap(value); 29058 forEach(element.find('option'), function(option) { 29059 option.selected = isDefined(items.get(option.value)); 29060 }); 29061 }; 29062 29063 // we have to do it on each watch since ngModel watches reference, but 29064 // we need to work of an array, so we need to see if anything was inserted/removed 29065 var lastView, lastViewRef = NaN; 29066 scope.$watch(function selectMultipleWatch() { 29067 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 29068 lastView = shallowCopy(ngModelCtrl.$viewValue); 29069 ngModelCtrl.$render(); 29070 } 29071 lastViewRef = ngModelCtrl.$viewValue; 29072 }); 29073 29074 // If we are a multiple select then value is now a collection 29075 // so the meaning of $isEmpty changes 29076 ngModelCtrl.$isEmpty = function(value) { 29077 return !value || value.length === 0; 29078 }; 29079 29080 } 29081 } 29082 29083 function selectPostLink(scope, element, attrs, ctrls) { 29084 // if ngModel is not defined, we don't need to do anything 29085 var ngModelCtrl = ctrls[1]; 29086 if (!ngModelCtrl) return; 29087 29088 var selectCtrl = ctrls[0]; 29089 29090 // We delegate rendering to the `writeValue` method, which can be changed 29091 // if the select can have multiple selected values or if the options are being 29092 // generated by `ngOptions`. 29093 // This must be done in the postLink fn to prevent $render to be called before 29094 // all nodes have been linked correctly. 29095 ngModelCtrl.$render = function() { 29096 selectCtrl.writeValue(ngModelCtrl.$viewValue); 29097 }; 29098 } 29099}; 29100 29101 29102// The option directive is purely designed to communicate the existence (or lack of) 29103// of dynamically created (and destroyed) option elements to their containing select
vendor: 2,837 bytes, lines 29104-29174
29104// directive via its controller. 29105var optionDirective = ['$interpolate', function($interpolate) { 29106 return { 29107 restrict: 'E', 29108 priority: 100, 29109 compile: function(element, attr) { 29110 29111 if (isDefined(attr.value)) { 29112 // If the value attribute is defined, check if it contains an interpolation 29113 var interpolateValueFn = $interpolate(attr.value, true); 29114 } else { 29115 // If the value attribute is not defined then we fall back to the 29116 // text content of the option element, which may be interpolated 29117 var interpolateTextFn = $interpolate(element.text(), true); 29118 if (!interpolateTextFn) { 29119 attr.$set('value', element.text()); 29120 } 29121 } 29122 29123 return function(scope, element, attr) { 29124 29125 // This is an optimization over using ^^ since we don't want to have to search 29126 // all the way to the root of the DOM for every single option element 29127 var selectCtrlName = '$selectController', 29128 parent = element.parent(), 29129 selectCtrl = parent.data(selectCtrlName) || 29130 parent.parent().data(selectCtrlName); // in case we are in optgroup 29131 29132 if (selectCtrl) { 29133 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn); 29134 } 29135 }; 29136 } 29137 }; 29138}]; 29139 29140var styleDirective = valueFn({ 29141 restrict: 'E', 29142 terminal: false 29143}); 29144 29145/** 29146 * @ngdoc directive 29147 * @name ngRequired 29148 * 29149 * @description 29150 * 29151 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 29152 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be 29153 * applied to custom controls. 29154 * 29155 * The directive sets the `required` attribute on the element if the Angular expression inside 29156 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we 29157 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} 29158 * for more info. 29159 * 29160 * The validator will set the `required` error key to true if the `required` attribute is set and 29161 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty` with the 29162 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the 29163 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing 29164 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. 29165 * 29166 * @example 29167 * <example name="ngRequiredDirective" module="ngRequiredExample"> 29168 * <file name="index.html"> 29169 * <script> 29170 * angular.module('ngRequiredExample', []) 29171 * .controller('ExampleController', ['$scope', function($scope) { 29172 * $scope.required = true; 29173 * }]); 29174 * </script>
29175 * <div ng-controller="ExampleController"> 29176 * <form name="form"> 29177 * <label for="required">Toggle required: </label> 29178 * <input type="checkbox" ng-model="required" id="required" /> 29179 * <br> 29180 * <label for="input">This input must be filled if `required` is true: </label> 29181 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> 29182 * <hr> 29183 * required error set? = <code>{{form.input.$error.required}}</code><br> 29184 * model = <code>{{model}}</code> 29185 * </form> 29186 * </div> 29187 * </file> 29188 * <file name="protractor.js" type="protractor"> 29189 var required = element(by.binding('form.input.$error.required')); 29190 var model = element(by.binding('model')); 29191 var input = element(by.id('input')); 29192 29193 it('should set the required error', function() { 29194 expect(required.getText()).toContain('true'); 29195 29196 input.sendKeys('123'); 29197 expect(required.getText()).not.toContain('true'); 29198 expect(model.getText()).toContain('123'); 29199 }); 29200 * </file> 29201 * </example> 29202 */ 29203var requiredDirective = function() { 29204 return { 29205 restrict: 'A', 29206 require: '?ngModel', 29207 link: function(scope, elm, attr, ctrl) { 29208 if (!ctrl) return; 29209 attr.required = true; // force truthy in case we are on non input element 29210 29211 ctrl.$validators.required = function(modelValue, viewValue) { 29212 return !attr.required || !ctrl.$isEmpty(viewValue); 29213 }; 29214 29215 attr.$observe('required', function() { 29216 ctrl.$validate(); 29217 }); 29218 } 29219 }; 29220}; 29221 29222/** 29223 * @ngdoc directive 29224 * @name ngPattern 29225 * 29226 * @description 29227 * 29228 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 29229 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 29230 * 29231 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 29232 * does not match a RegExp which is obtained by evaluating the Angular expression given in the 29233 * `ngPattern` attribute value: 29234 * * If the expression evaluates to a RegExp object, then this is used directly. 29235 * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it 29236 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 29237 * 29238 * <div class="alert alert-info"> 29239 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 29240 * start at the index of the last search's match, thus not taking the whole input value into 29241 * account. 29242 * </div> 29243 * 29244 * <div class="alert alert-info"> 29245 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two 29246 * differences: 29247 * <ol> 29248 * <li> 29249 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is 29250 * not available. 29251 * </li> 29252 * <li> 29253 * The `ngPattern` attribute must be an expression, while the `pattern` value must be 29254 * interpolated. 29255 * </li> 29256 * </ol> 29257 * </div> 29258 * 29259 * @example 29260 * <example name="ngPatternDirective" module="ngPatternExample"> 29261 * <file name="index.html"> 29262 * <script> 29263 * angular.module('ngPatternExample', []) 29264 * .controller('ExampleController', ['$scope', function($scope) { 29265 * $scope.regex = '\\d+'; 29266 * }]); 29267 * </script>
29268 * <div ng-controller="ExampleController"> 29269 * <form name="form"> 29270 * <label for="regex">Set a pattern (regex string): </label> 29271 * <input type="text" ng-model="regex" id="regex" /> 29272 * <br> 29273 * <label for="input">This input is restricted by the current pattern: </label> 29274 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> 29275 * <hr> 29276 * input valid? = <code>{{form.input.$valid}}</code><br> 29277 * model = <code>{{model}}</code> 29278 * </form> 29279 * </div> 29280 * </file> 29281 * <file name="protractor.js" type="protractor"> 29282 var model = element(by.binding('model')); 29283 var input = element(by.id('input')); 29284 29285 it('should validate the input with the default pattern', function() { 29286 input.sendKeys('aaa'); 29287 expect(model.getText()).not.toContain('aaa'); 29288 29289 input.clear().then(function() { 29290 input.sendKeys('123'); 29291 expect(model.getText()).toContain('123'); 29292 }); 29293 }); 29294 * </file> 29295 * </example> 29296 */ 29297var patternDirective = function() { 29298 return { 29299 restrict: 'A', 29300 require: '?ngModel', 29301 link: function(scope, elm, attr, ctrl) { 29302 if (!ctrl) return; 29303 29304 var regexp, patternExp = attr.ngPattern || attr.pattern; 29305 attr.$observe('pattern', function(regex) { 29306 if (isString(regex) && regex.length > 0) { 29307 regex = new RegExp('^' + regex + '$'); 29308 } 29309 29310 if (regex && !regex.test) { 29311 throw minErr('ngPattern')('noregexp', 29312 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 29313 regex, startingTag(elm)); 29314 } 29315 29316 regexp = regex || undefined; 29317 ctrl.$validate(); 29318 }); 29319 29320 ctrl.$validators.pattern = function(modelValue, viewValue) { 29321 // HTML5 pattern constraint validates the input value, so we validate the viewValue 29322 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 29323 }; 29324 } 29325 }; 29326}; 29327 29328/** 29329 * @ngdoc directive 29330 * @name ngMaxlength 29331 * 29332 * @description 29333 * 29334 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 29335 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 29336 * 29337 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 29338 * is longer than the integer obtained by evaluating the Angular expression given in the 29339 * `ngMaxlength` attribute value. 29340 * 29341 * <div class="alert alert-info"> 29342 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two 29343 * differences: 29344 * <ol> 29345 * <li> 29346 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint 29347 * validation is not available. 29348 * </li> 29349 * <li> 29350 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be 29351 * interpolated. 29352 * </li> 29353 * </ol> 29354 * </div> 29355 * 29356 * @example 29357 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> 29358 * <file name="index.html"> 29359 * <script> 29360 * angular.module('ngMaxlengthExample', []) 29361 * .controller('ExampleController', ['$scope', function($scope) { 29362 * $scope.maxlength = 5; 29363 * }]); 29364 * </script>
29365 * <div ng-controller="ExampleController"> 29366 * <form name="form"> 29367 * <label for="maxlength">Set a maxlength: </label> 29368 * <input type="number" ng-model="maxlength" id="maxlength" /> 29369 * <br> 29370 * <label for="input">This input is restricted by the current maxlength: </label> 29371 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> 29372 * <hr> 29373 * input valid? = <code>{{form.input.$valid}}</code><br> 29374 * model = <code>{{model}}</code> 29375 * </form> 29376 * </div> 29377 * </file> 29378 * <file name="protractor.js" type="protractor"> 29379 var model = element(by.binding('model')); 29380 var input = element(by.id('input')); 29381 29382 it('should validate the input with the default maxlength', function() { 29383 input.sendKeys('abcdef'); 29384 expect(model.getText()).not.toContain('abcdef'); 29385 29386 input.clear().then(function() { 29387 input.sendKeys('abcde'); 29388 expect(model.getText()).toContain('abcde'); 29389 }); 29390 }); 29391 * </file> 29392 * </example> 29393 */ 29394var maxlengthDirective = function() { 29395 return { 29396 restrict: 'A', 29397 require: '?ngModel', 29398 link: function(scope, elm, attr, ctrl) { 29399 if (!ctrl) return; 29400 29401 var maxlength = -1; 29402 attr.$observe('maxlength', function(value) { 29403 var intVal = toInt(value); 29404 maxlength = isNaN(intVal) ? -1 : intVal; 29405 ctrl.$validate(); 29406 }); 29407 ctrl.$validators.maxlength = function(modelValue, viewValue) { 29408 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 29409 }; 29410 } 29411 }; 29412}; 29413 29414/** 29415 * @ngdoc directive 29416 * @name ngMinlength 29417 * 29418 * @description 29419 * 29420 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 29421 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 29422 * 29423 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 29424 * is shorter than the integer obtained by evaluating the Angular expression given in the 29425 * `ngMinlength` attribute value. 29426 * 29427 * <div class="alert alert-info"> 29428 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two 29429 * differences: 29430 * <ol> 29431 * <li> 29432 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint 29433 * validation is not available. 29434 * </li> 29435 * <li> 29436 * The `ngMinlength` value must be an expression, while the `minlength` value must be 29437 * interpolated. 29438 * </li> 29439 * </ol> 29440 * </div> 29441 * 29442 * @example 29443 * <example name="ngMinlengthDirective" module="ngMinlengthExample"> 29444 * <file name="index.html"> 29445 * <script> 29446 * angular.module('ngMinlengthExample', []) 29447 * .controller('ExampleController', ['$scope', function($scope) { 29448 * $scope.minlength = 3; 29449 * }]); 29450 * </script>
29451 * <div ng-controller="ExampleController"> 29452 * <form name="form"> 29453 * <label for="minlength">Set a minlength: </label> 29454 * <input type="number" ng-model="minlength" id="minlength" /> 29455 * <br> 29456 * <label for="input">This input is restricted by the current minlength: </label> 29457 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> 29458 * <hr> 29459 * input valid? = <code>{{form.input.$valid}}</code><br> 29460 * model = <code>{{model}}</code> 29461 * </form> 29462 * </div> 29463 * </file> 29464 * <file name="protractor.js" type="protractor"> 29465 var model = element(by.binding('model')); 29466 var input = element(by.id('input')); 29467 29468 it('should validate the input with the default minlength', function() { 29469 input.sendKeys('ab'); 29470 expect(model.getText()).not.toContain('ab'); 29471 29472 input.sendKeys('abc'); 29473 expect(model.getText()).toContain('abc'); 29474 }); 29475 * </file> 29476 * </example> 29477 */ 29478var minlengthDirective = function() { 29479 return { 29480 restrict: 'A', 29481 require: '?ngModel', 29482 link: function(scope, elm, attr, ctrl) { 29483 if (!ctrl) return; 29484 29485 var minlength = 0; 29486 attr.$observe('minlength', function(value) { 29487 minlength = toInt(value) || 0; 29488 ctrl.$validate(); 29489 }); 29490 ctrl.$validators.minlength = function(modelValue, viewValue) { 29491 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 29492 }; 29493 } 29494 }; 29495}; 29496 29497if (window.angular.bootstrap) { 29498 //AngularJS is already loaded, so we can return here... 29499 console.log('WARNING: Tried to load angular more than once.'); 29500 return; 29501} 29502 29503//try to bind to jquery now so that one can write jqLite(document).ready() 29504//but we will rebind on bootstrap again. 29505bindJQuery(); 29506 29507publishExternalAPI(angular); 29508 29509angular.module("ngLocale", [], ["$provide", function($provide) { 29510var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 29511function getDecimals(n) { 29512 n = n + ''; 29513 var i = n.indexOf('.'); 29514 return (i == -1) ? 0 : n.length - i - 1; 29515} 29516 29517function getVF(n, opt_precision) { 29518 var v = opt_precision; 29519 29520 if (undefined === v) { 29521 v = Math.min(getDecimals(n), 3); 29522 } 29523 29524 var base = Math.pow(10, v); 29525 var f = ((n * base) | 0) % base; 29526 return {v: v, f: f}; 29527} 29528 29529$provide.value("$locale", { 29530 "DATETIME_FORMATS": { 29531 "AMPMS": [ 29532 "AM", 29533 "PM" 29534 ], 29535 "DAY": [ 29536 "Sunday", 29537 "Monday", 29538 "Tuesday", 29539 "Wednesday", 29540 "Thursday", 29541 "Friday", 29542 "Saturday" 29543 ], 29544 "ERANAMES": [ 29545 "Before Christ", 29546 "Anno Domini" 29547 ], 29548 "ERAS": [ 29549 "BC", 29550 "AD" 29551 ], 29552 "FIRSTDAYOFWEEK": 6, 29553 "MONTH": [ 29554 "January", 29555 "February", 29556 "March", 29557 "April", 29558 "May", 29559 "June", 29560 "July", 29561 "August", 29562 "September", 29563 "October", 29564 "November", 29565 "December" 29566 ], 29567 "SHORTDAY": [ 29568 "Sun", 29569 "Mon", 29570 "Tue", 29571 "Wed", 29572 "Thu", 29573 "Fri", 29574 "Sat" 29575 ], 29576 "SHORTMONTH": [ 29577 "Jan", 29578 "Feb", 29579 "Mar", 29580 "Apr", 29581 "May", 29582 "Jun", 29583 "Jul", 29584 "Aug", 29585 "Sep", 29586 "Oct", 29587 "Nov", 29588 "Dec" 29589 ], 29590 "STANDALONEMONTH": [ 29591 "January", 29592 "February", 29593 "March", 29594 "April", 29595 "May", 29596 "June", 29597 "July", 29598 "August", 29599 "September", 29600 "October", 29601 "November", 29602 "December" 29603 ], 29604 "WEEKENDRANGE": [ 29605 5, 29606 6 29607 ], 29608 "fullDate": "EEEE, MMMM d, y", 29609 "longDate": "MMMM d, y", 29610 "medium": "MMM d, y h:mm:ss a", 29611 "mediumDate": "MMM d, y", 29612 "mediumTime": "h:mm:ss a", 29613 "short": "M/d/yy h:mm a", 29614 "shortDate": "M/d/yy", 29615 "shortTime": "h:mm a" 29616 }, 29617 "NUMBER_FORMATS": { 29618 "CURRENCY_SYM": "$", 29619 "DECIMAL_SEP": ".", 29620 "GROUP_SEP": ",", 29621 "PATTERNS": [ 29622 { 29623 "gSize": 3, 29624 "lgSize": 3, 29625 "maxFrac": 3, 29626 "minFrac": 0, 29627 "minInt": 1, 29628 "negPre": "-", 29629 "negSuf": "", 29630 "posPre": "", 29631 "posSuf": "" 29632 }, 29633 { 29634 "gSize": 3, 29635 "lgSize": 3, 29636 "maxFrac": 2, 29637 "minFrac": 2, 29638 "minInt": 1, 29639 "negPre": "-\u00a4", 29640 "negSuf": "", 29641 "posPre": "\u00a4", 29642 "posSuf": "" 29643 } 29644 ] 29645 }, 29646 "id": "en-us", 29647 "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;} 29648}); 29649}]); 29650 29651 jqLite(document).ready(function() { 29652 angularInit(document, bootstrap); 29653 }); 29654 29655})(window, document); 29656 29657!window.angular.$$csp().noInlineStyle && window.angular.element(document.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>'); 29658/** 29659 * @license AngularJS v1.4.9 29660 * (c) 2010-2015 Google, Inc. http://angularjs.org 29661 * License: MIT 29662 */ 29663(function(window, angular, undefined) {'use strict'; 29664 29665/** 29666 * @ngdoc module 29667 * @name ngRoute 29668 * @description 29669 * 29670 * # ngRoute 29671 * 29672 * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. 29673 * 29674 * ## Example 29675 * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 29676 * 29677 * 29678 * <div doc-module-components="ngRoute"></div> 29679 */ 29680 /* global -ngRouteModule */ 29681var ngRouteModule = angular.module('ngRoute', ['ng']). 29682 provider('$route', $RouteProvider), 29683 $routeMinErr = angular.$$minErr('ngRoute'); 29684 29685/** 29686 * @ngdoc provider 29687 * @name $routeProvider 29688 * 29689 * @description 29690 *
29691 * Used for configuring routes. 29692 * 29693 * ## Example 29694 * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 29695 * 29696 * ## Dependencies 29697 * Requires the {@link ngRoute `ngRoute`} module to be installed. 29698 */ 29699function $RouteProvider() { 29700 function inherit(parent, extra) { 29701 return angular.extend(Object.create(parent), extra); 29702 } 29703 29704 var routes = {}; 29705 29706 /** 29707 * @ngdoc method 29708 * @name $routeProvider#when 29709 * 29710 * @param {string} path Route path (matched against `$location.path`). If `$location.path` 29711 * contains redundant trailing slash or is missing one, the route will still match and the 29712 * `$location.path` will be updated to add or drop the trailing slash to exactly match the 29713 * route definition. 29714 * 29715 * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up 29716 * to the next slash are matched and stored in `$routeParams` under the given `name` 29717 * when the route matches. 29718 * * `path` can contain named groups starting with a colon and ending with a star: 29719 * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` 29720 * when the route matches. 29721 * * `path` can contain optional named groups with a question mark: e.g.`:name?`. 29722 * 29723 * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 29724 * `/color/brown/largecode/code/with/slashes/edit` and extract: 29725 * 29726 * * `color: brown` 29727 * * `largecode: code/with/slashes`. 29728 * 29729 * 29730 * @param {Object} route Mapping information to be assigned to `$route.current` on route 29731 * match. 29732 * 29733 * Object properties: 29734 * 29735 * - `controller` â `{(string|function()=}` â Controller fn that should be associated with 29736 * newly created scope or the name of a {@link angular.Module#controller registered 29737 * controller} if passed as a string. 29738 * - `controllerAs` â `{string=}` â An identifier name for a reference to the controller. 29739 * If present, the controller will be published to scope under the `controllerAs` name. 29740 * - `template` â `{string=|function()=}` â html template as a string or a function that 29741 * returns an html template as a string which should be used by {@link 29742 * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 29743 * This property takes precedence over `templateUrl`. 29744 * 29745 * If `template` is a function, it will be called with the following parameters: 29746 * 29747 * - `{Array.<Object>}` - route parameters extracted from the current 29748 * `$location.path()` by applying the current route 29749 * 29750 * - `templateUrl` â `{string=|function()=}` â path or function that returns a path to an html 29751 * template that should be used by {@link ngRoute.directive:ngView ngView}. 29752 * 29753 * If `templateUrl` is a function, it will be called with the following parameters: 29754 * 29755 * - `{Array.<Object>}` - route parameters extracted from the current 29756 * `$location.path()` by applying the current route 29757 * 29758 * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should 29759 * be injected into the controller. If any of these dependencies are promises, the router 29760 * will wait for them all to be resolved or one to be rejected before the controller is 29761 * instantiated. 29762 * If all the promises are resolved successfully, the values of the resolved promises are 29763 * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is 29764 * fired. If any of the promises are rejected the 29765 * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object 29766 * is: 29767 * 29768 * - `key` â `{string}`: a name of a dependency to be injected into the controller. 29769 * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 29770 * Otherwise if function, then it is {@link auto.$injector#invoke injected} 29771 * and the return value is treated as the dependency. If the result is a promise, it is 29772 * resolved before its value is injected into the controller. Be aware that
29773 * `ngRoute.$routeParams` will still refer to the previous route within these resolve 29774 * functions. Use `$route.current.params` to access the new route parameters, instead. 29775 * 29776 * - `redirectTo` â {(string|function())=} â value to update 29777 * {@link ng.$location $location} path with and trigger route redirection. 29778 * 29779 * If `redirectTo` is a function, it will be called with the following parameters: 29780 * 29781 * - `{Object.<string>}` - route parameters extracted from the current 29782 * `$location.path()` by applying the current route templateUrl. 29783 * - `{string}` - current `$location.path()` 29784 * - `{Object}` - current `$location.search()` 29785 * 29786 * The custom `redirectTo` function is expected to return a string which will be used 29787 * to update `$location.path()` and `$location.search()`. 29788 * 29789 * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` 29790 * or `$location.hash()` changes. 29791 * 29792 * If the option is set to `false` and url in the browser changes, then 29793 * `$routeUpdate` event is broadcasted on the root scope. 29794 * 29795 * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 29796 * 29797 * If the option is set to `true`, then the particular route can be matched without being 29798 * case sensitive 29799 * 29800 * @returns {Object} self 29801 * 29802 * @description 29803 * Adds a new route definition to the `$route` service. 29804 */ 29805 this.when = function(path, route) { 29806 //copy original route object to preserve params inherited from proto chain 29807 var routeCopy = angular.copy(route); 29808 if (angular.isUndefined(routeCopy.reloadOnSearch)) { 29809 routeCopy.reloadOnSearch = true; 29810 } 29811 if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { 29812 routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; 29813 } 29814 routes[path] = angular.extend( 29815 routeCopy, 29816 path && pathRegExp(path, routeCopy) 29817 ); 29818 29819 // create redirection for trailing slashes 29820 if (path) { 29821 var redirectPath = (path[path.length - 1] == '/') 29822 ? path.substr(0, path.length - 1) 29823 : path + '/'; 29824 29825 routes[redirectPath] = angular.extend( 29826 {redirectTo: path}, 29827 pathRegExp(redirectPath, routeCopy) 29828 ); 29829 } 29830 29831 return this; 29832 }; 29833 29834 /** 29835 * @ngdoc property 29836 * @name $routeProvider#caseInsensitiveMatch 29837 * @description 29838 * 29839 * A boolean property indicating if routes defined 29840 * using this provider should be matched using a case insensitive 29841 * algorithm. Defaults to `false`. 29842 */ 29843 this.caseInsensitiveMatch = false; 29844 29845 /** 29846 * @param path {string} path 29847 * @param opts {Object} options 29848 * @return {?Object} 29849 * 29850 * @description 29851 * Normalizes the given path, returning a regular expression 29852 * and the original path. 29853 * 29854 * Inspired by pathRexp in visionmedia/express/lib/utils.js. 29855 */ 29856 function pathRegExp(path, opts) { 29857 var insensitive = opts.caseInsensitiveMatch, 29858 ret = { 29859 originalPath: path, 29860 regexp: path 29861 }, 29862 keys = ret.keys = []; 29863 29864 path = path 29865 .replace(/([().])/g, '\\$1') 29866 .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { 29867 var optional = option === '?' ? option : null; 29868 var star = option === '*' ? option : null; 29869 keys.push({ name: key, optional: !!optional }); 29870 slash = slash || ''; 29871 return '' 29872 + (optional ? '' : slash) 29873 + '(?:' 29874 + (optional ? slash : '') 29875 + (star && '(.+?)' || '([^/]+)') 29876 + (optional || '') 29877 + ')' 29878 + (optional || ''); 29879 }) 29880 .replace(/([\/$\*])/g, '\\$1'); 29881 29882 ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 29883 return ret; 29884 } 29885 29886 /** 29887 * @ngdoc method 29888 * @name $routeProvider#otherwise 29889 * 29890 * @description 29891 * Sets route definition that will be used on route change when no other route definition 29892 * is matched. 29893 * 29894 * @param {Object|string} params Mapping information to be assigned to `$route.current`. 29895 * If called with a string, the value maps to `redirectTo`. 29896 * @returns {Object} self 29897 */ 29898 this.otherwise = function(params) { 29899 if (typeof params === 'string') { 29900 params = {redirectTo: params}; 29901 } 29902 this.when(null, params); 29903 return this; 29904 }; 29905 29906 29907 this.$get = ['$rootScope', 29908 '$location',
29909 '$routeParams', 29910 '$q', 29911 '$injector', 29912 '$templateRequest', 29913 '$sce', 29914 function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { 29915 29916 /** 29917 * @ngdoc service 29918 * @name $route 29919 * @requires $location 29920 * @requires $routeParams 29921 * 29922 * @property {Object} current Reference to the current route definition. 29923 * The route definition contains: 29924 * 29925 * - `controller`: The controller constructor as define in route definition. 29926 * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 29927 * controller instantiation. The `locals` contain 29928 * the resolved values of the `resolve` map. Additionally the `locals` also contain: 29929 * 29930 * - `$scope` - The current route scope. 29931 * - `$template` - The current route template HTML. 29932 * 29933 * @property {Object} routes Object with all route configuration Objects as its properties. 29934 * 29935 * @description 29936 * `$route` is used for deep-linking URLs to controllers and views (HTML partials). 29937 * It watches `$location.url()` and tries to map the path to an existing route definition. 29938 * 29939 * Requires the {@link ngRoute `ngRoute`} module to be installed. 29940 * 29941 * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 29942 * 29943 * The `$route` service is typically used in conjunction with the 29944 * {@link ngRoute.directive:ngView `ngView`} directive and the 29945 * {@link ngRoute.$routeParams `$routeParams`} service. 29946 * 29947 * @example 29948 * This example shows how changing the URL hash causes the `$route` to match a route against the 29949 * URL, and the `ngView` pulls in the partial. 29950 * 29951 * <example name="$route-service" module="ngRouteExample" 29952 * deps="angular-route.js" fixBase="true"> 29953 * <file name="index.html"> 29954 * <div ng-controller="MainController"> 29955 * Choose: 29956 * <a href="Book/Moby">Moby</a> | 29957 * <a href="Book/Moby/ch/1">Moby: Ch1</a> | 29958 * <a href="Book/Gatsby">Gatsby</a> | 29959 * <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | 29960 * <a href="Book/Scarlet">Scarlet Letter</a><br/> 29961 * 29962 * <div ng-view></div> 29963 * 29964 * <hr /> 29965 * 29966 * <pre>$location.path() = {{$location.path()}}</pre> 29967 * <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> 29968 * <pre>$route.current.params = {{$route.current.params}}</pre> 29969 * <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> 29970 * <pre>$routeParams = {{$routeParams}}</pre> 29971 * </div> 29972 * </file> 29973 * 29974 * <file name="book.html"> 29975 * controller: {{name}}<br /> 29976 * Book Id: {{params.bookId}}<br /> 29977 * </file> 29978 * 29979 * <file name="chapter.html"> 29980 * controller: {{name}}<br /> 29981 * Book Id: {{params.bookId}}<br /> 29982 * Chapter Id: {{params.chapterId}} 29983 * </file> 29984 * 29985 * <file name="script.js"> 29986 * angular.module('ngRouteExample', ['ngRoute']) 29987 * 29988 * .controller('MainController', function($scope, $route, $routeParams, $location) { 29989 * $scope.$route = $route; 29990 * $scope.$location = $location; 29991 * $scope.$routeParams = $routeParams; 29992 * }) 29993 * 29994 * .controller('BookController', function($scope, $routeParams) { 29995 * $scope.name = "BookController"; 29996 * $scope.params = $routeParams; 29997 * }) 29998 * 29999 * .controller('ChapterController', function($scope, $routeParams) { 30000 * $scope.name = "ChapterController"; 30001 * $scope.params = $routeParams; 30002 * }) 30003 * 30004 * .config(function($routeProvider, $locationProvider) { 30005 * $routeProvider 30006 * .when('/Book/:bookId', { 30007 * templateUrl: 'book.html', 30008 * controller: 'BookController', 30009 * resolve: { 30010 * // I will cause a 1 second delay 30011 * delay: function($q, $timeout) {
30012 * var delay = $q.defer(); 30013 * $timeout(delay.resolve, 1000); 30014 * return delay.promise; 30015 * } 30016 * } 30017 * }) 30018 * .when('/Book/:bookId/ch/:chapterId', { 30019 * templateUrl: 'chapter.html', 30020 * controller: 'ChapterController' 30021 * }); 30022 * 30023 * // configure html5 to get links working on jsfiddle 30024 * $locationProvider.html5Mode(true); 30025 * }); 30026 * 30027 * </file> 30028 * 30029 * <file name="protractor.js" type="protractor"> 30030 * it('should load and compile correct template', function() { 30031 * element(by.linkText('Moby: Ch1')).click(); 30032 * var content = element(by.css('[ng-view]')).getText(); 30033 * expect(content).toMatch(/controller\: ChapterController/); 30034 * expect(content).toMatch(/Book Id\: Moby/); 30035 * expect(content).toMatch(/Chapter Id\: 1/); 30036 * 30037 * element(by.partialLinkText('Scarlet')).click(); 30038 * 30039 * content = element(by.css('[ng-view]')).getText(); 30040 * expect(content).toMatch(/controller\: BookController/); 30041 * expect(content).toMatch(/Book Id\: Scarlet/); 30042 * }); 30043 * </file> 30044 * </example> 30045 */ 30046 30047 /** 30048 * @ngdoc event 30049 * @name $route#$routeChangeStart 30050 * @eventType broadcast on root scope 30051 * @description 30052 * Broadcasted before a route change. At this point the route services starts 30053 * resolving all of the dependencies needed for the route change to occur. 30054 * Typically this involves fetching the view template as well as any dependencies 30055 * defined in `resolve` route property. Once all of the dependencies are resolved 30056 * `$routeChangeSuccess` is fired. 30057 * 30058 * The route change (and the `$location` change that triggered it) can be prevented 30059 * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} 30060 * for more details about event object. 30061 * 30062 * @param {Object} angularEvent Synthetic event object. 30063 * @param {Route} next Future route information. 30064 * @param {Route} current Current route information. 30065 */ 30066 30067 /** 30068 * @ngdoc event 30069 * @name $route#$routeChangeSuccess 30070 * @eventType broadcast on root scope 30071 * @description 30072 * Broadcasted after a route change has happened successfully. 30073 * The `resolve` dependencies are now available in the `current.locals` property. 30074 * 30075 * {@link ngRoute.directive:ngView ngView} listens for the directive 30076 * to instantiate the controller and render the view. 30077 * 30078 * @param {Object} angularEvent Synthetic event object. 30079 * @param {Route} current Current route information. 30080 * @param {Route|Undefined} previous Previous route information, or undefined if current is 30081 * first route entered. 30082 */ 30083 30084 /** 30085 * @ngdoc event 30086 * @name $route#$routeChangeError 30087 * @eventType broadcast on root scope 30088 * @description 30089 * Broadcasted if any of the resolve promises are rejected. 30090 * 30091 * @param {Object} angularEvent Synthetic event object 30092 * @param {Route} current Current route information. 30093 * @param {Route} previous Previous route information. 30094 * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 30095 */ 30096 30097 /** 30098 * @ngdoc event 30099 * @name $route#$routeUpdate 30100 * @eventType broadcast on root scope 30101 * @description 30102 * The `reloadOnSearch` property has been set to false, and we are reusing the same 30103 * instance of the Controller. 30104 * 30105 * @param {Object} angularEvent Synthetic event object 30106 * @param {Route} current Current/previous route information. 30107 */ 30108 30109 var forceReload = false, 30110 preparedRoute, 30111 preparedRouteIsUpdateOnly, 30112 $route = { 30113 routes: routes, 30114 30115 /** 30116 * @ngdoc method 30117 * @name $route#reload 30118 * 30119 * @description 30120 * Causes `$route` service to reload the current route even if 30121 * {@link ng.$location $location} hasn't changed. 30122 * 30123 * As a result of that, {@link ngRoute.directive:ngView ngView} 30124 * creates new scope and reinstantiates the controller. 30125 */ 30126 reload: function() { 30127 forceReload = true; 30128 $rootScope.$evalAsync(function() { 30129 // Don't support cancellation of a reload for now... 30130 prepareRoute(); 30131 commitRoute(); 30132 }); 30133 }, 30134 30135 /** 30136 * @ngdoc method 30137 * @name $route#updateParams 30138 *
30139 * @description 30140 * Causes `$route` service to update the current URL, replacing 30141 * current route parameters with those specified in `newParams`. 30142 * Provided property names that match the route's path segment 30143 * definitions will be interpolated into the location's path, while 30144 * remaining properties will be treated as query params. 30145 * 30146 * @param {!Object<string, string>} newParams mapping of URL parameter names to values 30147 */ 30148 updateParams: function(newParams) { 30149 if (this.current && this.current.$$route) { 30150 newParams = angular.extend({}, this.current.params, newParams); 30151 $location.path(interpolate(this.current.$$route.originalPath, newParams)); 30152 // interpolate modifies newParams, only query params are left 30153 $location.search(newParams); 30154 } else { 30155 throw $routeMinErr('norout', 'Tried updating route when with no current route'); 30156 } 30157 } 30158 }; 30159 30160 $rootScope.$on('$locationChangeStart', prepareRoute); 30161 $rootScope.$on('$locationChangeSuccess', commitRoute); 30162 30163 return $route; 30164 30165 ///////////////////////////////////////////////////// 30166 30167 /** 30168 * @param on {string} current url 30169 * @param route {Object} route regexp to match the url against 30170 * @return {?Object} 30171 * 30172 * @description 30173 * Check if the route matches the current url. 30174 * 30175 * Inspired by match in 30176 * visionmedia/express/lib/router/router.js. 30177 */ 30178 function switchRouteMatcher(on, route) { 30179 var keys = route.keys, 30180 params = {}; 30181 30182 if (!route.regexp) return null; 30183 30184 var m = route.regexp.exec(on); 30185 if (!m) return null; 30186 30187 for (var i = 1, len = m.length; i < len; ++i) { 30188 var key = keys[i - 1]; 30189 30190 var val = m[i]; 30191 30192 if (key && val) { 30193 params[key.name] = val; 30194 } 30195 } 30196 return params; 30197 } 30198 30199 function prepareRoute($locationEvent) { 30200 var lastRoute = $route.current; 30201 30202 preparedRoute = parseRoute(); 30203 preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route 30204 && angular.equals(preparedRoute.pathParams, lastRoute.pathParams) 30205 && !preparedRoute.reloadOnSearch && !forceReload; 30206 30207 if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { 30208 if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { 30209 if ($locationEvent) { 30210 $locationEvent.preventDefault(); 30211 } 30212 } 30213 } 30214 } 30215 30216 function commitRoute() { 30217 var lastRoute = $route.current; 30218 var nextRoute = preparedRoute; 30219 30220 if (preparedRouteIsUpdateOnly) { 30221 lastRoute.params = nextRoute.params; 30222 angular.copy(lastRoute.params, $routeParams); 30223 $rootScope.$broadcast('$routeUpdate', lastRoute); 30224 } else if (nextRoute || lastRoute) { 30225 forceReload = false; 30226 $route.current = nextRoute; 30227 if (nextRoute) { 30228 if (nextRoute.redirectTo) { 30229 if (angular.isString(nextRoute.redirectTo)) { 30230 $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) 30231 .replace(); 30232 } else { 30233 $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) 30234 .replace(); 30235 } 30236 } 30237 } 30238 30239 $q.when(nextRoute). 30240 then(function() { 30241 if (nextRoute) { 30242 var locals = angular.extend({}, nextRoute.resolve), 30243 template, templateUrl; 30244 30245 angular.forEach(locals, function(value, key) { 30246 locals[key] = angular.isString(value) ? 30247 $injector.get(value) : $injector.invoke(value, null, null, key); 30248 }); 30249 30250 if (angular.isDefined(template = nextRoute.template)) { 30251 if (angular.isFunction(template)) { 30252 template = template(nextRoute.params); 30253 } 30254 } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { 30255 if (angular.isFunction(templateUrl)) { 30256 templateUrl = templateUrl(nextRoute.params); 30257 } 30258 if (angular.isDefined(templateUrl)) { 30259 nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl); 30260 template = $templateRequest(templateUrl); 30261 } 30262 } 30263 if (angular.isDefined(template)) { 30264 locals['$template'] = template; 30265 } 30266 return $q.all(locals); 30267 } 30268 }). 30269 then(function(locals) { 30270 // after route change 30271 if (nextRoute == $route.current) { 30272 if (nextRoute) { 30273 nextRoute.locals = locals;
vendor: 4,780 bytes, lines 30274-30408
30274 angular.copy(nextRoute.params, $routeParams); 30275 } 30276 $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); 30277 } 30278 }, function(error) { 30279 if (nextRoute == $route.current) { 30280 $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); 30281 } 30282 }); 30283 } 30284 } 30285 30286 30287 /** 30288 * @returns {Object} the current active route, by matching it against the URL 30289 */ 30290 function parseRoute() { 30291 // Match a route 30292 var params, match; 30293 angular.forEach(routes, function(route, path) { 30294 if (!match && (params = switchRouteMatcher($location.path(), route))) { 30295 match = inherit(route, { 30296 params: angular.extend({}, $location.search(), params), 30297 pathParams: params}); 30298 match.$$route = route; 30299 } 30300 }); 30301 // No route matched; fallback to "otherwise" route 30302 return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 30303 } 30304 30305 /** 30306 * @returns {string} interpolation of the redirect path with the parameters 30307 */ 30308 function interpolate(string, params) { 30309 var result = []; 30310 angular.forEach((string || '').split(':'), function(segment, i) { 30311 if (i === 0) { 30312 result.push(segment); 30313 } else { 30314 var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); 30315 var key = segmentMatch[1]; 30316 result.push(params[key]); 30317 result.push(segmentMatch[2] || ''); 30318 delete params[key]; 30319 } 30320 }); 30321 return result.join(''); 30322 } 30323 }]; 30324} 30325 30326ngRouteModule.provider('$routeParams', $RouteParamsProvider); 30327 30328 30329/** 30330 * @ngdoc service 30331 * @name $routeParams 30332 * @requires $route 30333 * 30334 * @description 30335 * The `$routeParams` service allows you to retrieve the current set of route parameters. 30336 * 30337 * Requires the {@link ngRoute `ngRoute`} module to be installed. 30338 * 30339 * The route parameters are a combination of {@link ng.$location `$location`}'s 30340 * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. 30341 * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. 30342 * 30343 * In case of parameter name collision, `path` params take precedence over `search` params. 30344 * 30345 * The service guarantees that the identity of the `$routeParams` object will remain unchanged 30346 * (but its properties will likely change) even when a route change occurs. 30347 * 30348 * Note that the `$routeParams` are only updated *after* a route change completes successfully. 30349 * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 30350 * Instead you can use `$route.current.params` to access the new route's parameters. 30351 * 30352 * @example 30353 * ```js 30354 * // Given: 30355 * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby 30356 * // Route: /Chapter/:chapterId/Section/:sectionId 30357 * // 30358 * // Then 30359 * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} 30360 * ``` 30361 */ 30362function $RouteParamsProvider() { 30363 this.$get = function() { return {}; }; 30364} 30365 30366ngRouteModule.directive('ngView', ngViewFactory); 30367ngRouteModule.directive('ngView', ngViewFillContentFactory); 30368 30369 30370/** 30371 * @ngdoc directive 30372 * @name ngView 30373 * @restrict ECA 30374 * 30375 * @description 30376 * # Overview 30377 * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 30378 * including the rendered template of the current route into the main layout (`index.html`) file. 30379 * Every time the current route changes, the included view changes with it according to the 30380 * configuration of the `$route` service. 30381 * 30382 * Requires the {@link ngRoute `ngRoute`} module to be installed. 30383 * 30384 * @animations 30385 * enter - animation is used to bring new content into the browser. 30386 * leave - animation is used to animate existing content away. 30387 * 30388 * The enter and leave animation occur concurrently. 30389 * 30390 * @scope 30391 * @priority 400 30392 * @param {string=} onload Expression to evaluate whenever the view updates. 30393 * 30394 * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll 30395 * $anchorScroll} to scroll the viewport after the view is updated. 30396 * 30397 * - If the attribute is not set, disable scrolling. 30398 * - If the attribute is set without value, enable scrolling. 30399 * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated 30400 * as an expression yields a truthy value. 30401 * @example 30402 <example name="ngView-directive" module="ngViewExample" 30403 deps="angular-route.js;angular-animate.js" 30404 animations="true" fixBase="true"> 30405 <file name="index.html"> 30406 <div ng-controller="MainCtrl as main"> 30407 Choose: 30408 <a href="Book/Moby">Moby</a> |
30409 <a href="Book/Moby/ch/1">Moby: Ch1</a> | 30410 <a href="Book/Gatsby">Gatsby</a> | 30411 <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | 30412 <a href="Book/Scarlet">Scarlet Letter</a><br/> 30413 30414 <div class="view-animate-container"> 30415 <div ng-view class="view-animate"></div> 30416 </div> 30417 <hr /> 30418 30419 <pre>$location.path() = {{main.$location.path()}}</pre> 30420 <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> 30421 <pre>$route.current.params = {{main.$route.current.params}}</pre> 30422 <pre>$routeParams = {{main.$routeParams}}</pre> 30423 </div> 30424 </file> 30425 30426 <file name="book.html"> 30427 <div> 30428 controller: {{book.name}}<br /> 30429 Book Id: {{book.params.bookId}}<br /> 30430 </div> 30431 </file> 30432 30433 <file name="chapter.html"> 30434 <div> 30435 controller: {{chapter.name}}<br /> 30436 Book Id: {{chapter.params.bookId}}<br /> 30437 Chapter Id: {{chapter.params.chapterId}} 30438 </div> 30439 </file> 30440 30441 <file name="animations.css"> 30442 .view-animate-container { 30443 position:relative; 30444 height:100px!important; 30445 background:white; 30446 border:1px solid black; 30447 height:40px; 30448 overflow:hidden; 30449 } 30450 30451 .view-animate { 30452 padding:10px; 30453 } 30454 30455 .view-animate.ng-enter, .view-animate.ng-leave { 30456 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 30457 30458 display:block; 30459 width:100%; 30460 border-left:1px solid black; 30461 30462 position:absolute; 30463 top:0; 30464 left:0; 30465 right:0; 30466 bottom:0; 30467 padding:10px; 30468 } 30469 30470 .view-animate.ng-enter { 30471 left:100%; 30472 } 30473 .view-animate.ng-enter.ng-enter-active { 30474 left:0; 30475 } 30476 .view-animate.ng-leave.ng-leave-active { 30477 left:-100%; 30478 } 30479 </file> 30480 30481 <file name="script.js"> 30482 angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) 30483 .config(['$routeProvider', '$locationProvider', 30484 function($routeProvider, $locationProvider) { 30485 $routeProvider 30486 .when('/Book/:bookId', { 30487 templateUrl: 'book.html', 30488 controller: 'BookCtrl', 30489 controllerAs: 'book' 30490 }) 30491 .when('/Book/:bookId/ch/:chapterId', { 30492 templateUrl: 'chapter.html', 30493 controller: 'ChapterCtrl', 30494 controllerAs: 'chapter' 30495 }); 30496 30497 $locationProvider.html5Mode(true); 30498 }]) 30499 .controller('MainCtrl', ['$route', '$routeParams', '$location', 30500 function($route, $routeParams, $location) { 30501 this.$route = $route; 30502 this.$location = $location; 30503 this.$routeParams = $routeParams; 30504 }]) 30505 .controller('BookCtrl', ['$routeParams', function($routeParams) { 30506 this.name = "BookCtrl"; 30507 this.params = $routeParams; 30508 }]) 30509 .controller('ChapterCtrl', ['$routeParams', function($routeParams) { 30510 this.name = "ChapterCtrl"; 30511 this.params = $routeParams; 30512 }]); 30513 30514 </file> 30515 30516 <file name="protractor.js" type="protractor"> 30517 it('should load and compile correct template', function() { 30518 element(by.linkText('Moby: Ch1')).click(); 30519 var content = element(by.css('[ng-view]')).getText(); 30520 expect(content).toMatch(/controller\: ChapterCtrl/); 30521 expect(content).toMatch(/Book Id\: Moby/); 30522 expect(content).toMatch(/Chapter Id\: 1/); 30523 30524 element(by.partialLinkText('Scarlet')).click(); 30525 30526 content = element(by.css('[ng-view]')).getText(); 30527 expect(content).toMatch(/controller\: BookCtrl/); 30528 expect(content).toMatch(/Book Id\: Scarlet/); 30529 }); 30530 </file> 30531 </example> 30532 */ 30533 30534 30535/** 30536 * @ngdoc event 30537 * @name ngView#$viewContentLoaded 30538 * @eventType emit on the current ngView scope 30539 * @description 30540 * Emitted every time the ngView content is reloaded. 30541 */ 30542ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; 30543function ngViewFactory($route, $anchorScroll, $animate) { 30544 return { 30545 restrict: 'ECA', 30546 terminal: true, 30547 priority: 400, 30548 transclude: 'element', 30549 link: function(scope, $element, attr, ctrl, $transclude) { 30550 var currentScope, 30551 currentElement, 30552 previousLeaveAnimation, 30553 autoScrollExp = attr.autoscroll, 30554 onloadExp = attr.onload || ''; 30555 30556 scope.$on('$routeChangeSuccess', update); 30557 update(); 30558 30559 function cleanupLastView() { 30560 if (previousLeaveAnimation) { 30561 $animate.cancel(previousLeaveAnimation); 30562 previousLeaveAnimation = null; 30563 } 30564 30565 if (currentScope) { 30566 currentScope.$destroy(); 30567 currentScope = null; 30568 } 30569 if (currentElement) { 30570 previousLeaveAnimation = $animate.leave(currentElement); 30571 previousLeaveAnimation.then(function() { 30572 previousLeaveAnimation = null; 30573 }); 30574 currentElement = null; 30575 } 30576 } 30577
30578 function update() { 30579 var locals = $route.current && $route.current.locals, 30580 template = locals && locals.$template; 30581 30582 if (angular.isDefined(template)) { 30583 var newScope = scope.$new(); 30584 var current = $route.current; 30585 30586 // Note: This will also link all children of ng-view that were contained in the original 30587 // html. If that content contains controllers, ... they could pollute/change the scope. 30588 // However, using ng-view on an element with additional content does not make sense... 30589 // Note: We can't remove them in the cloneAttchFn of $transclude as that 30590 // function is called before linking the content, which would apply child 30591 // directives to non existing elements. 30592 var clone = $transclude(newScope, function(clone) { 30593 $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { 30594 if (angular.isDefined(autoScrollExp) 30595 && (!autoScrollExp || scope.$eval(autoScrollExp))) { 30596 $anchorScroll(); 30597 } 30598 }); 30599 cleanupLastView(); 30600 }); 30601 30602 currentElement = clone; 30603 currentScope = current.scope = newScope; 30604 currentScope.$emit('$viewContentLoaded'); 30605 currentScope.$eval(onloadExp); 30606 } else { 30607 cleanupLastView(); 30608 } 30609 } 30610 } 30611 }; 30612} 30613 30614// This directive is called during the $transclude call of the first `ngView` directive. 30615// It will replace and compile the content of the element with the loaded template. 30616// We need this directive so that the element content is already filled when 30617// the link function of another directive on the same element as ngView 30618// is called. 30619ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; 30620function ngViewFillContentFactory($compile, $controller, $route) { 30621 return { 30622 restrict: 'ECA', 30623 priority: -400, 30624 link: function(scope, $element) { 30625 var current = $route.current, 30626 locals = current.locals; 30627 30628 $element.html(locals.$template); 30629 30630 var link = $compile($element.contents()); 30631 30632 if (current.controller) { 30633 locals.$scope = scope; 30634 var controller = $controller(current.controller, locals); 30635 if (current.controllerAs) { 30636 scope[current.controllerAs] = controller; 30637 } 30638 $element.data('$ngControllerController', controller); 30639 $element.children().data('$ngControllerController', controller); 30640 } 30641 30642 link(scope); 30643 } 30644 }; 30645} 30646 30647 30648})(window, window.angular); 30649 30650/** 30651 * An Angular module that gives you access to the browsers local storage 30652 * @version v0.2.5 - 2016-02-23 30653 * @link https://github.com/grevory/angular-local-storage 30654 * @author grevory <[email protected]> 30655 * @license MIT License, http://www.opensource.org/licenses/MIT 30656 */ 30657(function (window, angular) { 30658var isDefined = angular.isDefined, 30659 isUndefined = angular.isUndefined, 30660 isNumber = angular.isNumber, 30661 isObject = angular.isObject, 30662 isArray = angular.isArray, 30663 extend = angular.extend, 30664 toJson = angular.toJson; 30665 30666angular 30667 .module('LocalStorageModule', []) 30668 .provider('localStorageService', function() { 30669 // You should set a prefix to avoid overwriting any local storage variables from the rest of your app 30670 // e.g. localStorageServiceProvider.setPrefix('yourAppName'); 30671 // With provider you can use config as this: 30672 // myApp.config(function (localStorageServiceProvider) { 30673 // localStorageServiceProvider.prefix = 'yourAppName'; 30674 // }); 30675 this.prefix = 'ls'; 30676 30677 // You could change web storage type localstorage or sessionStorage 30678 this.storageType = 'localStorage'; 30679 30680 // Cookie options (usually in case of fallback) 30681 // expiry = Number of days before cookies expire // 0 = Does not expire 30682 // path = The web path the cookie represents 30683 this.cookie = { 30684 expiry: 30, 30685 path: '/' 30686 }; 30687 30688 // Send signals for each of the following actions? 30689 this.notify = { 30690 setItem: true, 30691 removeItem: false 30692 }; 30693 30694 // Setter for the prefix 30695 this.setPrefix = function(prefix) { 30696 this.prefix = prefix; 30697 return this; 30698 }; 30699 30700 // Setter for the storageType 30701 this.setStorageType = function(storageType) { 30702 this.storageType = storageType; 30703 return this; 30704 }; 30705 30706 // Setter for cookie config 30707 this.setStorageCookie = function(exp, path) { 30708 this.cookie.expiry = exp; 30709 this.cookie.path = path; 30710 return this; 30711 }; 30712 30713 // Setter for cookie domain 30714 this.setStorageCookieDomain = function(domain) { 30715 this.cookie.domain = domain; 30716 return this; 30717 }; 30718 30719 // Setter for notification config 30720 // itemSet & itemRemove should be booleans 30721 this.setNotify = function(itemSet, itemRemove) { 30722 this.notify = { 30723 setItem: itemSet, 30724 removeItem: itemRemove 30725 }; 30726 return this; 30727 }; 30728 30729 this.$get = ['$rootScope', '$window', '$document', '$parse', function($rootScope, $window, $document, $parse) { 30730 var self = this; 30731 var prefix = self.prefix; 30732 var cookie = self.cookie; 30733 var notify = self.notify; 30734 var storageType = self.storageType; 30735 var webStorage; 30736 30737 // When Angular's $document is not available 30738 if (!$document) { 30739 $document = document; 30740 } else if ($document[0]) { 30741 $document = $document[0]; 30742 } 30743 30744 // If there is a prefix set in the config lets use that with an appended period for readability 30745 if (prefix.substr(-1) !== '.') { 30746 prefix = !!prefix ? prefix + '.' : ''; 30747 } 30748 var deriveQualifiedKey = function(key) { 30749 return prefix + key; 30750 }; 30751 // Checks the browser to see if local storage is supported 30752 var browserSupportsLocalStorage = (function () { 30753 try { 30754 var supported = (storageType in $window && $window[storageType] !== null); 30755 30756 // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage 30757 // is available, but trying to call .setItem throws an exception. 30758 // 30759 // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage 30760 // that exceeded the quota." 30761 var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7)); 30762 if (supported) { 30763 webStorage = $window[storageType]; 30764 webStorage.setItem(key, ''); 30765 webStorage.removeItem(key); 30766 } 30767 30768 return supported; 30769 } catch (e) { 30770 storageType = 'cookie'; 30771 $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 30772 return false;
30773 } 30774 }()); 30775 30776 // Directly adds a value to local storage 30777 // If local storage is not available in the browser use cookies 30778 // Example use: localStorageService.add('library','angular'); 30779 var addToLocalStorage = function (key, value) { 30780 // Let's convert undefined values to null to get the value consistent 30781 if (isUndefined(value)) { 30782 value = null; 30783 } else { 30784 value = toJson(value); 30785 } 30786 30787 // If this browser does not support local storage use cookies 30788 if (!browserSupportsLocalStorage || self.storageType === 'cookie') { 30789 if (!browserSupportsLocalStorage) { 30790 $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 30791 } 30792 30793 if (notify.setItem) { 30794 $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'}); 30795 } 30796 return addToCookies(key, value); 30797 } 30798 30799 try { 30800 if (webStorage) { 30801 webStorage.setItem(deriveQualifiedKey(key), value); 30802 } 30803 if (notify.setItem) { 30804 $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType}); 30805 } 30806 } catch (e) { 30807 $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 30808 return addToCookies(key, value); 30809 } 30810 return true; 30811 }; 30812 30813 // Directly get a value from local storage 30814 // Example use: localStorageService.get('library'); // returns 'angular' 30815 var getFromLocalStorage = function (key) { 30816 30817 if (!browserSupportsLocalStorage || self.storageType === 'cookie') { 30818 if (!browserSupportsLocalStorage) { 30819 $rootScope.$broadcast('LocalStorageModule.notification.warning','LOCAL_STORAGE_NOT_SUPPORTED'); 30820 } 30821 30822 return getFromCookies(key); 30823 } 30824 30825 var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null; 30826 // angular.toJson will convert null to 'null', so a proper conversion is needed 30827 // FIXME not a perfect solution, since a valid 'null' string can't be stored 30828 if (!item || item === 'null') { 30829 return null; 30830 } 30831 30832 try { 30833 return JSON.parse(item); 30834 } catch (e) { 30835 return item; 30836 } 30837 }; 30838 30839 // Remove an item from local storage 30840 // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular' 30841 var removeFromLocalStorage = function () { 30842 var i, key; 30843 for (i=0; i<arguments.length; i++) { 30844 key = arguments[i]; 30845 if (!browserSupportsLocalStorage || self.storageType === 'cookie') { 30846 if (!browserSupportsLocalStorage) { 30847 $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 30848 } 30849 30850 if (notify.removeItem) { 30851 $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'}); 30852 } 30853 removeFromCookies(key); 30854 } 30855 else { 30856 try { 30857 webStorage.removeItem(deriveQualifiedKey(key)); 30858 if (notify.removeItem) { 30859 $rootScope.$broadcast('LocalStorageModule.notification.removeitem', { 30860 key: key, 30861 storageType: self.storageType 30862 }); 30863 } 30864 } catch (e) { 30865 $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 30866 removeFromCookies(key); 30867 } 30868 } 30869 } 30870 }; 30871 30872 // Return array of keys for local storage 30873 // Example use: var keys = localStorageService.keys() 30874 var getKeysForLocalStorage = function () { 30875 30876 if (!browserSupportsLocalStorage) { 30877 $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 30878 return false; 30879 } 30880 30881 var prefixLength = prefix.length; 30882 var keys = []; 30883 for (var key in webStorage) { 30884 // Only return keys that are for this app 30885 if (key.substr(0,prefixLength) === prefix) { 30886 try { 30887 keys.push(key.substr(prefixLength)); 30888 } catch (e) { 30889 $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description); 30890 return []; 30891 } 30892 } 30893 } 30894 return keys; 30895 }; 30896 30897 // Remove all data for this app from local storage 30898 // Also optionally takes a regular expression string and removes the matching key-value pairs 30899 // Example use: localStorageService.clearAll(); 30900 // Should be used mostly for development purposes 30901 var clearAllFromLocalStorage = function (regularExpression) { 30902 30903 // Setting both regular expressions independently 30904 // Empty strings result in catchall RegExp 30905 var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp(); 30906 var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp(); 30907 30908 if (!browserSupportsLocalStorage || self.storageType === 'cookie') { 30909 if (!browserSupportsLocalStorage) { 30910 $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); 30911 } 30912 return clearAllFromCookies(); 30913 } 30914 30915 var prefixLength = prefix.length; 30916 30917 for (var key in webStorage) { 30918 // Only remove items that are for this app and match the regular expression 30919 if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) { 30920 try { 30921 removeFromLocalStorage(key.substr(prefixLength)); 30922 } catch (e) { 30923 $rootScope.$broadcast('LocalStorageModule.notification.error',e.message); 30924 return clearAllFromCookies(); 30925 } 30926 } 30927 } 30928 return true; 30929 }; 30930 30931 // Checks the browser to see if cookies are supported 30932 var browserSupportsCookies = (function() { 30933 try { 30934 return $window.navigator.cookieEnabled || 30935 ("cookie" in $document && ($document.cookie.length > 0 || 30936 ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1)); 30937 } catch (e) { 30938 $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); 30939 return false;
30940 } 30941 }()); 30942 30943 // Directly adds a value to cookies 30944 // Typically used as a fallback is local storage is not available in the browser 30945 // Example use: localStorageService.cookie.add('library','angular'); 30946 var addToCookies = function (key, value, daysToExpiry) { 30947 30948 if (isUndefined(value)) { 30949 return false; 30950 } else if(isArray(value) || isObject(value)) { 30951 value = toJson(value); 30952 } 30953 30954 if (!browserSupportsCookies) { 30955 $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); 30956 return false; 30957 } 30958 30959 try { 30960 var expiry = '', 30961 expiryDate = new Date(), 30962 cookieDomain = ''; 30963 30964 if (value === null) { 30965 // Mark that the cookie has expired one day ago 30966 expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000)); 30967 expiry = "; expires=" + expiryDate.toGMTString(); 30968 value = ''; 30969 } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) { 30970 expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000)); 30971 expiry = "; expires=" + expiryDate.toGMTString(); 30972 } else if (cookie.expiry !== 0) { 30973 expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000)); 30974 expiry = "; expires=" + expiryDate.toGMTString(); 30975 } 30976 if (!!key) { 30977 var cookiePath = "; path=" + cookie.path; 30978 if(cookie.domain){ 30979 cookieDomain = "; domain=" + cookie.domain; 30980 } 30981 $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain; 30982 } 30983 } catch (e) { 30984 $rootScope.$broadcast('LocalStorageModule.notification.error',e.message); 30985 return false; 30986 } 30987 return true; 30988 }; 30989 30990 // Directly get a value from a cookie 30991 // Example use: localStorageService.cookie.get('library'); // returns 'angular' 30992 var getFromCookies = function (key) { 30993 if (!browserSupportsCookies) { 30994 $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); 30995 return false; 30996 } 30997 30998 var cookies = $document.cookie && $document.cookie.split(';') || []; 30999 for(var i=0; i < cookies.length; i++) { 31000 var thisCookie = cookies[i]; 31001 while (thisCookie.charAt(0) === ' ') { 31002 thisCookie = thisCookie.substring(1,thisCookie.length); 31003 } 31004 if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) { 31005 var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length)); 31006 try { 31007 return JSON.parse(storedValues); 31008 } catch(e) { 31009 return storedValues; 31010 } 31011 } 31012 } 31013 return null; 31014 }; 31015 31016 var removeFromCookies = function (key) { 31017 addToCookies(key,null); 31018 }; 31019 31020 var clearAllFromCookies = function () { 31021 var thisCookie = null, thisKey = null; 31022 var prefixLength = prefix.length; 31023 var cookies = $document.cookie.split(';'); 31024 for(var i = 0; i < cookies.length; i++) { 31025 thisCookie = cookies[i]; 31026 31027 while (thisCookie.charAt(0) === ' ') { 31028 thisCookie = thisCookie.substring(1, thisCookie.length); 31029 } 31030 31031 var key = thisCookie.substring(prefixLength, thisCookie.indexOf('=')); 31032 removeFromCookies(key); 31033 } 31034 }; 31035 31036 var getStorageType = function() { 31037 return storageType; 31038 }; 31039 31040 // Add a listener on scope variable to save its changes to local storage 31041 // Return a function which when called cancels binding 31042 var bindToScope = function(scope, key, def, lsKey) { 31043 lsKey = lsKey || key; 31044 var value = getFromLocalStorage(lsKey); 31045 31046 if (value === null && isDefined(def)) { 31047 value = def; 31048 } else if (isObject(value) && isObject(def)) { 31049 value = extend(value, def); 31050 } 31051 31052 $parse(key).assign(scope, value); 31053 31054 return scope.$watch(key, function(newVal) { 31055 addToLocalStorage(lsKey, newVal); 31056 }, isObject(scope[key])); 31057 }; 31058 31059 // Return localStorageService.length 31060 // ignore keys that not owned 31061 var lengthOfLocalStorage = function() { 31062 var count = 0; 31063 var storage = $window[storageType]; 31064 for(var i = 0; i < storage.length; i++) { 31065 if(storage.key(i).indexOf(prefix) === 0 ) { 31066 count++; 31067 } 31068 } 31069 return count; 31070 }; 31071 31072 return { 31073 isSupported: browserSupportsLocalStorage, 31074 getStorageType: getStorageType, 31075 set: addToLocalStorage, 31076 add: addToLocalStorage, //DEPRECATED 31077 get: getFromLocalStorage, 31078 keys: getKeysForLocalStorage, 31079 remove: removeFromLocalStorage, 31080 clearAll: clearAllFromLocalStorage, 31081 bind: bindToScope, 31082 deriveKey: deriveQualifiedKey, 31083 length: lengthOfLocalStorage, 31084 cookie: { 31085 isSupported: browserSupportsCookies, 31086 set: addToCookies, 31087 add: addToCookies, //DEPRECATED 31088 get: getFromCookies,
31089 remove: removeFromCookies, 31090 clearAll: clearAllFromCookies 31091 } 31092 }; 31093 }]; 31094 }); 31095})(window, window.angular); 31096/** 31097 * @license AngularJS v1.4.11 31098 * (c) 2010-2015 Google, Inc. http://angularjs.org 31099 * License: MIT 31100 */ 31101(function(window, angular, undefined) {'use strict'; 31102 31103/* jshint ignore:start */ 31104var noop = angular.noop; 31105var copy = angular.copy; 31106var extend = angular.extend; 31107var jqLite = angular.element; 31108var forEach = angular.forEach; 31109var isArray = angular.isArray; 31110var isString = angular.isString; 31111var isObject = angular.isObject; 31112var isUndefined = angular.isUndefined; 31113var isDefined = angular.isDefined; 31114var isFunction = angular.isFunction; 31115var isElement = angular.isElement; 31116 31117var ELEMENT_NODE = 1; 31118var COMMENT_NODE = 8; 31119 31120var ADD_CLASS_SUFFIX = '-add'; 31121var REMOVE_CLASS_SUFFIX = '-remove'; 31122var EVENT_CLASS_PREFIX = 'ng-'; 31123var ACTIVE_CLASS_SUFFIX = '-active'; 31124var PREPARE_CLASS_SUFFIX = '-prepare'; 31125 31126var NG_ANIMATE_CLASSNAME = 'ng-animate'; 31127var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren'; 31128 31129// Detect proper transitionend/animationend event names. 31130var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; 31131 31132// If unprefixed events are not supported but webkit-prefixed are, use the latter.
31133// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them. 31134// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend` 31135// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`. 31136// Register both events in case `window.onanimationend` is not supported because of that, 31137// do the same for `transitionend` as Safari is likely to exhibit similar behavior. 31138// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit 31139// therefore there is no reason to test anymore for other vendor prefixes: 31140// http://caniuse.com/#search=transition 31141if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) { 31142 CSS_PREFIX = '-webkit-'; 31143 TRANSITION_PROP = 'WebkitTransition'; 31144 TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; 31145} else { 31146 TRANSITION_PROP = 'transition'; 31147 TRANSITIONEND_EVENT = 'transitionend'; 31148} 31149 31150if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) { 31151 CSS_PREFIX = '-webkit-'; 31152 ANIMATION_PROP = 'WebkitAnimation'; 31153 ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; 31154} else { 31155 ANIMATION_PROP = 'animation'; 31156 ANIMATIONEND_EVENT = 'animationend'; 31157} 31158 31159var DURATION_KEY = 'Duration'; 31160var PROPERTY_KEY = 'Property'; 31161var DELAY_KEY = 'Delay'; 31162var TIMING_KEY = 'TimingFunction'; 31163var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; 31164var ANIMATION_PLAYSTATE_KEY = 'PlayState'; 31165var SAFE_FAST_FORWARD_DURATION_VALUE = 9999; 31166 31167var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY; 31168var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY; 31169var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY; 31170var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY; 31171 31172var isPromiseLike = function(p) { 31173 return p && p.then ? true : false; 31174}; 31175 31176var ngMinErr = angular.$$minErr('ng'); 31177function assertArg(arg, name, reason) { 31178 if (!arg) { 31179 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 31180 } 31181 return arg; 31182} 31183 31184function mergeClasses(a,b) { 31185 if (!a && !b) return ''; 31186 if (!a) return b; 31187 if (!b) return a; 31188 if (isArray(a)) a = a.join(' '); 31189 if (isArray(b)) b = b.join(' '); 31190 return a + ' ' + b; 31191} 31192 31193function packageStyles(options) { 31194 var styles = {}; 31195 if (options && (options.to || options.from)) { 31196 styles.to = options.to; 31197 styles.from = options.from; 31198 } 31199 return styles; 31200} 31201 31202function pendClasses(classes, fix, isPrefix) { 31203 var className = ''; 31204 classes = isArray(classes) 31205 ? classes 31206 : classes && isString(classes) && classes.length 31207 ? classes.split(/\s+/) 31208 : []; 31209 forEach(classes, function(klass, i) { 31210 if (klass && klass.length > 0) { 31211 className += (i > 0) ? ' ' : ''; 31212 className += isPrefix ? fix + klass 31213 : klass + fix; 31214 } 31215 }); 31216 return className; 31217} 31218 31219function removeFromArray(arr, val) { 31220 var index = arr.indexOf(val); 31221 if (val >= 0) { 31222 arr.splice(index, 1); 31223 } 31224} 31225 31226function stripCommentsFromElement(element) { 31227 if (element instanceof jqLite) { 31228 switch (element.length) { 31229 case 0: 31230 return element; 31231 break; 31232 31233 case 1: 31234 // there is no point of stripping anything if the element 31235 // is the only element within the jqLite wrapper. 31236 // (it's important that we retain the element instance.) 31237 if (element[0].nodeType === ELEMENT_NODE) { 31238 return element; 31239 } 31240 break; 31241 31242 default: 31243 return jqLite(extractElementNode(element)); 31244 break; 31245 } 31246 } 31247 31248 if (element.nodeType === ELEMENT_NODE) { 31249 return jqLite(element); 31250 } 31251} 31252 31253function extractElementNode(element) { 31254 if (!element[0]) return element; 31255 for (var i = 0; i < element.length; i++) { 31256 var elm = element[i]; 31257 if (elm.nodeType == ELEMENT_NODE) { 31258 return elm; 31259 } 31260 } 31261} 31262 31263function $$addClass($$jqLite, element, className) { 31264 forEach(element, function(elm) { 31265 $$jqLite.addClass(elm, className); 31266 }); 31267} 31268 31269function $$removeClass($$jqLite, element, className) { 31270 forEach(element, function(elm) { 31271 $$jqLite.removeClass(elm, className); 31272 }); 31273} 31274 31275function applyAnimationClassesFactory($$jqLite) { 31276 return function(element, options) { 31277 if (options.addClass) { 31278 $$addClass($$jqLite, element, options.addClass); 31279 options.addClass = null; 31280 } 31281 if (options.removeClass) { 31282 $$removeClass($$jqLite, element, options.removeClass); 31283 options.removeClass = null; 31284 } 31285 } 31286} 31287 31288function prepareAnimationOptions(options) { 31289 options = options || {}; 31290 if (!options.$$prepared) { 31291 var domOperation = options.domOperation || noop; 31292 options.domOperation = function() { 31293 options.$$domOperationFired = true; 31294 domOperation(); 31295 domOperation = noop; 31296 }; 31297 options.$$prepared = true; 31298 } 31299 return options; 31300} 31301 31302function applyAnimationStyles(element, options) { 31303 applyAnimationFromStyles(element, options); 31304 applyAnimationToStyles(element, options); 31305} 31306 31307function applyAnimationFromStyles(element, options) { 31308 if (options.from) { 31309 element.css(options.from); 31310 options.from = null; 31311 } 31312} 31313 31314function applyAnimationToStyles(element, options) { 31315 if (options.to) { 31316 element.css(options.to); 31317 options.to = null; 31318 } 31319} 31320
vendor: 8,033 bytes, lines 31321-31601
31321function mergeAnimationDetails(element, oldAnimation, newAnimation) { 31322 var target = oldAnimation.options || {}; 31323 var newOptions = newAnimation.options || {}; 31324 31325 var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || ''); 31326 var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || ''); 31327 var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove); 31328 31329 if (newOptions.preparationClasses) { 31330 target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses); 31331 delete newOptions.preparationClasses; 31332 } 31333 31334 // noop is basically when there is no callback; otherwise something has been set 31335 var realDomOperation = target.domOperation !== noop ? target.domOperation : null; 31336 31337 extend(target, newOptions); 31338 31339 // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this. 31340 if (realDomOperation) { 31341 target.domOperation = realDomOperation; 31342 } 31343 31344 if (classes.addClass) { 31345 target.addClass = classes.addClass; 31346 } else { 31347 target.addClass = null; 31348 } 31349 31350 if (classes.removeClass) { 31351 target.removeClass = classes.removeClass; 31352 } else { 31353 target.removeClass = null; 31354 } 31355 31356 oldAnimation.addClass = target.addClass; 31357 oldAnimation.removeClass = target.removeClass; 31358 31359 return target; 31360} 31361 31362function resolveElementClasses(existing, toAdd, toRemove) { 31363 var ADD_CLASS = 1; 31364 var REMOVE_CLASS = -1; 31365 31366 var flags = {}; 31367 existing = splitClassesToLookup(existing); 31368 31369 toAdd = splitClassesToLookup(toAdd); 31370 forEach(toAdd, function(value, key) { 31371 flags[key] = ADD_CLASS; 31372 }); 31373 31374 toRemove = splitClassesToLookup(toRemove); 31375 forEach(toRemove, function(value, key) { 31376 flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS; 31377 }); 31378 31379 var classes = { 31380 addClass: '', 31381 removeClass: '' 31382 }; 31383 31384 forEach(flags, function(val, klass) { 31385 var prop, allow; 31386 if (val === ADD_CLASS) { 31387 prop = 'addClass'; 31388 allow = !existing[klass]; 31389 } else if (val === REMOVE_CLASS) { 31390 prop = 'removeClass'; 31391 allow = existing[klass]; 31392 } 31393 if (allow) { 31394 if (classes[prop].length) { 31395 classes[prop] += ' '; 31396 } 31397 classes[prop] += klass; 31398 } 31399 }); 31400 31401 function splitClassesToLookup(classes) { 31402 if (isString(classes)) { 31403 classes = classes.split(' '); 31404 } 31405 31406 var obj = {}; 31407 forEach(classes, function(klass) { 31408 // sometimes the split leaves empty string values 31409 // incase extra spaces were applied to the options 31410 if (klass.length) { 31411 obj[klass] = true; 31412 } 31413 }); 31414 return obj; 31415 } 31416 31417 return classes; 31418} 31419 31420function getDomNode(element) { 31421 return (element instanceof angular.element) ? element[0] : element; 31422} 31423 31424function applyGeneratedPreparationClasses(element, event, options) { 31425 var classes = ''; 31426 if (event) { 31427 classes = pendClasses(event, EVENT_CLASS_PREFIX, true); 31428 } 31429 if (options.addClass) { 31430 classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX)); 31431 } 31432 if (options.removeClass) { 31433 classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX)); 31434 } 31435 if (classes.length) { 31436 options.preparationClasses = classes; 31437 element.addClass(classes); 31438 } 31439} 31440 31441function clearGeneratedClasses(element, options) { 31442 if (options.preparationClasses) { 31443 element.removeClass(options.preparationClasses); 31444 options.preparationClasses = null; 31445 } 31446 if (options.activeClasses) { 31447 element.removeClass(options.activeClasses); 31448 options.activeClasses = null; 31449 } 31450} 31451 31452function blockTransitions(node, duration) { 31453 // we use a negative delay value since it performs blocking 31454 // yet it doesn't kill any existing transitions running on the 31455 // same element which makes this safe for class-based animations 31456 var value = duration ? '-' + duration + 's' : ''; 31457 applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]); 31458 return [TRANSITION_DELAY_PROP, value]; 31459} 31460 31461function blockKeyframeAnimations(node, applyBlock) { 31462 var value = applyBlock ? 'paused' : ''; 31463 var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY; 31464 applyInlineStyle(node, [key, value]); 31465 return [key, value]; 31466} 31467 31468function applyInlineStyle(node, styleTuple) { 31469 var prop = styleTuple[0]; 31470 var value = styleTuple[1]; 31471 node.style[prop] = value; 31472} 31473 31474function concatWithSpace(a,b) { 31475 if (!a) return b; 31476 if (!b) return a; 31477 return a + ' ' + b; 31478} 31479 31480var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { 31481 var queue, cancelFn; 31482 31483 function scheduler(tasks) { 31484 // we make a copy since RAFScheduler mutates the state 31485 // of the passed in array variable and this would be difficult 31486 // to track down on the outside code 31487 queue = queue.concat(tasks); 31488 nextTick(); 31489 } 31490 31491 queue = scheduler.queue = []; 31492 31493 /* waitUntilQuiet does two things: 31494 * 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through 31495 * 2. It will delay the next wave of tasks from running until the quiet `fn` has run. 31496 * 31497 * The motivation here is that animation code can request more time from the scheduler 31498 * before the next wave runs. This allows for certain DOM properties such as classes to 31499 * be resolved in time for the next animation to run. 31500 */ 31501 scheduler.waitUntilQuiet = function(fn) { 31502 if (cancelFn) cancelFn(); 31503 31504 cancelFn = $$rAF(function() { 31505 cancelFn = null; 31506 fn(); 31507 nextTick(); 31508 }); 31509 }; 31510 31511 return scheduler; 31512 31513 function nextTick() { 31514 if (!queue.length) return; 31515 31516 var items = queue.shift(); 31517 for (var i = 0; i < items.length; i++) { 31518 items[i](); 31519 } 31520 31521 if (!cancelFn) { 31522 $$rAF(function() { 31523 if (!cancelFn) nextTick(); 31524 }); 31525 } 31526 } 31527}]; 31528 31529/** 31530 * @ngdoc directive 31531 * @name ngAnimateChildren 31532 * @restrict AE 31533 * @element ANY 31534 * 31535 * @description 31536 * 31537 * ngAnimateChildren allows you to specify that children of this element should animate even if any 31538 * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move` 31539 * (structural) animation, child elements that also have an active structural animation are not animated. 31540 * 31541 * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation). 31542 * 31543 * 31544 * @param {string} ngAnimateChildren If the value is empty, `true` or `on`, 31545 * then child animations are allowed. If the value is `false`, child animations are not allowed. 31546 * 31547 * @example 31548 * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true"> 31549 <file name="index.html"> 31550 <div ng-controller="mainController as main"> 31551 <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label> 31552 <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label> 31553 <hr> 31554 <div ng-animate-children="{{main.animateChildren}}"> 31555 <div ng-if="main.enterElement" class="container"> 31556 List of items: 31557 <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item {{item}}</div> 31558 </div> 31559 </div> 31560 </div> 31561 </file> 31562 <file name="animations.css"> 31563 31564 .container.ng-enter, 31565 .container.ng-leave { 31566 transition: all ease 1.5s; 31567 } 31568 31569 .container.ng-enter, 31570 .container.ng-leave-active { 31571 opacity: 0; 31572 } 31573 31574 .container.ng-leave, 31575 .container.ng-enter-active { 31576 opacity: 1; 31577 } 31578 31579 .item { 31580 background: firebrick; 31581 color: #FFF; 31582 margin-bottom: 10px; 31583 } 31584 31585 .item.ng-enter, 31586 .item.ng-leave { 31587 transition: transform 1.5s ease; 31588 } 31589 31590 .item.ng-enter { 31591 transform: translateX(50px); 31592 } 31593 31594 .item.ng-enter-active { 31595 transform: translateX(0); 31596 } 31597 </file> 31598 <file name="script.js"> 31599 angular.module('ngAnimateChildren', ['ngAnimate']) 31600 .controller('mainController', function() { 31601 this.animateChildren = false;
31602 this.enterElement = false; 31603 }); 31604 </file> 31605 </example> 31606 */ 31607var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) { 31608 return { 31609 link: function(scope, element, attrs) { 31610 var val = attrs.ngAnimateChildren; 31611 if (angular.isString(val) && val.length === 0) { //empty attribute 31612 element.data(NG_ANIMATE_CHILDREN_DATA, true); 31613 } else { 31614 // Interpolate and set the value, so that it is available to 31615 // animations that run right after compilation 31616 setData($interpolate(val)(scope)); 31617 attrs.$observe('ngAnimateChildren', setData); 31618 } 31619 31620 function setData(value) { 31621 value = value === 'on' || value === 'true'; 31622 element.data(NG_ANIMATE_CHILDREN_DATA, value); 31623 } 31624 } 31625 }; 31626}]; 31627 31628var ANIMATE_TIMER_KEY = '$$animateCss'; 31629 31630/** 31631 * @ngdoc service 31632 * @name $animateCss 31633 * @kind object 31634 * 31635 * @description 31636 * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes 31637 * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT 31638 * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or 31639 * directives to create more complex animations that can be purely driven using CSS code. 31640 * 31641 * Note that only browsers that support CSS transitions and/or keyframe animations are capable of 31642 * rendering animations triggered via `$animateCss` (bad news for IE9 and lower). 31643 * 31644 * ## Usage 31645 * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that 31646 * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however, 31647 * any automatic control over cancelling animations and/or preventing animations from being run on 31648 * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to 31649 * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger 31650 * the CSS animation. 31651 * 31652 * The example below shows how we can create a folding animation on an element using `ng-if`: 31653 * 31654 * ```html 31655 * <!-- notice the `fold-animation` CSS class --> 31656 * <div ng-if="onOff" class="fold-animation"> 31657 * This element will go BOOM 31658 * </div> 31659 * <button ng-click="onOff=true">Fold In</button> 31660 * ``` 31661 * 31662 * Now we create the **JavaScript animation** that will trigger the CSS transition: 31663 * 31664 * ```js 31665 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { 31666 * return { 31667 * enter: function(element, doneFn) { 31668 * var height = element[0].offsetHeight; 31669 * return $animateCss(element, { 31670 * from: { height:'0px' }, 31671 * to: { height:height + 'px' }, 31672 * duration: 1 // one second 31673 * }); 31674 * } 31675 * } 31676 * }]); 31677 * ``` 31678 * 31679 * ## More Advanced Uses 31680 * 31681 * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks 31682 * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code. 31683 * 31684 * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation, 31685 * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with 31686 * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order 31687 * to provide a working animation that will run in CSS. 31688 * 31689 * The example below showcases a more advanced version of the `.fold-animation` from the example above: 31690 * 31691 * ```js 31692 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { 31693 * return { 31694 * enter: function(element, doneFn) { 31695 * var height = element[0].offsetHeight; 31696 * return $animateCss(element, { 31697 * addClass: 'red large-text pulse-twice', 31698 * easing: 'ease-out', 31699 * from: { height:'0px' }, 31700 * to: { height:height + 'px' }, 31701 * duration: 1 // one second 31702 * }); 31703 * } 31704 * } 31705 * }]); 31706 * ``` 31707 * 31708 * Since we're adding/removing CSS classes then the CSS transition will also pick those up: 31709 * 31710 * ```css 31711 * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code, 31712 * the CSS classes below will be transitioned despite them being defined as regular CSS classes */ 31713 * .red { background:red; } 31714 * .large-text { font-size:20px; } 31715 * 31716 * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */ 31717 * .pulse-twice { 31718 * animation: 0.5s pulse linear 2; 31719 * -webkit-animation: 0.5s pulse linear 2; 31720 * } 31721 * 31722 * @keyframes pulse { 31723 * from { transform: scale(0.5); } 31724 * to { transform: scale(1.5); } 31725 * } 31726 * 31727 * @-webkit-keyframes pulse { 31728 * from { -webkit-transform: scale(0.5); } 31729 * to { -webkit-transform: scale(1.5); } 31730 * } 31731 * ``` 31732 * 31733 * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen. 31734 * 31735 * ## How the Options are handled 31736 * 31737 * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation 31738 * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to al
31738so animate some inline 31739 * styles using the `from` and `to` properties. 31740 * 31741 * ```js 31742 * var animator = $animateCss(element, { 31743 * from: { background:'red' }, 31744 * to: { background:'blue' } 31745 * }); 31746 * animator.start(); 31747 * ``` 31748 * 31749 * ```css 31750 * .rotating-animation { 31751 * animation:0.5s rotate linear; 31752 * -webkit-animation:0.5s rotate linear; 31753 * } 31754 * 31755 * @keyframes rotate { 31756 * from { transform: rotate(0deg); } 31757 * to { transform: rotate(360deg); } 31758 * } 31759 * 31760 * @-webkit-keyframes rotate { 31761 * from { -webkit-transform: rotate(0deg); } 31762 * to { -webkit-transform: rotate(360deg); } 31763 * } 31764 * ``` 31765 * 31766 * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is 31767 * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition 31768 * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition 31769 * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied 31770 * and spread across the transition and keyframe animation. 31771 * 31772 * ## What is returned 31773 * 31774 * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually 31775 * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are 31776 * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties: 31777 * 31778 * ```js 31779 * var animator = $animateCss(element, { ... }); 31780 * ``` 31781 * 31782 * Now what do the contents of our `animator` variable look like: 31783 * 31784 * ```js 31785 * { 31786 * // starts the animation 31787 * start: Function, 31788 * 31789 * // ends (aborts) the animation 31790 * end: Function 31791 * } 31792 * ``` 31793 * 31794 * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends. 31795 * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and stlyes may have been 31796 * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties 31797 * and that changing them will not reconfigure the parameters of the animation. 31798 * 31799 * ### runner.done() vs runner.then() 31800 * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the 31801 * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**. 31802 * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()` 31803 * unless you really need a digest to kick off afterwards. 31804 * 31805 * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss 31806 * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). 31807 * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works. 31808 * 31809 * @param {DOMElement} element the element that will be animated 31810 * @param {object} options the animation-related options that will be applied during the animation 31811 * 31812 * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied 31813 * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.) 31814 * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and 31815 * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted. 31816 * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both). 31817 * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`). 31818 * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`). 31819 * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation. 31820 * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition. 31821 * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation. 31822 * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation. 31823 * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0` 31824 * is provided then the animation will be skipped entirely. 31825 * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is 31826 * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value 31827 * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same 31828 * CSS delay value. 31829 * * `stagger` - A numeric time value representing the delay between successively animated elements 31830 * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.}) 31831 * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a 31832 * * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`) 31833 * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved
31833in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.) 31834 * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once 31835 * the animation is closed. This is useful for when the styles are used purely for the sake of 31836 * the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation). 31837 * By default this value is set to `false`. 31838 * 31839 * @return {object} an object with start and end methods and details about the animation. 31840 * 31841 * * `start` - The method to start the animation. This will return a `Promise` when called. 31842 * * `end` - This method will cancel the animation and remove all applied CSS classes and styles. 31843 */ 31844var ONE_SECOND = 1000; 31845var BASE_TEN = 10; 31846 31847var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; 31848var CLOSING_TIME_BUFFER = 1.5; 31849 31850var DETECT_CSS_PROPERTIES = { 31851 transitionDuration: TRANSITION_DURATION_PROP, 31852 transitionDelay: TRANSITION_DELAY_PROP, 31853 transitionProperty: TRANSITION_PROP + PROPERTY_KEY, 31854 animationDuration: ANIMATION_DURATION_PROP, 31855 animationDelay: ANIMATION_DELAY_PROP, 31856 animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY 31857}; 31858 31859var DETECT_STAGGER_CSS_PROPERTIES = { 31860 transitionDuration: TRANSITION_DURATION_PROP, 31861 transitionDelay: TRANSITION_DELAY_PROP, 31862 animationDuration: ANIMATION_DURATION_PROP, 31863 animationDelay: ANIMATION_DELAY_PROP 31864}; 31865 31866function getCssKeyframeDurationStyle(duration) { 31867 return [ANIMATION_DURATION_PROP, duration + 's']; 31868} 31869 31870function getCssDelayStyle(delay, isKeyframeAnimation) { 31871 var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP; 31872 return [prop, delay + 's']; 31873} 31874 31875function computeCssStyles($window, element, properties) { 31876 var styles = Object.create(null); 31877 var detectedStyles = $window.getComputedStyle(element) || {}; 31878 forEach(properties, function(formalStyleName, actualStyleName) { 31879 var val = detectedStyles[formalStyleName]; 31880 if (val) { 31881 var c = val.charAt(0); 31882 31883 // only numerical-based values have a negative sign or digit as the first value 31884 if (c === '-' || c === '+' || c >= 0) { 31885 val = parseMaxTime(val); 31886 } 31887 31888 // by setting this to null in the event that the delay is not set or is set directly as 0 31889 // then we can still allow for zegative values to be used later on and not mistake this 31890 // value for being greater than any other negative value. 31891 if (val === 0) { 31892 val = null; 31893 } 31894 styles[actualStyleName] = val; 31895 } 31896 }); 31897 31898 return styles; 31899} 31900 31901function parseMaxTime(str) { 31902 var maxValue = 0; 31903 var values = str.split(/\s*,\s*/); 31904 forEach(values, function(value) { 31905 // it's always safe to consider only second values and omit `ms` values since 31906 // getComputedStyle will always handle the conversion for us 31907 if (value.charAt(value.length - 1) == 's') { 31908 value = value.substring(0, value.length - 1); 31909 } 31910 value = parseFloat(value) || 0; 31911 maxValue = maxValue ? Math.max(value, maxValue) : value; 31912 }); 31913 return maxValue; 31914} 31915 31916function truthyTimingValue(val) { 31917 return val === 0 || val != null; 31918} 31919 31920function getCssTransitionDurationStyle(duration, applyOnlyDuration) { 31921 var style = TRANSITION_PROP; 31922 var value = duration + 's'; 31923 if (applyOnlyDuration) { 31924 style += DURATION_KEY; 31925 } else { 31926 value += ' linear all'; 31927 } 31928 return [style, value]; 31929} 31930 31931function createLocalCacheLookup() { 31932 var cache = Object.create(null); 31933 return { 31934 flush: function() { 31935 cache = Object.create(null); 31936 }, 31937 31938 count: function(key) { 31939 var entry = cache[key]; 31940 return entry ? entry.total : 0; 31941 }, 31942 31943 get: function(key) { 31944 var entry = cache[key]; 31945 return entry && entry.value; 31946 }, 31947 31948 put: function(key, value) { 31949 if (!cache[key]) { 31950 cache[key] = { total: 1, value: value }; 31951 } else { 31952 cache[key].total++; 31953 } 31954 } 31955 }; 31956} 31957 31958// we do not reassign an already present style value since 31959// if we detect the style property value again we may be 31960// detecting styles that were added via the `from` styles. 31961// We make use of `isDefined` here since an empty string 31962// or null value (which is what getPropertyValue will return 31963// for a non-existing style) will still be marked as a valid 31964// value for the style (a falsy value implies that the style 31965// is to be removed at the end of the animation). If we had a simple 31966// "OR" statement then it would not be enough to catch that. 31967function registerRestorableStyles(backup, node, properties) { 31968 forEach(properties, function(prop) { 31969 backup[prop] = isDefined(backup[prop]) 31970 ? backup[prop] 31971 : node.style.getPropertyValue(prop); 31972 }); 31973} 31974 31975var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { 31976 var gcsLookup = createLocalCacheLookup(); 31977 var gcsStaggerLookup = createLocalCacheLookup(); 31978 31979 this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', 31980 '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue', 31981 function($window, $$jqLite, $$AnimateRunner, $timeout, 31982 $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) { 31983 31984 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 31985 31986 var parentCounter = 0;
vendor: 45,156 bytes, lines 31987-33290
31987 function gcsHashFn(node, extraClasses) { 31988 var KEY = "$$ngAnimateParentKey"; 31989 var parentNode = node.parentNode; 31990 var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter); 31991 return parentID + '-' + node.getAttribute('class') + '-' + extraClasses; 31992 } 31993 31994 function computeCachedCssStyles(node, className, cacheKey, properties) { 31995 var timings = gcsLookup.get(cacheKey); 31996 31997 if (!timings) { 31998 timings = computeCssStyles($window, node, properties); 31999 if (timings.animationIterationCount === 'infinite') { 32000 timings.animationIterationCount = 1; 32001 } 32002 } 32003 32004 // we keep putting this in multiple times even though the value and the cacheKey are the same 32005 // because we're keeping an interal tally of how many duplicate animations are detected. 32006 gcsLookup.put(cacheKey, timings); 32007 return timings; 32008 } 32009 32010 function computeCachedCssStaggerStyles(node, className, cacheKey, properties) { 32011 var stagger; 32012 32013 // if we have one or more existing matches of matching elements 32014 // containing the same parent + CSS styles (which is how cacheKey works) 32015 // then staggering is possible 32016 if (gcsLookup.count(cacheKey) > 0) { 32017 stagger = gcsStaggerLookup.get(cacheKey); 32018 32019 if (!stagger) { 32020 var staggerClassName = pendClasses(className, '-stagger'); 32021 32022 $$jqLite.addClass(node, staggerClassName); 32023 32024 stagger = computeCssStyles($window, node, properties); 32025 32026 // force the conversion of a null value to zero incase not set 32027 stagger.animationDuration = Math.max(stagger.animationDuration, 0); 32028 stagger.transitionDuration = Math.max(stagger.transitionDuration, 0); 32029 32030 $$jqLite.removeClass(node, staggerClassName); 32031 32032 gcsStaggerLookup.put(cacheKey, stagger); 32033 } 32034 } 32035 32036 return stagger || {}; 32037 } 32038 32039 var cancelLastRAFRequest; 32040 var rafWaitQueue = []; 32041 function waitUntilQuiet(callback) { 32042 rafWaitQueue.push(callback); 32043 $$rAFScheduler.waitUntilQuiet(function() { 32044 gcsLookup.flush(); 32045 gcsStaggerLookup.flush(); 32046 32047 // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. 32048 // PLEASE EXAMINE THE `$$forceReflow` service to understand why. 32049 var pageWidth = $$forceReflow(); 32050 32051 // we use a for loop to ensure that if the queue is changed 32052 // during this looping then it will consider new requests 32053 for (var i = 0; i < rafWaitQueue.length; i++) { 32054 rafWaitQueue[i](pageWidth); 32055 } 32056 rafWaitQueue.length = 0; 32057 }); 32058 } 32059 32060 function computeTimings(node, className, cacheKey) { 32061 var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES); 32062 var aD = timings.animationDelay; 32063 var tD = timings.transitionDelay; 32064 timings.maxDelay = aD && tD 32065 ? Math.max(aD, tD) 32066 : (aD || tD); 32067 timings.maxDuration = Math.max( 32068 timings.animationDuration * timings.animationIterationCount, 32069 timings.transitionDuration); 32070 32071 return timings; 32072 } 32073 32074 return function init(element, initialOptions) { 32075 // all of the animation functions should create 32076 // a copy of the options data, however, if a 32077 // parent service has already created a copy then 32078 // we should stick to using that 32079 var options = initialOptions || {}; 32080 if (!options.$$prepared) { 32081 options = prepareAnimationOptions(copy(options)); 32082 } 32083 32084 var restoreStyles = {}; 32085 var node = getDomNode(element); 32086 if (!node 32087 || !node.parentNode 32088 || !$$animateQueue.enabled()) { 32089 return closeAndReturnNoopAnimator(); 32090 } 32091 32092 var temporaryStyles = []; 32093 var classes = element.attr('class'); 32094 var styles = packageStyles(options); 32095 var animationClosed; 32096 var animationPaused; 32097 var animationCompleted; 32098 var runner; 32099 var runnerHost; 32100 var maxDelay; 32101 var maxDelayTime; 32102 var maxDuration; 32103 var maxDurationTime; 32104 var startTime; 32105 var events = []; 32106 32107 if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) { 32108 return closeAndReturnNoopAnimator(); 32109 } 32110 32111 var method = options.event && isArray(options.event) 32112 ? options.event.join(' ') 32113 : options.event; 32114 32115 var isStructural = method && options.structural; 32116 var structuralClassName = ''; 32117 var addRemoveClassName = ''; 32118 32119 if (isStructural) { 32120 structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true); 32121 } else if (method) { 32122 structuralClassName = method; 32123 } 32124 32125 if (options.addClass) { 32126 addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX); 32127 } 32128 32129 if (options.removeClass) { 32130 if (addRemoveClassName.length) { 32131 addRemoveClassName += ' '; 32132 } 32133 addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX); 32134 } 32135 32136 // there may be a situation where a structural animation is combined together 32137 // with CSS classes that need to resolve before the animation is computed. 32138 // However this means that there is no explicit CSS code to block the animation 32139 // from happening (by setting 0s none in the class name). If this is the case 32140 // we need to apply the classes before the first rAF so we know to continue if 32141 // there actually is a detected transition or keyframe animation 32142 if (options.applyClassesEarly && addRemoveClassName.length) { 32143 applyAnimationClasses(element, options); 32144 } 32145 32146 var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim(); 32147 var fullClassName = classes + ' ' + preparationClasses; 32148 var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX); 32149 var hasToStyles = styles.to && Object.keys(styles.to).length > 0; 32150 var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0; 32151 32152 // there is no way we can trigger an animation if no styles and 32153 // no classes are being applied which would then trigger a transition, 32154 // unless there a is raw keyframe value that is applied to the element. 32155 if (!containsKeyframeAnimation 32156 && !hasToStyles 32157 && !preparationClasses) { 32158 return closeAndReturnNoopAnimator(); 32159 } 32160 32161 var cacheKey, stagger; 32162 if (options.stagger > 0) { 32163 var staggerVal = parseFloat(options.stagger); 32164 stagger = { 32165 transitionDelay: staggerVal, 32166 animationDelay: staggerVal, 32167 transitionDuration: 0, 32168 animationDuration: 0 32169 }; 32170 } else { 32171 cacheKey = gcsHashFn(node, fullClassName); 32172 stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES); 32173 } 32174 32175 if (!options.$$skipPreparationClasses) { 32176 $$jqLite.addClass(element, preparationClasses); 32177 } 32178 32179 var applyOnlyDuration; 32180 32181 if (options.transitionStyle) { 32182 var transitionStyle = [TRANSITION_PROP, options.transitionStyle]; 32183 applyInlineStyle(node, transitionStyle); 32184 temporaryStyles.push(transitionStyle); 32185 } 32186 32187 if (options.duration >= 0) { 32188 applyOnlyDuration = node.style[TRANSITION_PROP].length > 0; 32189 var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration); 32190 32191 // we set the duration so that it will be picked up by getComputedStyle later 32192 applyInlineStyle(node, durationStyle); 32193 temporaryStyles.push(durationStyle); 32194 } 32195 32196 if (options.keyframeStyle) { 32197 var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle]; 32198 applyInlineStyle(node, keyframeStyle); 32199 temporaryStyles.push(keyframeStyle); 32200 } 32201 32202 var itemIndex = stagger 32203 ? options.staggerIndex >= 0 32204 ? options.staggerIndex 32205 : gcsLookup.count(cacheKey) 32206 : 0; 32207 32208 var isFirst = itemIndex === 0; 32209 32210 // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY 32211 // without causing any combination of transitions to kick in. By adding a negative delay value 32212 // it forces the setup class' transition to end immediately. We later then remove the negative 32213 // transition delay to allow for the transition to naturally do it's thing. The beauty here is 32214 // that if there is no transition defined then nothing will happen and this will also allow 32215 // other transitions to be stacked on top of each other without any chopping them out. 32216 if (isFirst && !options.skipBlocking) { 32217 blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE); 32218 } 32219 32220 var timings = computeTimings(node, fullClassName, cacheKey); 32221 var relativeDelay = timings.maxDelay; 32222 maxDelay = Math.max(relativeDelay, 0); 32223 maxDuration = timings.maxDuration; 32224 32225 var flags = {}; 32226 flags.hasTransitions = timings.transitionDuration > 0; 32227 flags.hasAnimations = timings.animationDuration > 0; 32228 flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all'; 32229 flags.applyTransitionDuration = hasToStyles && ( 32230 (flags.hasTransitions && !flags.hasTransitionAll) 32231 || (flags.hasAnimations && !flags.hasTransitions)); 32232 flags.applyAnimationDuration = options.duration && flags.hasAnimations; 32233 flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions); 32234 flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations; 32235 flags.recalculateTimingStyles = addRemoveClassName.length > 0; 32236 32237 if (flags.applyTransitionDuration || flags.applyAnimationDuration) { 32238 maxDuration = options.duration ? parseFloat(options.duration) : maxDuration; 32239 32240 if (flags.applyTransitionDuration) { 32241 flags.hasTransitions = true; 32242 timings.transitionDuration = maxDuration; 32243 applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0; 32244 temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration)); 32245 } 32246 32247 if (flags.applyAnimationDuration) { 32248 flags.hasAnimations = true; 32249 timings.animationDuration = maxDuration; 32250 temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration)); 32251 } 32252 } 32253 32254 if (maxDuration === 0 && !flags.recalculateTimingStyles) { 32255 return closeAndReturnNoopAnimator(); 32256 } 32257 32258 if (options.delay != null) { 32259 var delayStyle; 32260 if (typeof options.delay !== "boolean") { 32261 delayStyle = parseFloat(options.delay); 32262 // number in options.delay means we have to recalculate the delay for the closing timeout 32263 maxDelay = Math.max(delayStyle, 0); 32264 } 32265 32266 if (flags.applyTransitionDelay) { 32267 temporaryStyles.push(getCssDelayStyle(delayStyle)); 32268 } 32269 32270 if (flags.applyAnimationDelay) { 32271 temporaryStyles.push(getCssDelayStyle(delayStyle, true)); 32272 } 32273 } 32274 32275 // we need to recalculate the delay value since we used a pre-emptive negative 32276 // delay value and the delay value is required for the final event checking. This 32277 // property will ensure that this will happen after the RAF phase has passed. 32278 if (options.duration == null && timings.transitionDuration > 0) { 32279 flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst; 32280 } 32281 32282 maxDelayTime = maxDelay * ONE_SECOND; 32283 maxDurationTime = maxDuration * ONE_SECOND; 32284 if (!options.skipBlocking) { 32285 flags.blockTransition = timings.transitionDuration > 0; 32286 flags.blockKeyframeAnimation = timings.animationDuration > 0 && 32287 stagger.animationDelay > 0 && 32288 stagger.animationDuration === 0; 32289 } 32290 32291 if (options.from) { 32292 if (options.cleanupStyles) { 32293 registerRestorableStyles(restoreStyles, node, Object.keys(options.from)); 32294 } 32295 applyAnimationFromStyles(element, options); 32296 } 32297 32298 if (flags.blockTransition || flags.blockKeyframeAnimation) { 32299 applyBlocking(maxDuration); 32300 } else if (!options.skipBlocking) { 32301 blockTransitions(node, false); 32302 } 32303 32304 // TODO(matsko): for 1.5 change this code to have an animator object for better debugging 32305 return { 32306 $$willAnimate: true, 32307 end: endFn, 32308 start: function() { 32309 if (animationClosed) return; 32310 32311 runnerHost = { 32312 end: endFn, 32313 cancel: cancelFn, 32314 resume: null, //this will be set during the start() phase 32315 pause: null 32316 }; 32317 32318 runner = new $$AnimateRunner(runnerHost); 32319 32320 waitUntilQuiet(start); 32321 32322 // we don't have access to pause/resume the animation 32323 // since it hasn't run yet. AnimateRunner will therefore 32324 // set noop functions for resume and pause and they will 32325 // later be overridden once the animation is triggered 32326 return runner; 32327 } 32328 }; 32329 32330 function endFn() { 32331 close(); 32332 } 32333 32334 function cancelFn() { 32335 close(true); 32336 } 32337 32338 function close(rejected) { // jshint ignore:line 32339 // if the promise has been called already then we shouldn't close 32340 // the animation again 32341 if (animationClosed || (animationCompleted && animationPaused)) return; 32342 animationClosed = true; 32343 animationPaused = false; 32344 32345 if (!options.$$skipPreparationClasses) { 32346 $$jqLite.removeClass(element, preparationClasses); 32347 } 32348 $$jqLite.removeClass(element, activeClasses); 32349 32350 blockKeyframeAnimations(node, false); 32351 blockTransitions(node, false); 32352 32353 forEach(temporaryStyles, function(entry) { 32354 // There is only one way to remove inline style properties entirely from elements. 32355 // By using `removeProperty` this works, but we need to convert camel-cased CSS 32356 // styles down to hyphenated values. 32357 node.style[entry[0]] = ''; 32358 }); 32359 32360 applyAnimationClasses(element, options); 32361 applyAnimationStyles(element, options); 32362 32363 if (Object.keys(restoreStyles).length) { 32364 forEach(restoreStyles, function(value, prop) { 32365 value ? node.style.setProperty(prop, value) 32366 : node.style.removeProperty(prop); 32367 }); 32368 } 32369 32370 // the reason why we have this option is to allow a synchronous closing callback 32371 // that is fired as SOON as the animation ends (when the CSS is removed) or if 32372 // the animation never takes off at all. A good example is a leave animation since 32373 // the element must be removed just after the animation is over or else the element 32374 // will appear on screen for one animation frame causing an overbearing flicker. 32375 if (options.onDone) { 32376 options.onDone(); 32377 } 32378 32379 if (events && events.length) { 32380 // Remove the transitionend / animationend listener(s) 32381 element.off(events.join(' '), onAnimationProgress); 32382 } 32383 32384 //Cancel the fallback closing timeout and remove the timer data 32385 var animationTimerData = element.data(ANIMATE_TIMER_KEY); 32386 if (animationTimerData) { 32387 $timeout.cancel(animationTimerData[0].timer); 32388 element.removeData(ANIMATE_TIMER_KEY); 32389 } 32390 32391 // if the preparation function fails then the promise is not setup 32392 if (runner) { 32393 runner.complete(!rejected); 32394 } 32395 } 32396 32397 function applyBlocking(duration) { 32398 if (flags.blockTransition) { 32399 blockTransitions(node, duration); 32400 } 32401 32402 if (flags.blockKeyframeAnimation) { 32403 blockKeyframeAnimations(node, !!duration); 32404 } 32405 } 32406 32407 function closeAndReturnNoopAnimator() { 32408 runner = new $$AnimateRunner({ 32409 end: endFn, 32410 cancel: cancelFn 32411 }); 32412 32413 // should flush the cache animation 32414 waitUntilQuiet(noop); 32415 close(); 32416 32417 return { 32418 $$willAnimate: false, 32419 start: function() { 32420 return runner; 32421 }, 32422 end: endFn 32423 }; 32424 } 32425 32426 function onAnimationProgress(event) { 32427 event.stopPropagation(); 32428 var ev = event.originalEvent || event; 32429 32430 // we now always use `Date.now()` due to the recent changes with 32431 // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info) 32432 var timeStamp = ev.$manualTimeStamp || Date.now(); 32433 32434 /* Firefox (or possibly just Gecko) likes to not round values up 32435 * when a ms measurement is used for the animation */ 32436 var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); 32437 32438 /* $manualTimeStamp is a mocked timeStamp value which is set 32439 * within browserTrigger(). This is only here so that tests can 32440 * mock animations properly. Real events fallback to event.timeStamp, 32441 * or, if they don't, then a timeStamp is automatically created for them. 32442 * We're checking to see if the timeStamp surpasses the expected delay, 32443 * but we're using elapsedTime instead of the timeStamp on the 2nd 32444 * pre-condition since animationPauseds sometimes close off early */ 32445 if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { 32446 // we set this flag to ensure that if the transition is paused then, when resumed, 32447 // the animation will automatically close itself since transitions cannot be paused. 32448 animationCompleted = true; 32449 close(); 32450 } 32451 } 32452 32453 function start() { 32454 if (animationClosed) return; 32455 if (!node.parentNode) { 32456 close(); 32457 return; 32458 } 32459 32460 // even though we only pause keyframe animations here the pause flag 32461 // will still happen when transitions are used. Only the transition will 32462 // not be paused since that is not possible. If the animation ends when 32463 // paused then it will not complete until unpaused or cancelled. 32464 var playPause = function(playAnimation) { 32465 if (!animationCompleted) { 32466 animationPaused = !playAnimation; 32467 if (timings.animationDuration) { 32468 var value = blockKeyframeAnimations(node, animationPaused); 32469 animationPaused 32470 ? temporaryStyles.push(value) 32471 : removeFromArray(temporaryStyles, value); 32472 } 32473 } else if (animationPaused && playAnimation) { 32474 animationPaused = false; 32475 close(); 32476 } 32477 }; 32478 32479 // checking the stagger duration prevents an accidently cascade of the CSS delay style 32480 // being inherited from the parent. If the transition duration is zero then we can safely 32481 // rely that the delay value is an intential stagger delay style. 32482 var maxStagger = itemIndex > 0 32483 && ((timings.transitionDuration && stagger.transitionDuration === 0) || 32484 (timings.animationDuration && stagger.animationDuration === 0)) 32485 && Math.max(stagger.animationDelay, stagger.transitionDelay); 32486 if (maxStagger) { 32487 $timeout(triggerAnimationStart, 32488 Math.floor(maxStagger * itemIndex * ONE_SECOND), 32489 false); 32490 } else { 32491 triggerAnimationStart(); 32492 } 32493 32494 // this will decorate the existing promise runner with pause/resume methods 32495 runnerHost.resume = function() { 32496 playPause(true); 32497 }; 32498 32499 runnerHost.pause = function() { 32500 playPause(false); 32501 }; 32502 32503 function triggerAnimationStart() { 32504 // just incase a stagger animation kicks in when the animation 32505 // itself was cancelled entirely 32506 if (animationClosed) return; 32507 32508 applyBlocking(false); 32509 32510 forEach(temporaryStyles, function(entry) { 32511 var key = entry[0]; 32512 var value = entry[1]; 32513 node.style[key] = value; 32514 }); 32515 32516 applyAnimationClasses(element, options); 32517 $$jqLite.addClass(element, activeClasses); 32518 32519 if (flags.recalculateTimingStyles) { 32520 fullClassName = node.className + ' ' + preparationClasses; 32521 cacheKey = gcsHashFn(node, fullClassName); 32522 32523 timings = computeTimings(node, fullClassName, cacheKey); 32524 relativeDelay = timings.maxDelay; 32525 maxDelay = Math.max(relativeDelay, 0); 32526 maxDuration = timings.maxDuration; 32527 32528 if (maxDuration === 0) { 32529 close(); 32530 return; 32531 } 32532 32533 flags.hasTransitions = timings.transitionDuration > 0; 32534 flags.hasAnimations = timings.animationDuration > 0; 32535 } 32536 32537 if (flags.applyAnimationDelay) { 32538 relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay) 32539 ? parseFloat(options.delay) 32540 : relativeDelay; 32541 32542 maxDelay = Math.max(relativeDelay, 0); 32543 timings.animationDelay = relativeDelay; 32544 delayStyle = getCssDelayStyle(relativeDelay, true); 32545 temporaryStyles.push(delayStyle); 32546 node.style[delayStyle[0]] = delayStyle[1]; 32547 } 32548 32549 maxDelayTime = maxDelay * ONE_SECOND; 32550 maxDurationTime = maxDuration * ONE_SECOND; 32551 32552 if (options.easing) { 32553 var easeProp, easeVal = options.easing; 32554 if (flags.hasTransitions) { 32555 easeProp = TRANSITION_PROP + TIMING_KEY; 32556 temporaryStyles.push([easeProp, easeVal]); 32557 node.style[easeProp] = easeVal; 32558 } 32559 if (flags.hasAnimations) { 32560 easeProp = ANIMATION_PROP + TIMING_KEY; 32561 temporaryStyles.push([easeProp, easeVal]); 32562 node.style[easeProp] = easeVal; 32563 } 32564 } 32565 32566 if (timings.transitionDuration) { 32567 events.push(TRANSITIONEND_EVENT); 32568 } 32569 32570 if (timings.animationDuration) { 32571 events.push(ANIMATIONEND_EVENT); 32572 } 32573 32574 startTime = Date.now(); 32575 var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime; 32576 var endTime = startTime + timerTime; 32577 32578 var animationsData = element.data(ANIMATE_TIMER_KEY) || []; 32579 var setupFallbackTimer = true; 32580 if (animationsData.length) { 32581 var currentTimerData = animationsData[0]; 32582 setupFallbackTimer = endTime > currentTimerData.expectedEndTime; 32583 if (setupFallbackTimer) { 32584 $timeout.cancel(currentTimerData.timer); 32585 } else { 32586 animationsData.push(close); 32587 } 32588 } 32589 32590 if (setupFallbackTimer) { 32591 var timer = $timeout(onAnimationExpired, timerTime, false); 32592 animationsData[0] = { 32593 timer: timer, 32594 expectedEndTime: endTime 32595 }; 32596 animationsData.push(close); 32597 element.data(ANIMATE_TIMER_KEY, animationsData); 32598 } 32599 32600 if (events.length) { 32601 element.on(events.join(' '), onAnimationProgress); 32602 } 32603 32604 if (options.to) { 32605 if (options.cleanupStyles) { 32606 registerRestorableStyles(restoreStyles, node, Object.keys(options.to)); 32607 } 32608 applyAnimationToStyles(element, options); 32609 } 32610 } 32611 32612 function onAnimationExpired() { 32613 var animationsData = element.data(ANIMATE_TIMER_KEY); 32614 32615 // this will be false in the event that the element was 32616 // removed from the DOM (via a leave animation or something 32617 // similar) 32618 if (animationsData) { 32619 for (var i = 1; i < animationsData.length; i++) { 32620 animationsData[i](); 32621 } 32622 element.removeData(ANIMATE_TIMER_KEY); 32623 } 32624 } 32625 } 32626 }; 32627 }]; 32628}]; 32629 32630var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) { 32631 $$animationProvider.drivers.push('$$animateCssDriver'); 32632 32633 var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim'; 32634 var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor'; 32635 32636 var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out'; 32637 var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in'; 32638 32639 function isDocumentFragment(node) { 32640 return node.parentNode && node.parentNode.nodeType === 11; 32641 } 32642 32643 this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document', 32644 function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) { 32645 32646 // only browsers that support these properties can render animations 32647 if (!$sniffer.animations && !$sniffer.transitions) return noop; 32648 32649 var bodyNode = $document[0].body; 32650 var rootNode = getDomNode($rootElement); 32651 32652 var rootBodyElement = jqLite( 32653 // this is to avoid using something that exists outside of the body 32654 // we also special case the doc fragement case because our unit test code 32655 // appends the $rootElement to the body after the app has been bootstrapped 32656 isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode 32657 ); 32658 32659 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 32660 32661 return function initDriverFn(animationDetails) { 32662 return animationDetails.from && animationDetails.to 32663 ? prepareFromToAnchorAnimation(animationDetails.from, 32664 animationDetails.to, 32665 animationDetails.classes, 32666 animationDetails.anchors) 32667 : prepareRegularAnimation(animationDetails); 32668 }; 32669 32670 function filterCssClasses(classes) { 32671 //remove all the `ng-` stuff 32672 return classes.replace(/\bng-\S+\b/g, ''); 32673 } 32674 32675 function getUniqueValues(a, b) { 32676 if (isString(a)) a = a.split(' '); 32677 if (isString(b)) b = b.split(' '); 32678 return a.filter(function(val) { 32679 return b.indexOf(val) === -1; 32680 }).join(' '); 32681 } 32682 32683 function prepareAnchoredAnimation(classes, outAnchor, inAnchor) { 32684 var clone = jqLite(getDomNode(outAnchor).cloneNode(true)); 32685 var startingClasses = filterCssClasses(getClassVal(clone)); 32686 32687 outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); 32688 inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); 32689 32690 clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME); 32691 32692 rootBodyElement.append(clone); 32693 32694 var animatorIn, animatorOut = prepareOutAnimation(); 32695 32696 // the user may not end up using the `out` animation and 32697 // only making use of the `in` animation or vice-versa. 32698 // In either case we should allow this and not assume the 32699 // animation is over unless both animations are not used. 32700 if (!animatorOut) { 32701 animatorIn = prepareInAnimation(); 32702 if (!animatorIn) { 32703 return end(); 32704 } 32705 } 32706 32707 var startingAnimator = animatorOut || animatorIn; 32708 32709 return { 32710 start: function() { 32711 var runner; 32712 32713 var currentAnimation = startingAnimator.start(); 32714 currentAnimation.done(function() { 32715 currentAnimation = null; 32716 if (!animatorIn) { 32717 animatorIn = prepareInAnimation(); 32718 if (animatorIn) { 32719 currentAnimation = animatorIn.start(); 32720 currentAnimation.done(function() { 32721 currentAnimation = null; 32722 end(); 32723 runner.complete(); 32724 }); 32725 return currentAnimation; 32726 } 32727 } 32728 // in the event that there is no `in` animation 32729 end(); 32730 runner.complete(); 32731 }); 32732 32733 runner = new $$AnimateRunner({ 32734 end: endFn, 32735 cancel: endFn 32736 }); 32737 32738 return runner; 32739 32740 function endFn() { 32741 if (currentAnimation) { 32742 currentAnimation.end(); 32743 } 32744 } 32745 } 32746 }; 32747 32748 function calculateAnchorStyles(anchor) { 32749 var styles = {}; 32750 32751 var coords = getDomNode(anchor).getBoundingClientRect(); 32752 32753 // we iterate directly since safari messes up and doesn't return 32754 // all the keys for the coods object when iterated 32755 forEach(['width','height','top','left'], function(key) { 32756 var value = coords[key]; 32757 switch (key) { 32758 case 'top': 32759 value += bodyNode.scrollTop; 32760 break; 32761 case 'left': 32762 value += bodyNode.scrollLeft; 32763 break; 32764 } 32765 styles[key] = Math.floor(value) + 'px'; 32766 }); 32767 return styles; 32768 } 32769 32770 function prepareOutAnimation() { 32771 var animator = $animateCss(clone, { 32772 addClass: NG_OUT_ANCHOR_CLASS_NAME, 32773 delay: true, 32774 from: calculateAnchorStyles(outAnchor) 32775 }); 32776 32777 // read the comment within `prepareRegularAnimation` to understand 32778 // why this check is necessary 32779 return animator.$$willAnimate ? animator : null; 32780 } 32781 32782 function getClassVal(element) { 32783 return element.attr('class') || ''; 32784 } 32785 32786 function prepareInAnimation() { 32787 var endingClasses = filterCssClasses(getClassVal(inAnchor)); 32788 var toAdd = getUniqueValues(endingClasses, startingClasses); 32789 var toRemove = getUniqueValues(startingClasses, endingClasses); 32790 32791 var animator = $animateCss(clone, { 32792 to: calculateAnchorStyles(inAnchor), 32793 addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd, 32794 removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove, 32795 delay: true 32796 }); 32797 32798 // read the comment within `prepareRegularAnimation` to understand 32799 // why this check is necessary 32800 return animator.$$willAnimate ? animator : null; 32801 } 32802 32803 function end() { 32804 clone.remove(); 32805 outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); 32806 inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); 32807 } 32808 } 32809 32810 function prepareFromToAnchorAnimation(from, to, classes, anchors) { 32811 var fromAnimation = prepareRegularAnimation(from, noop); 32812 var toAnimation = prepareRegularAnimation(to, noop); 32813 32814 var anchorAnimations = []; 32815 forEach(anchors, function(anchor) { 32816 var outElement = anchor['out']; 32817 var inElement = anchor['in']; 32818 var animator = prepareAnchoredAnimation(classes, outElement, inElement); 32819 if (animator) { 32820 anchorAnimations.push(animator); 32821 } 32822 }); 32823 32824 // no point in doing anything when there are no elements to animate 32825 if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return; 32826 32827 return { 32828 start: function() { 32829 var animationRunners = []; 32830 32831 if (fromAnimation) { 32832 animationRunners.push(fromAnimation.start()); 32833 } 32834 32835 if (toAnimation) { 32836 animationRunners.push(toAnimation.start()); 32837 } 32838 32839 forEach(anchorAnimations, function(animation) { 32840 animationRunners.push(animation.start()); 32841 }); 32842 32843 var runner = new $$AnimateRunner({ 32844 end: endFn, 32845 cancel: endFn // CSS-driven animations cannot be cancelled, only ended 32846 }); 32847 32848 $$AnimateRunner.all(animationRunners, function(status) { 32849 runner.complete(status); 32850 }); 32851 32852 return runner; 32853 32854 function endFn() { 32855 forEach(animationRunners, function(runner) { 32856 runner.end(); 32857 }); 32858 } 32859 } 32860 }; 32861 } 32862 32863 function prepareRegularAnimation(animationDetails) { 32864 var element = animationDetails.element; 32865 var options = animationDetails.options || {}; 32866 32867 if (animationDetails.structural) { 32868 options.event = animationDetails.event; 32869 options.structural = true; 32870 options.applyClassesEarly = true; 32871 32872 // we special case the leave animation since we want to ensure that 32873 // the element is removed as soon as the animation is over. Otherwise 32874 // a flicker might appear or the element may not be removed at all 32875 if (animationDetails.event === 'leave') { 32876 options.onDone = options.domOperation; 32877 } 32878 } 32879 32880 // We assign the preparationClasses as the actual animation event since 32881 // the internals of $animateCss will just suffix the event token values 32882 // with `-active` to trigger the animation. 32883 if (options.preparationClasses) { 32884 options.event = concatWithSpace(options.event, options.preparationClasses); 32885 } 32886 32887 var animator = $animateCss(element, options); 32888 32889 // the driver lookup code inside of $$animation attempts to spawn a 32890 // driver one by one until a driver returns a.$$willAnimate animator object. 32891 // $animateCss will always return an object, however, it will pass in 32892 // a flag as a hint as to whether an animation was detected or not 32893 return animator.$$willAnimate ? animator : null; 32894 } 32895 }]; 32896}]; 32897 32898// TODO(matsko): use caching here to speed things up for detection 32899// TODO(matsko): add documentation 32900// by the time... 32901 32902var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) { 32903 this.$get = ['$injector', '$$AnimateRunner', '$$jqLite', 32904 function($injector, $$AnimateRunner, $$jqLite) { 32905 32906 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 32907 // $animateJs(element, 'enter'); 32908 return function(element, event, classes, options) { 32909 var animationClosed = false; 32910 32911 // the `classes` argument is optional and if it is not used 32912 // then the classes will be resolved from the element's className 32913 // property as well as options.addClass/options.removeClass. 32914 if (arguments.length === 3 && isObject(classes)) { 32915 options = classes; 32916 classes = null; 32917 } 32918 32919 options = prepareAnimationOptions(options); 32920 if (!classes) { 32921 classes = element.attr('class') || ''; 32922 if (options.addClass) { 32923 classes += ' ' + options.addClass; 32924 } 32925 if (options.removeClass) { 32926 classes += ' ' + options.removeClass; 32927 } 32928 } 32929 32930 var classesToAdd = options.addClass; 32931 var classesToRemove = options.removeClass; 32932 32933 // the lookupAnimations function returns a series of animation objects that are 32934 // matched up with one or more of the CSS classes. These animation objects are 32935 // defined via the module.animation factory function. If nothing is detected then 32936 // we don't return anything which then makes $animation query the next driver. 32937 var animations = lookupAnimations(classes); 32938 var before, after; 32939 if (animations.length) { 32940 var afterFn, beforeFn; 32941 if (event == 'leave') { 32942 beforeFn = 'leave'; 32943 afterFn = 'afterLeave'; // TODO(matsko): get rid of this 32944 } else { 32945 beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1); 32946 afterFn = event; 32947 } 32948 32949 if (event !== 'enter' && event !== 'move') { 32950 before = packageAnimations(element, event, options, animations, beforeFn); 32951 } 32952 after = packageAnimations(element, event, options, animations, afterFn); 32953 } 32954 32955 // no matching animations 32956 if (!before && !after) return; 32957 32958 function applyOptions() { 32959 options.domOperation(); 32960 applyAnimationClasses(element, options); 32961 } 32962 32963 function close() { 32964 animationClosed = true; 32965 applyOptions(); 32966 applyAnimationStyles(element, options); 32967 } 32968 32969 var runner; 32970 32971 return { 32972 $$willAnimate: true, 32973 end: function() { 32974 if (runner) { 32975 runner.end(); 32976 } else { 32977 close(); 32978 runner = new $$AnimateRunner(); 32979 runner.complete(true); 32980 } 32981 return runner; 32982 }, 32983 start: function() { 32984 if (runner) { 32985 return runner; 32986 } 32987 32988 runner = new $$AnimateRunner(); 32989 var closeActiveAnimations; 32990 var chain = []; 32991 32992 if (before) { 32993 chain.push(function(fn) { 32994 closeActiveAnimations = before(fn); 32995 }); 32996 } 32997 32998 if (chain.length) { 32999 chain.push(function(fn) { 33000 applyOptions(); 33001 fn(true); 33002 }); 33003 } else { 33004 applyOptions(); 33005 } 33006 33007 if (after) { 33008 chain.push(function(fn) { 33009 closeActiveAnimations = after(fn); 33010 }); 33011 } 33012 33013 runner.setHost({ 33014 end: function() { 33015 endAnimations(); 33016 }, 33017 cancel: function() { 33018 endAnimations(true); 33019 } 33020 }); 33021 33022 $$AnimateRunner.chain(chain, onComplete); 33023 return runner; 33024 33025 function onComplete(success) { 33026 close(success); 33027 runner.complete(success); 33028 } 33029 33030 function endAnimations(cancelled) { 33031 if (!animationClosed) { 33032 (closeActiveAnimations || noop)(cancelled); 33033 onComplete(cancelled); 33034 } 33035 } 33036 } 33037 }; 33038 33039 function executeAnimationFn(fn, element, event, options, onDone) { 33040 var args; 33041 switch (event) { 33042 case 'animate': 33043 args = [element, options.from, options.to, onDone]; 33044 break; 33045 33046 case 'setClass': 33047 args = [element, classesToAdd, classesToRemove, onDone]; 33048 break; 33049 33050 case 'addClass': 33051 args = [element, classesToAdd, onDone]; 33052 break; 33053 33054 case 'removeClass': 33055 args = [element, classesToRemove, onDone]; 33056 break; 33057 33058 default: 33059 args = [element, onDone]; 33060 break; 33061 } 33062 33063 args.push(options); 33064 33065 var value = fn.apply(fn, args); 33066 if (value) { 33067 if (isFunction(value.start)) { 33068 value = value.start(); 33069 } 33070 33071 if (value instanceof $$AnimateRunner) { 33072 value.done(onDone); 33073 } else if (isFunction(value)) { 33074 // optional onEnd / onCancel callback 33075 return value; 33076 } 33077 } 33078 33079 return noop; 33080 } 33081 33082 function groupEventedAnimations(element, event, options, animations, fnName) { 33083 var operations = []; 33084 forEach(animations, function(ani) { 33085 var animation = ani[fnName]; 33086 if (!animation) return; 33087 33088 // note that all of these animations will run in parallel 33089 operations.push(function() { 33090 var runner; 33091 var endProgressCb; 33092 33093 var resolved = false; 33094 var onAnimationComplete = function(rejected) { 33095 if (!resolved) { 33096 resolved = true; 33097 (endProgressCb || noop)(rejected); 33098 runner.complete(!rejected); 33099 } 33100 }; 33101 33102 runner = new $$AnimateRunner({ 33103 end: function() { 33104 onAnimationComplete(); 33105 }, 33106 cancel: function() { 33107 onAnimationComplete(true); 33108 } 33109 }); 33110 33111 endProgressCb = executeAnimationFn(animation, element, event, options, function(result) { 33112 var cancelled = result === false; 33113 onAnimationComplete(cancelled); 33114 }); 33115 33116 return runner; 33117 }); 33118 }); 33119 33120 return operations; 33121 } 33122 33123 function packageAnimations(element, event, options, animations, fnName) { 33124 var operations = groupEventedAnimations(element, event, options, animations, fnName); 33125 if (operations.length === 0) { 33126 var a,b; 33127 if (fnName === 'beforeSetClass') { 33128 a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass'); 33129 b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass'); 33130 } else if (fnName === 'setClass') { 33131 a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass'); 33132 b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass'); 33133 } 33134 33135 if (a) { 33136 operations = operations.concat(a); 33137 } 33138 if (b) { 33139 operations = operations.concat(b); 33140 } 33141 } 33142 33143 if (operations.length === 0) return; 33144 33145 // TODO(matsko): add documentation 33146 return function startAnimation(callback) { 33147 var runners = []; 33148 if (operations.length) { 33149 forEach(operations, function(animateFn) { 33150 runners.push(animateFn()); 33151 }); 33152 } 33153 33154 runners.length ? $$AnimateRunner.all(runners, callback) : callback(); 33155 33156 return function endFn(reject) { 33157 forEach(runners, function(runner) { 33158 reject ? runner.cancel() : runner.end(); 33159 }); 33160 }; 33161 }; 33162 } 33163 }; 33164 33165 function lookupAnimations(classes) { 33166 classes = isArray(classes) ? classes : classes.split(' '); 33167 var matches = [], flagMap = {}; 33168 for (var i=0; i < classes.length; i++) { 33169 var klass = classes[i], 33170 animationFactory = $animateProvider.$$registeredAnimations[klass]; 33171 if (animationFactory && !flagMap[klass]) { 33172 matches.push($injector.get(animationFactory)); 33173 flagMap[klass] = true; 33174 } 33175 } 33176 return matches; 33177 } 33178 }]; 33179}]; 33180 33181var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) { 33182 $$animationProvider.drivers.push('$$animateJsDriver'); 33183 this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) { 33184 return function initDriverFn(animationDetails) { 33185 if (animationDetails.from && animationDetails.to) { 33186 var fromAnimation = prepareAnimation(animationDetails.from); 33187 var toAnimation = prepareAnimation(animationDetails.to); 33188 if (!fromAnimation && !toAnimation) return; 33189 33190 return { 33191 start: function() { 33192 var animationRunners = []; 33193 33194 if (fromAnimation) { 33195 animationRunners.push(fromAnimation.start()); 33196 } 33197 33198 if (toAnimation) { 33199 animationRunners.push(toAnimation.start()); 33200 } 33201 33202 $$AnimateRunner.all(animationRunners, done); 33203 33204 var runner = new $$AnimateRunner({ 33205 end: endFnFactory(), 33206 cancel: endFnFactory() 33207 }); 33208 33209 return runner; 33210 33211 function endFnFactory() { 33212 return function() { 33213 forEach(animationRunners, function(runner) { 33214 // at this point we cannot cancel animations for groups just yet. 1.5+ 33215 runner.end(); 33216 }); 33217 }; 33218 } 33219 33220 function done(status) { 33221 runner.complete(status); 33222 } 33223 } 33224 }; 33225 } else { 33226 return prepareAnimation(animationDetails); 33227 } 33228 }; 33229 33230 function prepareAnimation(animationDetails) { 33231 // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations 33232 var element = animationDetails.element; 33233 var event = animationDetails.event; 33234 var options = animationDetails.options; 33235 var classes = animationDetails.classes; 33236 return $$animateJs(element, event, classes, options); 33237 } 33238 }]; 33239}]; 33240 33241var NG_ANIMATE_ATTR_NAME = 'data-ng-animate'; 33242var NG_ANIMATE_PIN_DATA = '$ngAnimatePin'; 33243var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { 33244 var PRE_DIGEST_STATE = 1; 33245 var RUNNING_STATE = 2; 33246 var ONE_SPACE = ' '; 33247 33248 var rules = this.rules = { 33249 skip: [], 33250 cancel: [], 33251 join: [] 33252 }; 33253 33254 function makeTruthyCssClassMap(classString) { 33255 if (!classString) { 33256 return null; 33257 } 33258 33259 var keys = classString.split(ONE_SPACE); 33260 var map = Object.create(null); 33261 33262 forEach(keys, function(key) { 33263 map[key] = true; 33264 }); 33265 return map; 33266 } 33267 33268 function hasMatchingClasses(newClassString, currentClassString) { 33269 if (newClassString && currentClassString) { 33270 var currentClassMap = makeTruthyCssClassMap(currentClassString); 33271 return newClassString.split(ONE_SPACE).some(function(className) { 33272 return currentClassMap[className]; 33273 }); 33274 } 33275 } 33276 33277 function isAllowed(ruleType, element, currentAnimation, previousAnimation) { 33278 return rules[ruleType].some(function(fn) { 33279 return fn(element, currentAnimation, previousAnimation); 33280 }); 33281 } 33282 33283 function hasAnimationClasses(animation, and) { 33284 var a = (animation.addClass || '').length > 0; 33285 var b = (animation.removeClass || '').length > 0; 33286 return and ? a && b : a || b; 33287 } 33288 33289 rules.join.push(function(element, newAnimation, currentAnimation) { 33290 // if the new animation is class-based then we can just tack that on
33291 return !newAnimation.structural && hasAnimationClasses(newAnimation); 33292 }); 33293 33294 rules.skip.push(function(element, newAnimation, currentAnimation) { 33295 // there is no need to animate anything if no classes are being added and 33296 // there is no structural animation that will be triggered 33297 return !newAnimation.structural && !hasAnimationClasses(newAnimation); 33298 }); 33299 33300 rules.skip.push(function(element, newAnimation, currentAnimation) { 33301 // why should we trigger a new structural animation if the element will 33302 // be removed from the DOM anyway? 33303 return currentAnimation.event == 'leave' && newAnimation.structural; 33304 }); 33305 33306 rules.skip.push(function(element, newAnimation, currentAnimation) { 33307 // if there is an ongoing current animation then don't even bother running the class-based animation 33308 return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural; 33309 }); 33310 33311 rules.cancel.push(function(element, newAnimation, currentAnimation) { 33312 // there can never be two structural animations running at the same time 33313 return currentAnimation.structural && newAnimation.structural; 33314 }); 33315 33316 rules.cancel.push(function(element, newAnimation, currentAnimation) { 33317 // if the previous animation is already running, but the new animation will 33318 // be triggered, but the new animation is structural 33319 return currentAnimation.state === RUNNING_STATE && newAnimation.structural; 33320 }); 33321 33322 rules.cancel.push(function(element, newAnimation, currentAnimation) { 33323 var nA = newAnimation.addClass; 33324 var nR = newAnimation.removeClass; 33325 var cA = currentAnimation.addClass; 33326 var cR = currentAnimation.removeClass; 33327 33328 // early detection to save the global CPU shortage :) 33329 if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) { 33330 return false; 33331 } 33332 33333 return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA); 33334 }); 33335 33336 this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', 33337 '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow', 33338 function($$rAF, $rootScope, $rootElement, $document, $$HashMap, 33339 $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) { 33340 33341 var activeAnimationsLookup = new $$HashMap(); 33342 var disabledElementsLookup = new $$HashMap(); 33343 var animationsEnabled = null; 33344 // $document might be mocked out in tests and won't include a real document. 33345 // Providing an empty object with hidden = true will prevent animations from running 33346 var rawDocument = $document[0] || {hidden: true}; 33347 33348 function postDigestTaskFactory() { 33349 var postDigestCalled = false; 33350 return function(fn) { 33351 // we only issue a call to postDigest before 33352 // it has first passed. This prevents any callbacks 33353 // from not firing once the animation has completed 33354 // since it will be out of the digest cycle. 33355 if (postDigestCalled) { 33356 fn(); 33357 } else { 33358 $rootScope.$$postDigest(function() { 33359 postDigestCalled = true; 33360 fn(); 33361 }); 33362 } 33363 }; 33364 } 33365 33366 // Wait until all directive and route-related templates are downloaded and 33367 // compiled. The $templateRequest.totalPendingRequests variable keeps track of 33368 // all of the remote templates being currently downloaded. If there are no 33369 // templates currently downloading then the watcher will still fire anyway. 33370 var deregisterWatch = $rootScope.$watch( 33371 function() { return $templateRequest.totalPendingRequests === 0; }, 33372 function(isEmpty) { 33373 if (!isEmpty) return; 33374 deregisterWatch(); 33375 33376 // Now that all templates have been downloaded, $animate will wait until 33377 // the post digest queue is empty before enabling animations. By having two 33378 // calls to $postDigest calls we can ensure that the flag is enabled at the 33379 // very end of the post digest queue. Since all of the animations in $animate 33380 // use $postDigest, it's important that the code below executes at the end. 33381 // This basically means that the page is fully downloaded and compiled before 33382 // any animations are triggered. 33383 $rootScope.$$postDigest(function() { 33384 $rootScope.$$postDigest(function() { 33385 // we check for null directly in the event that the application already called 33386 // .enabled() with whatever arguments that it provided it with 33387 if (animationsEnabled === null) { 33388 animationsEnabled = true; 33389 } 33390 }); 33391 }); 33392 } 33393 ); 33394 33395 var callbackRegistry = {}; 33396 33397 // remember that the classNameFilter is set during the provider/config 33398 // stage therefore we can optimize here and setup a helper function 33399 var classNameFilter = $animateProvider.classNameFilter(); 33400 var isAnimatableClassName = !classNameFilter 33401 ? function() { return true; } 33402 : function(className) { 33403 return classNameFilter.test(className); 33404 }; 33405 33406 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 33407
33408 function normalizeAnimationDetails(element, animation) { 33409 return mergeAnimationDetails(element, animation, {}); 33410 } 33411 33412 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 33413 var contains = Node.prototype.contains || function(arg) { 33414 // jshint bitwise: false 33415 return this === arg || !!(this.compareDocumentPosition(arg) & 16); 33416 // jshint bitwise: true 33417 }; 33418 33419 function findCallbacks(parent, element, event) { 33420 var targetNode = getDomNode(element); 33421 var targetParentNode = getDomNode(parent); 33422 33423 var matches = []; 33424 var entries = callbackRegistry[event]; 33425 if (entries) { 33426 forEach(entries, function(entry) { 33427 if (contains.call(entry.node, targetNode)) { 33428 matches.push(entry.callback); 33429 } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) { 33430 matches.push(entry.callback); 33431 } 33432 }); 33433 } 33434 33435 return matches; 33436 } 33437 33438 return { 33439 on: function(event, container, callback) { 33440 var node = extractElementNode(container); 33441 callbackRegistry[event] = callbackRegistry[event] || []; 33442 callbackRegistry[event].push({ 33443 node: node, 33444 callback: callback 33445 }); 33446 }, 33447 33448 off: function(event, container, callback) { 33449 var entries = callbackRegistry[event]; 33450 if (!entries) return; 33451 33452 callbackRegistry[event] = arguments.length === 1 33453 ? null 33454 : filterFromRegistry(entries, container, callback); 33455 33456 function filterFromRegistry(list, matchContainer, matchCallback) { 33457 var containerNode = extractElementNode(matchContainer); 33458 return list.filter(function(entry) { 33459 var isMatch = entry.node === containerNode && 33460 (!matchCallback || entry.callback === matchCallback); 33461 return !isMatch; 33462 }); 33463 } 33464 }, 33465 33466 pin: function(element, parentElement) { 33467 assertArg(isElement(element), 'element', 'not an element'); 33468 assertArg(isElement(parentElement), 'parentElement', 'not an element'); 33469 element.data(NG_ANIMATE_PIN_DATA, parentElement); 33470 }, 33471 33472 push: function(element, event, options, domOperation) { 33473 options = options || {}; 33474 options.domOperation = domOperation; 33475 return queueAnimation(element, event, options); 33476 }, 33477 33478 // this method has four signatures: 33479 // () - global getter 33480 // (bool) - global setter 33481 // (element) - element getter 33482 // (element, bool) - element setter<F37> 33483 enabled: function(element, bool) { 33484 var argCount = arguments.length; 33485 33486 if (argCount === 0) { 33487 // () - Global getter 33488 bool = !!animationsEnabled; 33489 } else { 33490 var hasElement = isElement(element); 33491 33492 if (!hasElement) { 33493 // (bool) - Global setter 33494 bool = animationsEnabled = !!element; 33495 } else { 33496 var node = getDomNode(element); 33497 var recordExists = disabledElementsLookup.get(node); 33498 33499 if (argCount === 1) { 33500 // (element) - Element getter 33501 bool = !recordExists; 33502 } else { 33503 // (element, bool) - Element setter 33504 disabledElementsLookup.put(node, !bool); 33505 } 33506 } 33507 } 33508 33509 return bool; 33510 } 33511 }; 33512 33513 function queueAnimation(element, event, initialOptions) { 33514 // we always make a copy of the options since 33515 // there should never be any side effects on 33516 // the input data when running `$animateCss`. 33517 var options = copy(initialOptions); 33518 33519 var node, parent; 33520 element = stripCommentsFromElement(element); 33521 if (element) { 33522 node = getDomNode(element); 33523 parent = element.parent(); 33524 } 33525 33526 options = prepareAnimationOptions(options); 33527 33528 // we create a fake runner with a working promise. 33529 // These methods will become available after the digest has passed 33530 var runner = new $$AnimateRunner(); 33531 33532 // this is used to trigger callbacks in postDigest mode 33533 var runInNextPostDigestOrNow = postDigestTaskFactory(); 33534 33535 if (isArray(options.addClass)) { 33536 options.addClass = options.addClass.join(' '); 33537 } 33538 33539 if (options.addClass && !isString(options.addClass)) { 33540 options.addClass = null; 33541 } 33542 33543 if (isArray(options.removeClass)) { 33544 options.removeClass = options.removeClass.join(' '); 33545 } 33546 33547 if (options.removeClass && !isString(options.removeClass)) { 33548 options.removeClass = null; 33549 } 33550 33551 if (options.from && !isObject(options.from)) { 33552 options.from = null; 33553 } 33554 33555 if (options.to && !isObject(options.to)) { 33556 options.to = null; 33557 } 33558 33559 // there are situations where a directive issues an animation for
33560 // a jqLite wrapper that contains only comment nodes... If this 33561 // happens then there is no way we can perform an animation 33562 if (!node) { 33563 close(); 33564 return runner; 33565 } 33566 33567 var className = [node.className, options.addClass, options.removeClass].join(' '); 33568 if (!isAnimatableClassName(className)) { 33569 close(); 33570 return runner; 33571 } 33572 33573 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; 33574 33575 var documentHidden = rawDocument.hidden; 33576 33577 // this is a hard disable of all animations for the application or on 33578 // the element itself, therefore there is no need to continue further 33579 // past this point if not enabled 33580 // Animations are also disabled if the document is currently hidden (page is not visible 33581 // to the user), because browsers slow down or do not flush calls to requestAnimationFrame 33582 var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node); 33583 var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {}; 33584 var hasExistingAnimation = !!existingAnimation.state; 33585 33586 // there is no point in traversing the same collection of parent ancestors if a followup 33587 // animation will be run on the same element that already did all that checking work 33588 if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) { 33589 skipAnimations = !areAnimationsAllowed(element, parent, event); 33590 } 33591 33592 if (skipAnimations) { 33593 // Callbacks should fire even if the document is hidden (regression fix for issue #14120) 33594 if (documentHidden) notifyProgress(runner, event, 'start'); 33595 close(); 33596 if (documentHidden) notifyProgress(runner, event, 'close'); 33597 return runner; 33598 } 33599 33600 if (isStructural) { 33601 closeChildAnimations(element); 33602 } 33603 33604 var newAnimation = { 33605 structural: isStructural, 33606 element: element, 33607 event: event, 33608 addClass: options.addClass, 33609 removeClass: options.removeClass, 33610 close: close, 33611 options: options, 33612 runner: runner 33613 }; 33614 33615 if (hasExistingAnimation) { 33616 var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation); 33617 if (skipAnimationFlag) { 33618 if (existingAnimation.state === RUNNING_STATE) { 33619 close(); 33620 return runner; 33621 } else { 33622 mergeAnimationDetails(element, existingAnimation, newAnimation); 33623 return existingAnimation.runner; 33624 } 33625 } 33626 var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation); 33627 if (cancelAnimationFlag) { 33628 if (existingAnimation.state === RUNNING_STATE) { 33629 // this will end the animation right away and it is safe 33630 // to do so since the animation is already running and the 33631 // runner callback code will run in async 33632 existingAnimation.runner.end(); 33633 } else if (existingAnimation.structural) { 33634 // this means that the animation is queued into a digest, but 33635 // hasn't started yet. Therefore it is safe to run the close 33636 // method which will call the runner methods in async. 33637 existingAnimation.close(); 33638 } else { 33639 // this will merge the new animation options into existing animation options 33640 mergeAnimationDetails(element, existingAnimation, newAnimation); 33641 33642 return existingAnimation.runner; 33643 } 33644 } else { 33645 // a joined animation means that this animation will take over the existing one 33646 // so an example would involve a leave animation taking over an enter. Then when 33647 // the postDigest kicks in the enter will be ignored. 33648 var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation); 33649 if (joinAnimationFlag) { 33650 if (existingAnimation.state === RUNNING_STATE) { 33651 normalizeAnimationDetails(element, newAnimation); 33652 } else {
vendor: 4,089 bytes, lines 33653-33738
33653 applyGeneratedPreparationClasses(element, isStructural ? event : null, options); 33654 33655 event = newAnimation.event = existingAnimation.event; 33656 options = mergeAnimationDetails(element, existingAnimation, newAnimation); 33657 33658 //we return the same runner since only the option values of this animation will 33659 //be fed into the `existingAnimation`. 33660 return existingAnimation.runner; 33661 } 33662 } 33663 } 33664 } else { 33665 // normalization in this case means that it removes redundant CSS classes that 33666 // already exist (addClass) or do not exist (removeClass) on the element 33667 normalizeAnimationDetails(element, newAnimation); 33668 } 33669 33670 // when the options are merged and cleaned up we may end up not having to do 33671 // an animation at all, therefore we should check this before issuing a post 33672 // digest callback. Structural animations will always run no matter what. 33673 var isValidAnimation = newAnimation.structural; 33674 if (!isValidAnimation) { 33675 // animate (from/to) can be quickly checked first, otherwise we check if any classes are present 33676 isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0) 33677 || hasAnimationClasses(newAnimation); 33678 } 33679 33680 if (!isValidAnimation) { 33681 close(); 33682 clearElementAnimationState(element); 33683 return runner; 33684 } 33685 33686 // the counter keeps track of cancelled animations 33687 var counter = (existingAnimation.counter || 0) + 1; 33688 newAnimation.counter = counter; 33689 33690 markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation); 33691 33692 $rootScope.$$postDigest(function() { 33693 var animationDetails = activeAnimationsLookup.get(node); 33694 var animationCancelled = !animationDetails; 33695 animationDetails = animationDetails || {}; 33696 33697 // if addClass/removeClass is called before something like enter then the 33698 // registered parent element may not be present. The code below will ensure 33699 // that a final value for parent element is obtained 33700 var parentElement = element.parent() || []; 33701 33702 // animate/structural/class-based animations all have requirements. Otherwise there 33703 // is no point in performing an animation. The parent node must also be set. 33704 var isValidAnimation = parentElement.length > 0 33705 && (animationDetails.event === 'animate' 33706 || animationDetails.structural 33707 || hasAnimationClasses(animationDetails)); 33708 33709 // this means that the previous animation was cancelled 33710 // even if the follow-up animation is the same event 33711 if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) { 33712 // if another animation did not take over then we need 33713 // to make sure that the domOperation and options are 33714 // handled accordingly 33715 if (animationCancelled) { 33716 applyAnimationClasses(element, options); 33717 applyAnimationStyles(element, options); 33718 } 33719 33720 // if the event changed from something like enter to leave then we do 33721 // it, otherwise if it's the same then the end result will be the same too 33722 if (animationCancelled || (isStructural && animationDetails.event !== event)) { 33723 options.domOperation(); 33724 runner.end(); 33725 } 33726 33727 // in the event that the element animation was not cancelled or a follow-up animation 33728 // isn't allowed to animate from here then we need to clear the state of the element 33729 // so that any future animations won't read the expired animation data. 33730 if (!isValidAnimation) { 33731 clearElementAnimationState(element); 33732 } 33733 33734 return; 33735 } 33736 33737 // this combined multiple class to addClass / removeClass into a setClass event 33738 // so long as a structural event did not take over the animation
33739 event = !animationDetails.structural && hasAnimationClasses(animationDetails, true) 33740 ? 'setClass' 33741 : animationDetails.event; 33742 33743 markElementAnimationState(element, RUNNING_STATE); 33744 var realRunner = $$animation(element, event, animationDetails.options); 33745 33746 // this will update the runner's flow-control events based on 33747 // the `realRunner` object. 33748 runner.setHost(realRunner); 33749 notifyProgress(runner, event, 'start', {}); 33750 33751 realRunner.done(function(status) { 33752 close(!status); 33753 var animationDetails = activeAnimationsLookup.get(node); 33754 if (animationDetails && animationDetails.counter === counter) { 33755 clearElementAnimationState(getDomNode(element)); 33756 } 33757 notifyProgress(runner, event, 'close', {}); 33758 }); 33759 }); 33760 33761 return runner; 33762 33763 function notifyProgress(runner, event, phase, data) { 33764 runInNextPostDigestOrNow(function() { 33765 var callbacks = findCallbacks(parent, element, event); 33766 if (callbacks.length) { 33767 // do not optimize this call here to RAF because 33768 // we don't know how heavy the callback code here will 33769 // be and if this code is buffered then this can 33770 // lead to a performance regression. 33771 $$rAF(function() { 33772 forEach(callbacks, function(callback) { 33773 callback(element, phase, data); 33774 }); 33775 }); 33776 } 33777 }); 33778 runner.progress(event, phase, data); 33779 } 33780 33781 function close(reject) { // jshint ignore:line 33782 clearGeneratedClasses(element, options); 33783 applyAnimationClasses(element, options); 33784 applyAnimationStyles(element, options); 33785 options.domOperation(); 33786 runner.complete(!reject); 33787 } 33788 } 33789 33790 function closeChildAnimations(element) { 33791 var node = getDomNode(element); 33792 var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']'); 33793 forEach(children, function(child) { 33794 var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME)); 33795 var animationDetails = activeAnimationsLookup.get(child); 33796 if (animationDetails) { 33797 switch (state) { 33798 case RUNNING_STATE: 33799 animationDetails.runner.end(); 33800 /* falls through */ 33801 case PRE_DIGEST_STATE: 33802 activeAnimationsLookup.remove(child); 33803 break; 33804 } 33805 } 33806 }); 33807 } 33808 33809 function clearElementAnimationState(element) { 33810 var node = getDomNode(element); 33811 node.removeAttribute(NG_ANIMATE_ATTR_NAME); 33812 activeAnimationsLookup.remove(node); 33813 } 33814 33815 function isMatchingElement(nodeOrElmA, nodeOrElmB) { 33816 return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB); 33817 } 33818 33819 /** 33820 * This fn returns false if any of the following is true: 33821 * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed 33822 * b) a parent element has an ongoing structural animation, and animateChildren is false 33823 * c) the element is not a child of the body 33824 * d) the element is not a child of the $rootElement 33825 */ 33826 function areAnimationsAllowed(element, parentElement, event) { 33827 var bodyElement = jqLite(rawDocument.body); 33828 var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML'; 33829 var rootElementDetected = isMatchingElement(element, $rootElement); 33830 var parentAnimationDetected = false; 33831 var animateChildren; 33832 var elementDisabled = disabledElementsLookup.get(getDomNode(element)); 33833 33834 var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA); 33835 if (parentHost) { 33836 parentElement = parentHost; 33837 } 33838 33839 parentElement = getDomNode(parentElement); 33840 33841 while (parentElement) { 33842 if (!rootElementDetected) { 33843 // angular doesn't want to attempt to animate elements outside of the application 33844 // therefore we need to ensure that the rootElement is an ancestor of the current element 33845 rootElementDetected = isMatchingElement(parentElement, $rootElement); 33846 } 33847 33848 if (parentElement.nodeType !== ELEMENT_NODE) { 33849 // no point in inspecting the #document element 33850 break; 33851 } 33852 33853 var details = activeAnimationsLookup.get(parentElement) || {}; 33854 // either an enter, leave or move animation will commence 33855 // therefore we can't allow any animations to take place 33856 // but if a parent animation is class-based then that's ok 33857 if (!parentAnimationDetected) { 33858 var parentElementDisabled = disabledElementsLookup.get(parentElement); 33859 33860 if (parentElementDisabled === true && elementDisabled !== false) { 33861 // disable animations if the user hasn't explicitly enabled animations on the 33862 // current element 33863 elementDisabled = true; 33864 // element is disabled via parent element, no need to check anything else 33865 break; 33866 } else if (parentElementDisabled === false) { 33867 elementDisabled = false;
vendor: 4,740 bytes, lines 33868-34018
33868 } 33869 parentAnimationDetected = details.structural; 33870 } 33871 33872 if (isUndefined(animateChildren) || animateChildren === true) { 33873 var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA); 33874 if (isDefined(value)) { 33875 animateChildren = value; 33876 } 33877 } 33878 33879 // there is no need to continue traversing at this point 33880 if (parentAnimationDetected && animateChildren === false) break; 33881 33882 if (!bodyElementDetected) { 33883 // we also need to ensure that the element is or will be a part of the body element 33884 // otherwise it is pointless to even issue an animation to be rendered 33885 bodyElementDetected = isMatchingElement(parentElement, bodyElement); 33886 } 33887 33888 if (bodyElementDetected && rootElementDetected) { 33889 // If both body and root have been found, any other checks are pointless, 33890 // as no animation data should live outside the application 33891 break; 33892 } 33893 33894 if (!rootElementDetected) { 33895 // If no rootElement is detected, check if the parentElement is pinned to another element 33896 parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA); 33897 if (parentHost) { 33898 // The pin target element becomes the next parent element 33899 parentElement = getDomNode(parentHost); 33900 continue; 33901 } 33902 } 33903 33904 parentElement = parentElement.parentNode; 33905 } 33906 33907 var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true; 33908 return allowAnimation && rootElementDetected && bodyElementDetected; 33909 } 33910 33911 function markElementAnimationState(element, state, details) { 33912 details = details || {}; 33913 details.state = state; 33914 33915 var node = getDomNode(element); 33916 node.setAttribute(NG_ANIMATE_ATTR_NAME, state); 33917 33918 var oldValue = activeAnimationsLookup.get(node); 33919 var newValue = oldValue 33920 ? extend(oldValue, details) 33921 : details; 33922 activeAnimationsLookup.put(node, newValue); 33923 } 33924 }]; 33925}]; 33926 33927var $$AnimationProvider = ['$animateProvider', function($animateProvider) { 33928 var NG_ANIMATE_REF_ATTR = 'ng-animate-ref'; 33929 33930 var drivers = this.drivers = []; 33931 33932 var RUNNER_STORAGE_KEY = '$$animationRunner'; 33933 33934 function setRunner(element, runner) { 33935 element.data(RUNNER_STORAGE_KEY, runner); 33936 } 33937 33938 function removeRunner(element) { 33939 element.removeData(RUNNER_STORAGE_KEY); 33940 } 33941 33942 function getRunner(element) { 33943 return element.data(RUNNER_STORAGE_KEY); 33944 } 33945 33946 this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', 33947 function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) { 33948 33949 var animationQueue = []; 33950 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 33951 33952 function sortAnimations(animations) { 33953 var tree = { children: [] }; 33954 var i, lookup = new $$HashMap(); 33955 33956 // this is done first beforehand so that the hashmap 33957 // is filled with a list of the elements that will be animated 33958 for (i = 0; i < animations.length; i++) { 33959 var animation = animations[i]; 33960 lookup.put(animation.domNode, animations[i] = { 33961 domNode: animation.domNode, 33962 fn: animation.fn, 33963 children: [] 33964 }); 33965 } 33966 33967 for (i = 0; i < animations.length; i++) { 33968 processNode(animations[i]); 33969 } 33970 33971 return flatten(tree); 33972 33973 function processNode(entry) { 33974 if (entry.processed) return entry; 33975 entry.processed = true; 33976 33977 var elementNode = entry.domNode; 33978 var parentNode = elementNode.parentNode; 33979 lookup.put(elementNode, entry); 33980 33981 var parentEntry; 33982 while (parentNode) { 33983 parentEntry = lookup.get(parentNode); 33984 if (parentEntry) { 33985 if (!parentEntry.processed) { 33986 parentEntry = processNode(parentEntry); 33987 } 33988 break; 33989 } 33990 parentNode = parentNode.parentNode; 33991 } 33992 33993 (parentEntry || tree).children.push(entry); 33994 return entry; 33995 } 33996 33997 function flatten(tree) { 33998 var result = []; 33999 var queue = []; 34000 var i; 34001 34002 for (i = 0; i < tree.children.length; i++) { 34003 queue.push(tree.children[i]); 34004 } 34005 34006 var remainingLevelEntries = queue.length; 34007 var nextLevelEntries = 0; 34008 var row = []; 34009 34010 for (i = 0; i < queue.length; i++) { 34011 var entry = queue[i]; 34012 if (remainingLevelEntries <= 0) { 34013 remainingLevelEntries = nextLevelEntries; 34014 nextLevelEntries = 0; 34015 result.push(row); 34016 row = []; 34017 } 34018 row.push(entry.fn);
34019 entry.children.forEach(function(childEntry) { 34020 nextLevelEntries++; 34021 queue.push(childEntry); 34022 }); 34023 remainingLevelEntries--; 34024 } 34025 34026 if (row.length) { 34027 result.push(row); 34028 } 34029 34030 return result; 34031 } 34032 } 34033 34034 // TODO(matsko): document the signature in a better way 34035 return function(element, event, options) { 34036 options = prepareAnimationOptions(options); 34037 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; 34038 34039 // there is no animation at the current moment, however 34040 // these runner methods will get later updated with the 34041 // methods leading into the driver's end/cancel methods 34042 // for now they just stop the animation from starting 34043 var runner = new $$AnimateRunner({ 34044 end: function() { close(); }, 34045 cancel: function() { close(true); } 34046 }); 34047 34048 if (!drivers.length) { 34049 close(); 34050 return runner; 34051 } 34052 34053 setRunner(element, runner); 34054 34055 var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass)); 34056 var tempClasses = options.tempClasses; 34057 if (tempClasses) { 34058 classes += ' ' + tempClasses; 34059 options.tempClasses = null; 34060 } 34061 34062 var prepareClassName; 34063 if (isStructural) { 34064 prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; 34065 $$jqLite.addClass(element, prepareClassName); 34066 } 34067 34068 animationQueue.push({ 34069 // this data is used by the postDigest code and passed into 34070 // the driver step function 34071 element: element, 34072 classes: classes, 34073 event: event, 34074 structural: isStructural, 34075 options: options, 34076 beforeStart: beforeStart, 34077 close: close 34078 }); 34079 34080 element.on('$destroy', handleDestroyedElement); 34081 34082 // we only want there to be one function called within the post digest 34083 // block. This way we can group animations for all the animations that 34084 // were apart of the same postDigest flush call. 34085 if (animationQueue.length > 1) return runner; 34086 34087 $rootScope.$$postDigest(function() { 34088 var animations = []; 34089 forEach(animationQueue, function(entry) { 34090 // the element was destroyed early on which removed the runner 34091 // form its storage. This means we can't animate this element 34092 // at all and it already has been closed due to destruction. 34093 if (getRunner(entry.element)) { 34094 animations.push(entry); 34095 } else { 34096 entry.close(); 34097 } 34098 }); 34099 34100 // now any future animations will be in another postDigest 34101 animationQueue.length = 0; 34102 34103 var groupedAnimations = groupAnimations(animations); 34104 var toBeSortedAnimations = []; 34105 34106 forEach(groupedAnimations, function(animationEntry) { 34107 toBeSortedAnimations.push({ 34108 domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element), 34109 fn: function triggerAnimationStart() { 34110 // it's important that we apply the `ng-animate` CSS class and the 34111 // temporary classes before we do any driver invoking since these 34112 // CSS classes may be required for proper CSS detection. 34113 animationEntry.beforeStart(); 34114 34115 var startAnimationFn, closeFn = animationEntry.close; 34116 34117 // in the event that the element was removed before the digest runs or 34118 // during the RAF sequencing then we should not trigger the animation. 34119 var targetElement = animationEntry.anchors 34120 ? (animationEntry.from.element || animationEntry.to.element) 34121 : animationEntry.element; 34122 34123 if (getRunner(targetElement)) { 34124 var operation = invokeFirstDriver(animationEntry); 34125 if (operation) { 34126 startAnimationFn = operation.start; 34127 } 34128 } 34129 34130 if (!startAnimationFn) { 34131 closeFn(); 34132 } else { 34133 var animationRunner = startAnimationFn(); 34134 animationRunner.done(function(status) { 34135 closeFn(!status); 34136 }); 34137 updateAnimationRunners(animationEntry, animationRunner); 34138 } 34139 } 34140 }); 34141 }); 34142 34143 // we need to sort each of the animations in order of parent to child 34144 // relationships. This ensures that the child classes are applied at the 34145 // right time. 34146 $$rAFScheduler(sortAnimations(toBeSortedAnimations)); 34147 }); 34148 34149 return runner; 34150 34151 // TODO(matsko): change to reference nodes 34152 function getAnchorNodes(node) { 34153 var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']'; 34154 var items = node.hasAttribute(NG_ANIMATE_REF_ATTR) 34155 ? [node] 34156 : node.querySelectorAll(SELECTOR); 34157 var anchors = [];
vendor: 4,203 bytes, lines 34158-34276
34158 forEach(items, function(node) { 34159 var attr = node.getAttribute(NG_ANIMATE_REF_ATTR); 34160 if (attr && attr.length) { 34161 anchors.push(node); 34162 } 34163 }); 34164 return anchors; 34165 } 34166 34167 function groupAnimations(animations) { 34168 var preparedAnimations = []; 34169 var refLookup = {}; 34170 forEach(animations, function(animation, index) { 34171 var element = animation.element; 34172 var node = getDomNode(element); 34173 var event = animation.event; 34174 var enterOrMove = ['enter', 'move'].indexOf(event) >= 0; 34175 var anchorNodes = animation.structural ? getAnchorNodes(node) : []; 34176 34177 if (anchorNodes.length) { 34178 var direction = enterOrMove ? 'to' : 'from'; 34179 34180 forEach(anchorNodes, function(anchor) { 34181 var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR); 34182 refLookup[key] = refLookup[key] || {}; 34183 refLookup[key][direction] = { 34184 animationID: index, 34185 element: jqLite(anchor) 34186 }; 34187 }); 34188 } else { 34189 preparedAnimations.push(animation); 34190 } 34191 }); 34192 34193 var usedIndicesLookup = {}; 34194 var anchorGroups = {}; 34195 forEach(refLookup, function(operations, key) { 34196 var from = operations.from; 34197 var to = operations.to; 34198 34199 if (!from || !to) { 34200 // only one of these is set therefore we can't have an 34201 // anchor animation since all three pieces are required 34202 var index = from ? from.animationID : to.animationID; 34203 var indexKey = index.toString(); 34204 if (!usedIndicesLookup[indexKey]) { 34205 usedIndicesLookup[indexKey] = true; 34206 preparedAnimations.push(animations[index]); 34207 } 34208 return; 34209 } 34210 34211 var fromAnimation = animations[from.animationID]; 34212 var toAnimation = animations[to.animationID]; 34213 var lookupKey = from.animationID.toString(); 34214 if (!anchorGroups[lookupKey]) { 34215 var group = anchorGroups[lookupKey] = { 34216 structural: true, 34217 beforeStart: function() { 34218 fromAnimation.beforeStart(); 34219 toAnimation.beforeStart(); 34220 }, 34221 close: function() { 34222 fromAnimation.close(); 34223 toAnimation.close(); 34224 }, 34225 classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes), 34226 from: fromAnimation, 34227 to: toAnimation, 34228 anchors: [] // TODO(matsko): change to reference nodes 34229 }; 34230 34231 // the anchor animations require that the from and to elements both have at least 34232 // one shared CSS class which effictively marries the two elements together to use 34233 // the same animation driver and to properly sequence the anchor animation. 34234 if (group.classes.length) { 34235 preparedAnimations.push(group); 34236 } else { 34237 preparedAnimations.push(fromAnimation); 34238 preparedAnimations.push(toAnimation); 34239 } 34240 } 34241 34242 anchorGroups[lookupKey].anchors.push({ 34243 'out': from.element, 'in': to.element 34244 }); 34245 }); 34246 34247 return preparedAnimations; 34248 } 34249 34250 function cssClassesIntersection(a,b) { 34251 a = a.split(' '); 34252 b = b.split(' '); 34253 var matches = []; 34254 34255 for (var i = 0; i < a.length; i++) { 34256 var aa = a[i]; 34257 if (aa.substring(0,3) === 'ng-') continue; 34258 34259 for (var j = 0; j < b.length; j++) { 34260 if (aa === b[j]) { 34261 matches.push(aa); 34262 break; 34263 } 34264 } 34265 } 34266 34267 return matches.join(' '); 34268 } 34269 34270 function invokeFirstDriver(animationDetails) { 34271 // we loop in reverse order since the more general drivers (like CSS and JS) 34272 // may attempt more elements, but custom drivers are more particular 34273 for (var i = drivers.length - 1; i >= 0; i--) { 34274 var driverName = drivers[i]; 34275 if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check 34276
34277 var factory = $injector.get(driverName); 34278 var driver = factory(animationDetails); 34279 if (driver) { 34280 return driver; 34281 } 34282 } 34283 } 34284 34285 function beforeStart() { 34286 element.addClass(NG_ANIMATE_CLASSNAME); 34287 if (tempClasses) { 34288 $$jqLite.addClass(element, tempClasses); 34289 } 34290 if (prepareClassName) { 34291 $$jqLite.removeClass(element, prepareClassName); 34292 prepareClassName = null; 34293 } 34294 } 34295 34296 function updateAnimationRunners(animation, newRunner) { 34297 if (animation.from && animation.to) { 34298 update(animation.from.element); 34299 update(animation.to.element); 34300 } else { 34301 update(animation.element); 34302 } 34303 34304 function update(element) { 34305 getRunner(element).setHost(newRunner); 34306 } 34307 } 34308 34309 function handleDestroyedElement() { 34310 var runner = getRunner(element); 34311 if (runner && (event !== 'leave' || !options.$$domOperationFired)) { 34312 runner.end(); 34313 } 34314 } 34315 34316 function close(rejected) { // jshint ignore:line 34317 element.off('$destroy', handleDestroyedElement); 34318 removeRunner(element); 34319 34320 applyAnimationClasses(element, options); 34321 applyAnimationStyles(element, options); 34322 options.domOperation(); 34323 34324 if (tempClasses) { 34325 $$jqLite.removeClass(element, tempClasses); 34326 } 34327 34328 element.removeClass(NG_ANIMATE_CLASSNAME); 34329 runner.complete(!rejected); 34330 } 34331 }; 34332 }]; 34333}]; 34334 34335/* global angularAnimateModule: true, 34336 34337 $$AnimateAsyncRunFactory, 34338 $$rAFSchedulerFactory, 34339 $$AnimateChildrenDirective, 34340 $$AnimateQueueProvider, 34341 $$AnimationProvider, 34342 $AnimateCssProvider, 34343 $$AnimateCssDriverProvider, 34344 $$AnimateJsProvider, 34345 $$AnimateJsDriverProvider, 34346*/ 34347 34348/** 34349 * @ngdoc module 34350 * @name ngAnimate 34351 * @description 34352 * 34353 * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via 34354 * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app. 34355 * 34356 * <div doc-module-components="ngAnimate"></div> 34357 * 34358 * # Usage 34359 * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based 34360 * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For 34361 * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within 34362 * the HTML element that the animation will be triggered on. 34363 * 34364 * ## Directive Support 34365 * The following directives are "animation aware": 34366 * 34367 * | Directive | Supported Animations | 34368 * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| 34369 * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | 34370 * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | 34371 * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | 34372 * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | 34373 * | {@link ng.directive:ngIf#animations ngIf} | enter and leave | 34374 * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) | 34375 * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) | 34376 * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel}
34376 | add and remove (dirty, pristine, valid, invalid & all other validations) | 34377 * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) | 34378 * | {@link module:ngMessages#animations ngMessage} | enter and leave | 34379 * 34380 * (More information can be found by visiting each the documentation associated with each directive.) 34381 * 34382 * ## CSS-based Animations 34383 * 34384 * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML 34385 * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation. 34386 * 34387 * The example below shows how an `enter` animation can be made possible on an element using `ng-if`: 34388 * 34389 * ```html 34390 * <div ng-if="bool" class="fade"> 34391 * Fade me in out 34392 * </div> 34393 * <button ng-click="bool=true">Fade In!</button> 34394 * <button ng-click="bool=false">Fade Out!</button> 34395 * ``` 34396 * 34397 * Notice the CSS class **fade**? We can now create the CSS transition code that references this class: 34398 * 34399 * ```css 34400 * /* The starting CSS styles for the enter animation */ 34401 * .fade.ng-enter { 34402 * transition:0.5s linear all; 34403 * opacity:0; 34404 * } 34405 * 34406 * /* The finishing CSS styles for the enter animation */ 34407 * .fade.ng-enter.ng-enter-active { 34408 * opacity:1; 34409 * } 34410 * ``` 34411 * 34412 * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two 34413 * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition 34414 * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards. 34415 * 34416 * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions: 34417 * 34418 * ```css 34419 * /* now the element will fade out before it is removed from the DOM */ 34420 * .fade.ng-leave { 34421 * transition:0.5s linear all; 34422 * opacity:1; 34423 * } 34424 * .fade.ng-leave.ng-leave-active { 34425 * opacity:0; 34426 * } 34427 * ``` 34428 * 34429 * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class: 34430 * 34431 * ```css 34432 * /* there is no need to define anything inside of the destination 34433 * CSS class since the keyframe will take charge of the animation */ 34434 * .fade.ng-leave { 34435 * animation: my_fade_animation 0.5s linear; 34436 * -webkit-animation: my_fade_animation 0.5s linear; 34437 * } 34438 * 34439 * @keyframes my_fade_animation { 34440 * from { opacity:1; } 34441 * to { opacity:0; } 34442 * } 34443 * 34444 * @-webkit-keyframes my_fade_animation { 34445 * from { opacity:1; } 34446 * to { opacity:0; } 34447 * } 34448 * ``` 34449 * 34450 * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element. 34451 * 34452 * ### CSS Class-based Animations 34453 * 34454 * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different 34455 * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added 34456 * and removed. 34457 * 34458 * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class: 34459 * 34460 * ```html 34461 * <div ng-show="bool" class="fade"> 34462 * Show and hide me 34463 * </div> 34464 * <button ng-click="bool=true">Toggle</button> 34465 * 34466 * <style> 34467 * .fade.ng-hide { 34468 * transition:0.5s linear all; 34469 * opacity:0; 34470 * } 34471 * </style> 34472 * ``` 34473 * 34474 * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since 34475 * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest. 34476 * 34477 * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation 34478 * with CSS styles. 34479 * 34480 * ```html 34481 * <div ng-class="{on:onOff}" class="highlight"> 34482 * Highlight this box 34483 * </div> 34484 * <button ng-click="onOff=!onOff">Toggle</button> 34485 * 34486 * <style> 34487 * .highlight { 34488 * transition:0.5s linear all; 34489 * } 34490 * .highlight.on-add { 34491 * background:white; 34492 * } 34493 * .highlight.on { 34494 * background:yellow; 34495 * } 34496 * .highlight.on-remove { 34497 * background:black; 34498 * } 34499 * </style> 34500 * ``` 34501 * 34502 * We can also make use of CSS keyframes by placing them within the CSS classes. 34503 * 34504 * 34505 * ### CSS Staggering Animations 34506 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a 34507 * curtain-like effect. The ngAnimate module (versions >
34507=1.2) supports staggering animations and the stagger effect can be 34508 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for 34509 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an 34510 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). 34511 * 34512 * ```css 34513 * .my-animation.ng-enter { 34514 * /* standard transition code */ 34515 * transition: 1s linear all; 34516 * opacity:0; 34517 * } 34518 * .my-animation.ng-enter-stagger { 34519 * /* this will have a 100ms delay between each successive leave animation */ 34520 * transition-delay: 0.1s; 34521 * 34522 * /* As of 1.4.4, this must always be set: it signals ngAnimate 34523 * to not accidentally inherit a delay property from another CSS class */ 34524 * transition-duration: 0s; 34525 * } 34526 * .my-animation.ng-enter.ng-enter-active { 34527 * /* standard transition styles */ 34528 * opacity:1; 34529 * } 34530 * ``` 34531 * 34532 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations 34533 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this 34534 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation 34535 * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired. 34536 * 34537 * The following code will issue the **ng-leave-stagger** event on the element provided: 34538 * 34539 * ```js 34540 * var kids = parent.children(); 34541 * 34542 * $animate.leave(kids[0]); //stagger index=0 34543 * $animate.leave(kids[1]); //stagger index=1 34544 * $animate.leave(kids[2]); //stagger index=2 34545 * $animate.leave(kids[3]); //stagger index=3 34546 * $animate.leave(kids[4]); //stagger index=4 34547 * 34548 * window.requestAnimationFrame(function() { 34549 * //stagger has reset itself 34550 * $animate.leave(kids[5]); //stagger index=0 34551 * $animate.leave(kids[6]); //stagger index=1 34552 * 34553 * $scope.$digest(); 34554 * }); 34555 * ``` 34556 * 34557 * Stagger animations are currently only supported within CSS-defined animations. 34558 * 34559 * ### The `ng-animate` CSS class 34560 * 34561 * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation. 34562 * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations). 34563 * 34564 * Therefore, animations can be applied to an element using this temporary class directly via CSS. 34565 * 34566 * ```css 34567 * .zipper.ng-animate { 34568 * transition:0.5s linear all; 34569 * } 34570 * .zipper.ng-enter { 34571 * opacity:0; 34572 * } 34573 * .zipper.ng-enter.ng-enter-active { 34574 * opacity:1; 34575 * } 34576 * .zipper.ng-leave { 34577 * opacity:1; 34578 * } 34579 * .zipper.ng-leave.ng-leave-active { 34580 * opacity:0; 34581 * } 34582 * ``` 34583 * 34584 * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove 34585 * the CSS class once an animation has completed.) 34586 * 34587 * 34588 * ### The `ng-[event]-prepare` class 34589 * 34590 * This is a special class that can be used to prevent unwanted flickering / flash of content before 34591 * the actual animation starts. The class is added as soon as an animation is initialized, but removed 34592 * before the actual animation starts (after waiting for a $digest). 34593 * It is also only added for *structural* animations (`enter`, `move`, and `leave`). 34594 * 34595 * In practice, flickering can appear when nesting elements with structural animations such as `ngIf` 34596 * into elements that have class-based animations such as `ngClass`. 34597 * 34598 * ```html 34599 * <div ng-class="{red: myProp}"> 34600 * <div ng-class="{blue: myProp}"> 34601 * <div class="message" ng-if="myProp"></div> 34602 * </div> 34603 * </div> 34604 * ``` 34605 * 34606 * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating. 34607 * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: 34608 * 34609 * ```css 34610 * .message.ng-enter-prepare { 34611 * opacity: 0; 34612 * } 34613 * 34614 * ``` 34615 * 34616 * ## JavaScript-based Animations 34617 * 34618 * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared 34619 * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the 34620 * `module.animation()` module function we can register the ainmation. 34621 * 34622 * Let's see an example of a enter/leave animation using `ngRepeat`: 34623 * 34624 * ```html 34625 * <div ng-repeat="item in items" class="slide"> 34626 * {{ item }} 34627 * </div> 34628 * ``` 34629 * 34630 * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`: 34631 * 34632 * ```js 34633 * myModule.animation('.slide', [function() { 34634 * return { 34635 * // make note that other events (like addClass/removeClass) 34636 * // have different function input parameters 34637 * enter: function(element, doneFn) { 34638 * jQuery(element).fadeIn(1000, doneFn); 34639 * 34640 * // remember to call doneFn so that angular 34641 * // knows that the animation has concluded 34642 * }, 34643 * 34644 * move: function(element, doneFn) { 34645 * jQuery(element).fadeIn(1000, doneFn); 34646 * }, 34647 * 34648 * leave: function(element, doneFn) { 34649 * jQuery(element).fadeOut(1000, doneFn); 34650 * } 34651 * } 34652 * }]); 34653 * ``` 34654 * 34655 * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as 34656 * greensock.js and velocity.js. 34657 * 34658 * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define 34659 * our animations inside of the same registered animation, however, the function input arguments are a bit different: 34660 * 34661 * ```html 34662 * <div ng-class="color" class="colorful"> 34663 * this box is moody 34664 * </div> 34665 * <button ng-click="color='red'">Change to red</button> 34666 * <button ng-click="color='blue'">Change to blue</button> 34667 * <button ng-click="color='green'">Change to green</button> 34668 * ``` 34669 * 34670 * ```js 34671 * myModule.animation('.colorful', [function() { 34672 * return { 34673 * addClass: function(element, className, doneFn) {
34674 * // do some cool animation and call the doneFn 34675 * }, 34676 * removeClass: function(element, className, doneFn) { 34677 * // do some cool animation and call the doneFn 34678 * }, 34679 * setClass: function(element, addedClass, removedClass, doneFn) { 34680 * // do some cool animation and call the doneFn 34681 * } 34682 * } 34683 * }]); 34684 * ``` 34685 * 34686 * ## CSS + JS Animations Together 34687 * 34688 * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular, 34689 * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking 34690 * charge of the animation**: 34691 * 34692 * ```html 34693 * <div ng-if="bool" class="slide"> 34694 * Slide in and out 34695 * </div> 34696 * ``` 34697 * 34698 * ```js 34699 * myModule.animation('.slide', [function() { 34700 * return { 34701 * enter: function(element, doneFn) { 34702 * jQuery(element).slideIn(1000, doneFn); 34703 * } 34704 * } 34705 * }]); 34706 * ``` 34707 * 34708 * ```css 34709 * .slide.ng-enter { 34710 * transition:0.5s linear all; 34711 * transform:translateY(-100px); 34712 * } 34713 * .slide.ng-enter.ng-enter-active { 34714 * transform:translateY(0); 34715 * } 34716 * ``` 34717 * 34718 * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the 34719 * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from 34720 * our own JS-based animation code: 34721 * 34722 * ```js 34723 * myModule.animation('.slide', ['$animateCss', function($animateCss) { 34724 * return { 34725 * enter: function(element) { 34726* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`. 34727 * return $animateCss(element, { 34728 * event: 'enter', 34729 * structural: true 34730 * }); 34731 * } 34732 * } 34733 * }]); 34734 * ``` 34735 * 34736 * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework. 34737 * 34738 * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or 34739 * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that 34740 * data into `$animateCss` directly: 34741 * 34742 * ```js 34743 * myModule.animation('.slide', ['$animateCss', function($animateCss) { 34744 * return { 34745 * enter: function(element) { 34746 * return $animateCss(element, { 34747 * event: 'enter', 34748 * structural: true, 34749 * addClass: 'maroon-setting', 34750 * from: { height:0 }, 34751 * to: { height: 200 } 34752 * }); 34753 * } 34754 * } 34755 * }]); 34756 * ``` 34757 * 34758 * Now we can fill in the rest via our transition CSS code: 34759 * 34760 * ```css 34761 * /* the transition tells ngAnimate to make the animation happen */ 34762 * .slide.ng-enter { transition:0.5s linear all; } 34763 * 34764 * /* this extra CSS class will be absorbed into the transition 34765 * since the $animateCss code is adding the class */ 34766 * .maroon-setting { background:red; } 34767 * ``` 34768 * 34769 * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over. 34770 * 34771 * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}. 34772 * 34773 * ## Animation Anchoring (via `ng-animate-ref`) 34774 * 34775 * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between 34776 * structural areas of an application (like views) by pairing up elements using an attribute 34777 * called `ng-animate-ref`. 34778 * 34779 * Let's say for example we have two views that are managed by `ng-view` and we want to show 34780 * that there is a relationship between two components situated in within these views. By using the 34781 * `ng-animate-ref` attribute we can identify that the two components are paired together and we 34782 * can then attach an animation, which is triggered when the view changes. 34783 * 34784 * Say for example we have the following template code: 34785 * 34786 * ```html 34787 * <!-- index.html --> 34788 * <div ng-view class="view-animation"> 34789 * </div> 34790 * 34791 * <!-- home.html --> 34792 * <a href="#/banner-page"> 34793 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner"> 34794 * </a> 34795 * 34796 * <!-- banner-page.html --> 34797 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner"> 34798 * ``` 34799 *
34800 * Now, when the view changes (once the link is clicked), ngAnimate will examine the 34801 * HTML contents to see if there is a match reference between any components in the view 34802 * that is leaving and the view that is entering. It will scan both the view which is being 34803 * removed (leave) and inserted (enter) to see if there are any paired DOM elements that 34804 * contain a matching ref value. 34805 * 34806 * The two images match since they share the same ref value. ngAnimate will now create a 34807 * transport element (which is a clone of the first image element) and it will then attempt 34808 * to animate to the position of the second image element in the next view. For the animation to 34809 * work a special CSS class called `ng-anchor` will be added to the transported element. 34810 * 34811 * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then 34812 * ngAnimate will handle the entire transition for us as well as the addition and removal of 34813 * any changes of CSS classes between the elements: 34814 * 34815 * ```css 34816 * .banner.ng-anchor { 34817 * /* this animation will last for 1 second since there are 34818 * two phases to the animation (an `in` and an `out` phase) */ 34819 * transition:0.5s linear all; 34820 * } 34821 * ``` 34822 * 34823 * We also **must** include animations for the views that are being entered and removed 34824 * (otherwise anchoring wouldn't be possible since the new view would be inserted right away). 34825 * 34826 * ```css 34827 * .view-animation.ng-enter, .view-animation.ng-leave { 34828 * transition:0.5s linear all; 34829 * position:fixed; 34830 * left:0; 34831 * top:0; 34832 * width:100%; 34833 * } 34834 * .view-animation.ng-enter { 34835 * transform:translateX(100%); 34836 * } 34837 * .view-animation.ng-leave, 34838 * .view-animation.ng-enter.ng-enter-active { 34839 * transform:translateX(0%); 34840 * } 34841 * .view-animation.ng-leave.ng-leave-active { 34842 * transform:translateX(-100%); 34843 * } 34844 * ``` 34845 * 34846 * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur: 34847 * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away 34848 * from its origin. Once that animation is over then the `in` stage occurs which animates the 34849 * element to its destination. The reason why there are two animations is to give enough time 34850 * for the enter animation on the new element to be ready. 34851 * 34852 * The example above sets up a transition for both the in and out phases, but we can also target the out or 34853 * in phases directly via `ng-anchor-out` and `ng-anchor-in`. 34854 * 34855 * ```css 34856 * .banner.ng-anchor-out { 34857 * transition: 0.5s linear all; 34858 * 34859 * /* the scale will be applied during the out animation, 34860 * but will be animated away when the in animation runs */ 34861 * transform: scale(1.2); 34862 * } 34863 * 34864 * .banner.ng-anchor-in { 34865 * transition: 1s linear all; 34866 * } 34867 * ``` 34868 * 34869 * 34870 * 34871 * 34872 * ### Anchoring Demo 34873 * 34874 <example module="anchoringExample" 34875 name="anchoringExample" 34876 id="anchoringExample" 34877 deps="angular-animate.js;angular-route.js" 34878 animations="true"> 34879 <file name="index.html"> 34880 <a href="#/">Home</a> 34881 <hr /> 34882 <div class="view-container"> 34883 <div ng-view class="view"></div> 34884 </div> 34885 </file> 34886 <file name="script.js"> 34887 angular.module('anchoringExample', ['ngAnimate', 'ngRoute']) 34888 .config(['$routeProvider', function($routeProvider) { 34889 $routeProvider.when('/', { 34890 templateUrl: 'home.html', 34891 controller: 'HomeController as home' 34892 }); 34893 $routeProvider.when('/profile/:id', { 34894 templateUrl: 'profile.html', 34895 controller: 'ProfileController as profile' 34896 }); 34897 }]) 34898 .run(['$rootScope', function($rootScope) { 34899 $rootScope.records = [ 34900 { id:1, title: "Miss Beulah Roob" }, 34901 { id:2, title: "Trent Morissette" }, 34902 { id:3, title: "Miss Ava Pouros" }, 34903 { id:4, title: "Rod Pouros" }, 34904 { id:5, title: "Abdul Rice" }, 34905 { id:6, title: "Laurie Rutherford Sr." }, 34906 { id:7, title: "Nakia McLaughlin" }, 34907 { id:8, title: "Jordon Blanda DVM" }, 34908 { id:9, title: "Rhoda Hand" }, 34909 { id:10, title: "Alexandrea Sauer" } 34910 ]; 34911 }]) 34912 .controller('HomeController', [function() { 34913 //empty 34914 }]) 34915 .controller('ProfileController', ['$rootScope', '$routeParam
34915s', function($rootScope, $routeParams) { 34916 var index = parseInt($routeParams.id, 10); 34917 var record = $rootScope.records[index - 1]; 34918 34919 this.title = record.title; 34920 this.id = record.id; 34921 }]); 34922 </file> 34923 <file name="home.html"> 34924 <h2>Welcome to the home page</h1> 34925 <p>Please click on an element</p> 34926 <a class="record" 34927 ng-href="#/profile/{{ record.id }}" 34928 ng-animate-ref="{{ record.id }}" 34929 ng-repeat="record in records"> 34930 {{ record.title }} 34931 </a> 34932 </file> 34933 <file name="profile.html"> 34934 <div class="profile record" ng-animate-ref="{{ profile.id }}"> 34935 {{ profile.title }} 34936 </div> 34937 </file> 34938 <file name="animations.css"> 34939 .record { 34940 display:block; 34941 font-size:20px; 34942 } 34943 .profile { 34944 background:black; 34945 color:white; 34946 font-size:100px; 34947 } 34948 .view-container { 34949 position:relative; 34950 } 34951 .view-container > .view.ng-animate { 34952 position:absolute; 34953 top:0; 34954 left:0; 34955 width:100%; 34956 min-height:500px; 34957 } 34958 .view.ng-enter, .view.ng-leave, 34959 .record.ng-anchor { 34960 transition:0.5s linear all; 34961 } 34962 .view.ng-enter { 34963 transform:translateX(100%); 34964 } 34965 .view.ng-enter.ng-enter-active, .view.ng-leave { 34966 transform:translateX(0%); 34967 } 34968 .view.ng-leave.ng-leave-active { 34969 transform:translateX(-100%); 34970 } 34971 .record.ng-anchor-out { 34972 background:red; 34973 } 34974 </file> 34975 </example> 34976 * 34977 * ### How is the element transported? 34978 * 34979 * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting 34980 * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element 34981 * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The 34982 * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match 34983 * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied 34984 * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class 34985 * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element 34986 * will become visible since the shim class will be removed. 34987 * 34988 * ### How is the morphing handled? 34989 * 34990 * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out 34991 * what CSS classes differ between the starting element and the destination element. These different CSS classes 34992 * will be added/removed on the anchor element and a transition will be applied (the transition that is provided 34993 * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will 34994 * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that 34995 * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since 34996 * the cloned element is placed inside of root element which is likely close to the body element). 34997 * 34998 * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body. 34999 * 35000 * 35001 * ## Using $animate in your directive code 35002 * 35003 * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application? 35004 * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be c
35004onsumed by animations. Let's 35005 * imagine we have a greeting box that shows and hides itself when the data changes 35006 * 35007 * ```html 35008 * <greeting-box active="onOrOff">Hi there</greeting-box> 35009 * ``` 35010 * 35011 * ```js 35012 * ngModule.directive('greetingBox', ['$animate', function($animate) { 35013 * return function(scope, element, attrs) { 35014 * attrs.$observe('active', function(value) { 35015 * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on'); 35016 * }); 35017 * }); 35018 * }]); 35019 * ``` 35020 * 35021 * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element 35022 * in our HTML code then we can trigger a CSS or JS animation to happen. 35023 * 35024 * ```css 35025 * /* normally we would create a CSS class to reference on the element */ 35026 * greeting-box.on { transition:0.5s linear all; background:green; color:white; } 35027 * ``` 35028 * 35029 * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's 35030 * possible be sure to visit the {@link ng.$animate $animate service API page}. 35031 * 35032 * 35033 * ### Preventing Collisions With Third Party Libraries 35034 * 35035 * Some third-party frameworks place animation duration defaults across many element or className 35036 * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which 35037 * is expecting actual animations on these elements and has to wait for their completion. 35038 * 35039 * You can prevent this unwanted behavior by using a prefix on all your animation classes: 35040 * 35041 * ```css 35042 * /* prefixed with animate- */ 35043 * .animate-fade-add.animate-fade-add-active { 35044 * transition:1s linear all; 35045 * opacity:0; 35046 * } 35047 * ``` 35048 * 35049 * You then configure `$animate` to enforce this prefix: 35050 * 35051 * ```js 35052 * $animateProvider.classNameFilter(/animate-/); 35053 * ``` 35054 * 35055 * This also may provide your application with a speed boost since only specific elements containing CSS class prefix 35056 * will be evaluated for animation when any DOM changes occur in the application. 35057 * 35058 * ## Callbacks and Promises 35059 * 35060 * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger 35061 * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has 35062 * ended by chaining onto the returned promise that animation method returns. 35063 * 35064 * ```js 35065 * // somewhere within the depths of the directive 35066 * $animate.enter(element, parent).then(function() { 35067 * //the animation has completed 35068 * }); 35069 * ``` 35070 * 35071 * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case 35072 * anymore.) 35073 * 35074 * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering 35075 * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view 35076 * routing controller to hook into that: 35077 * 35078 * ```js 35079 * ngModule.controller('HomePageController', ['$animate', function($animate) { 35080 * $animate.on('enter', ngViewElement, function(element) { 35081 * // the animation for this route has completed 35082 * }]); 35083 * }]) 35084 * ``` 35085 * 35086 * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.) 35087 */ 35088 35089/** 35090 * @ngdoc service 35091 * @name $animate 35092 * @kind object 35093 * 35094 * @description 35095 * The ngAnimate `$animate` service documentation is the same for the core `$animate` service. 35096 * 35097 * Click here {@link ng.$animate to learn more about animations with `$animate`}. 35098 */ 35099angular.module('ngAnimate', []) 35100 .directive('ngAnimateChildren', $$AnimateChildrenDirective) 35101 .factory('$$rAFScheduler', $$rAFSchedulerFactory) 35102 35103 .provider('$$animateQueue', $$AnimateQueueProvider) 35104 .provider('$$animation', $$AnimationProvider) 35105 35106 .provider('$animateCss', $AnimateCssProvider) 35107 .provider('$$animateCssDriver', $$AnimateCssDriverProvider) 35108
35109 .provider('$$animateJs', $$AnimateJsProvider) 35110 .provider('$$animateJsDriver', $$AnimateJsDriverProvider); 35111 35112 35113})(window, window.angular); 35114 35115(function (angular) { 35116 angular.module("ui.materialize", ["ui.materialize.ngModel", "ui.materialize.collapsible", "ui.materialize.toast", "ui.materialize.sidenav", "ui.materialize.material_select", "ui.materialize.dropdown", "ui.materialize.inputfield", "ui.materialize.input_date", "ui.materialize.tabs", "ui.materialize.pagination", "ui.materialize.pushpin", "ui.materialize.scrollspy", "ui.materialize.parallax","ui.materialize.modal", "ui.materialize.tooltipped", "ui.materialize.slider", "ui.materialize.materialboxed"]); 35117 35118 angular.module("ui.materialize.ngModel", []) 35119 .directive("ngModel",["$timeout", function($timeout){ 35120 return { 35121 restrict: 'A', 35122 priority: -1, // lower priority than built-in ng-model so it runs first 35123 link: function(scope, element, attr) { 35124 scope.$watch(attr.ngModel,function(value){ 35125 $timeout(function () { 35126 if (value){ 35127 element.trigger("change"); 35128 } else if(element.attr('placeholder') === undefined) { 35129 if(!element.is(":focus")) 35130 element.trigger("blur"); 35131 } 35132 }); 35133 }); 35134 } 35135 }; 35136 }]); 35137 35138 35139 /* example usage: 35140 <div slider height='500' transition='400'></div> 35141 */ 35142 angular.module("ui.materialize.slider", []) 35143 .directive("slider", ["$timeout", function($timeout){ 35144 return { 35145 restrict: 'A', 35146 scope: { 35147 height: '=', 35148 transition: '=', 35149 interval: '=', 35150 indicators: '=' 35151 }, 35152 link: function(scope, element, attrs) { 35153 element.addClass("slider"); 35154 $timeout(function(){ 35155 element.slider({ 35156 height: (angular.isDefined(scope.height)) ? scope.height : 400, 35157 transition: (angular.isDefined(scope.transition)) ? scope.transition : 500, 35158 interval: (angular.isDefined(scope.interval)) ? scope.interval : 6000, 35159 indicators: (angular.isDefined(scope.indicators)) ? scope.indicators : true 35160 }); 35161 }); 35162 } 35163 }; 35164 }]); 35165 35166 35167 angular.module("ui.materialize.collapsible", []) 35168 .directive("collapsible", ["$timeout", function ($timeout) { 35169 return { 35170 link: function (scope, element, attrs) { 35171 $timeout(function () { 35172 element.collapsible(); 35173 }); 35174 if ("watch" in attrs) { 35175 scope.$watch(function () { 35176 return element[0].innerHTML; 35177 }, function (oldVal, newVal) { 35178 if (oldVal !== newVal) { 35179 $timeout(function () { 35180 element.collapsible(); 35181 }); 35182 } 35183 }); 35184 } 35185 } 35186 }; 35187 }]); 35188 35189 angular.module("ui.materialize.parallax", []) 35190 .directive("parallax", ["$timeout", function($timeout){ 35191 return { 35192 link: function(scope, element, attrs) { 35193 $timeout(function(){ 35194 element.parallax(); 35195 }); 35196 } 35197 }; 35198 }]); 35199 35200 angular.module("ui.materialize.toast", []) 35201 .constant("toastConfig", { 35202 duration: 3000 35203 }) 35204 .directive("toast", ["toastConfig", function (toastConfig) { 35205 return { 35206 scope: { 35207 message: "@", 35208 duration: "@", 35209 callback: "&" 35210 }, 35211 link: function (scope, element, attrs) { 35212 element.bind(attrs.toast, function () { 35213 var message = (angular.isDefined(scope.message)) ? scope.message : ""; 35214 var toastclass = (angular.isDefined(attrs.toastclass)) ? attrs.toastclass : ""; 35215 Materialize.toast(message, scope.duration ? scope.duration : toastConfig.duration, toastclass, scope.callback); 35216 }); 35217 } 35218 }; 35219 }]); 35220 35221 angular.module('ui.materialize.pushpin', []) 35222 .directive('pushpin', [function(){ 35223 return { 35224 restrict: 'AE', 35225 require: [ 35226 '?pushpinTop', 35227 '?pushpinOffset', 35228 '?pushpinBottom' 35229 ], 35230 link: function (scope, element, attrs) { 35231 var top = attrs.pushpinTop || 0; 35232 var offset = attrs.pushpinOffset || 0; 35233 var bottom = attrs.pushpinBottom || Infinity; 35234 setTimeout(function () { 35235 element.pushpin({top: top, offset: offset, bottom: bottom}); 35236 }, 0); 35237 35238 } 35239 }; 35240 }]); 35241 35242 // TODO: Add some documentation for this. 35243 angular.module("ui.materialize.scrollspy", []) 35244 .directive("scrollspy", ["$timeout", function($timeout){ 35245 return { 35246 restrict: 'A', 35247 link: function(scope, element, attrs) { 35248 element.addClass("scrollspy"); 35249 $timeout(function(){ 35250 element.scrollSpy(); 35251 }); 35252 } 35253 }; 35254 }]); 35255 35256 angular.module("ui.materialize.tabs", []) 35257 .directive("tabs", [function(){ 35258 return { 35259 link: function (scope, element, attrs) { 35260 element.tabs(); 35261 } 35262 }; 35263 }]); 35264 35265 // Example: <a href="#" data-activates="nav-mobile" class="button-collapse top-nav" data-sidenav="left" data-menuwidth="500" data-closeonclick="true"> 35266 // data-activates is handled by the jQuery plugin. 35267 angular.module("ui.materialize.sidenav", []) 35268 .directive("sidenav", [function () { 35269 return { 35270 scope: { 35271 menuwidth: "@", 35272 closeonclick: "@" 35273 }, 35274 link: function (scope, element, attrs) { 35275 element.sideNav({ 35276 menuWidth: (angular.isDefined(scope.menuwidth)) ? scope.menuwidth : undefined, 35277 edge: attrs.sidenav ? attrs.sidenav : "left", 35278 closeOnClick: (angular.isDefined(scope.closeonclick)) ? scope.closeonclick == "true" : undefined 35279 }); 35280 } 35281 }; 35282 }]); 35283 35284 // This works, unless the content inside the select changes. 35285 angular.module("ui.materialize.material_select", []) 35286 .directive("materialSelect", ["$compile", "$timeout", function ($compile, $timeout) { 35287 return { 35288 link: function (scope, element, attrs) { 35289 if (element.is("select")) { 35290 $compile(element.contents())(scope); 35291 function initSelect() { 35292 element.siblings(".caret").remove(); 35293 element.material_select(); 35294 } 35295 $timeout(initSelect); 35296 if (attrs.ngModel) { 35297 scope.$watch(attrs.ngModel, initSelect); 35298 } 35299 if ("watch" in attrs) { 35300 scope.$watch(function () { 35301 return element[0].innerHTML; 35302 }, function (oldVal, newVal) { 35303 if (oldVal !== newVal) { 35304 $timeout(initSelect); 35305 } 35306 }); 35307 } 35308 } 35309 } 35310 }; 35311 }]); 35312 35313 /* 35314 Example usage, notice the empty dropdown tag in the dropdown trigger. 35315 <!-- Dropdown Trigger -->
35316 <a class='dropdown-button btn' href='javascript:void(0);' data-activates='demoDropdown' 35317 dropdown constrain-width="false"> 35318 Select a demo 35319 </a> 35320 <!-- Dropdown Structure --> 35321 <ul id='demoDropdown' class='dropdown-content'> 35322 <li ng-repeat="demo in demoDefiniftions"> 35323 <a href="javascript:void(0);" ng-click="selectDemo(demo)">{{demo.name}}</a> 35324 </li> 35325 </ul>*/ 35326 angular.module("ui.materialize.dropdown", []) 35327 .directive("dropdown", ["$compile", "$timeout", function ($compile, $timeout) { 35328 return { 35329 scope: { 35330 inDuration: "@", 35331 outDuration: "@", 35332 constrainWidth: "@", 35333 hover: "@", 35334 alignment: "@", 35335 gutter: "@", 35336 belowOrigin: "@" 35337 }, 35338 link: function (scope, element, attrs) { 35339 $timeout(function () { 35340 $compile(element.contents())(scope); 35341 element.dropdown({ 35342 inDuration: (angular.isDefined(scope.inDuration)) ? scope.inDuration : undefined, 35343 outDuration: (angular.isDefined(scope.outDuration)) ? scope.outDuration : undefined, 35344 constrain_width: (angular.isDefined(scope.constrainWidth)) ? scope.constrainWidth : undefined, 35345 hover: (angular.isDefined(scope.hover)) ? scope.hover : undefined, 35346 alignment: (angular.isDefined(scope.alignment)) ? scope.alignment : undefined, 35347 gutter: (angular.isDefined(scope.gutter)) ? scope.gutter : undefined, 35348 belowOrigin: (angular.isDefined(scope.belowOrigin)) ? scope.belowOrigin : undefined 35349 }); 35350 }); 35351 } 35352 }; 35353 }]); 35354 35355 /** 35356 * Instead of adding the .input-field class to a div surrounding a label and input, add the attribute input-field. 35357 * That way it will also work when angular destroys/recreates the elements. 35358 * 35359 * Example: 35360 <inputfield style="margin-top:10px"> 35361 <label>{{name}}:</label> 35362 <input type="text" name="{{name}}" ng-model="value"> 35363 </inputfield> 35364 */ 35365 angular.module("ui.materialize.inputfield", []) 35366 .directive('inputField', ["$compile", "$timeout", function ($compile, $timeout) { 35367 return { 35368 transclude: true, 35369 scope: {}, 35370 link: function (scope, element) { 35371 $timeout(function () { 35372 Materialize.updateTextFields(); 35373 35374 // The "> > [selector]", is to restrict to only those tags that are direct children of the directive element. Otherwise we might hit to many elements with the selectors. 35375 35376 // Triggering autoresize of the textareas. 35377 element.find("> > .materialize-textarea").each(function () { 35378 var that = $(this); 35379 that.addClass("materialize-textarea"); 35380 that.trigger("autoresize"); 35381 var model = that.attr("ng-model"); 35382 if (model) { 35383 scope.$parent.$watch(model, function (a, b) { 35384 if (a !== b) { 35385 $timeout(function () { 35386 that.trigger("autoresize"); 35387 }); 35388 } 35389 }); 35390 } 35391 }); 35392 35393 // Adding char-counters. 35394 element.find('> > .materialize-textarea, > > input').each(function (index, countable) { 35395 countable = angular.element(countable); 35396 if (!countable.siblings('span[class="character-counter"]').length) { 35397 countable.characterCounter(); 35398 } 35399 }); 35400 }); 35401 }, 35402 template: '<div ng-transclude class="input-field"></div>' 35403 }; 35404 }]); 35405 35406 /** 35407 * Add pickadate directive 35408 * Type text is mandatory 35409 * Example: 35410 <input input-date 35411 type="text" 35412 name="created" 35413 id="inputCreated" 35414 ng-model="currentTime" 35415 format="dd/mm/yyyy" 35416 months-full="{{ monthFr }}" 35417 months-short="{{ monthShortFr }}" 35418 weekdays-full="{{ weekdaysFullFr }}" 35419 weekdays-short="{{ weekdaysShortFr }}" 35420 weekdays-letter="{{ weekdaysLetterFr }}" 35421 disable="disable" 35422 today="today" 35423 clear="clear" 35424 close="close" 35425 on-start="onStart()" 35426 on-render="onRender()" 35427 on-open="onOpen()"
vendor: 4,643 bytes, lines 35428-35524
35428 on-close="onClose()" 35429 on-set="onSet()" 35430 on-stop="onStop()" /> 35431 */ 35432 angular.module("ui.materialize.input_date", []) 35433 .directive('inputDate', ["$compile", "$timeout", function ($compile, $timeout) { 35434 // Fix for issue 46. This gotta be a bug in the materialize code, but this fixes it. 35435 var style = $('<style>#inputCreated_root {outline: none;}</style>'); 35436 $('html > head').append(style); 35437 35438 // Define Prototype Date format 35439 // Use like this 35440 // today = new Date(); 35441 // var dateString = today.format("dd-m-yy"); 35442 var dateFormat = function () { 35443 35444 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, 35445 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, 35446 timezoneClip = /[^-+\dA-Z]/g, 35447 pad = function (val, len) { 35448 val = String(val); 35449 len = len || 2; 35450 while (val.length < len) { 35451 val = "0" + val; 35452 } 35453 return val; 35454 }; 35455 35456 // Regexes and supporting functions are cached through closure 35457 return function (date, mask, utc) { 35458 35459 var dF = dateFormat; 35460 35461 // You can't provide utc if you skip other args (use the "UTC:" mask prefix) 35462 if (arguments.length === 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { 35463 mask = date; 35464 date = undefined; 35465 } 35466 35467 // Passing date through Date applies Date.parse, if necessary 35468 date = date ? new Date(date) : new Date(); 35469 if (isNaN(date)) throw SyntaxError("invalid date"); 35470 35471 mask = String(dF.masks[mask] || mask || dF.masks["default"]); 35472 35473 // Allow setting the utc argument via the mask 35474 if (mask.slice(0, 4) == "UTC:") { 35475 mask = mask.slice(4); 35476 utc = true; 35477 } 35478 35479 var _ = utc ? "getUTC" : "get", 35480 d = date[ _ + "Date" ](), 35481 D = date[ _ + "Day" ](), 35482 m = date[ _ + "Month" ](), 35483 y = date[ _ + "FullYear" ](), 35484 H = date[ _ + "Hours" ](), 35485 M = date[ _ + "Minutes" ](), 35486 s = date[ _ + "Seconds" ](), 35487 L = date[ _ + "Milliseconds" ](), 35488 o = utc ? 0 : date.getTimezoneOffset(), 35489 flags = { 35490 d: d, 35491 dd: pad(d), 35492 ddd: dF.i18n.dayNames[D], 35493 dddd: dF.i18n.dayNames[D + 7], 35494 m: m + 1, 35495 mm: pad(m + 1), 35496 mmm: dF.i18n.monthNames[m], 35497 mmmm: dF.i18n.monthNames[m + 12], 35498 yy: String(y).slice(2), 35499 yyyy: y, 35500 h: H % 12 || 12, 35501 hh: pad(H % 12 || 12), 35502 H: H, 35503 HH: pad(H), 35504 M: M, 35505 MM: pad(M), 35506 s: s, 35507 ss: pad(s), 35508 l: pad(L, 3), 35509 L: pad(L > 99 ? Math.round(L / 10) : L), 35510 t: H < 12 ? "a" : "p", 35511 tt: H < 12 ? "am" : "pm", 35512 T: H < 12 ? "A" : "P", 35513 TT: H < 12 ? "AM" : "PM", 35514 Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), 35515 o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), 35516 S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] 35517 }; 35518 35519 return mask.replace(token, function ($0) { 35520 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); 35521 }); 35522 }; 35523 }(); 35524
35525 // Some common format strings 35526 dateFormat.masks = { 35527 "default": "ddd mmm dd yyyy HH:MM:ss", 35528 shortDate: "m/d/yy", 35529 mediumDate: "mmm d, yyyy", 35530 longDate: "mmmm d, yyyy", 35531 fullDate: "dddd, mmmm d, yyyy", 35532 shortTime: "h:MM TT", 35533 mediumTime: "h:MM:ss TT", 35534 longTime: "h:MM:ss TT Z", 35535 isoDate: "yyyy-mm-dd", 35536 isoTime: "HH:MM:ss", 35537 isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", 35538 isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" 35539 }; 35540 35541 // Internationalization strings 35542 dateFormat.i18n = { 35543 dayNames: [ 35544 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 35545 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 35546 ], 35547 monthNames: [ 35548 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 35549 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
35549November", "December" 35550 ] 35551 }; 35552 35553 // For convenience... 35554 Date.prototype.format = function (mask, utc) { 35555 return dateFormat(this, mask, utc); 35556 }; 35557 35558 /** 35559 * Validate date object 35560 * @param {Date} date 35561 * @return {Boolean} 35562 */ 35563 var isValidDate = function(date) { 35564 if( Object.prototype.toString.call(date) === '[object Date]' ) { 35565 return !isNaN(date.getTime()); 35566 } 35567 return false; 35568 }; 35569 35570 return { 35571 require: 'ngModel', 35572 scope: { 35573 container: "@", 35574 format: "@", 35575 formatSubmit: "@", 35576 monthsFull: "@", 35577 monthsShort: "@", 35578 weekdaysFull: "@", 35579 weekdaysLetter: "@", 35580 firstDay: "=", 35581 disable: "=", 35582 today: "=", 35583 clear: "=", 35584 close: "=", 35585 selectYears: "=", 35586 onStart: "&", 35587 onRender: "&", 35588 onOpen: "&", 35589 onClose: "&", 35590 onSet: "&", 35591 onStop: "&", 35592 ngReadonly: "=?", 35593 max: "@", 35594 min: "@" 35595 }, 35596 link: function (scope, element, attrs, ngModelCtrl) { 35597 35598 ngModelCtrl.$formatters.unshift(function (modelValue) { 35599 if (modelValue) { 35600 var date = new Date(modelValue); 35601 return (angular.isDefined(scope.format)) ? date.format(scope.format) : date.format('d mmmm, yyyy'); 35602 } 35603 return null; 35604 }); 35605 35606 var monthsFull = (angular.isDefined(scope.monthsFull)) ? scope.$eval(scope.monthsFull) : undefined, 35607 monthsShort = (angular.isDefined(scope.monthsShort)) ? scope.$eval(scope.monthsShort) : undefined, 35608 weekdaysFull = (angular.isDefined(scope.weekdaysFull)) ? scope.$eval(scope.weekdaysFull) : undefined, 35609 weekdaysLetter = (angular.isDefined(scope.weekdaysLetter)) ? scope.$eval(scope.weekdaysLetter) : undefined; 35610 35611 35612 $compile(element.contents())(scope); 35613 if (!(scope.ngReadonly)) { 35614 $timeout(function () { 35615 var pickadateInput = element.pickadate({ 35616 container : (angular.isDefined(scope.container)) ? scope.container : 'body', 35617 format: (angular.isDefined(scope.format)) ? scope.format : undefined, 35618 formatSubmit: (angular.isDefined(scope.formatSubmit)) ? scope.formatSubmit : undefined, 35619 monthsFull: (angular.isDefined(monthsFull)) ? monthsFull : undefined, 35620 monthsShort: (angular.isDefined(monthsShort)) ? monthsShort : undefined, 35621 weekdaysFull: (angular.isDefined(weekdaysFull)) ? weekdaysFull : undefined, 35622 weekdaysLetter: (angular.isDefined(weekdaysLetter)) ? weekdaysLetter : undefined, 35623 firstDay: (angular.isDefined(scope.firstDay)) ? scope.firstDay : 0, 35624 disable: (angular.isDefined(scope.disable)) ? scope.disable : undefined, 35625 today: (angular.isDefined(scope.today)) ? scope.today : undefined, 35626 clear: (angular.isDefined(scope.clear)) ? scope.clear : undefined, 35627 close: (angular.isDefined(scope.close)) ? scope.close : undefined, 35628 selectYears: (angular.isDefined(scope.selectYears)) ? scope.selectYears : undefined, 35629 onStart: (angular.isDefined(scope.onStart)) ? function(){ scope.onStart(); } : undefined, 35630 onRender: (angular.isDefined(scope.onRender)) ? function(){ scope.onRender(); } : undefined, 35631 onOpen: (angular.isDefined(scope.onOpen)) ? function(){ scope.onOpen(); } : undefined, 35632 onClose: (angular.isDefined(scope.onClose)) ? function(){ scope.onClose(); }
35632 : undefined, 35633 onSet: (angular.isDefined(scope.onSet)) ? function(){ scope.onSet(); } : undefined, 35634 onStop: (angular.isDefined(scope.onStop)) ? function(){ scope.onStop(); } : undefined 35635 }); 35636 //pickadate API 35637 var picker = pickadateInput.pickadate('picker'); 35638 35639 //watcher of min and max 35640 scope.$watch('max', function(newMax) { 35641 if( picker ) { 35642 var maxDate = new Date(newMax); 35643 picker.set({max: isValidDate(maxDate) ? maxDate : false}); 35644 } 35645 }); 35646 scope.$watch('min', function(newMin) { 35647 if( picker ) { 35648 var minDate = new Date(newMin); 35649 picker.set({min: isValidDate(minDate) ? minDate : false}); 35650 } 35651 }); 35652 }); 35653 } 35654 } 35655 }; 35656 }]); 35657 35658 /** 35659 * Example: 35660 <pagination 35661 page="1" 35662 page-size="10" 35663 total="100" 35664 pagination-action="changePage(page)" 35665 ul-class="customClass"> 35666 * ul-class could be either an object or a string 35667 * 35668 * Based on https://github.com/brantwills/Angular-Paging 35669 */ 35670 angular.module("ui.materialize.pagination", []) 35671 .directive('pagination', function () { 35672 35673 // Assign null-able scope values from settings 35674 function setScopeValues(scope, attrs) { 35675 scope.List = []; 35676 scope.Hide = false;
35677 scope.page = parseInt(scope.page) || 1; 35678 scope.total = parseInt(scope.total) || 0; 35679 scope.dots = scope.dots || '...'; 35680 scope.ulClass = scope.ulClass || attrs.ulClass || 'pagination'; 35681 scope.adjacent = parseInt(scope.adjacent) || 2; 35682 scope.activeClass = 'active'; 35683 scope.disabledClass = 'disabled'; 35684 35685 scope.scrollTop = scope.$eval(attrs.scrollTop); 35686 scope.hideIfEmpty = scope.$eval(attrs.hideIfEmpty); 35687 scope.showPrevNext = scope.$eval(attrs.showPrevNext); 35688 } 35689 35690 // Validate and clean up any scope values 35691 // This happens after we have set the 35692 // scope values 35693 function validateScopeValues(scope, pageCount) { 35694 // Block where the page is larger than the pageCount 35695 if (scope.page > pageCount) { 35696 scope.page = pageCount; 35697 } 35698 35699 // Block where the page is less than 0 35700 if (scope.page <= 0) { 35701 scope.page = 1; 35702 } 35703 35704 // Block where adjacent value is 0 or below 35705 if (scope.adjacent <= 0) { 35706 scope.adjacent = 2; 35707 } 35708 35709 // Hide from page if we have 1 or less pages 35710 // if directed to hide empty 35711 if (pageCount <= 1) { 35712 scope.Hide = scope.hideIfEmpty; 35713 } 35714 } 35715 35716 // Internal Pagination Click Action 35717 function internalAction(scope, page) { 35718 // Block clicks we try to load the active page 35719 if (scope.page == page) { 35720 return; 35721 } 35722 35723 // Update the page in scope and fire any paging actions 35724 scope.page = page; 35725 scope.paginationAction({ 35726 page: page 35727 }); 35728 35729 // If allowed scroll up to the top of the page 35730 if (scope.scrollTop) { 35731 scrollTo(0, 0); 35732 } 35733 } 35734 35735 // Add Range of Numbers 35736 function addRange(start, finish, scope) { 35737 var i = 0; 35738 for (i = start; i <= finish; i++) { 35739 var item = { 35740 value: i.toString(), 35741 liClass: scope.page == i ? scope.activeClass : 'waves-effect', 35742 action: function() { 35743 internalAction(scope, this.value); 35744 } 35745 }; 35746 35747 scope.List.push(item); 35748 } 35749 } 35750 35751 // Add Dots ie: 1 2 [...] 10 11 12 [...] 56 57 35752 function addDots(scope) { 35753 scope.List.push({ 35754 value: scope.dots 35755 }); 35756 } 35757 35758 // Add First Pages 35759 function addFirst(scope, next) { 35760 addRange(1, 2, scope); 35761 35762 // We ignore dots if the next value is 3 35763 // ie: 1 2 [...] 3 4 5 becomes just 1 2 3 4 5 35764 if (next != 3) { 35765 addDots(scope); 35766 } 35767 } 35768 35769 /** 35770 * Add the first, previous, next, and last buttons if desired 35771 * The logic is defined by the mode of interest 35772 * This method will simply return if the scope.showPrevNext is false 35773 * This method will simply return if there are no pages to display 35774 * 35775 * @param {Object} scope - The local directive scope object 35776 * @param {int} pageCount - The last page number or total page count 35777 * @param {string} mode - The mode of interest either prev or last 35778 */ 35779 function addPrevNext(scope, pageCount, mode){ 35780 35781 // Ignore if we are not showing 35782 // or there are no pages to display 35783 if (!scope.showPrevNext || pageCount < 1) { return; } 35784 35785 // Local variables to help determine logic 35786 var disabled, alpha, beta; 35787 35788 35789 // Determine logic based on the mode of interest 35790 // Calculate the previous / next page and if the click actions are allowed 35791 if(mode === 'prev') { 35792 35793 disabled = scope.page - 1 <= 0; 35794 var prevPage = scope.page - 1 <= 0 ? 1 : scope.page - 1; 35795 35796 alpha = { value : "<<", title: 'First Page', page: 1 }; 35797 beta = { value: "<", title: 'Previous Page', page: prevPage }; 35798 35799 } else { 35800 35801 disabled = scope.page + 1 > pageCount; 35802 var nextPage = scope.page + 1 >= pageCount ? pageCount : scope.page + 1; 35803 35804 alpha = { value : ">", title: 'Next Page', page: nextPage }; 35805 beta = { value: ">>", title: 'Last Page', page: pageCount }; 35806 } 35807 35808 // Create the Add Item Function 35809 var addItem = function(item, disabled){ 35810 scope.List.push({ 35811 value: item.value, 35812 title: item.title, 35813 liClass: disabled ? scope.disabledClass : '', 35814 action: function(){ 35815 if(!disabled) { 35816 internalAction(scope, item.page); 35817 } 35818 } 35819 }); 35820 }; 35821 35822 // Add our items 35823 addItem(alpha, disabled); 35824 addItem(beta, disabled); 35825 } 35826 35827 function addLast(pageCount, scope, prev) { 35828 // We ignore dots if the previous value is one less that our start range
35829 // ie: 1 2 3 4 [...] 5 6 becomes just 1 2 3 4 5 6 35830 if (prev != pageCount -2) { 35831 addDots(scope); 35832 } 35833 35834 addRange(pageCount -1, pageCount, scope); 35835 } 35836 35837 // Main build function 35838 function build(scope, attrs) { 35839 35840 // Block divide by 0 and empty page size 35841 if (!scope.pageSize || scope.pageSize < 0) 35842 { 35843 return; 35844 } 35845 35846 // Assign scope values 35847 setScopeValues(scope, attrs); 35848 35849 // local variables 35850 var start, 35851 size = scope.adjacent * 2, 35852 pageCount = Math.ceil(scope.total / scope.pageSize); 35853 35854 // Validation Scope 35855 validateScopeValues(scope, pageCount); 35856 35857 // Add the Next and Previous buttons to our list 35858 addPrevNext(scope, pageCount, 'prev'); 35859 35860 if (pageCount < (5 + size)) { 35861 35862 start = 1; 35863 addRange(start, pageCount, scope); 35864 35865 } else { 35866 35867 var finish; 35868 35869 if (scope.page <= (1 + size)) { 35870 35871 start = 1; 35872 finish = 2 + size + (scope.adjacent - 1); 35873 35874 addRange(start, finish, scope); 35875 addLast(pageCount, scope, finish); 35876 35877 } else if (pageCount - size > scope.page && scope.page > size) { 35878 35879 start = scope.page - scope.adjacent; 35880 finish = scope.page + scope.adjacent; 35881 35882 addFirst(scope, start); 35883 addRange(start, finish, scope); 35884 addLast(pageCount, scope, finish); 35885 35886 } else { 35887 35888 start = pageCount - (1 + size + (scope.adjacent - 1)); 35889 finish = pageCount; 35890 35891 addFirst(scope, start); 35892 addRange(start, finish, scope); 35893 35894 } 35895 } 35896 addPrevNext(scope, pageCount, 'next'); 35897 } 35898 35899 return { 35900 restrict: 'EA', 35901 scope: { 35902 page: '=', 35903 pageSize: '=', 35904 total: '=', 35905 dots: '@', 35906 hideIfEmpty: '@', 35907 adjacent: '@', 35908 scrollTop: '@', 35909 showPrevNext: '@', 35910 paginationAction: '&', 35911 ulClass: '=?' 35912 }, 35913 template: 35914 '<ul ng-hide="Hide" ng-class="ulClass"> ' + 35915 '<li ' + 35916 'ng-class="Item.liClass" ' + 35917 'ng-click="Item.action()" ' + 35918 'ng-repeat="Item in List"> ' + 35919 '<a href> ' + 35920 '<span ng-bind="Item.value"></span> ' + 35921 '</a>' + 35922 '</ul>', 35923 link: function (scope, element, attrs) { 35924 35925 // Hook in our watched items 35926 scope.$watchCollection('[page, total, pageSize]', function () { 35927 build(scope, attrs); 35928 }); 35929 } 35930 }; 35931 }); 35932 35933 /* example usage: 35934 <!-- Modal Trigger --> 35935 <a class='btn' href='#demoModal' modal>show Modal</a> 35936 <!-- Modal Structure --> 35937 <div id="demoModal" class="modal"> 35938 <div class="modal-content"> 35939 <h4>Modal Header</h4> 35940 <p>A bunch of text</p> 35941 </div> 35942 <div class="modal-footer"> 35943 <a href="#!" class=" modal-action modal-close waves-effect waves-green btn-flat">Agree</a> 35944 </div> 35945 </div> 35946 */ 35947 angular.module("ui.materialize.modal", []) 35948 .directive("modal", ["$compile", "$timeout", function ($compile, $timeout) { 35949 return { 35950 scope: { 35951 dismissible: "=", 35952 opacity: "@", 35953 inDuration: "@", 35954 outDuration: "@" 35955 }, 35956 link: function (scope, element, attrs) { 35957 $timeout(function () { 35958 $compile(element.contents())(scope); 35959 element.leanModal({
35960 dismissible: (angular.isDefined(scope.dismissible)) ? scope.dismissible : undefined, 35961 opacity: (angular.isDefined(scope.opacity)) ? scope.opacity : undefined, 35962 in_duration: (angular.isDefined(scope.inDuration)) ? scope.inDuration : undefined, 35963 out_duration: (angular.isDefined(scope.outDuration)) ? scope.outDuration : undefined 35964 }); 35965 }); 35966 } 35967 }; 35968 }]); 35969 35970 35971 /* example usage: 35972 <!-- data-position can be : bottom, top, left, or right --> 35973 <!-- data-delay controls delay before tooltip shows (in milliseconds)--> 35974 <a class="btn" tooltipped data-position="bottom" data-delay="50" data-tooltip="I am tooltip">Hover me!</a> 35975 */ 35976 angular.module("ui.materialize.tooltipped", []) 35977 .directive("tooltipped", ["$compile", "$timeout", function ($compile, $timeout) { 35978 return { 35979 restrict: "EA", 35980 scope: true, 35981 link: function (scope, element, attrs) { 35982 element.addClass("tooltipped"); 35983 $compile(element.contents())(scope); 35984 35985 $timeout(function () { 35986 element.tooltip(); 35987 }); 35988 scope.$on('$destroy', function () { 35989 element.tooltip("remove"); 35990 }); 35991 } 35992 }; 35993 }]); 35994 35995 /* example usage: 35996 <!-- normal materialboxed --> 35997 <img materialboxed class="materialboxed responsive-img" width="650" src="images/sample-1.jpg"> 35998 35999 <!-- caption materialboxed --> 36000 <img materialboxed class="materialboxed" data-caption="A picture of some deer and tons of trees" width="250" src="iamges/nature_portrait_by_pw_fotografie-d63tx0n.jpg"> 36001 36002 */ 36003 angular.module("ui.materialize.materialboxed", []) 36004 .directive("materialboxed", ["$timeout", function($timeout){ 36005 return { 36006 restrict: 'A', 36007 link: function(scope, element, attrs) { 36008 36009 $timeout(function(){ 36010 element.materialbox(); 36011 }); 36012 36013 } 36014 }; 36015 }]); 36016 36017}(angular)); 36018 36019/** 36020 * Created by rrgood on 3/7/16. 36021 */ 36022'use strict'; 36023var app = angular.module("DrawingsTool", ['ngRoute','LocalStorageModule','ngAnimate','ui.materialize']); 36024 36025app.config(['$routeProvider', function ($routeProvider) { 36026 $routeProvider 36027 .when('/', {templateUrl: 'partials/main.html', controller: 'MainCtrl'}) 36028 .when('/main/:part?', {templateUrl: 'partials/main.html', controller: 'MainCtrl'}) 36029 .when('/details', {templateUrl: 'partials/details.html', controller: 'DetailsCtrl'}) 36030 .otherwise({redirectTo: ''}); 36031}]); 36032 36033//setup default settings for http calls 36034app.config(['$httpProvider', function ($httpProvider){ 36035 // Use x-www-form-urlencoded Content-Type 36036 $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'; 36037 var param = function(obj) { 36038 var query = '', name, value, fullSubName, subName, subValue, innerObj, i; 36039 for(name in obj) { 36040 value = obj[name]; 36041 if(value instanceof Array) { 36042 for(i=0; i<value.length; ++i) { 36043 subValue = value[i]; 36044 fullSubName = name + '[' + i + ']'; 36045 innerObj = {}; 36046 innerObj[fullSubName] = subValue; 36047 query += param(innerObj) + '&'; 36048 } 36049 } 36050 else if(value instanceof Object) { 36051 for(subName in value) { 36052 subValue = value[subName]; 36053 fullSubName = name + '[' + subName + ']'; 36054 innerObj = {}; 36055 innerObj[fullSubName] = subValue; 36056 query += param(innerObj) + '&'; 36057 } 36058 } 36059 else if(value !== undefined && value !== null) 36060 query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&'; 36061 } 36062 return query.length ? query.substr(0, query.length - 1) : query; 36063 }; 36064 // Override $http service's default transformRequest 36065 $httpProvider.defaults.transformRequest = [function(data) { 36066 return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data; 36067 }]; 36068}]); 36069 36070//Constants 36071app.constant('appConstants',{
36072 serverUrl: 'https://www.rittal.com/us_en/apps/services/drawing.php', 36073 fileDwgUrl: 'https://www.rittal.com/us_en/apps/drawing/files/dwg/', 36074 filePdfUrl: 'https://www.rittal.com/us_en/apps/drawing/files/pdf/', 36075 fileStpUrl: 'https://www.rittal.com/us_en/apps/drawing/files/stp/' 36076}); 36077 36078app.directive('animClass',['$rootScope',function($rootScope){ 36079 return { 36080 link: function(scope, elm, attrs){ 36081 //var enterClass = $route.current.animate;//change this to get what class direction slide*** 36082 var enterClass = $rootScope.slideClass; 36083 elm.addClass(enterClass); 36084 scope.$on('$destroy',function(){ 36085 elm.removeClass(enterClass); 36086 //elm.addClass($route.current.animate) 36087 elm.addClass($rootScope.slideClass) 36088 }) 36089 } 36090 } 36091}]); 36092/** 36093 * Created by rrgood on 3/7/16. 36094 */ 36095app.controller('DetailsCtrl', ['$scope','$rootScope','$location', '$sce' ,'PartModel', 'appConstants', function($scope, $rootScope, $location, $sce, PartModel, appConstants){ 36096 36097 //get part object 36098 $scope.part = PartModel.getPart(); 36099 if(!$scope.part){ 36100 $location.path('/main'); 36101 return; 36102 } 36103 $scope.appConstants = appConstants; 36104 $scope.pageUrl = $location.absUrl().replace('details','main') + '/' + $scope.part.partNumber; 36105 //console.log('Part: '+angular.toJson($scope.part)); 36106 //console.log('URL: '+$scope.appConstants.fileDwgUrl+$scope.part.fileDwg); 36107 //$scope.setPageHeight(); 36108 36109 $scope.getUrl = function(part, iframe){ 36110 var iframe = iframe || false; 36111 part = part.substring(0, 4)+'.'+part.substring(4, part.length); 36112 var url = 'https://rittal-usa-embedded.partcommunity.com/3d-cad-models/?part='+part+'&catalog=rittal&hidePortlets=searchHeader'; 36113 if(iframe) return $sce.trustAsResourceUrl(url); 36114 return url; 36115 } 36116 36117 $scope.copyToClipboard = function(string) { 36118 navigator.clipboard.writeText(string) 36119 .then(alert('URL copied to your clipboard')); 36120 } 36121 36122 $scope.goBack = function(){ 36123 $rootScope.slideClass = 'slideRight'; 36124 parent.passedPartNumber = '';//***reset url params 36125 $location.path('/main'); 36126 }; 36127 36128}]); 36129/** 36130 * Created by rrgood on 3/7/16. 36131 */ 36132app.controller('MainCtrl', ['$scope','$rootScope','$location','$timeout','RESTfulCallsService','PartModel','$routeParams', function($scope, $rootScope, $location, $timeout, RESTfulCallsService, PartModel, $routeParams){ 36133 36134 //options params 36135 $scope.optionList = {}; 36136 $scope.optionList.partNumberList = []; 36137 $scope.optionList.mainParts = []; 36138 $scope.optionList.descList = []; 36139 $scope.optionList.heightList = []; 36140 $scope.optionList.widthList = []; 36141 $scope.optionList.depthList = []; 36142 $scope.optionList.materialList = []; 36143 $scope.optionList.mainMaterialList = []; 36144 36145 //defaults 36146 $scope.dataLoaded = false; 36147 $scope.partsDataAll = [];//create object to hold all parts 36148 $scope.partsDataCur = [];//create object to hold current filtered parts 36149 //params 36150 $scope.paramsObject = {}; 36151 $scope.paramsObject.paramsSelected = false; 36152 $scope.paramsObject.pn = ''; 36153 $scope.paramsObject.part = ''; 36154 $scope.paramsObject.desc = ''; 36155 $scope.paramsObject.height = ''; 36156 $scope.paramsObject.width = ''; 36157 $scope.paramsObject.depth = ''; 36158 $scope.paramsObject.materialNumber = ''; 36159 $scope.paramsObject.material = ''; 36160 $scope.timeoutPromise; 36161 $scope.token = ''; 36162 36163 //call service to retrieve parts 36164 $scope.retrievePartsData = function(){ 36165 $scope.pageLoader = true; 36166 RESTfulCallsService.getAllParts() 36167 .success(function(data){ 36168 if(parent.passedPartNumber!==''){ 36169 // console.log('PASSED PART NUMBER: '+parent.passedPartNumber); 36170 for(var p in data.data){ 36171 if(data.data[p].partNumber==parent.passedPartNumber) { 36172 //console.log('part: ' + angular.toJson(data.data[p])); 36173 $scope.goToDetails(data.data[p]); 36174 } 36175 } 36176 } 36177 $scope.partsDataAll = data.data; 36178 $scope.populateLists();//populate selection lists 36179 $scope.dataLoaded = true; 36180 $scope.pageLoader = false;
36181 }).error(function(err,res){ 36182 alert('Error!'); 36183 $scope.dataLoaded = false; 36184 $scope.pageLoader = false; 36185 }); 36186 }; 36187 36188 $scope.removeDupes = function(list){ 36189 var result = [];//remove duplicates 36190 $.each(list, function(i, e) { 36191 if ($.inArray(e, result) == -1) result.push(angular.copy(e)); 36192 }); 36193 return result;//sort list 36194 //return result.sort(function(a, b){return a-b});//sort list 36195 }; 36196 36197 $scope.removeDupesDims = function(list){ 36198 var usedObjects = {}; 36199 for (var i = list.length - 1;i >= 0;i--) { 36200 var so = JSON.stringify(list[i]); 36201 if (usedObjects[so]) { 36202 list.splice(i, 1); 36203 } else { 36204 usedObjects[so] = true; 36205 } 36206 } 36207 //console.log('list: '+angular.toJson(list)); 36208 return list; 36209 }; 36210 36211 $scope.getPNList = function(list){ 36212 $scope.optionList.partNumberList = $scope.removeDupes(list); 36213 $scope.optionList.mainParts = angular.copy($scope.optionList.partNumberList); 36214 }; 36215 36216 $scope.getDescList = function(list){ 36217 $scope.optionList.descList = $scope.removeDupes(list).sort(); 36218 }; 36219 36220 $scope.getHeightList = function(list){ 36221 $scope.optionList.heightList = $scope.removeDupesDims(list); 36222 }; 36223 36224 $scope.getWidthList = function(list){ 36225 $scope.optionList.widthList = $scope.removeDupesDims(list); 36226 }; 36227 36228 $scope.getDepthList = function(list){ 36229 $scope.optionList.depthList = $scope.removeDupesDims(list); 36230 }; 36231 36232 $scope.getMaterialList = function(list){ 36233 $scope.optionList.materialList = $scope.removeDupes(list); 36234 $scope.optionList.mainMaterialList = angular.copy($scope.optionList.materialList); 36235 }; 36236 36237 $scope.populateLists = function(){ 36238 var list = $scope.partsDataAll; 36239 if($scope.paramsObject.paramsSelected){//use cur list if params are chosen 36240 list = $scope.partsDataCur; 36241 } 36242 var listP = [];// $scope.optionList.partNumberList; 36243 var listDesc = [];// $scope.optionList.descList; 36244 var listM = [];// $scope.optionList.materialList; 36245 var listF = [];// $scope.optionList.familyList; 36246 var listH = [];// $scope.optionList.heightList; 36247 var listW = [];// $scope.optionList.widthList; 36248 var listD = [];// $scope.optionList.depthList; 36249 var params = {}; 36250 for(var l in list){ 36251 listP.push(list[l].partNumber); 36252 if(list[l].description !== null && list[l].description !== ''){ 36253 listDesc.push(list[l].description); 36254 params['part_number'] = list[l].partNumber; 36255 } 36256 var objH = {}; 36257 objH.standard = parseInt(list[l].heightStandard); 36258 params['height'] = list[l].heightMetric; 36259 objH.metric = parseInt(list[l].heightMetric); 36260 listH.push(objH); 36261 var objW = {}; 36262 objW.standard = parseInt(list[l].widthStandard); 36263 objW.metric = parseInt(list[l].widthMetric); 36264 params['width'] = list[l].widthMetric; 36265 listW.push(objW); 36266 var objD = {}; 36267 objD.standard = parseInt(list[l].depthStandard); 36268 objD.metric = parseInt(list[l].depthMetric); 36269 params['depth'] = list[l].depthMetric; 36270 listD.push(objD); 36271 if(list[l].materialNumber != null && list[l].materialNumber != '') 36272 listM.push(list[l].materialNumber); 36273 } 36274 $scope.getPNList(listP); 36275 $scope.getDescList(listDesc); 36276 $scope.getMaterialList(listM); 36277 // $scope.getFamilyList(listF); 36278 $scope.getHeightList(listH); 36279 $scope.getWidthList(listW); 36280 $scope.getDepthList(listD); 36281 }; 36282 36283 $scope.selectPn = function(part){ 36284 $scope.paramsObject.pn = part; 36285 $('.chosen-container.part-number').trigger('click'); 36286 }; 36287 36288 $scope.selectMaterialNumber = function(part){ 36289 $scope.paramsObject.materialNumber = part; 36290 $('.chosen-container.material-number').trigger('click'); 36291 }; 36292 36293 $scope.filterPartList = function(){ 36294 if($scope.paramsObject.part == ''){ 36295 $scope.optionList.partNumberList = []; 36296 $scope.optionList.mainParts.filter(function(x){ $scope.optionList.partNumberList.push(x); return true; }); 36297 } else { 36298 var list = []; 36299 var partNumber = $scope.paramsObject.part.replace(/[.-]/g,''); 36300 $scope.optionList.mainParts.filter(function(pn){ 36301 if(pn.indexOf(partNumber) > -1 || partNumber === ''){ 36302 list.push(pn); 36303 } 36304 }); 36305 $scope.optionList.partNumberList = list; 36306 } 36307 }; 36308 36309 $scope.filterMaterialList = function(){ 36310 if($scope.paramsObject.material == ''){ 36311 $scope.optionList.materialList = []; 36312 $scope.optionList.mainMaterialList.filter(function(x){ $scope.optionList.materialList.push(x); return true; }); 36313 } else { 36314 var list = []; 36315 var partNumber = $scope.paramsObject.material; 36316 $scope.optionList.mainMaterialList.filter(function(pn){ 36317 if(pn.indexOf(partNumber) > -1 || partNumber === ''){ 36318 list.push(pn); 36319 } 36320 }); 36321 $scope.optionList.materialList = list; 36322 } 36323 }; 36324 36325 //*******Call on load functions**********//
36326 if($routeParams.part) 36327 parent.passedPartNumber = $routeParams.part; 36328 $scope.retrievePartsData();//make call for parts data on load 36329 //*******End on load functions***********// 36330 36331 //watch for parametric selections 36332 $scope.$watch("paramsObject.pn", function(newValue, oldValue){ 36333 var strNew = typeof newValue !== 'undefined' ? newValue.toString() : ""; 36334 var strOld = typeof oldValue !== 'undefined' ? oldValue.toString() : ""; 36335 if(strNew !== strOld) { 36336 $scope.refreshResultsList(); 36337 } 36338 }); 36339 $scope.$watch("paramsObject.part", function(newValue, oldValue){ 36340 var strNew = typeof newValue !== 'undefined' ? newValue.toString() : ""; 36341 var strOld = typeof oldValue !== 'undefined' ? oldValue.toString() : ""; 36342 if(strNew !== strOld) { 36343 $scope.filterPartList(); 36344 } 36345 }); 36346 $scope.$watch("paramsObject.desc", function(newValue, oldValue){ 36347 //console.log("OLD DESC: "+oldValue); 36348 //console.log("NEW DESC: "+newValue); 36349 if(newValue.toString()!==oldValue.toString()) { 36350 $scope.refreshResultsList(); 36351 } 36352 }); 36353 $scope.$watch("paramsObject.height", function(newValue, oldValue){ 36354 //console.log("OLD H: "+oldValue); 36355 //console.log("NEW H: "+newValue); 36356 if(newValue.toString()!==oldValue.toString()) { 36357 $scope.refreshResultsList(); 36358 } 36359 }); 36360 $scope.$watch("paramsObject.width", function(newValue, oldValue){ 36361 //console.log("OLD W: "+oldValue); 36362 //console.log("NEW W: "+newValue); 36363 if(newValue.toString()!==oldValue.toString()) { 36364 $scope.refreshResultsList(); 36365 } 36366 }); 36367 $scope.$watch("paramsObject.depth", function(newValue, oldValue){ 36368 //console.log("OLD D: "+oldValue); 36369 //console.log("NEW D: "+newValue); 36370 if(newValue.toString()!==oldValue.toString()) { 36371 $scope.refreshResultsList(); 36372 } 36373 }); 36374 $scope.$watch("paramsObject.materialNumber", function(newValue, oldValue){ 36375 var strNew = typeof newValue !== 'undefined' ? newValue.toString() : ""; 36376 var strOld = typeof oldValue !== 'undefined' ? oldValue.toString() : ""; 36377 if(strNew !== strOld) { 36378 $scope.refreshResultsList(); 36379 } 36380 }); 36381 $scope.$watch("paramsObject.material", function(newValue, oldValue){ 36382 var strNew = typeof newValue !== 'undefined' ? newValue.toString() : ""; 36383 var strOld = typeof oldValue !== 'undefined' ? oldValue.toString() : ""; 36384 if(strNew !== strOld) { 36385 $scope.filterMaterialList(); 36386 } 36387 }); 36388 36389 $scope.refreshResultsList = function(){ 36390 //console.log("refreshResultsList Called!"); 36391 if ($scope.paramsObject.pn !== '' || $scope.paramsObject.desc !== '' || $scope.paramsObject.height !== '' || $scope.paramsObject.width !== '' || $scope.paramsObject.depth !== '' || $scope.paramsObject.materialNumber !== '') { 36392 $scope.paramsObject.paramsSelected = true; 36393 $scope.filterResults();//filter results down 36394 $scope.populateLists();//repopulate param lists 36395 } else { 36396 $scope.paramsObject.paramsSelected = false; 36397 $scope.populateLists();//repopulate param lists 36398 $scope.partsDataCur = [];//clear out list if no params selected 36399 } 36400 }; 36401 36402 $scope.filterResults = function(){ 36403 //console.log("filterResults Called!"); 36404 $scope.partsDataCur = [];//clear current list before new query 36405 for(var dp in $scope.partsDataAll){ 36406 //console.log("ph* "+$scope.partsDataAll[sp].partNumber); 36407 if($scope.filterPN(dp) && $scope.filterDesc(dp) && $scope.filterHeight(dp) && $scope.filterWidth(dp) && $scope.filterDepth(dp) && $scope.filterMaterial(dp)){ 36408 $scope.partsDataCur.push($scope.partsDataAll[dp]); 36409 } 36410 } 36411 }; 36412 36413 $scope.filterPN = function(indexPN){ 36414 //check if searched substring is part of full pn OR no pn substring entered 36415 var partNumber = $scope.paramsObject.pn; 36416 return $scope.partsDataAll[indexPN].partNumber.indexOf(partNumber) > -1 || partNumber === ''; 36417 }; 36418 36419 $scope.filterDesc = function(indexDesc){ 36420 //check if searched substring is part of full pn OR no pn substring entered 36421 var check = false;
36422 if($scope.paramsObject.desc!==null && $scope.partsDataAll[indexDesc].description!==null){ 36423 check = $scope.partsDataAll[indexDesc].description.indexOf($scope.paramsObject.desc) > -1; 36424 } 36425 return check; 36426 //return $scope.partsDataAll[indexDesc].description.indexOf($scope.paramsObject.desc) > -1 || $scope.paramsObject.desc===''; 36427 }; 36428 36429 $scope.filterHeight = function(indexH){ 36430 return $scope.partsDataAll[indexH].heightMetric===$scope.paramsObject.height || $scope.paramsObject.height===''; 36431 }; 36432 36433 $scope.filterWidth = function(indexW){ 36434 return $scope.partsDataAll[indexW].widthMetric===$scope.paramsObject.width || $scope.paramsObject.width===''; 36435 }; 36436 36437 $scope.filterDepth = function(indexD){ 36438 return $scope.partsDataAll[indexD].depthMetric===$scope.paramsObject.depth || $scope.paramsObject.depth===''; 36439 }; 36440 36441 $scope.filterMaterial = function(index){ 36442 return ($scope.paramsObject.materialNumber != '' && $scope.partsDataAll[index].materialNumber != null && $scope.partsDataAll[index].materialNumber.indexOf($scope.paramsObject.materialNumber) > -1) || $scope.paramsObject.materialNumber === ''; 36443 }; 36444 36445 $scope.resetParams = function(){ 36446 $scope.paramsObject = {}; 36447 $scope.paramsObject.paramsSelected = false; 36448 $scope.paramsObject.pn = ''; 36449 $scope.paramsObject.part = ''; 36450 $scope.paramsObject.desc = ''; 36451 $scope.paramsObject.height = ''; 36452 $scope.paramsObject.width = ''; 36453 $scope.paramsObject.depth = ''; 36454 $scope.paramsObject.materialNumber = ''; 36455 $scope.paramsObject.material = ''; 36456 }; 36457 36458 $scope.goToDetails = function(dp){ 36459 PartModel.setPart(dp); 36460 //console.log(angular.toJson(dp)); 36461 $rootScope.slideClass = 'slideLeft'; 36462 $location.path('/details'); 36463 }; 36464 36465 // $scope.goBack = function(){ 36466 // $rootScope.slideClass = 'slideRight'; 36467 // $location.path('/main'); 36468 // }; 36469 36470}]); 36471/** 36472 * Created by rrgood on 3/10/16. 36473 */ 36474app.service('PartModel', function(){ 36475 this.part = null; 36476 36477 this.setPart = function(p) { 36478 this.part = p; 36479 }; 36480 this.getPart = function() { 36481 return this.part; 36482 }; 36483 36484}); 36485/** 36486 * Created by rrgood on 3/7/16. 36487 */ 36488app.service('RESTfulCallsService', ['$http','$rootScope','appConstants', function($http,$rootScope,appConstants) { 36489 36490 this.getAllParts = function(){ 36491 //console.log('Call service to retrieve all parts data...'); 36492 var query = []; 36493 var promise = $http.get(appConstants.serverUrl+'?get=ALL') 36494 .success(function(data){ 36495 //console.log("Retrieved Parts Data: "+angular.toJson(data)); 36496 return data; 36497 }).error(function(err,res){ 36498 console.log("An ERROR occurred while retrieving parts data..."); 36499 }); 36500 return promise; 36501 }; 36502}]); 36503/** 36504 * Created by rrgood on 10/30/15. 36505 */ 36506 36507//setup model 36508$(document).on('keydown', '.chosen-search input', function(e){ 36509 var obj = $('.chosen-results .highlighted'); 36510 var code = e.keyCode || e.which; 36511 var scroll = false; 36512 if(code == 40){ 36513 if(obj.length > 0){ 36514 scroll = obj.next(); 36515 if(scroll.length == 0) return; 36516 obj.removeClass('highlighted'); 36517 scroll.addClass('highlighted'); 36518 } else { 36519 scroll = $('.chosen-results li:first'); 36520 scroll.addClass('highlighted'); 36521 } 36522 } 36523 if(code == 38){ 36524 if(obj.length > 0){ 36525 scroll = obj.prev(); 36526 if(scroll.length == 0) return; 36527 obj.removeClass('highlighted'); 36528 scroll.addClass('highlighted'); 36529 } else { 36530 scroll = $('.chosen-results li:last'); 36531 scroll.addClass('highlighted'); 36532 } 36533 } 36534 if(scroll){ 36535 console.log(scroll.offset().top,$('.chosen-results').offset().top,(scroll.offset().top-$('.chosen-results').offset().top)+5); 36536 $('.chosen-results').scrollTop((scroll.offset().top-$('.chosen-results').offset().top)+5); 36537 } 36538 if(code == 13){ 36539 if(obj.length > 0){ 36540 obj.trigger('click'); 36541 } 36542 } 36543}); 36544$(document).on('click','.chosen-container',function(e){ 36545 if($(e.target).parents('.chosen-drop').length > 0){ 36546 return ; 36547 } 36548 if($(this).hasClass('chosen-with-drop')){ 36549 $(this).removeClass('chosen-with-drop'); 36550 } else { 36551 $(this).addClass('chosen-with-drop'); 36552 $(this).find('input').focus(); 36553 $(this).find('.chosen-results .highlighted').removeClass('highlighted'); 36554 $(this).find('.chosen-results li:first').addClass('highlighted'); 36555 } 36556}); 36557$(document).on('mouseenter','.chosen-results li', function(){ 36558 //$('.chosen-results .highlighted').removeClass('highlighted'); 36559 $(this).addClass('highlighted'); 36560}) 36561$(document).on('mouseleave','.chosen-results li', function(){ 36562 $(this).removeClass('highlighted'); 36563}); 36564
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.