1/** 2 * @license AngularJS v1.2.0rc1 3 * (c) 2010-2012 Google, Inc. http://angularjs.org 4 * License: MIT 5 */ 6(function(window, document, undefined) {'use strict'; 7 8/** 9 * @description 10 * 11 * This object provides a utility for producing rich Error messages within 12 * Angular. It can be called as follows: 13 * 14 * var exampleMinErr = minErr('example'); 15 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 16 * 17 * The above creates an instance of minErr in the example namespace. The 18 * resulting error will have a namespaced error code of example.one. The 19 * resulting error will replace {0} with the value of foo, and {1} with the 20 * value of bar. The object is not restricted in the number of arguments it can 21 * take. 22 * 23 * If fewer arguments are specified than necessary for interpolation, the extra 24 * interpolation markers will be preserved in the final string. 25 * 26 * Since data will be parsed statically during a build step, some restrictions 27 * are applied with respect to how minErr instances are created and called. 28 * Instances should have names of the form namespaceMinErr for a minErr created 29 * using minErr('namespace') . Error codes, namespaces and template strings 30 * should all be static strings, not variables or general expressions. 31 * 32 * @param {string} module The namespace to use for the new minErr instance. 33 * @returns {function(string, string, ...): Error} instance 34 */ 35 36function minErr(module) { 37 return function () { 38 var prefix = '[' + (module ? module + ':' : '') + arguments[0] + '] ', 39 template = arguments[1], 40 templateArgs = arguments, 41 message; 42 43 message = prefix + template.replace(/\{\d+\}/g, function (match) { 44 var index = +match.slice(1, -1), arg; 45 46 if (index + 2 < templateArgs.length) { 47 arg = templateArgs[index + 2]; 48 if (isFunction(arg)) { 49 return arg.toString().replace(/ ?\{[\s\S]*$/, ''); 50 } else if (isUndefined(arg)) { 51 return 'undefined'; 52 } else if (!isString(arg)) { 53 return toJson(arg); 54 } 55 return arg; 56 } 57 return match; 58 }); 59 60 return new Error(message); 61 }; 62} 63 64//////////////////////////////////// 65 66/** 67 * hasOwnProperty may be overwritten by a property of the same name, or entirely 68 * absent from an object that does not inherit Object.prototype; this copy is 69 * used instead 70 */ 71var hasOwnPropertyFn = Object.prototype.hasOwnProperty; 72var hasOwnPropertyLocal = function(obj, key) { 73 return hasOwnPropertyFn.call(obj, key); 74}; 75 76/** 77 * @ngdoc function 78 * @name angular.lowercase 79 * @function 80 * 81 * @description Converts the specified string to lowercase. 82 * @param {string} string String to be converted to lowercase. 83 * @returns {string} Lowercased string. 84 */ 85var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; 86 87 88/** 89 * @ngdoc function 90 * @name angular.uppercase 91 * @function 92 * 93 * @description Converts the specified string to uppercase. 94 * @param {string} string String to be converted to uppercase. 95 * @returns {string} Uppercased string. 96 */ 97var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; 98 99 100var manualLowercase = function(s) { 101 return isString(s) 102 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 103 : s; 104}; 105var manualUppercase = function(s) { 106 return isString(s) 107 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 108 : s; 109}; 110 111 112// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 113// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 114// with correct but slower alternatives. 115if ('i' !== 'I'.toLowerCase()) { 116 lowercase = manualLowercase; 117 uppercase = manualUppercase; 118} 119 120 121var /** holds major version number for IE or NaN for real browsers */ 122 msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]), 123 jqLite, // delay binding since jQuery could be loaded after us. 124 jQuery, // delay binding 125 slice = [].slice, 126 push = [].push, 127 toString = Object.prototype.toString, 128 ngMinErr = minErr('ng'), 129 130 131 _angular = window.angular, 132 /** @name angular */ 133 angular = window.angular || (window.angular = {}), 134 angularModule, 135 nodeName_, 136 uid = ['0', '0', '0']; 137 138/** 139 * @private 140 * @param {*} obj 141 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...) 142 */ 143function isArrayLike(obj) { 144 if (obj == null || isWindow(obj)) { 145 return false;
146 } 147 148 var length = obj.length; 149 150 if (obj.nodeType === 1 && length) { 151 return true; 152 } 153 154 return isArray(obj) || !isFunction(obj) && ( 155 length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj 156 ); 157} 158 159/** 160 * @ngdoc function 161 * @name angular.forEach 162 * @function 163 * 164 * @description 165 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 166 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` 167 * is the value of an object property or an array element and `key` is the object property key or 168 * array element index. Specifying a `context` for the function is optional. 169 * 170 * Note: this function was previously known as `angular.foreach`. 171 * 172 <pre> 173 var values = {name: 'misko', gender: 'male'}; 174 var log = []; 175 angular.forEach(values, function(value, key){ 176 this.push(key + ': ' + value); 177 }, log); 178 expect(log).toEqual(['name: misko', 'gender:male']); 179 </pre> 180 * 181 * @param {Object|Array} obj Object to iterate over. 182 * @param {Function} iterator Iterator function. 183 * @param {Object=} context Object to become context (`this`) for the iterator function. 184 * @returns {Object|Array} Reference to `obj`. 185 */ 186function forEach(obj, iterator, context) { 187 var key; 188 if (obj) { 189 if (isFunction(obj)){ 190 for (key in obj) { 191 if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) { 192 iterator.call(context, obj[key], key); 193 } 194 } 195 } else if (obj.forEach && obj.forEach !== forEach) { 196 obj.forEach(iterator, context); 197 } else if (isArrayLike(obj)) { 198 for (key = 0; key < obj.length; key++) 199 iterator.call(context, obj[key], key); 200 } else { 201 for (key in obj) { 202 if (obj.hasOwnProperty(key)) { 203 iterator.call(context, obj[key], key); 204 } 205 } 206 } 207 } 208 return obj; 209} 210 211function sortedKeys(obj) { 212 var keys = []; 213 for (var key in obj) { 214 if (obj.hasOwnProperty(key)) { 215 keys.push(key); 216 } 217 } 218 return keys.sort(); 219} 220 221function forEachSorted(obj, iterator, context) { 222 var keys = sortedKeys(obj); 223 for ( var i = 0; i < keys.length; i++) { 224 iterator.call(context, obj[keys[i]], keys[i]); 225 } 226 return keys; 227} 228 229 230/** 231 * when using forEach the params are value, key, but it is often useful to have key, value. 232 * @param {function(string, *)} iteratorFn 233 * @returns {function(*, string)} 234 */ 235function reverseParams(iteratorFn) { 236 return function(value, key) { iteratorFn(key, value) }; 237} 238 239/** 240 * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric 241 * characters such as '012ABC'. The reason why we are not using simply a number counter is that 242 * the number string gets longer over time, and it can also overflow, where as the nextId 243 * will grow much slower, it is a string, and it will never overflow. 244 * 245 * @returns an unique alpha-numeric string 246 */ 247function nextUid() { 248 var index = uid.length; 249 var digit; 250 251 while(index) { 252 index--;
253 digit = uid[index].charCodeAt(0); 254 if (digit == 57 /*'9'*/) { 255 uid[index] = 'A'; 256 return uid.join(''); 257 } 258 if (digit == 90 /*'Z'*/) { 259 uid[index] = '0'; 260 } else { 261 uid[index] = String.fromCharCode(digit + 1); 262 return uid.join(''); 263 } 264 } 265 uid.unshift('0'); 266 return uid.join(''); 267} 268 269 270/** 271 * Set or clear the hashkey for an object. 272 * @param obj object 273 * @param h the hashkey (!truthy to delete the hashkey) 274 */ 275function setHashKey(obj, h) { 276 if (h) { 277 obj.$$hashKey = h; 278 } 279 else { 280 delete obj.$$hashKey; 281 } 282} 283 284/** 285 * @ngdoc function 286 * @name angular.extend 287 * @function 288 * 289 * @description 290 * Extends the destination object `dst` by copying all of the properties from the `src` object(s) 291 * to `dst`. You can specify multiple `src` objects. 292 * 293 * @param {Object} dst Destination object. 294 * @param {...Object} src Source object(s). 295 * @returns {Object} Reference to `dst`. 296 */ 297function extend(dst) { 298 var h = dst.$$hashKey; 299 forEach(arguments, function(obj){ 300 if (obj !== dst) { 301 forEach(obj, function(value, key){ 302 dst[key] = value; 303 }); 304 } 305 }); 306 307 setHashKey(dst,h); 308 return dst; 309} 310 311function int(str) { 312 return parseInt(str, 10); 313} 314 315 316function inherit(parent, extra) { 317 return extend(new (extend(function() {}, {prototype:parent}))(), extra); 318} 319 320/** 321 * @ngdoc function 322 * @name angular.noop 323 * @function 324 * 325 * @description 326 * A function that performs no operations. This function can be useful when writing code in the 327 * functional style. 328 <pre> 329 function foo(callback) { 330 var result = calculateResult(); 331 (callback || angular.noop)(result); 332 } 333 </pre> 334 */ 335function noop() {} 336noop.$inject = []; 337 338 339/** 340 * @ngdoc function 341 * @name angular.identity 342 * @function 343 * 344 * @description 345 * A function that returns its first argument. This function is useful when writing code in the 346 * functional style. 347 * 348 <pre> 349 function transformer(transformationFn, value) { 350 return (transformationFn || angular.identity)(value); 351 }; 352 </pre> 353 */ 354function identity($) {return $;} 355identity.$inject = []; 356 357 358function valueFn(value) {return function() {return value;};} 359 360/** 361 * @ngdoc function 362 * @name angular.isUndefined 363 * @function 364 * 365 * @description 366 * Determines if a reference is undefined. 367 * 368 * @param {*} value Reference to check. 369 * @returns {boolean} True if `value` is undefined. 370 */ 371function isUndefined(value){return typeof value == 'undefined';} 372 373 374/** 375 * @ngdoc function 376 * @name angular.isDefined 377 * @function 378 * 379 * @description 380 * Determines if a reference is defined. 381 * 382 * @param {*} value Reference to check. 383 * @returns {boolean} True if `value` is defined. 384 */ 385function isDefined(value){return typeof value != 'undefined';} 386 387 388/** 389 * @ngdoc function 390 * @name angular.isObject 391 * @function 392 * 393 * @description 394 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 395 * considered to be objects. 396 * 397 * @param {*} value Reference to check. 398 * @returns {boolean} True if `value` is an `Object` but not `null`. 399 */ 400function isObject(value){return value != null && typeof value == 'object';} 401 402 403/** 404 * @ngdoc function 405 * @name angular.isString 406 * @function 407 * 408 * @description 409 * Determines if a reference is a `String`. 410 * 411 * @param {*} value Reference to check. 412 * @returns {boolean} True if `value` is a `String`. 413 */ 414function isString(value){return typeof value == 'string';} 415 416 417/** 418 * @ngdoc function 419 * @name angular.isNumber 420 * @function 421 * 422 * @description 423 * Determines if a reference is a `Number`. 424 * 425 * @param {*} value Reference to check. 426 * @returns {boolean} True if `value` is a `Number`. 427 */ 428function isNumber(value){return typeof value == 'number';} 429 430 431/** 432 * @ngdoc function 433 * @name angular.isDate 434 * @function 435 * 436 * @description 437 * Determines if a value is a date. 438 * 439 * @param {*} value Reference to check. 440 * @returns {boolean} True if `value` is a `Date`. 441 */ 442function isDate(value){ 443 return toString.apply(value) == '[object Date]'; 444} 445 446 447/** 448 * @ngdoc function 449 * @name angular.isArray 450 * @function 451 * 452 * @description 453 * Determines if a reference is an `Array`. 454 * 455 * @param {*} value Reference to check. 456 * @returns {boolean} True if `value` is an `Array`. 457 */ 458function isArray(value) { 459 return toString.apply(value) == '[object Array]'; 460} 461 462 463/** 464 * @ngdoc function 465 * @name angular.isFunction 466 * @function 467 * 468 * @description 469 * Determines if a reference is a `Function`. 470 * 471 * @param {*} value Reference to check. 472 * @returns {boolean} True if `value` is a `Function`. 473 */ 474function isFunction(value){return typeof value == 'function';} 475 476 477/** 478 * Determines if a value is a regular expression object. 479 * 480 * @private 481 * @param {*} value Reference to check. 482 * @returns {boolean} True if `value` is a `RegExp`. 483 */ 484function isRegExp(value) { 485 return toString.apply(value) == '[object RegExp]'; 486} 487 488 489/** 490 * Checks if `obj` is a window object. 491 * 492 * @private 493 * @param {*} obj Object to check 494 * @returns {boolean} True if `obj` is a window obj. 495 */ 496function isWindow(obj) { 497 return obj && obj.document && obj.location && obj.alert && obj.setInterval; 498} 499 500 501function isScope(obj) { 502 return obj && obj.$evalAsync && obj.$watch; 503} 504 505 506function isFile(obj) { 507 return toString.apply(obj) === '[object File]'; 508} 509 510 511function isBoolean(value) { 512 return typeof value == 'boolean'; 513} 514 515 516var trim = (function() { 517 // native trim is way faster: http://jsperf.com/angular-trim-test 518 // but IE doesn't have it... :-( 519 // TODO: we should move this into IE/ES5 polyfill 520 if (!String.prototype.trim) { 521 return function(value) { 522 return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; 523 }; 524 } 525 return function(value) { 526 return isString(value) ? value.trim() : value; 527 }; 528})(); 529 530 531/** 532 * @ngdoc function 533 * @name angular.isElement 534 * @function 535 * 536 * @description 537 * Determines if a reference is a DOM element (or wrapped jQuery element). 538 * 539 * @param {*} value Reference to check. 540 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 541 */ 542function isElement(node) { 543 return node && 544 (node.nodeName // we are a direct element 545 || (node.on && node.find)); // we have an on and find method part of jQuery API 546} 547 548/** 549 * @param str 'key1,key2,...' 550 * @returns {object} in the form of {key1:true, key2:true, ...} 551 */ 552function makeMap(str){ 553 var obj = {}, items = str.split(","), i; 554 for ( i = 0; i < items.length; i++ ) 555 obj[ items[i] ] = true; 556 return obj; 557} 558 559 560if (msie < 9) { 561 nodeName_ = function(element) { 562 element = element.nodeName ? element : element[0]; 563 return (element.scopeName && element.scopeName != 'HTML') 564 ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; 565 }; 566} else { 567 nodeName_ = function(element) { 568 return element.nodeName ? element.nodeName : element[0].nodeName; 569 }; 570} 571 572 573function map(obj, iterator, context) { 574 var results = []; 575 forEach(obj, function(value, index, list) { 576 results.push(iterator.call(context, value, index, list)); 577 }); 578 return results; 579} 580 581 582/** 583 * @description 584 * Determines the number of elements in an array, the number of properties an object has, or 585 * the length of a string. 586 *
587 * Note: This function is used to augment the Object type in Angular expressions. See 588 * {@link angular.Object} for more information about Angular arrays. 589 * 590 * @param {Object|Array|string} obj Object, array, or string to inspect. 591 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object 592 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. 593 */ 594function size(obj, ownPropsOnly) { 595 var size = 0, key; 596 597 if (isArray(obj) || isString(obj)) { 598 return obj.length; 599 } else if (isObject(obj)){ 600 for (key in obj) 601 if (!ownPropsOnly || obj.hasOwnProperty(key)) 602 size++; 603 } 604 605 return size; 606} 607 608 609function includes(array, obj) { 610 return indexOf(array, obj) != -1; 611} 612 613function indexOf(array, obj) { 614 if (array.indexOf) return array.indexOf(obj); 615 616 for ( var i = 0; i < array.length; i++) { 617 if (obj === array[i]) return i; 618 } 619 return -1; 620} 621 622function arrayRemove(array, value) { 623 var index = indexOf(array, value); 624 if (index >=0) 625 array.splice(index, 1); 626 return value; 627} 628 629function isLeafNode (node) { 630 if (node) { 631 switch (node.nodeName) { 632 case "OPTION": 633 case "PRE": 634 case "TITLE": 635 return true; 636 } 637 } 638 return false; 639} 640 641/** 642 * @ngdoc function 643 * @name angular.copy 644 * @function 645 * 646 * @description 647 * Creates a deep copy of `source`, which should be an object or an array. 648 * 649 * * If no destination is supplied, a copy of the object or array is created. 650 * * If a destination is provided, all of its elements (for array) or properties (for objects) 651 * are deleted and then all elements/properties from the source are copied to it. 652 * * If `source` is not an object or array, `source` is returned. 653 * 654 * Note: this function is used to augment the Object type in Angular expressions. See 655 * {@link ng.$filter} for more information about Angular arrays. 656 * 657 * @param {*} source The source that will be used to make a copy. 658 * Can be any type, including primitives, `null`, and `undefined`. 659 * @param {(Object|Array)=} destination Destination into which the source is copied. If 660 * provided, must be of the same type as `source`. 661 * @returns {*} The copy or updated `destination`, if `destination` was specified. 662 */ 663function copy(source, destination){ 664 if (isWindow(source) || isScope(source)) { 665 throw ngMinErr('cpws', "Can't copy! Making copies of Window or Scope instances is not supported."); 666 } 667 668 if (!destination) { 669 destination = source; 670 if (source) { 671 if (isArray(source)) { 672 destination = copy(source, []); 673 } else if (isDate(source)) { 674 destination = new Date(source.getTime()); 675 } else if (isRegExp(source)) { 676 destination = new RegExp(source.source); 677 } else if (isObject(source)) { 678 destination = copy(source, {}); 679 } 680 } 681 } else { 682 if (source === destination) throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); 683 if (isArray(source)) { 684 destination.length = 0; 685 for ( var i = 0; i < source.length; i++) { 686 destination.push(copy(source[i])); 687 } 688 } else { 689 var h = destination.$$hashKey; 690 forEach(destination, function(value, key){ 691 delete destination[key]; 692 }); 693 for ( var key in source) { 694 destination[key] = copy(source[key]); 695 } 696 setHashKey(destination,h); 697 } 698 } 699 return destination; 700} 701 702/** 703 * Create a shallow copy of an object 704 */ 705function shallowCopy(src, dst) { 706 dst = dst || {}; 707 708 for(var key in src) { 709 if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') { 710 dst[key] = src[key]; 711 } 712 } 713 714 return dst; 715} 716 717 718/** 719 * @ngdoc function 720 * @name angular.equals 721 * @function 722 * 723 * @description 724 * Determines if two objects or two values are equivalent. Supports value types, regular expressions, arrays and 725 * objects. 726 * 727 * Two objects or values are considered equivalent if at least one of the following is true: 728 * 729 * * Both objects or values pass `===` comparison. 730 * * Both objects or values are of the same type and all of their properties pass `===` comparison. 731 * * Both values are NaN. (In JavasScript, NaN == NaN => false. But we consider two NaN as equal) 732 * * Both values represent the same regular expression (In JavasScript,
733 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 734 * representation matches). 735 * 736 * During a property comparison, properties of `function` type and properties with names 737 * that begin with `$` are ignored. 738 * 739 * Scope and DOMWindow objects are being compared only by identify (`===`). 740 * 741 * @param {*} o1 Object or value to compare. 742 * @param {*} o2 Object or value to compare. 743 * @returns {boolean} True if arguments are equal. 744 */ 745function equals(o1, o2) { 746 if (o1 === o2) return true; 747 if (o1 === null || o2 === null) return false; 748 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 749 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 750 if (t1 == t2) { 751 if (t1 == 'object') { 752 if (isArray(o1)) { 753 if (!isArray(o2)) return false; 754 if ((length = o1.length) == o2.length) { 755 for(key=0; key<length; key++) { 756 if (!equals(o1[key], o2[key])) return false; 757 } 758 return true; 759 } 760 } else if (isDate(o1)) { 761 return isDate(o2) && o1.getTime() == o2.getTime(); 762 } else if (isRegExp(o1) && isRegExp(o2)) { 763 return o1.toString() == o2.toString(); 764 } else { 765 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 766 keySet = {}; 767 for(key in o1) { 768 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 769 if (!equals(o1[key], o2[key])) return false; 770 keySet[key] = true; 771 } 772 for(key in o2) { 773 if (!keySet.hasOwnProperty(key) && 774 key.charAt(0) !== '$' && 775 o2[key] !== undefined && 776 !isFunction(o2[key])) return false; 777 } 778 return true; 779 } 780 } 781 } 782 return false; 783} 784 785 786function concat(array1, array2, index) { 787 return array1.concat(slice.call(array2, index)); 788} 789 790function sliceArgs(args, startIndex) { 791 return slice.call(args, startIndex || 0); 792} 793 794 795/** 796 * @ngdoc function 797 * @name angular.bind 798 * @function 799 * 800 * @description 801 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 802 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 803 * known as [function currying](http://en.wikipedia.org/wiki/Currying). 804 * 805 * @param {Object} self Context which `fn` should be evaluated in. 806 * @param {function()} fn Function to be bound. 807 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 808 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 809 */ 810function bind(self, fn) { 811 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 812 if (isFunction(fn) && !(fn instanceof RegExp)) { 813 return curryArgs.length 814 ? function() { 815 return arguments.length 816 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) 817 : fn.apply(self, curryArgs); 818 } 819 : function() { 820 return arguments.length 821 ? fn.apply(self, arguments) 822 : fn.call(self); 823 }; 824 } else { 825 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 826 return fn; 827 } 828} 829 830 831function toJsonReplacer(key, value) { 832 var val = value; 833 834 if (/^\$+/.test(key)) { 835 val = undefined; 836 } else if (isWindow(value)) { 837 val = '$WINDOW'; 838 } else if (value && document === value) { 839 val = '$DOCUMENT'; 840 } else if (isScope(value)) { 841 val = '$SCOPE'; 842 } 843 844 return val; 845} 846 847 848/** 849 * @ngdoc function 850 * @name angular.toJson 851 * @function 852 * 853 * @description 854 * Serializes input into a JSON-formatted string. Properties with leading $ characters will be 855 * stripped since angular uses this notation internally. 856 * 857 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 858 * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. 859 * @returns {string|undefined} JSON-ified string representing `obj`. 860 */ 861function toJson(obj, pretty) { 862 if (typeof obj === 'undefined') return undefined; 863 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); 864} 865 866 867/** 868 * @ngdoc function 869 * @name angular.fromJson 870 * @function 871 * 872 * @description 873 * Deserializes a JSON string. 874 * 875 * @param {string}
875 json JSON string to deserialize. 876 * @returns {Object|Array|Date|string|number} Deserialized thingy. 877 */ 878function fromJson(json) { 879 return isString(json) 880 ? JSON.parse(json) 881 : json; 882} 883 884 885function toBoolean(value) { 886 if (value && value.length !== 0) { 887 var v = lowercase("" + value); 888 value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); 889 } else { 890 value = false; 891 } 892 return value; 893} 894 895/** 896 * @returns {string} Returns the string representation of the element. 897 */ 898function startingTag(element) { 899 element = jqLite(element).clone(); 900 try { 901 // turns out IE does not let you set .html() on elements which 902 // are not allowed to have children. So we just ignore it. 903 element.html(''); 904 } catch(e) {} 905 // As Per DOM Standards 906 var TEXT_NODE = 3; 907 var elemHtml = jqLite('<div>').append(element).html(); 908 try { 909 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : 910 elemHtml. 911 match(/^(<[^>]+>)/)[1]. 912 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 913 } catch(e) { 914 return lowercase(elemHtml); 915 } 916 917} 918 919 920///////////////////////////////////////////////// 921 922/** 923 * Tries to decode the URI component without throwing an exception. 924 * 925 * @private 926 * @param str value potential URI component to check. 927 * @returns {boolean} True if `value` can be decoded 928 * with the decodeURIComponent function. 929 */ 930function tryDecodeURIComponent(value) { 931 try { 932 return decodeURIComponent(value); 933 } catch(e) { 934 // Ignore any invalid uri component 935 } 936} 937 938 939/** 940 * Parses an escaped url query string into key-value pairs. 941 * @returns Object.<(string|boolean)> 942 */ 943function parseKeyValue(/**string*/keyValue) { 944 var obj = {}, key_value, key; 945 forEach((keyValue || "").split('&'), function(keyValue){ 946 if ( keyValue ) { 947 key_value = keyValue.split('='); 948 key = tryDecodeURIComponent(key_value[0]); 949 if ( isDefined(key) ) { 950 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 951 if (!obj[key]) { 952 obj[key] = val; 953 } else if(isArray(obj[key])) { 954 obj[key].push(val); 955 } else { 956 obj[key] = [obj[key],val]; 957 } 958 } 959 } 960 }); 961 return obj; 962} 963 964function toKeyValue(obj) { 965 var parts = []; 966 forEach(obj, function(value, key) { 967 if (isArray(value)) { 968 forEach(value, function(arrayValue) { 969 parts.push(encodeUriQuery(key, true) + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 970 }); 971 } else { 972 parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true))); 973 } 974 }); 975 return parts.length ? parts.join('&') : ''; 976} 977 978 979/** 980 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 981 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 982 * segments: 983 * segment = *pchar 984 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 985 * pct-encoded = "%" HEXDIG HEXDIG 986 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 987 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 988 * / "*" / "+" / "," / ";" / "=" 989 */ 990function encodeUriSegment(val) { 991 return encodeUriQuery(val, true). 992 replace(/%26/gi, '&'). 993 replace(/%3D/gi, '='). 994 replace(/%2B/gi, '+'); 995} 996 997 998/** 999 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1000 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1001 * encoded per http://tools.ietf.org/html/rfc3986: 1002 * query = *( pchar / "/" / "?" ) 1003 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1004 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1005 * pct-encoded = "%" HEXDIG HEXDIG 1006 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1007 * / "*" / "+" / "," / ";" / "=" 1008 */ 1009function encodeUriQuery(val, pctEncodeSpaces) { 1010 return encodeURIComponent(val). 1011 replace(/%40/gi, '@'). 1012 replace(/%3A/gi, ':'). 1013 replace(/%24/g, '$'). 1014 replace(/%2C/gi, ','). 1015 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1016} 1017 1018 1019/** 1020 * @ngdoc directive 1021 * @name ng.directive:ngApp 1022 * 1023 * @element ANY 1024 * @param {angular.Module} ngApp an optional application 1025 * {@link angular.module module} name to load. 1026 * 1027 * @description 1028 * 1029 * Use this directive to auto-bootstrap an application. Only 1030 * one ngApp directive can be used per HTML document. The directive 1031 * designates the root of the application and is typically placed 1032 * at the root of the page. 1033 * 1034 * The first ngApp found in the document will be auto-bootstrapped. To use multiple applications in an 1035 * HTML document you must manually bootstrap them using {@link angular.bootstrap}. 1036 * Applications cannot be nested. 1037 * 1038 * In the example below if the `ngApp` directive would not be placed 1039 * on the `html` element then the document would not be compiled 1040 * and the `{{ 1+2 }}` would not be resolved to `3`. 1041 * 1042 * `ngApp` is the easiest way to bootstrap an application. 1043 * 1044 <doc:example> 1045 <doc:source> 1046 I can add: 1 + 2 = {{ 1+2 }} 1047 </doc:source> 1048 </doc:example> 1049 * 1050 */ 1051function angularInit(element, bootstrap) { 1052 var elements = [element], 1053 appElement, 1054 module, 1055 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], 1056 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; 1057 1058 function append(element) { 1059 element && elements.push(element); 1060 } 1061 1062 forEach(names, function(name) { 1063 names[name] = true; 1064 append(document.getElementById(name)); 1065 name = name.replace(':', '\\:'); 1066 if (element.querySelectorAll) {
1067 forEach(element.querySelectorAll('.' + name), append); 1068 forEach(element.querySelectorAll('.' + name + '\\:'), append); 1069 forEach(element.querySelectorAll('[' + name + ']'), append); 1070 } 1071 }); 1072 1073 forEach(elements, function(element) { 1074 if (!appElement) { 1075 var className = ' ' + element.className + ' '; 1076 var match = NG_APP_CLASS_REGEXP.exec(className); 1077 if (match) { 1078 appElement = element; 1079 module = (match[2] || '').replace(/\s+/g, ','); 1080 } else { 1081 forEach(element.attributes, function(attr) { 1082 if (!appElement && names[attr.name]) { 1083 appElement = element; 1084 module = attr.value; 1085 } 1086 }); 1087 } 1088 } 1089 }); 1090 if (appElement) { 1091 bootstrap(appElement, module ? [module] : []); 1092 } 1093} 1094 1095/** 1096 * @ngdoc function 1097 * @name angular.bootstrap 1098 * @description 1099 * Use this function to manually start up angular application. 1100 * 1101 * See: {@link guide/bootstrap Bootstrap} 1102 * 1103 * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. 1104 * They must use {@link api/ng.directive:ngApp ngApp}. 1105 * 1106 * @param {Element} element DOM element which is the root of angular application. 1107 * @param {Array<String|Function>=} modules an array of module declarations. See: {@link angular.module modules} 1108 * @returns {AUTO.$injector} Returns the newly created injector for this app. 1109 */ 1110function bootstrap(element, modules) { 1111 var doBootstrap = function() { 1112 element = jqLite(element); 1113 1114 if (element.injector()) { 1115 var tag = (element[0] === document) ? 'document' : startingTag(element); 1116 throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); 1117 } 1118 1119 modules = modules || []; 1120 modules.unshift(['$provide', function($provide) { 1121 $provide.value('$rootElement', element); 1122 }]); 1123 modules.unshift('ng'); 1124 var injector = createInjector(modules); 1125 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', 1126 function(scope, element, compile, injector, animate) { 1127 scope.$apply(function() { 1128 element.data('$injector', injector); 1129 compile(element)(scope); 1130 }); 1131 animate.enabled(true); 1132 }] 1133 ); 1134 return injector; 1135 }; 1136 1137 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1138 1139 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1140 return doBootstrap(); 1141 } 1142 1143 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1144 angular.resumeBootstrap = function(extraModules) { 1145 forEach(extraModules, function(module) { 1146 modules.push(module); 1147 }); 1148 doBootstrap(); 1149 }; 1150} 1151 1152var SNAKE_CASE_REGEXP = /[A-Z]/g; 1153function snake_case(name, separator){ 1154 separator = separator || '_'; 1155 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1156 return (pos ? separator : '') + letter.toLowerCase(); 1157 }); 1158} 1159 1160function bindJQuery() { 1161 // bind to jQuery if present; 1162 jQuery = window.jQuery; 1163 // reset to jQuery or default to us. 1164 if (jQuery) { 1165 jqLite = jQuery; 1166 extend(jQuery.fn, { 1167 scope: JQLitePrototype.scope, 1168 controller: JQLitePrototype.controller, 1169 injector: JQLitePrototype.injector, 1170 inheritedData: JQLitePrototype.inheritedData 1171 }); 1172 // Method signature: JQLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) 1173 JQLitePatchJQueryRemove('remove', true, true, false); 1174 JQLitePatchJQueryRemove('empty', false, false, false); 1175 JQLitePatchJQueryRemove('html', false, false, true); 1176 } else { 1177 jqLite = JQLite; 1178 } 1179 angular.element = jqLite; 1180} 1181 1182/** 1183 * throw error if the argument is falsy. 1184 */ 1185function assertArg(arg, name, reason) { 1186 if (!arg) { 1187 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1188 } 1189 return arg; 1190} 1191 1192function assertArgFn(arg, name, acceptArrayAnnotation) { 1193 if (acceptArrayAnnotation && isArray(arg)) { 1194 arg = arg[arg.length - 1]; 1195 } 1196 1197 assertArg(isFunction(arg), name, 'not a function, got ' + 1198 (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1199 return arg; 1200} 1201 1202/** 1203 * Return the value accessible from the object by path. Any undefined traversals are ignored 1204 * @param {Object} obj starting object 1205 * @param {string} path path to traverse 1206 * @param {boolean=true} bindFnToScope 1207 * @returns value as accessible by path 1208 */ 1209//TODO(misko): this function needs to be removed 1210function getter(obj, path, bindFnToScope) { 1211 if (!path) return obj; 1212 var keys = path.split('.'); 1213 var key; 1214 var lastInstance = obj; 1215 var len = keys.length; 1216 1217 for (var i = 0; i < len; i++) { 1218 key = keys[i]; 1219 if (obj) { 1220 obj = (lastInstance = obj)[key]; 1221 } 1222 } 1223 if (!bindFnToScope && isFunction(obj)) { 1224 return bind(lastInstance, obj); 1225 } 1226 return obj; 1227} 1228 1229/** 1230 * @ngdoc interface 1231 * @name angular.Module 1232 * @description 1233 * 1234 * Interface for configuring angular {@link angular.module modules}. 1235 */ 1236 1237function setupModuleLoader(window) { 1238 1239 function ensure(obj, name, factory) {
1240 return obj[name] || (obj[name] = factory()); 1241 } 1242 1243 return ensure(ensure(window, 'angular', Object), 'module', function() { 1244 /** @type {Object.<string, angular.Module>} */ 1245 var modules = {}; 1246 1247 /** 1248 * @ngdoc function 1249 * @name angular.module 1250 * @description 1251 * 1252 * The `angular.module` is a global place for creating and registering Angular modules. All 1253 * modules (angular core or 3rd party) that should be available to an application must be 1254 * registered using this mechanism. 1255 * 1256 * 1257 * # Module 1258 * 1259 * A module is a collection of services, directives, filters, and configuration information. 1260 * `angular.module` is used to configure the {@link AUTO.$injector $injector}. 1261 * 1262 * <pre> 1263 * // Create a new module 1264 * var myModule = angular.module('myModule', []); 1265 * 1266 * // register a new service 1267 * myModule.value('appName', 'MyCoolApp'); 1268 * 1269 * // configure existing services inside initialization blocks. 1270 * myModule.config(function($locationProvider) { 1271 * // Configure existing providers 1272 * $locationProvider.hashPrefix('!'); 1273 * }); 1274 * </pre> 1275 * 1276 * Then you can create an injector and load your modules like this: 1277 * 1278 * <pre> 1279 * var injector = angular.injector(['ng', 'MyModule']) 1280 * </pre> 1281 * 1282 * However it's more likely that you'll just use 1283 * {@link ng.directive:ngApp ngApp} or 1284 * {@link angular.bootstrap} to simplify this process for you. 1285 * 1286 * @param {!string} name The name of the module to create or retrieve. 1287 * @param {Array.<string>=} requires If specified then new module is being created. If unspecified then the 1288 * the module is being retrieved for further configuration. 1289 * @param {Function} configFn Optional configuration function for the module. Same as 1290 * {@link angular.Module#config Module#config()}. 1291 * @returns {module} new module with the {@link angular.Module} api. 1292 */ 1293 return function module(name, requires, configFn) { 1294 if (requires && modules.hasOwnProperty(name)) { 1295 modules[name] = null; 1296 } 1297 return ensure(modules, name, function() { 1298 if (!requires) { 1299 throw minErr('$injector')('nomod', "Module '{0}' is not available! You either misspelled the module name " + 1300 "or forgot to load it. If registering a module ensure that you specify the dependencies as the second " + 1301 "argument.", name); 1302 } 1303 1304 /** @type {!Array.<Array.<*>>} */ 1305 var invokeQueue = []; 1306 1307 /** @type {!Array.<Function>} */ 1308 var runBlocks = []; 1309 1310 var config = invokeLater('$injector', 'invoke'); 1311 1312 /** @type {angular.Module} */ 1313 var moduleInstance = { 1314 // Private state 1315 _invokeQueue: invokeQueue, 1316 _runBlocks: runBlocks, 1317 1318 /** 1319 * @ngdoc property 1320 * @name angular.Module#requires 1321 * @propertyOf angular.Module 1322 * @returns {Array.<string>} List of module names which must be loaded before this module. 1323 * @description 1324 * Holds the list of modules which the injector will load before the current module is loaded. 1325 */ 1326 requires: requires, 1327 1328 /** 1329 * @ngdoc property 1330 * @name angular.Module#name 1331 * @propertyOf angular.Module 1332 * @returns {string} Name of the module. 1333 * @description 1334 */ 1335 name: name, 1336 1337 1338 /** 1339 * @ngdoc method 1340 * @name angular.Module#provider 1341 * @methodOf angular.Module 1342 * @param {string} name service name 1343 * @param {Function} providerType Construction function for creating new instance of the service. 1344 * @description 1345 * See {@link AUTO.$provide#provider $provide.provider()}. 1346 */ 1347 provider: invokeLater('$provide', 'provider'), 1348 1349 /** 1350 * @ngdoc method 1351 * @name angular.Module#factory 1352 * @methodOf angular.Module 1353 * @param {string} name service name 1354 * @param {Function} providerFunction Function for creating new instance of the service.
1355 * @description 1356 * See {@link AUTO.$provide#factory $provide.factory()}. 1357 */ 1358 factory: invokeLater('$provide', 'factory'), 1359 1360 /** 1361 * @ngdoc method 1362 * @name angular.Module#service 1363 * @methodOf angular.Module 1364 * @param {string} name service name 1365 * @param {Function} constructor A constructor function that will be instantiated. 1366 * @description 1367 * See {@link AUTO.$provide#service $provide.service()}. 1368 */ 1369 service: invokeLater('$provide', 'service'), 1370 1371 /** 1372 * @ngdoc method 1373 * @name angular.Module#value 1374 * @methodOf angular.Module 1375 * @param {string} name service name 1376 * @param {*} object Service instance object. 1377 * @description 1378 * See {@link AUTO.$provide#value $provide.value()}. 1379 */ 1380 value: invokeLater('$provide', 'value'), 1381 1382 /** 1383 * @ngdoc method 1384 * @name angular.Module#constant 1385 * @methodOf angular.Module 1386 * @param {string} name constant name 1387 * @param {*} object Constant value. 1388 * @description 1389 * Because the constant are fixed, they get applied before other provide methods. 1390 * See {@link AUTO.$provide#constant $provide.constant()}. 1391 */ 1392 constant: invokeLater('$provide', 'constant', 'unshift'), 1393 1394 /** 1395 * @ngdoc method 1396 * @name angular.Module#animation 1397 * @methodOf angular.Module 1398 * @param {string} name animation name 1399 * @param {Function} animationFactory Factory function for creating new instance of an animation. 1400 * @description 1401 * 1402 * **NOTE**: animations are take effect only if the **ngAnimate** module is loaded. 1403 * 1404 * 1405 * Defines an animation hook that can be later used with {@link ngAnimate.$animate $animate} service and 1406 * directives that use this service. 1407 * 1408 * <pre> 1409 * module.animation('.animation-name', function($inject1, $inject2) { 1410 * return { 1411 * eventName : function(element, done) { 1412 * //code to run the animation 1413 * //once complete, then run done() 1414 * return function cancellationFunction(element) { 1415 * //code to cancel the animation 1416 * } 1417 * } 1418 * } 1419 * }) 1420 * </pre> 1421 * 1422 * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and 1423 * {@link ngAnimate ngAnimate module} for more information. 1424 */ 1425 animation: invokeLater('$animateProvider', 'register'), 1426 1427 /** 1428 * @ngdoc method 1429 * @name angular.Module#filter 1430 * @methodOf angular.Module 1431 * @param {string} name Filter name. 1432 * @param {Function} filterFactory Factory function for creating new instance of filter. 1433 * @description 1434 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1435 */ 1436 filter: invokeLater('$filterProvider', 'register'), 1437 1438 /** 1439 * @ngdoc method 1440 * @name angular.Module#controller 1441 * @methodOf angular.Module 1442 * @param {string} name Controller name. 1443 * @param {Function} constructor Controller constructor function. 1444 * @description 1445 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1446 */ 1447 controller: invokeLater('$controllerProvider', 'register'), 1448 1449 /** 1450 * @ngdoc method 1451 * @name angular.Module#directive 1452 * @methodOf angular.Module 1453 * @param {string} name directive name 1454 * @param {Function} directiveFactory Factory function for creating new instance of 1455 * directives. 1456 * @description 1457 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 1458 */ 1459 directive: invokeLater('$compileProvider', 'directive'), 1460 1461 /** 1462 * @ngdoc method 1463 * @name angular.Module#config 1464 * @methodOf angular.Module 1465 * @param {Function} configFn Execute this function on module load. Useful for service 1466 * configuration.
1467 * @description 1468 * Use this method to register work which needs to be performed on module loading. 1469 */ 1470 config: config, 1471 1472 /** 1473 * @ngdoc method 1474 * @name angular.Module#run 1475 * @methodOf angular.Module 1476 * @param {Function} initializationFn Execute this function after injector creation. 1477 * Useful for application initialization. 1478 * @description 1479 * Use this method to register work which should be performed when the injector is done 1480 * loading all modules. 1481 */ 1482 run: function(block) { 1483 runBlocks.push(block); 1484 return this; 1485 } 1486 }; 1487 1488 if (configFn) { 1489 config(configFn); 1490 } 1491 1492 return moduleInstance; 1493 1494 /** 1495 * @param {string} provider 1496 * @param {string} method 1497 * @param {String=} insertMethod 1498 * @returns {angular.Module} 1499 */ 1500 function invokeLater(provider, method, insertMethod) { 1501 return function() { 1502 invokeQueue[insertMethod || 'push']([provider, method, arguments]); 1503 return moduleInstance; 1504 } 1505 } 1506 }); 1507 }; 1508 }); 1509 1510} 1511 1512/** 1513 * @ngdoc property 1514 * @name angular.version 1515 * @description 1516 * An object that contains information about the current AngularJS version. This object has the 1517 * following properties: 1518 * 1519 * - `full` ââ¬â `{string}` ââ¬â Full version string, such as "0.9.18". 1520 * - `major` ââ¬â `{number}` ââ¬â Major version number, such as "0". 1521 * - `minor` ââ¬â `{number}` ââ¬â Minor version number, such as "9". 1522 * - `dot` ââ¬â `{number}` ââ¬â Dot version number, such as "18". 1523 * - `codeName` ââ¬â `{string}` ââ¬â Code name of the release, such as "jiggling-armfat". 1524 */ 1525var version = { 1526 full: '1.2.0rc1', // all of these placeholder strings will be replaced by grunt's 1527 major: 1, // package task 1528 minor: 2, 1529 dot: 0, 1530 codeName: 'spooky-giraffe' 1531}; 1532 1533 1534function publishExternalAPI(angular){ 1535 extend(angular, { 1536 'bootstrap': bootstrap, 1537 'copy': copy, 1538 'extend': extend, 1539 'equals': equals, 1540 'element': jqLite, 1541 'forEach': forEach, 1542 'injector': createInjector, 1543 'noop':noop, 1544 'bind':bind, 1545 'toJson': toJson, 1546 'fromJson': fromJson, 1547 'identity':identity, 1548 'isUndefined': isUndefined, 1549 'isDefined': isDefined, 1550 'isString': isString, 1551 'isFunction': isFunction, 1552 'isObject': isObject, 1553 'isNumber': isNumber, 1554 'isElement': isElement, 1555 'isArray': isArray, 1556 '$$minErr': minErr, 1557 'version': version, 1558 'isDate': isDate, 1559 'lowercase': lowercase, 1560 'uppercase': uppercase, 1561 'callbacks': {counter: 0} 1562 }); 1563 1564 angularModule = setupModuleLoader(window); 1565 try { 1566 angularModule('ngLocale'); 1567 } catch (e) { 1568 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 1569 } 1570 1571 angularModule('ng', ['ngLocale'], ['$provide', 1572 function ngModule($provide) { 1573 $provide.provider('$compile', $CompileProvider). 1574 directive({ 1575 a: htmlAnchorDirective, 1576 input: inputDirective, 1577 textarea: inputDirective, 1578 form: formDirective, 1579 script: scriptDirective, 1580 select: selectDirective, 1581 style: styleDirective, 1582 option: optionDirective, 1583 ngBind: ngBindDirective, 1584 ngBindHtml: ngBindHtmlDirective, 1585 ngBindTemplate: ngBindTemplateDirective, 1586 ngClass: ngClassDirective, 1587 ngClassEven: ngClassEvenDirective, 1588 ngClassOdd: ngClassOddDirective, 1589 ngCsp: ngCspDirective, 1590 ngCloak: ngCloakDirective, 1591 ngController: ngControllerDirective, 1592 ngForm: ngFormDirective, 1593 ngHide: ngHideDirective, 1594 ngIf: ngIfDirective, 1595 ngInclude: ngIncludeDirective, 1596 ngInit: ngInitDirective, 1597 ngNonBindable: ngNonBindableDirective, 1598 ngPluralize: ngPluralizeDirective, 1599 ngRepeat: ngRepeatDirective, 1600 ngShow: ngShowDirective, 1601 ngStyle: ngStyleDirective, 1602 ngSwitch: ngSwitchDirective, 1603 ngSwitchWhen: ngSwitchWhenDirective, 1604 ngSwitchDefault: ngSwitchDefaultDirective, 1605 ngOptions: ngOptionsDirective, 1606 ngTransclude: ngTranscludeDirective, 1607 ngModel: ngModelDirective, 1608 ngList: ngListDirective, 1609 ngChange: ngChangeDirective, 1610 required: requiredDirective, 1611 ngRequired: requiredDirective, 1612 ngValue: ngValueDirective 1613 }). 1614 directive(ngAttributeAliasDirectives). 1615 directive(ngEventDirectives); 1616 $provide.provider({ 1617 $anchorScroll: $AnchorScrollProvider, 1618 $animate: $AnimateProvider, 1619 $browser: $BrowserProvider,
1620 $cacheFactory: $CacheFactoryProvider, 1621 $controller: $ControllerProvider, 1622 $document: $DocumentProvider, 1623 $exceptionHandler: $ExceptionHandlerProvider, 1624 $filter: $FilterProvider, 1625 $interpolate: $InterpolateProvider, 1626 $http: $HttpProvider, 1627 $httpBackend: $HttpBackendProvider, 1628 $location: $LocationProvider, 1629 $log: $LogProvider, 1630 $parse: $ParseProvider, 1631 $rootScope: $RootScopeProvider, 1632 $q: $QProvider, 1633 $sce: $SceProvider, 1634 $sceDelegate: $SceDelegateProvider, 1635 $sniffer: $SnifferProvider, 1636 $templateCache: $TemplateCacheProvider, 1637 $timeout: $TimeoutProvider, 1638 $window: $WindowProvider, 1639 $$urlUtils: $$UrlUtilsProvider 1640 }); 1641 } 1642 ]); 1643} 1644 1645////////////////////////////////// 1646//JQLite 1647////////////////////////////////// 1648 1649/** 1650 * @ngdoc function 1651 * @name angular.element 1652 * @function 1653 * 1654 * @description 1655 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 1656 * `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if 1657 * jQuery is available, or a function that wraps the element or string in Angular's jQuery lite 1658 * implementation (commonly referred to as jqLite). 1659 * 1660 * Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded` 1661 * event fired. 1662 * 1663 * jqLite is a tiny, API-compatible subset of jQuery that allows 1664 * Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality 1665 * within a very small footprint, so only a subset of the jQuery API - methods, arguments and 1666 * invocation styles - are supported. 1667 * 1668 * Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never 1669 * raw DOM references. 1670 * 1671 * ## Angular's jqLite 1672 * Angular's lite version of jQuery provides only the following jQuery methods: 1673 * 1674 * - [addClass()](http://api.jquery.com/addClass/) 1675 * - [after()](http://api.jquery.com/after/) 1676 * - [append()](http://api.jquery.com/append/) 1677 * - [attr()](http://api.jquery.com/attr/) 1678 * - [bind()](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 1679 * - [children()](http://api.jquery.com/children/) - Does not support selectors 1680 * - [clone()](http://api.jquery.com/clone/) 1681 * - [contents()](http://api.jquery.com/contents/) 1682 * - [css()](http://api.jquery.com/css/) 1683 * - [data()](http://api.jquery.com/data/) 1684 * - [eq()](http://api.jquery.com/eq/) 1685 * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name 1686 * - [hasClass()](http://api.jquery.com/hasClass/) 1687 * - [html()](http://api.jquery.com/html/) 1688 * - [next()](http://api.jquery.com/next/) - Does not support selectors 1689 * - [on()](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 1690 * - [off()](http://api.jquery.com/off/) - Does not support namespaces or selectors 1691 * - [parent()](http://api.jquery.com/parent/) - Does not support selectors 1692 * - [prepend()](http://api.jquery.com/prepend/) 1693 * - [prop()](http://api.jquery.com/prop/) 1694 * - [ready()](http://api.jquery.com/ready/) 1695 * - [remove()](http://api.jquery.com/remove/) 1696 * - [removeAttr()](http://api.jquery.com/removeAttr/) 1697 * - [removeClass()](http://api.jquery.com/removeClass/) 1698 * - [removeData()](http://api.jquery.com/removeData/) 1699 * - [replaceWith()](http://api.jquery.com/replaceWith/) 1700 * - [text()](http://api.jquery.com/text/) 1701 * - [toggleClass()](http://api.jquery.com/toggleClass/) 1702 * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 1703 * - [unbind()](http://api.jquery.com/off/) - Does not support namespaces 1704 * - [val()](http://api.jquery.com/val/) 1705 * - [wrap()](http://api.jquery.com/wrap/) 1706 * 1707 * ## jQuery/jqLite Extras 1708 * Angular also provides the following additional methods and events to both jQuery and jqLite: 1709 * 1710 * ### Events 1711 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event 1712 * on all DOM nodes being removed. This can be used to clean up and 3rd party bindings to the DOM 1713 * element before it is removed. 1714 * ### Methods 1715 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 1716 * retrieves controller associated with the `ngController` directive. If `name` is provided as
1717 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 1718 * `'ngModel'`). 1719 * - `injector()` - retrieves the injector of the current element or its parent. 1720 * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current 1721 * element or its parent. 1722 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 1723 * parent element is reached. 1724 * 1725 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 1726 * @returns {Object} jQuery object. 1727 */ 1728 1729var jqCache = JQLite.cache = {}, 1730 jqName = JQLite.expando = 'ng-' + new Date().getTime(), 1731 jqId = 1, 1732 addEventListenerFn = (window.document.addEventListener 1733 ? function(element, type, fn) {element.addEventListener(type, fn, false);} 1734 : function(element, type, fn) {element.attachEvent('on' + type, fn);}), 1735 removeEventListenerFn = (window.document.removeEventListener 1736 ? function(element, type, fn) {element.removeEventListener(type, fn, false); } 1737 : function(element, type, fn) {element.detachEvent('on' + type, fn); }); 1738 1739function jqNextId() { return ++jqId; } 1740 1741 1742var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 1743var MOZ_HACK_REGEXP = /^moz([A-Z])/; 1744var jqLiteMinErr = minErr('jqLite'); 1745 1746/** 1747 * Converts snake_case to camelCase. 1748 * Also there is special case for Moz prefix starting with upper case letter. 1749 * @param name Name to normalize 1750 */ 1751function camelCase(name) { 1752 return name. 1753 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 1754 return offset ? letter.toUpperCase() : letter; 1755 }). 1756 replace(MOZ_HACK_REGEXP, 'Moz$1'); 1757} 1758 1759///////////////////////////////////////////// 1760// jQuery mutation patch 1761// 1762// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a 1763// $destroy event on all DOM nodes being removed. 1764// 1765///////////////////////////////////////////// 1766 1767function JQLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { 1768 var originalJqFn = jQuery.fn[name]; 1769 originalJqFn = originalJqFn.$original || originalJqFn; 1770 removePatch.$original = originalJqFn; 1771 jQuery.fn[name] = removePatch; 1772 1773 function removePatch(param) { 1774 var list = filterElems && param ? [this.filter(param)] : [this], 1775 fireEvent = dispatchThis, 1776 set, setIndex, setLength, 1777 element, childIndex, childLength, children; 1778 1779 if (!getterIfNoArguments || param != null) { 1780 while(list.length) { 1781 set = list.shift(); 1782 for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { 1783 element = jqLite(set[setIndex]); 1784 if (fireEvent) { 1785 element.triggerHandler('$destroy'); 1786 } else { 1787 fireEvent = !fireEvent; 1788 } 1789 for(childIndex = 0, childLength = (children = element.children()).length; 1790 childIndex < childLength; 1791 childIndex++) { 1792 list.push(jQuery(children[childIndex])); 1793 } 1794 } 1795 } 1796 } 1797 return originalJqFn.apply(this, arguments); 1798 } 1799} 1800 1801///////////////////////////////////////////// 1802function JQLite(element) { 1803 if (element instanceof JQLite) { 1804 return element; 1805 } 1806 if (!(this instanceof JQLite)) { 1807 if (isString(element) && element.charAt(0) != '<') { 1808 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 1809 } 1810 return new JQLite(element); 1811 } 1812 1813 if (isString(element)) { 1814 var div = document.createElement('div'); 1815 // Read about the NoScope elements here: 1816 // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx 1817 div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work! 1818 div.removeChild(div.firstChild); // remove the superfluous div 1819 JQLiteAddNodes(this, div.childNodes); 1820 var fragment = jqLite(document.createDocumentFragment()); 1821 fragment.append(this); // detach the elements from the temporary DOM div. 1822 } else { 1823 JQLiteAddNodes(this, element); 1824 } 1825} 1826 1827function JQLiteClone(element) { 1828 return element.cloneNode(true); 1829} 1830 1831function JQLiteDealoc(element){ 1832 JQLiteRemoveData(element); 1833 for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { 1834 JQLiteDealoc(children[i]); 1835 } 1836} 1837 1838function JQLiteOff(element, type, fn, unsupported) {
1839 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 1840 1841 var events = JQLiteExpandoStore(element, 'events'), 1842 handle = JQLiteExpandoStore(element, 'handle'); 1843 1844 if (!handle) return; //no listeners registered 1845 1846 if (isUndefined(type)) { 1847 forEach(events, function(eventHandler, type) { 1848 removeEventListenerFn(element, type, eventHandler); 1849 delete events[type]; 1850 }); 1851 } else { 1852 forEach(type.split(' '), function(type) { 1853 if (isUndefined(fn)) { 1854 removeEventListenerFn(element, type, events[type]); 1855 delete events[type]; 1856 } else { 1857 arrayRemove(events[type] || [], fn); 1858 } 1859 }); 1860 } 1861} 1862 1863function JQLiteRemoveData(element, name) { 1864 var expandoId = element[jqName], 1865 expandoStore = jqCache[expandoId]; 1866 1867 if (expandoStore) { 1868 if (name) { 1869 delete jqCache[expandoId].data[name]; 1870 return; 1871 } 1872 1873 if (expandoStore.handle) { 1874 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); 1875 JQLiteOff(element); 1876 } 1877 delete jqCache[expandoId]; 1878 element[jqName] = undefined; // ie does not allow deletion of attributes on elements. 1879 } 1880} 1881 1882function JQLiteExpandoStore(element, key, value) { 1883 var expandoId = element[jqName], 1884 expandoStore = jqCache[expandoId || -1]; 1885 1886 if (isDefined(value)) { 1887 if (!expandoStore) { 1888 element[jqName] = expandoId = jqNextId(); 1889 expandoStore = jqCache[expandoId] = {}; 1890 } 1891 expandoStore[key] = value; 1892 } else { 1893 return expandoStore && expandoStore[key]; 1894 } 1895} 1896 1897function JQLiteData(element, key, value) { 1898 var data = JQLiteExpandoStore(element, 'data'), 1899 isSetter = isDefined(value), 1900 keyDefined = !isSetter && isDefined(key), 1901 isSimpleGetter = keyDefined && !isObject(key); 1902 1903 if (!data && !isSimpleGetter) { 1904 JQLiteExpandoStore(element, 'data', data = {}); 1905 } 1906 1907 if (isSetter) { 1908 data[key] = value; 1909 } else { 1910 if (keyDefined) { 1911 if (isSimpleGetter) { 1912 // don't create data in this case. 1913 return data && data[key]; 1914 } else { 1915 extend(data, key); 1916 } 1917 } else { 1918 return data; 1919 } 1920 } 1921} 1922 1923function JQLiteHasClass(element, selector) { 1924 return ((" " + element.className + " ").replace(/[\n\t]/g, " "). 1925 indexOf( " " + selector + " " ) > -1); 1926} 1927 1928function JQLiteRemoveClass(element, cssClasses) { 1929 if (cssClasses) { 1930 forEach(cssClasses.split(' '), function(cssClass) { 1931 element.className = trim( 1932 (" " + element.className + " ") 1933 .replace(/[\n\t]/g, " ") 1934 .replace(" " + trim(cssClass) + " ", " ") 1935 ); 1936 }); 1937 } 1938} 1939 1940function JQLiteAddClass(element, cssClasses) { 1941 if (cssClasses) { 1942 forEach(cssClasses.split(' '), function(cssClass) { 1943 if (!JQLiteHasClass(element, cssClass)) { 1944 element.className = trim(element.className + ' ' + trim(cssClass)); 1945 } 1946 }); 1947 } 1948} 1949 1950function JQLiteAddNodes(root, elements) { 1951 if (elements) { 1952 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) 1953 ? elements 1954 : [ elements ]; 1955 for(var i=0; i < elements.length; i++) { 1956 root.push(elements[i]); 1957 } 1958 } 1959} 1960 1961function JQLiteController(element, name) { 1962 return JQLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); 1963} 1964 1965function JQLiteInheritedData(element, name, value) { 1966 element = jqLite(element); 1967 1968 // if element is the document object work with the html element instead 1969 // this makes $(document).scope() possible 1970 if(element[0].nodeType == 9) { 1971 element = element.find('html'); 1972 } 1973 1974 while (element.length) { 1975 if ((value = element.data(name)) !== undefined) return value; 1976 element = element.parent(); 1977 } 1978} 1979 1980////////////////////////////////////////// 1981// Functions which are declared directly. 1982////////////////////////////////////////// 1983var JQLitePrototype = JQLite.prototype = { 1984 ready: function(fn) { 1985 var fired = false; 1986 1987 function trigger() { 1988 if (fired) return; 1989 fired = true; 1990 fn(); 1991 } 1992 1993 // check if document already is loaded 1994 if (document.readyState === 'complete'){ 1995 setTimeout(trigger); 1996 } else { 1997 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 1998 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 1999 JQLite(window).on('load', trigger); // fallback to window.onload for others 2000 } 2001 }, 2002 toString: function() { 2003 var value = [];
2004 forEach(this, function(e){ value.push('' + e);}); 2005 return '[' + value.join(', ') + ']'; 2006 }, 2007 2008 eq: function(index) { 2009 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2010 }, 2011 2012 length: 0, 2013 push: push, 2014 sort: [].sort, 2015 splice: [].splice 2016}; 2017 2018////////////////////////////////////////// 2019// Functions iterating getter/setters. 2020// these functions return self on setter and 2021// value on get. 2022////////////////////////////////////////// 2023var BOOLEAN_ATTR = {}; 2024forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 2025 BOOLEAN_ATTR[lowercase(value)] = value; 2026}); 2027var BOOLEAN_ELEMENTS = {}; 2028forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 2029 BOOLEAN_ELEMENTS[uppercase(value)] = true; 2030}); 2031 2032function getBooleanAttrName(element, name) { 2033 // check dom last since we will most likely fail on name 2034 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2035 2036 // booleanAttr is here twice to minimize DOM access 2037 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; 2038} 2039 2040forEach({ 2041 data: JQLiteData, 2042 inheritedData: JQLiteInheritedData, 2043 2044 scope: function(element) { 2045 return JQLiteInheritedData(element, '$scope'); 2046 }, 2047 2048 controller: JQLiteController , 2049 2050 injector: function(element) { 2051 return JQLiteInheritedData(element, '$injector'); 2052 }, 2053 2054 removeAttr: function(element,name) { 2055 element.removeAttribute(name); 2056 }, 2057 2058 hasClass: JQLiteHasClass, 2059 2060 css: function(element, name, value) { 2061 name = camelCase(name); 2062 2063 if (isDefined(value)) { 2064 element.style[name] = value; 2065 } else { 2066 var val; 2067 2068 if (msie <= 8) { 2069 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why 2070 val = element.currentStyle && element.currentStyle[name]; 2071 if (val === '') val = 'auto'; 2072 } 2073 2074 val = val || element.style[name]; 2075 2076 if (msie <= 8) { 2077 // jquery weirdness :-/ 2078 val = (val === '') ? undefined : val; 2079 } 2080 2081 return val; 2082 } 2083 }, 2084 2085 attr: function(element, name, value){ 2086 var lowercasedName = lowercase(name); 2087 if (BOOLEAN_ATTR[lowercasedName]) { 2088 if (isDefined(value)) { 2089 if (!!value) { 2090 element[name] = true; 2091 element.setAttribute(name, lowercasedName); 2092 } else { 2093 element[name] = false; 2094 element.removeAttribute(lowercasedName); 2095 } 2096 } else { 2097 return (element[name] || 2098 (element.attributes.getNamedItem(name)|| noop).specified) 2099 ? lowercasedName 2100 : undefined; 2101 } 2102 } else if (isDefined(value)) { 2103 element.setAttribute(name, value); 2104 } else if (element.getAttribute) { 2105 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2106 // some elements (e.g. Document) don't have get attribute, so return undefined 2107 var ret = element.getAttribute(name, 2); 2108 // normalize non-existing attributes to undefined (as jQuery) 2109 return ret === null ? undefined : ret; 2110 } 2111 }, 2112 2113 prop: function(element, name, value) { 2114 if (isDefined(value)) { 2115 element[name] = value; 2116 } else { 2117 return element[name]; 2118 } 2119 }, 2120 2121 text: (function() { 2122 var NODE_TYPE_TEXT_PROPERTY = []; 2123 if (msie < 9) { 2124 NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ 2125 NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ 2126 } else { 2127 NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ 2128 NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ 2129 } 2130 getText.$dv = ''; 2131 return getText; 2132 2133 function getText(element, value) { 2134 var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType] 2135 if (isUndefined(value)) { 2136 return textProp ? element[textProp] : ''; 2137 } 2138 element[textProp] = value; 2139 } 2140 })(), 2141 2142 val: function(element, value) { 2143 if (isUndefined(value)) { 2144 if (nodeName_(element) === 'SELECT' && element.multiple) { 2145 var result = []; 2146 forEach(element.options, function (option) { 2147 if (option.selected) { 2148 result.push(option.value || option.text); 2149 } 2150 }); 2151 return result.length === 0 ? null : result; 2152 } 2153 return element.value; 2154 } 2155 element.value = value; 2156 }, 2157 2158 html: function(element, value) { 2159 if (isUndefined(value)) { 2160 return element.innerHTML; 2161 } 2162 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2163 JQLiteDealoc(childNodes[i]); 2164 } 2165 element.innerHTML = value; 2166 } 2167}, function(fn, name){ 2168 /** 2169 * Properties: writes return selection, reads return first value 2170 */ 2171 JQLite.prototype[name] = function(arg1, arg2) { 2172 var i, key; 2173 2174 // JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2175 // in a way that survives minification. 2176 if (((fn.length == 2 && (fn !== JQLiteHasClass && fn !== JQLiteController)) ? arg1 : arg2) === undefined) { 2177 if (isObject(arg1)) { 2178 2179 // we are a write, but the object properties are the key/values 2180 for(i=0; i < this.length; i++) { 2181 if (fn === JQLiteData) { 2182 // data() takes the whole object in jQuery 2183 fn(this[i], arg1); 2184 } else { 2185 for (key in arg1) { 2186 fn(this[i], key, arg1[key]); 2187 } 2188 } 2189 } 2190 // return self for chaining 2191 return this; 2192 } else { 2193 // we are a read, so read the first child. 2194 var value = fn.$dv; 2195 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2196 var jj = value == undefined ? Math.min(this.length, 1) : this.length; 2197 for (var j = 0; j < jj; j++) { 2198 var nodeValue = fn(this[j], arg1, arg2); 2199 value = value ? value + nodeValue : nodeValue; 2200 } 2201 return value; 2202 } 2203 } else { 2204 // we are a write, so apply to all children 2205 for(i=0; i < this.length; i++) { 2206 fn(this[i], arg1, arg2); 2207 } 2208 // return self for chaining 2209 return this; 2210 } 2211 }; 2212}); 2213 2214function createEventHandler(element, events) { 2215 var eventHandler = function (event, type) { 2216 if (!event.preventDefault) { 2217 event.preventDefault = function() { 2218 event.returnValue = false; //ie 2219 }; 2220 } 2221 2222 if (!event.stopPropagation) { 2223 event.stopPropagation = function() { 2224 event.cancelBubble = true; //ie 2225 }; 2226 } 2227 2228 if (!event.target) { 2229 event.target = event.srcElement || document; 2230 } 2231 2232 if (isUndefined(event.defaultPrevented)) { 2233 var prevent = event.preventDefault; 2234 event.preventDefault = function() { 2235 event.defaultPrevented = true; 2236 prevent.call(event); 2237 }; 2238 event.defaultPrevented = false;
2239 } 2240 2241 event.isDefaultPrevented = function() { 2242 return event.defaultPrevented || event.returnValue == false; 2243 }; 2244 2245 forEach(events[type || event.type], function(fn) { 2246 fn.call(element, event); 2247 }); 2248 2249 // Remove monkey-patched methods (IE), 2250 // as they would cause memory leaks in IE8. 2251 if (msie <= 8) { 2252 // IE7/8 does not allow to delete property on native object 2253 event.preventDefault = null; 2254 event.stopPropagation = null; 2255 event.isDefaultPrevented = null; 2256 } else { 2257 // It shouldn't affect normal browsers (native methods are defined on prototype). 2258 delete event.preventDefault; 2259 delete event.stopPropagation; 2260 delete event.isDefaultPrevented; 2261 } 2262 }; 2263 eventHandler.elem = element; 2264 return eventHandler; 2265} 2266 2267////////////////////////////////////////// 2268// Functions iterating traversal. 2269// These functions chain results into a single 2270// selector. 2271////////////////////////////////////////// 2272forEach({ 2273 removeData: JQLiteRemoveData, 2274 2275 dealoc: JQLiteDealoc, 2276 2277 on: function onFn(element, type, fn, unsupported){
2278 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 2279 2280 var events = JQLiteExpandoStore(element, 'events'), 2281 handle = JQLiteExpandoStore(element, 'handle'); 2282 2283 if (!events) JQLiteExpandoStore(element, 'events', events = {}); 2284 if (!handle) JQLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); 2285 2286 forEach(type.split(' '), function(type){ 2287 var eventFns = events[type]; 2288 2289 if (!eventFns) { 2290 if (type == 'mouseenter' || type == 'mouseleave') { 2291 var contains = document.body.contains || document.body.compareDocumentPosition ? 2292 function( a, b ) { 2293 var adown = a.nodeType === 9 ? a.documentElement : a, 2294 bup = b && b.parentNode; 2295 return a === bup || !!( bup && bup.nodeType === 1 && ( 2296 adown.contains ? 2297 adown.contains( bup ) : 2298 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 2299 )); 2300 } : 2301 function( a, b ) { 2302 if ( b ) { 2303 while ( (b = b.parentNode) ) { 2304 if ( b === a ) { 2305 return true; 2306 } 2307 } 2308 } 2309 return false; 2310 }; 2311 2312 events[type] = []; 2313 2314 // Refer to jQuery's implementation of mouseenter & mouseleave 2315 // Read about mouseenter and mouseleave: 2316 // http://www.quirksmode.org/js/events_mouse.html#link8 2317 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; 2318 2319 onFn(element, eventmap[type], function(event) { 2320 var target = this, related = event.relatedTarget; 2321 // For mousenter/leave call the handler if related is outside the target. 2322 // NB: No relatedTarget if the mouse left/entered the browser window 2323 if ( !related || (related !== target && !contains(target, related)) ){ 2324 handle(event, type); 2325 } 2326 }); 2327 2328 } else { 2329 addEventListenerFn(element, type, handle); 2330 events[type] = []; 2331 } 2332 eventFns = events[type] 2333 } 2334 eventFns.push(fn); 2335 }); 2336 }, 2337 2338 off: JQLiteOff, 2339 2340 replaceWith: function(element, replaceNode) { 2341 var index, parent = element.parentNode; 2342 JQLiteDealoc(element); 2343 forEach(new JQLite(replaceNode), function(node){ 2344 if (index) { 2345 parent.insertBefore(node, index.nextSibling); 2346 } else { 2347 parent.replaceChild(node, element); 2348 } 2349 index = node; 2350 }); 2351 }, 2352 2353 children: function(element) { 2354 var children = []; 2355 forEach(element.childNodes, function(element){ 2356 if (element.nodeType === 1) 2357 children.push(element); 2358 }); 2359 return children; 2360 }, 2361 2362 contents: function(element) { 2363 return element.childNodes || []; 2364 }, 2365 2366 append: function(element, node) { 2367 forEach(new JQLite(node), function(child){ 2368 if (element.nodeType === 1 || element.nodeType === 11) { 2369 element.appendChild(child); 2370 } 2371 }); 2372 }, 2373 2374 prepend: function(element, node) { 2375 if (element.nodeType === 1) { 2376 var index = element.firstChild; 2377 forEach(new JQLite(node), function(child){ 2378 element.insertBefore(child, index); 2379 }); 2380 } 2381 }, 2382 2383 wrap: function(element, wrapNode) { 2384 wrapNode = jqLite(wrapNode)[0]; 2385 var parent = element.parentNode; 2386 if (parent) { 2387 parent.replaceChild(wrapNode, element); 2388 } 2389 wrapNode.appendChild(element); 2390 }, 2391 2392 remove: function(element) { 2393 JQLiteDealoc(element); 2394 var parent = element.parentNode; 2395 if (parent) parent.removeChild(element); 2396 }, 2397 2398 after: function(element, newElement) { 2399 var index = element, parent = element.parentNode; 2400 forEach(new JQLite(newElement), function(node){ 2401 parent.insertBefore(node, index.nextSibling); 2402 index = node; 2403 }); 2404 }, 2405 2406 addClass: JQLiteAddClass, 2407 removeClass: JQLiteRemoveClass, 2408 2409 toggleClass: function(element, selector, condition) { 2410 if (isUndefined(condition)) { 2411 condition = !JQLiteHasClass(element, selector); 2412 } 2413 (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector); 2414 }, 2415 2416 parent: function(element) { 2417 var parent = element.parentNode; 2418 return parent && parent.nodeType !== 11 ? parent : null; 2419 }, 2420 2421 next: function(element) { 2422 if (element.nextElementSibling) { 2423 return element.nextElementSibling; 2424 } 2425 2426 // IE8 doesn't have nextElementSibling 2427 var elm = element.nextSibling; 2428 while (elm != null && elm.nodeType !== 1) { 2429 elm = elm.nextSibling; 2430 } 2431 return elm; 2432 }, 2433 2434 find: function(element, selector) { 2435 return element.getElementsByTagName(selector); 2436 }, 2437 2438 clone: JQLiteClone, 2439 2440 triggerHandler: function(element, eventName, eventData) { 2441 var eventFns = (JQLiteExpandoStore(element, 'events') || {})[eventName]; 2442 eventData = eventData || { 2443 preventDefault: noop, 2444 stopPropagation: noop 2445 }; 2446
2447 forEach(eventFns, function(fn) { 2448 fn.call(element, eventData); 2449 }); 2450 } 2451}, function(fn, name){ 2452 /** 2453 * chaining functions 2454 */ 2455 JQLite.prototype[name] = function(arg1, arg2, arg3) { 2456 var value; 2457 for(var i=0; i < this.length; i++) { 2458 if (value == undefined) { 2459 value = fn(this[i], arg1, arg2, arg3); 2460 if (value !== undefined) { 2461 // any function which returns a value needs to be wrapped 2462 value = jqLite(value); 2463 } 2464 } else { 2465 JQLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 2466 } 2467 } 2468 return value == undefined ? this : value; 2469 }; 2470 2471 // bind legacy bind/unbind to on/off 2472 JQLite.prototype.bind = JQLite.prototype.on; 2473 JQLite.prototype.unbind = JQLite.prototype.off; 2474}); 2475 2476/** 2477 * Computes a hash of an 'obj'. 2478 * Hash of a: 2479 * string is string 2480 * number is number as string 2481 * object is either result of calling $$hashKey function on the object or uniquely generated id, 2482 * that is also assigned to the $$hashKey property of the object. 2483 * 2484 * @param obj 2485 * @returns {string} hash string such that the same input will have the same hash string. 2486 * The resulting string key is in 'type:hashKey' format. 2487 */ 2488function hashKey(obj) { 2489 var objType = typeof obj, 2490 key; 2491 2492 if (objType == 'object' && obj !== null) { 2493 if (typeof (key = obj.$$hashKey) == 'function') { 2494 // must invoke on object to keep the right this 2495 key = obj.$$hashKey(); 2496 } else if (key === undefined) { 2497 key = obj.$$hashKey = nextUid(); 2498 } 2499 } else { 2500 key = obj; 2501 } 2502 2503 return objType + ':' + key; 2504} 2505 2506/** 2507 * HashMap which can use objects as keys 2508 */ 2509function HashMap(array){ 2510 forEach(array, this.put, this); 2511} 2512HashMap.prototype = { 2513 /** 2514 * Store key value pair 2515 * @param key key to store can be any type 2516 * @param value value to store can be any type 2517 */ 2518 put: function(key, value) { 2519 this[hashKey(key)] = value; 2520 }, 2521 2522 /** 2523 * @param key 2524 * @returns the value for the key 2525 */ 2526 get: function(key) { 2527 return this[hashKey(key)]; 2528 }, 2529 2530 /** 2531 * Remove the key/value pair 2532 * @param key 2533 */ 2534 remove: function(key) { 2535 var value = this[key = hashKey(key)]; 2536 delete this[key]; 2537 return value; 2538 } 2539}; 2540 2541/** 2542 * @ngdoc function 2543 * @name angular.injector 2544 * @function 2545 * 2546 * @description 2547 * Creates an injector function that can be used for retrieving services as well as for 2548 * dependency injection (see {@link guide/di dependency injection}). 2549 * 2550 2551 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 2552 * {@link angular.module}. The `ng` module must be explicitly added. 2553 * @returns {function()} Injector function. See {@link AUTO.$injector $injector}. 2554 * 2555 * @example 2556 * Typical usage 2557 * <pre> 2558 * // create an injector 2559 * var $injector = angular.injector(['ng']); 2560 * 2561 * // use the injector to kick off your application 2562 * // use the type inference to auto inject arguments, or use impl
2562icit injection 2563 * $injector.invoke(function($rootScope, $compile, $document){ 2564 * $compile($document)($rootScope); 2565 * $rootScope.$digest(); 2566 * }); 2567 * </pre> 2568 */ 2569 2570 2571/** 2572 * @ngdoc overview 2573 * @name AUTO 2574 * @description 2575 * 2576 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}. 2577 */ 2578 2579var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 2580var FN_ARG_SPLIT = /,/; 2581var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 2582var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 2583var $injectorMinErr = minErr('$injector'); 2584function annotate(fn) { 2585 var $inject, 2586 fnText, 2587 argDecl, 2588 last; 2589 2590 if (typeof fn == 'function') { 2591 if (!($inject = fn.$inject)) { 2592 $inject = []; 2593 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 2594 argDecl = fnText.match(FN_ARGS); 2595 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ 2596 arg.replace(FN_ARG, function(all, underscore, name){ 2597 $inject.push(name); 2598 }); 2599 }); 2600 fn.$inject = $inject; 2601 } 2602 } else if (isArray(fn)) { 2603 last = fn.length - 1; 2604 assertArgFn(fn[last], 'fn'); 2605 $inject = fn.slice(0, last); 2606 } else { 2607 assertArgFn(fn, 'fn', true); 2608 } 2609 return $inject; 2610} 2611 2612/////////////////////////////////////// 2613 2614/** 2615 * @ngdoc object 2616 * @name AUTO.$injector 2617 * @function 2618 * 2619 * @description 2620 * 2621 * `$injector` is used to retrieve object instances as defined by 2622 * {@link AUTO.$provide provider}, instantiate types, invoke methods, 2623 * and load modules. 2624 * 2625 * The following always holds true: 2626 * 2627 * <pre> 2628 * var $injector = angular.injector(); 2629 * expect($injector.get('$injector')).toBe($injector); 2630 * expect($injector.invoke(function($injector){ 2631 * return $injector; 2632 * }).toBe($injector); 2633 * </pre> 2634 * 2635 * # Injection Function Annotation 2636 * 2637 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 2638 * following are all valid ways of annotating function with injection arguments and are equivalent. 2639 * 2640 * <pre> 2641 * // inferred (only works if code not minified/obfuscated) 2642 * $injector.invoke(function(serviceA){}); 2643 * 2644 * // annotated 2645 * function explicit(serviceA) {}; 2646 * explicit.$inject = ['serviceA']; 2647 * $injector.invoke(explicit); 2648 * 2649 * // inline 2650 * $injector.invoke(['serviceA', function(serviceA){}]); 2651 * </pre> 2652 * 2653 * ## Inference 2654 * 2655 * In JavaScript calling `toString()` on a function returns the function definition. The definition can then be 2656 * parsed and the function arguments can be extracted. *NOTE:* This does not work with minification, and obfuscation 2657 * tools since these tools change the argument names. 2658 * 2659 * ## `$inject` Annotation 2660 * By adding a `$inject` property onto a function the injection parameters can be specified. 2661 * 2662 * ## Inline 2663 * As an array of injection names, where the last item in the array is the function to call. 2664 */ 2665 2666/** 2667 * @ngdoc method 2668 * @name AUTO.$injector#get 2669 * @methodOf AUTO.$injector 2670 * 2671 * @description 2672 * Return an instance of the service. 2673 * 2674 * @param {string} name The name of the instance to retrieve. 2675 * @return {*} The instance. 2676 */ 2677 2678/** 2679 * @ngdoc method 2680 * @name AUTO.$injector#invoke 2681 * @methodOf AUTO.$injector 2682 * 2683 * @description 2684 * Invoke the method and supply the method arguments from the `$injector`. 2685 * 2686 * @param {!function} fn The function to invoke. The function arguments come form the function annotation. 2687 * @param {Object=} self The `this` for the invoked method. 2688 * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before 2689 * the `$injector` is consulted. 2690 * @returns {*} the value returned by the invoked `fn` function. 2691 */ 2692 2693/** 2694 * @ngdoc method 2695 * @name AUTO.$injector#has 2696 * @methodOf AUTO.$injector 2697 * 2698 * @description 2699 * Allows the user to query if the particular service exist. 2700 * 2701 * @param {string} Name of the service to query. 2702 * @returns {boolean} returns true if injector has given service. 2703 */ 2704 2705/** 2706 * @ngdoc method 2707 * @name AUTO.$injector#instantiate 2708 * @methodOf AUTO.$injector 2709 * @description 2710 * Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies 2711 * all of the arguments to the constructor function as specified by the constructor annotation. 2712 * 2713 * @param {function} Type Annotated constructor function. 2714 * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before 2715 * the `$injector` is consulted. 2716 * @returns {Object} new instance of `Type`. 2717 */ 2718 2719/** 2720 * @ngdoc method 2721 * @name AUTO.$injector#annotate 2722 * @methodOf AUTO.$injector 2723 * 2724 * @description 2725 * Returns an array of service names which the function is requesting for injection. This API is used by the injector 2726 * to determine which services need to be injected into the function when the function is invoked. There are three 2727 * ways in which the function can be annotated with the needed dependencies. 2728 * 2729 * # Argument names 2730 * 2731 * The simplest form is to extract the dependencies from the arguments of the function. This is done by convert
2731ing 2732 * the function into a string using `toString()` method and extracting the argument names. 2733 * <pre> 2734 * // Given 2735 * function MyController($scope, $route) { 2736 * // ... 2737 * } 2738 * 2739 * // Then 2740 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 2741 * </pre> 2742 * 2743 * This method does not work with code minification / obfuscation. For this reason the following annotation strategies 2744 * are supported. 2745 * 2746 * # The `$inject` property 2747 * 2748 * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of 2749 * services to be injected into the function. 2750 * <pre> 2751 * // Given 2752 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 2753 * // ... 2754 * } 2755 * // Define function dependencies 2756 * MyController.$inject = ['$scope', '$route']; 2757 * 2758 * // Then 2759 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 2760 * </pre> 2761 * 2762 * # The array notation 2763 * 2764 * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very 2765 * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives 2766 * minification is a better choice: 2767 * 2768 * <pre> 2769 * // We wish to write this (not minification / obfuscation safe) 2770 * injector.invoke(function($compile, $rootScope) { 2771 * // ... 2772 * }); 2773 * 2774 * // We are forced to write break inlining 2775 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 2776 * // ... 2777 * }; 2778 * tmpFn.$inject = ['$compile', '$rootScope']; 2779 * injector.invoke(tmpFn); 2780 * 2781 * // To better support inline function the inline annotation is supported 2782 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 2783 * // ... 2784 * }]); 2785 * 2786 * // Therefore 2787 * expect(injector.annotate( 2788 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 2789 * ).toEqual(['$compile', '$rootScope']); 2790 * </pre> 2791 * 2792 * @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described 2793 * above. 2794 * 2795 * @returns {Array.<string>} The names of the services which the function requires. 2796 */ 2797 2798 2799 2800 2801/** 2802 * @ngdoc object 2803 * @name AUTO.$provide 2804 * 2805 * @description 2806 * 2807 * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance. 2808 * The providers share the same name as the instance they create with `Provider` suffixed to them. 2809 * 2810 * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of 2811 * a service. The Provider can have additional methods which would allow for configuration of the provider. 2812 * 2813 * <pre> 2814 * function GreetProvider() { 2815 * var salutation = 'Hello'; 2816 * 2817 * this.salutation = function(text) { 2818 * salutation = text; 2819 * }; 2820 * 2821 * this.$get = function() { 2822 * return function (name) { 2823 * return salutation + ' ' + name + '!'; 2824 * }; 2825 * }; 2826 * } 2827 * 2828 * describe('Greeter', function(){ 2829 * 2830 * beforeEach(module(function($provide) { 2831 * $provide.provider('greet', GreetProvider); 2832 * })); 2833 * 2834 * it('should greet', inject(function(greet) { 2835 * expect(greet('angular')).toEqual('Hello angular!'); 2836 * })); 2837 * 2838 * it('should allow configuration of salutation', function() { 2839 * module(function(greetProvider) { 2840 * greetProvider.salutation('Ahoj'); 2841 * }); 2842 * inject(function(greet) { 2843 * expect(greet('angular')).toEqual('Ahoj angular!'); 2844 * }); 2845 * }); 2846 * </pre> 2847 */ 2848 2849/** 2850 * @ngdoc method 2851 * @name AUTO.$provide#provider 2852 * @methodOf AUTO.$provide 2853 * @description 2854 * 2855 * Register a provider for a service. The providers can be retrieved and can have additional configuration methods. 2856 * 2857 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key. 2858 * @param {(Object|function())} provider If the provider is: 2859 * 2860 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 2861 * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created. 2862 * - `Constructor`: a new instance of the provider will be created using 2863 * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`. 2864 * 2865 * @returns {Object} registered provider instance 2866 */ 2867 2868/** 2869 * @ngdoc method 2870 * @name AUTO.$provide#factory 2871 * @methodOf AUTO.$provide 2872 * @description 2873 * 2874 * A short hand for configuring services if only `$get` method is required. 2875 * 2876 * @param {string} name The name of the instance. 2877 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for 2878 * `$provide.provider(name, {$get: $getFn})`. 2879 * @returns {Object} registered provider instance 2880 */ 2881 2882 2883/** 2884 * @ngdoc method 2885 * @name AUTO.$provide#service 2886 * @methodOf AUTO.$provide 2887 * @description 2888 * 2889 * A short hand for registering service of given class. 2890 * 2891 * @param {string} name The name of the instance. 2892 * @param {Function} constructor A class (constructor function) that will be instantiated. 2893 * @returns {Object} registered provider instance 2894 */ 2895 2896 2897/** 2898 * @ngdoc method 2899 * @name AUTO.$provide#value 2900 * @methodOf AUTO.$provide 2901 * @description 2902 * 2903 * A short hand for configuring services if the `$get` method is a constant. 2904 * 2905 * @param {string} name The name of the instance. 2906 * @param {*} value The value. 2907 * @returns {Object} registered provider instance 2908 */ 2909 2910 2911/** 2912 * @ngdoc method 2913 * @name AUTO.$provide#constant 2914 * @methodOf AUTO.$provide 2915 * @description 2916 * 2917 * A constant value, but unlike {@link AUTO.$provide#value value} it can be injected 2918 * into configuration function (other modules) and it is not interceptable by 2919 * {@link AUTO.$provide#decorator decorator}. 2920 * 2921 * @param {string} name The name of the constant. 2922 * @param {*} value The constant value. 2923 * @returns {Object} registered instance 2924 */ 2925 2926 2927/** 2928 * @ngdoc method 2929 * @name AUTO.$provide#decorator 2930 * @methodOf AUTO.$provide 2931 * @description 2932 * 2933 * Decoration of service, allows the decorator to intercept the service instance creation. The 2934 * returned instance may be the original instance, or a new instance which delegates to the 2935 * original instance. 2936 * 2937 * @param {string} name The name of the service to decorate. 2938 * @param {function()} decorator This function will be invoked when the service needs to be 2939 * instantiated. The function is called using the {@link AUTO.$injector#invoke 2940 * injector.invoke} method and is therefore fully injectable. Local injection arguments: 2941 * 2942 * * `$delegate` - The original service instance, which can be monkey patched, configured, 2943 * decorated or delegated to. 2944 */ 2945 2946 2947function createInjector(modulesToLoad) { 2948 var INSTANTIATING = {}, 2949 providerSuffix = 'Provider', 2950 path = [], 2951 loadedModules = new HashMap(), 2952 providerCache = { 2953 $provide: { 2954 provider: supportObject(provider), 2955 factory: supportObject(factory), 2956 service: supportObject(service), 2957 value: supportObject(value), 2958 constant: supportObject(constant), 2959 decorator: decorator 2960 } 2961 }, 2962 providerInjector = (providerCache.$injector =
2963 createInternalInjector(providerCache, function() { 2964 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 2965 })), 2966 instanceCache = {}, 2967 instanceInjector = (instanceCache.$injector = 2968 createInternalInjector(instanceCache, function(servicename) { 2969 var provider = providerInjector.get(servicename + providerSuffix); 2970 return instanceInjector.invoke(provider.$get, provider); 2971 })); 2972 2973 2974 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 2975 2976 return instanceInjector; 2977 2978 //////////////////////////////////// 2979 // $provider 2980 //////////////////////////////////// 2981 2982 function supportObject(delegate) { 2983 return function(key, value) { 2984 if (isObject(key)) { 2985 forEach(key, reverseParams(delegate)); 2986 } else { 2987 return delegate(key, value); 2988 } 2989 } 2990 } 2991 2992 function provider(name, provider_) { 2993 if (isFunction(provider_) || isArray(provider_)) { 2994 provider_ = providerInjector.instantiate(provider_); 2995 } 2996 if (!provider_.$get) { 2997 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 2998 } 2999 return providerCache[name + providerSuffix] = provider_; 3000 } 3001 3002 function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } 3003 3004 function service(name, constructor) { 3005 return factory(name, ['$injector', function($injector) { 3006 return $injector.instantiate(constructor); 3007 }]); 3008 } 3009 3010 function value(name, value) { return factory(name, valueFn(value)); } 3011 3012 function constant(name, value) { 3013 providerCache[name] = value; 3014 instanceCache[name] = value; 3015 } 3016 3017 function decorator(serviceName, decorFn) { 3018 var origProvider = providerInjector.get(serviceName + providerSuffix), 3019 orig$get = origProvider.$get; 3020 3021 origProvider.$get = function() { 3022 var origInstance = instanceInjector.invoke(orig$get, origProvider); 3023 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 3024 }; 3025 } 3026 3027 //////////////////////////////////// 3028 // Module Loading 3029 //////////////////////////////////// 3030 function loadModules(modulesToLoad){ 3031 var runBlocks = []; 3032 forEach(modulesToLoad, function(module) { 3033 if (loadedModules.get(module)) return; 3034 loadedModules.put(module, true); 3035 3036 try { 3037 if (isString(module)) { 3038 var moduleFn = angularModule(module); 3039 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 3040 3041 for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { 3042 var invokeArgs = invokeQueue[i], 3043 provider = providerInjector.get(invokeArgs[0]); 3044 3045 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 3046 } 3047 } else if (isFunction(module)) { 3048 runBlocks.push(providerInjector.invoke(module)); 3049 } else if (isArray(module)) { 3050 runBlocks.push(providerInjector.invoke(module)); 3051 } else { 3052 assertArgFn(module, 'module'); 3053 } 3054 } catch (e) { 3055 if (isArray(module)) { 3056 module = module[module.length - 1]; 3057 } 3058 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 3059 // Safari & FF's stack traces don't contain error.message content unlike those of Chrome and IE 3060 // So if stack doesn't contain message, we create a new string that contains both. 3061 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 3062 e = e.message + '\n' + e.stack; 3063 } 3064 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", module, e.stack || e.message || e); 3065 } 3066 }); 3067 return runBlocks; 3068 } 3069 3070 //////////////////////////////////// 3071 // internal Injector 3072 //////////////////////////////////// 3073 3074 function createInternalInjector(cache, factory) { 3075 3076 function getService(serviceName) { 3077 if (cache.hasOwnProperty(serviceName)) { 3078 if (cache[serviceName] === INSTANTIATING) { 3079 throw $injectorMinErr('cdep', 'Circular dependency foun
3079d: {0}', path.join(' <- ')); 3080 } 3081 return cache[serviceName]; 3082 } else { 3083 try { 3084 path.unshift(serviceName); 3085 cache[serviceName] = INSTANTIATING; 3086 return cache[serviceName] = factory(serviceName); 3087 } finally { 3088 path.shift(); 3089 } 3090 } 3091 } 3092 3093 function invoke(fn, self, locals){ 3094 var args = [], 3095 $inject = annotate(fn), 3096 length, i, 3097 key; 3098 3099 for(i = 0, length = $inject.length; i < length; i++) { 3100 key = $inject[i]; 3101 if (typeof key !== 'string') { 3102 throw $injectorMinErr('itkn', 'Incorrect injection token! Expected service name as string, got {0}', key); 3103 } 3104 args.push( 3105 locals && locals.hasOwnProperty(key) 3106 ? locals[key] 3107 : getService(key) 3108 ); 3109 } 3110 if (!fn.$inject) { 3111 // this means that we must be an array. 3112 fn = fn[length]; 3113 } 3114 3115 3116 // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke 3117 switch (self ? -1 : args.length) { 3118 case 0: return fn(); 3119 case 1: return fn(args[0]); 3120 case 2: return fn(args[0], args[1]); 3121 case 3: return fn(args[0], args[1], args[2]); 3122 case 4: return fn(args[0], args[1], args[2], args[3]); 3123 case 5: return fn(args[0], args[1], args[2], args[3], args[4]); 3124 case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]); 3125 case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); 3126 case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); 3127 case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); 3128 case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); 3129 default: return fn.apply(self, args); 3130 } 3131 } 3132 3133 function instantiate(Type, locals) { 3134 var Constructor = function() {}, 3135 instance, returnedValue; 3136 3137 // Check if Type is annotated and use just the given function at n-1 as parameter 3138 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 3139 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; 3140 instance = new Constructor(); 3141 returnedValue = invoke(Type, instance, locals); 3142 3143 return isObject(returnedValue) ? returnedValue : instance; 3144 } 3145 3146 return { 3147 invoke: invoke, 3148 instantiate: instantiate, 3149 get: getService, 3150 annotate: annotate, 3151 has: function(name) { 3152 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 3153 } 3154 }; 3155 } 3156} 3157 3158/** 3159 * @ngdoc function 3160 * @name ng.$anchorScroll 3161 * @requires $window 3162 * @requires $location 3163 * @requires $rootScope 3164 * 3165 * @description 3166 * When called, it checks current value of `$location.hash()` and scroll to related element, 3167 * according to rules specified in 3168 * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}. 3169 * 3170 * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor. 3171 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. 3172 */ 3173function $AnchorScrollProvider() { 3174 3175 var autoScrollingEnabled = true; 3176 3177 this.disableAutoScrolling = function() { 3178 autoScrollingEnabled = false; 3179 }; 3180 3181 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 3182 var document = $window.document; 3183 3184 // helper function to get first anchor from a NodeList 3185 // can't use filter.filter, as it accepts only instances of Array 3186 // and IE can't convert NodeList to an array using [].slice 3187 // TODO(vojta): use filter if we change it to accept lists as well 3188 function getFirstAnchor(list) { 3189 var result = null; 3190 forEach(list, function(element) { 3191 if (!result && lowercase(element.nodeName) === 'a') result = element; 3192 }); 3193 return result; 3194 } 3195 3196 function scroll() { 3197 var hash = $location.hash(), elm; 3198 3199 // empty hash, scroll to the top of the page 3200 if (!hash) $window.scrollTo(0, 0); 3201 3202 // element with given id 3203 else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); 3204 3205 // first anchor with given name :-D 3206 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); 3207 3208 // no element and hash == 'top', scroll to the top of the page 3209 else if (hash === 'top') $window.scrollTo(0, 0); 3210 } 3211 3212 // does not scroll when user clicks on anchor link that is currently on 3213 // (no url change, no $location.hash() change), browser native does scroll 3214 if (autoScrollingEnabled) { 3215 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 3216 function autoScrollWatchAction() { 3217 $rootScope.$evalAsync(scroll); 3218 }); 3219 } 3220 3221 return scroll; 3222 }]; 3223} 3224 3225var $animateMinErr = minErr('$animate'); 3226 3227/** 3228 * @ngdoc object 3229 * @name ng.$animateProvider 3230 * 3231 * @description 3232 * Default implementation of $animate that doesn't perform any animations, instead just synchronously performs DOM 3233 * updates and calls done() callbacks. 3234 * 3235 * In order to enable animations the ngAnimate module has to be loaded. 3236 * 3237 * To see the functional implementation check out src/ngAnimate/animate.js 3238 */ 3239var $AnimateProvider = ['$provide', function($provide) { 3240 3241 this.$$selectors = {}; 3242 3243 3244 /** 3245 * @ngdoc function 3246 * @name ng.$animateProvider#register 3247 * @methodOf ng.$animateProvider 3248 * 3249 * @description 3250 * Registers a new injectable animation factory function. The factory function produces the animation object which 3251 * contains callback functions for each event that is expected to be animated. 3252 * 3253 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` must be called once the 3254 * element animation is complete. If a function is returned then the animation service will use this function to 3255 * cancel the animation whenever a cancel event is triggered. 3256 * 3257 * 3258 *<pre> 3259 * return { 3260 * eventFn : function(element, done) { 3261 * //code to run the animation 3262 * //once complete, then run done() 3263 * return function cancellationFunction() { 3264 * //code to cancel the animation 3265 * } 3266 * } 3267 * } 3268 *</pre> 3269 * 3270 * @param {string} name The name of the animation. 3271 * @param {function} factory The factory function that will be executed to return the animation object. 3272 */ 3273 this.register = function(name, factory) { 3274 var key = name + '-animation'; 3275 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 3276 "Expecting class selector starting with '.' got '{0}'.", name); 3277 this.$$selectors[name.substr(1)] = key; 3278 $provide.factory(key, factory); 3279 }; 3280 3281 this.$get = ['$timeout', function($timeout) { 3282 3283 /** 3284 * @ngdoc object 3285 * @name ng.$animate 3286 *
3287 * @description 3288 * The $animate service provides rudimentary DOM manipulation functions to insert, remove, move elements within 3289 * the DOM as well as adding and removing classes. This service is the core service used by the ngAnimate $animator 3290 * service which provides high-level animation hooks for CSS and JavaScript. 3291 * 3292 * $animate is available in the AngularJS core, however, the ngAnimate module must be included to enable full out 3293 * animation support. Otherwise, $animate will only perform simple DOM manipulation operations. 3294 * 3295 * To learn more about enabling animation support, click here to visit the {@link ngAnimate ngAnimate module page} 3296 * as well as the {@link ngAnimate.$animate ngAnimate $animate service page}. 3297 */ 3298 return { 3299 3300 /** 3301 * @ngdoc function 3302 * @name ng.$animate#enter 3303 * @methodOf ng.$animate 3304 * @function 3305 * 3306 * @description 3307 * Inserts the element into the DOM either after the `after` element or within the `parent` element. Once complete, 3308 * the done() callback will be fired (if provided). 3309 * 3310 * @param {jQuery/jqLite element} element the element which will be inserted into the DOM 3311 * @param {jQuery/jqLite element} parent the parent element which will append the element as a child (if the after element is not present) 3312 * @param {jQuery/jqLite element} after the sibling element which will append the element after itself 3313 * @param {function=} done callback function that will be called after the element has been inserted into the DOM 3314 */ 3315 enter : function(element, parent, after, done) { 3316 var afterNode = after && after[after.length - 1]; 3317 var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; 3318 // IE does not like undefined so we have to pass null. 3319 var afterNextSibling = (afterNode && afterNode.nextSibling) || null; 3320 forEach(element, function(node) { 3321 parentNode.insertBefore(node, afterNextSibling); 3322 }); 3323 $timeout(done || noop, 0, false); 3324 }, 3325 3326 /** 3327 * @ngdoc function 3328 * @name ng.$animate#leave 3329 * @methodOf ng.$animate 3330 * @function 3331 * 3332 * @description 3333 * Removes the element from the DOM. Once complete, the done() callback will be fired (if provided). 3334 * 3335 * @param {jQuery/jqLite element} element the element which will be removed from the DOM 3336 * @param {function=} done callback function that will be called after the element has been removed from the DOM 3337 */ 3338 leave : function(element, done) { 3339 element.remove(); 3340 $timeout(done || noop, 0, false); 3341 }, 3342 3343 /** 3344 * @ngdoc function 3345 * @name ng.$animate#move 3346 * @methodOf ng.$animate 3347 * @function 3348 * 3349 * @description 3350 * Moves the position of the provided element within the DOM to be placed either after the `after` element or inside of the `parent` element. 3351 * Once complete, the done() callback will be fired (if provided). 3352 * 3353 * @param {jQuery/jqLite element} element the element which will be moved around within the DOM 3354 * @param {jQuery/jqLite element} parent the parent element where the element will be inserted into (if the after element is not present) 3355 * @param {jQuery/jqLite element} after the sibling element where the element will be positioned next to 3356 * @param {function=} done the callback function (if provided) that will be fired after the element has been moved to it's new position 3357 */ 3358 move : function(element, parent, after, done) { 3359 // Do not remove element before insert. Removing will cause data associated with the 3360 // element to be dropped. Insert will implicitly do the remove. 3361 this.enter(element, parent, after, done); 3362 }, 3363 3364 /** 3365 * @ngdoc function 3366 * @name ng.$animate#addClass 3367 * @methodOf ng.$animate 3368 * @function 3369 * 3370 * @description 3371 * Adds the provided className CSS class value to the provided element. Once complete, the done() callback will be fired (if provided). 3372 * 3373 * @param {jQuery/jqLite element} element the element which will have the className value added to it 3374 * @param {string} className the CSS class which will be added to the element 3375 * @param {function=} done the callback function (if provided) that will be fired after the className value has been added to the element 3376 */ 3377 addClass : function(element, className, done) { 3378 className = isString(className) ? 3379 className : 3380 isArray(className) ? className.join(' ') : ''; 3381 element.addClass(className); 3382 $timeout(done || noop, 0, false); 3383 }, 3384 3385 /** 3386 * @ngdoc function 3387 * @name ng.$animate#removeClass 3388 * @methodOf ng.$animate 3389 * @function 3390 *
3391 * @description 3392 * Removes the provided className CSS class value from the provided element. Once complete, the done() callback will be fired (if provided). 3393 * 3394 * @param {jQuery/jqLite element} element the element which will have the className value removed from it 3395 * @param {string} className the CSS class which will be removed from the element 3396 * @param {function=} done the callback function (if provided) that will be fired after the className value has been removed from the element 3397 */ 3398 removeClass : function(element, className, done) { 3399 className = isString(className) ? 3400 className : 3401 isArray(className) ? className.join(' ') : ''; 3402 element.removeClass(className); 3403 $timeout(done || noop, 0, false); 3404 }, 3405 3406 enabled : noop 3407 }; 3408 }]; 3409}]; 3410 3411/** 3412 * ! This is a private undocumented service ! 3413 * 3414 * @name ng.$browser 3415 * @requires $log 3416 * @description 3417 * This object has two goals: 3418 * 3419 * - hide all the global state in the browser caused by the window object 3420 * - abstract away all the browser specific features and inconsistencies 3421 * 3422 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 3423 * service, which can be used for convenient testing of the application without the interaction with 3424 * the real browser apis. 3425 */ 3426/** 3427 * @param {object} window The global window object. 3428 * @param {object} document jQuery wrapped document. 3429 * @param {function()} XHR XMLHttpRequest constructor. 3430 * @param {object} $log console.log or an object with the same interface. 3431 * @param {object} $sniffer $sniffer service 3432 */ 3433function Browser(window, document, $log, $sniffer) { 3434 var self = this, 3435 rawDocument = document[0], 3436 location = window.location, 3437 history = window.history, 3438 setTimeout = window.setTimeout, 3439 clearTimeout = window.clearTimeout, 3440 pendingDeferIds = {}; 3441 3442 self.isMock = false; 3443 3444 var outstandingRequestCount = 0; 3445 var outstandingRequestCallbacks = []; 3446 3447 // TODO(vojta): remove this temporary api 3448 self.$$completeOutstandingRequest = completeOutstandingRequest; 3449 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 3450 3451 /** 3452 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 3453 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 3454 */ 3455 function completeOutstandingRequest(fn) { 3456 try { 3457 fn.apply(null, sliceArgs(arguments, 1)); 3458 } finally { 3459 outstandingRequestCount--; 3460 if (outstandingRequestCount === 0) { 3461 while(outstandingRequestCallbacks.length) { 3462 try { 3463 outstandingRequestCallbacks.pop()(); 3464 } catch (e) { 3465 $log.error(e); 3466 } 3467 } 3468 } 3469 } 3470 } 3471 3472 /** 3473 * @private 3474 * Note: this method is used only by scenario runner 3475 * TODO(vojta): prefix this method with $$ ? 3476 * @param {function()} callback Function that will be called when no outstanding request 3477 */ 3478 self.notifyWhenNoOutstandingRequests = function(callback) { 3479 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 3480 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 3481 // regular poller would result in flaky tests. 3482 forEach(pollFns, function(pollFn){ pollFn(); }); 3483 3484 if (outstandingRequestCount === 0) { 3485 callback(); 3486 } else { 3487 outstandingRequestCallbacks.push(callback); 3488 } 3489 }; 3490 3491 ////////////////////////////////////////////////////////////// 3492 // Poll Watcher API 3493 ////////////////////////////////////////////////////////////// 3494 var pollFns = [], 3495 pollTimeout; 3496 3497 /** 3498 * @name ng.$browser#addPollFn 3499 * @methodOf ng.$browser 3500 * 3501 * @param {function()} fn Poll function to add 3502 * 3503 * @description 3504 * Adds a function to the list of functions that poller periodically executes, 3505 * and starts polling if not started yet. 3506 * 3507 * @returns {function()} the added function 3508 */ 3509 self.addPollFn = function(fn) {
3510 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 3511 pollFns.push(fn); 3512 return fn; 3513 }; 3514 3515 /** 3516 * @param {number} interval How often should browser call poll functions (ms) 3517 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 3518 * 3519 * @description 3520 * Configures the poller to run in the specified intervals, using the specified 3521 * setTimeout fn and kicks it off. 3522 */ 3523 function startPoller(interval, setTimeout) { 3524 (function check() { 3525 forEach(pollFns, function(pollFn){ pollFn(); }); 3526 pollTimeout = setTimeout(check, interval); 3527 })(); 3528 } 3529 3530 ////////////////////////////////////////////////////////////// 3531 // URL API 3532 ////////////////////////////////////////////////////////////// 3533 3534 var lastBrowserUrl = location.href, 3535 baseElement = document.find('base'), 3536 replacedUrl = null; 3537 3538 /** 3539 * @name ng.$browser#url 3540 * @methodOf ng.$browser 3541 * 3542 * @description 3543 * GETTER: 3544 * Without any argument, this method just returns current value of location.href. 3545 * 3546 * SETTER: 3547 * With at least one argument, this method sets url to new value. 3548 * If html5 history api supported, pushState/replaceState is used, otherwise 3549 * location.href/location.replace is used. 3550 * Returns its own instance to allow chaining 3551 * 3552 * NOTE: this api is intended for use only by the $location service. Please use the 3553 * {@link ng.$location $location service} to change url. 3554 * 3555 * @param {string} url New url (when used as setter) 3556 * @param {boolean=} replace Should new url replace current history record ? 3557 */ 3558 self.url = function(url, replace) { 3559 // setter 3560 if (url) { 3561 if (lastBrowserUrl == url) return; 3562 lastBrowserUrl = url; 3563 if ($sniffer.history) { 3564 if (replace) history.replaceState(null, '', url); 3565 else { 3566 history.pushState(null, '', url); 3567 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 3568 baseElement.attr('href', baseElement.attr('href')); 3569 } 3570 } else { 3571 if (replace) { 3572 location.replace(url); 3573 replacedUrl = url; 3574 } else { 3575 location.href = url; 3576 replacedUrl = null; 3577 } 3578 } 3579 return self; 3580 // getter 3581 } else { 3582 // - the replacedUrl is a workaround for an IE8-9 issue with location.replace method that doesn't update 3583 // location.href synchronously 3584 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 3585 return replacedUrl || location.href.replace(/%27/g,"'"); 3586 } 3587 }; 3588 3589 var urlChangeListeners = [], 3590 urlChangeInit = false; 3591 3592 function fireUrlChange() { 3593 if (lastBrowserUrl == self.url()) return; 3594 3595 lastBrowserUrl = self.url(); 3596 forEach(urlChangeListeners, function(listener) { 3597 listener(self.url()); 3598 }); 3599 } 3600 3601 /** 3602 * @name ng.$browser#onUrlChange 3603 * @methodOf ng.$browser 3604 * @TODO(vojta): refactor to use node's syntax for events 3605 * 3606 * @description 3607 * Register callback function that will be called, when url changes. 3608 * 3609 * It's only called when the url is changed by outside of angular: 3610 * - user types different url into address bar 3611 * - user clicks on history (forward/back) button 3612 * - user clicks on a link 3613 * 3614 * It's not called when url is changed by $browser.url() method 3615 * 3616 * The listener gets called with new url as parameter. 3617 * 3618 * NOTE: this api is intended for use only by the $location service. Please use the 3619 * {@link ng.$location $location service} to monitor url changes in angular apps. 3620 * 3621 * @param {function(string)} listener Listener function to be called when url changes. 3622 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 3623 */ 3624 self.onUrlChange = function(callback) { 3625 if (!urlChangeInit) { 3626 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 3627 // don't fire popstate when user change the address bar and don't fire hashchange when url 3628 // changed by push/replaceState 3629 3630 // html5 history api - popstate event 3631 if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); 3632 // hashchange event 3633 if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); 3634 // polling 3635 else self.addPollFn(fireUrlChange); 3636 3637 urlChangeInit = true; 3638 } 3639 3640 urlChangeListeners.push(callback); 3641 return callback; 3642 }; 3643 3644 ////////////////////////////////////////////////////////////// 3645 // Misc API 3646 ////////////////////////////////////////////////////////////// 3647 3648 /** 3649 * Returns current <base href> 3650 * (always relative - without domain) 3651 * 3652 * @returns {string=} 3653 */ 3654 self.baseHref = function() { 3655 var href = baseElement.attr('href'); 3656 return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : ''; 3657 }; 3658 3659 ////////////////////////////////////////////////////////////// 3660 // Cookies API 3661 ////////////////////////////////////////////////////////////// 3662 var lastCookies = {}; 3663 var lastCookieString = ''; 3664 var cookiePath = self.baseHref(); 3665 3666 /** 3667 * @name ng.$browser#cookies 3668 * @methodOf ng.$browser 3669 * 3670 * @param {string=} name Cookie name 3671 * @param {string=} value Cookie value 3672 * 3673 * @description 3674 * The cookies method provides a 'private' low level access to browser cookies. 3675 * It is not meant to be used directly, use the $cookie service instead. 3676 * 3677 * The return values vary depending on the arguments that the method was called with as follows: 3678 * <ul> 3679 * <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li> 3680 * <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li> 3681 * <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li> 3682 * </ul> 3683 * 3684 * @returns {Object} Hash of all cookies (if called without any parameter) 3685 */ 3686 self.cookies = function(name, value) { 3687 var cookieLength, cookieArray, cookie, i, index; 3688 3689 if (name) { 3690 if (value === undefined) { 3691 rawDocument.cookie = escape(name) + "=;
3691path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 3692 } else { 3693 if (isString(value)) { 3694 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1; 3695 3696 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 3697 // - 300 cookies 3698 // - 20 cookies per unique domain 3699 // - 4096 bytes per cookie 3700 if (cookieLength > 4096) { 3701 $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+ 3702 cookieLength + " > 4096 bytes)!"); 3703 } 3704 } 3705 } 3706 } else { 3707 if (rawDocument.cookie !== lastCookieString) { 3708 lastCookieString = rawDocument.cookie; 3709 cookieArray = lastCookieString.split("; "); 3710 lastCookies = {}; 3711 3712 for (i = 0; i < cookieArray.length; i++) { 3713 cookie = cookieArray[i]; 3714 index = cookie.indexOf('='); 3715 if (index > 0) { //ignore nameless cookies 3716 var name = unescape(cookie.substring(0, index)); 3717 // the first value that is seen for a cookie is the most 3718 // specific one. values for the same cookie name that 3719 // follow are for less specific paths. 3720 if (lastCookies[name] === undefined) { 3721 lastCookies[name] = unescape(cookie.substring(index + 1)); 3722 } 3723 } 3724 } 3725 } 3726 return lastCookies; 3727 } 3728 }; 3729 3730 3731 /** 3732 * @name ng.$browser#defer 3733 * @methodOf ng.$browser 3734 * @param {function()} fn A function, who's execution should be deferred. 3735 * @param {number=} [delay=0] of milliseconds to defer the function execution. 3736 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 3737 * 3738 * @description 3739 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 3740 * 3741 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 3742 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 3743 * via `$browser.defer.flush()`. 3744 * 3745 */ 3746 self.defer = function(fn, delay) { 3747 var timeoutId; 3748 outstandingRequestCount++; 3749 timeoutId = setTimeout(function() { 3750 delete pendingDeferIds[timeoutId]; 3751 completeOutstandingRequest(fn); 3752 }, delay || 0); 3753 pendingDeferIds[timeoutId] = true; 3754 return timeoutId; 3755 }; 3756 3757 3758 /** 3759 * @name ng.$browser#defer.cancel 3760 * @methodOf ng.$browser.defer 3761 * 3762 * @description 3763 * Cancels a deferred task identified with `deferId`. 3764 * 3765 * @param {*} deferId Token returned by the `$browser.defer` function. 3766 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully canceled. 3767 */ 3768 self.defer.cancel = function(deferId) { 3769 if (pendingDeferIds[deferId]) { 3770 delete pendingDeferIds[deferId]; 3771 clearTimeout(deferId); 3772 completeOutstandingRequest(noop); 3773 return true; 3774 } 3775 return false; 3776 }; 3777 3778} 3779 3780function $BrowserProvider(){ 3781 this.$get = ['$window', '$log', '$sniffer', '$document', 3782 function( $window, $log, $sniffer, $document){ 3783 return new Browser($window, $document, $log, $sniffer); 3784 }]; 3785} 3786 3787/** 3788 * @ngdoc object 3789 * @name ng.$cacheFactory 3790 * 3791 * @description 3792 * Factory that constructs cache objects and gives access to them. 3793 * 3794 * <pre> 3795 * 3796 * var cache = $cacheFactory('cacheId'); 3797 * expect($cacheFactory.get('cacheId')).toBe(cache); 3798 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 3799 * 3800 * cache.put("key", "value"); 3801 * cache.put("another key", "another value"); 3802 * 3803 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); // Since we've specified no options on creation 3804 * 3805 * </pre> 3806 * 3807 * 3808 * @param {string} cacheId Name or id of the newly created cache. 3809 * @param {object=} options Options object that specifies the cache behavior. Properties: 3810 * 3811 * - `{number=}` `capacity` ââ¬â turns the cache into LRU cache. 3812 * 3813 * @returns {object} Newly created cache object with the following set of methods: 3814 * 3815 * - `{object}` `info()` ââ¬â Returns id, size, and options of cache. 3816 * - `{{*}}` `put({string} key, {*} value)` ââ¬â Puts a new key-value pair into the cache and returns it. 3817 * - `{{*}}` `get({string} key)` ââ¬â Returns cached value for `key` or undefined for cache miss. 3818 * - `{void}` `remove({string} key)` ââ¬â Removes a key-value pair from the cache. 3819 * - `{void}` `removeAll()` ââ¬â Removes all cached values. 3820 * - `{void}` `destroy()` ââ¬â Removes references to this cache from $cacheFactory. 3821 * 3822 */
3823function $CacheFactoryProvider() { 3824 3825 this.$get = function() { 3826 var caches = {}; 3827 3828 function cacheFactory(cacheId, options) { 3829 if (cacheId in caches) { 3830 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 3831 } 3832 3833 var size = 0, 3834 stats = extend({}, options, {id: cacheId}), 3835 data = {}, 3836 capacity = (options && options.capacity) || Number.MAX_VALUE, 3837 lruHash = {}, 3838 freshEnd = null, 3839 staleEnd = null; 3840 3841 return caches[cacheId] = { 3842 3843 put: function(key, value) { 3844 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 3845 3846 refresh(lruEntry); 3847 3848 if (isUndefined(value)) return; 3849 if (!(key in data)) size++; 3850 data[key] = value; 3851 3852 if (size > capacity) { 3853 this.remove(staleEnd.key); 3854 } 3855 3856 return value; 3857 }, 3858 3859 3860 get: function(key) { 3861 var lruEntry = lruHash[key]; 3862 3863 if (!lruEntry) return; 3864 3865 refresh(lruEntry); 3866 3867 return data[key]; 3868 }, 3869 3870 3871 remove: function(key) { 3872 var lruEntry = lruHash[key]; 3873 3874 if (!lruEntry) return; 3875 3876 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 3877 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 3878 link(lruEntry.n,lruEntry.p); 3879 3880 delete lruHash[key]; 3881 delete data[key]; 3882 size--; 3883 }, 3884 3885 3886 removeAll: function() { 3887 data = {}; 3888 size = 0; 3889 lruHash = {}; 3890 freshEnd = staleEnd = null; 3891 }, 3892 3893 3894 destroy: function() { 3895 data = null; 3896 stats = null; 3897 lruHash = null; 3898 delete caches[cacheId]; 3899 }, 3900 3901 3902 info: function() { 3903 return extend({}, stats, {size: size}); 3904 } 3905 }; 3906 3907 3908 /** 3909 * makes the `entry` the freshEnd of the LRU linked list 3910 */ 3911 function refresh(entry) { 3912 if (entry != freshEnd) { 3913 if (!staleEnd) { 3914 staleEnd = entry; 3915 } else if (staleEnd == entry) { 3916 staleEnd = entry.n; 3917 } 3918 3919 link(entry.n, entry.p); 3920 link(entry, freshEnd); 3921 freshEnd = entry; 3922 freshEnd.n = null; 3923 } 3924 } 3925 3926 3927 /** 3928 * bidirectionally links two entries of the LRU linked list 3929 */ 3930 function link(nextEntry, prevEntry) { 3931 if (nextEntry != prevEntry) { 3932 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 3933 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 3934 } 3935 } 3936 } 3937 3938 3939 /** 3940 * @ngdoc method 3941 * @name ng.$cacheFactory#info 3942 * @methodOf ng.$cacheFactory 3943 * 3944 * @description 3945 * Get information about all the of the caches that have been created 3946 * 3947 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 3948 */ 3949 cacheFactory.info = function() { 3950 var info = {}; 3951 forEach(caches, function(cache, cacheId) { 3952 info[cacheId] = cache.info(); 3953 }); 3954 return info; 3955 }; 3956 3957 3958 /** 3959 * @ngdoc method 3960 * @name ng.$cacheFactory#get 3961 * @methodOf ng.$cacheFactory 3962 * 3963 * @description 3964 * Get access to a cache object by the `cacheId` used when it was created. 3965 * 3966 * @param {string} cacheId Name or id of a cache to access. 3967 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 3968 */ 3969 cacheFactory.get = function(cacheId) { 3970 return caches[cacheId]; 3971 }; 3972 3973 3974 return cacheFactory; 3975 }; 3976} 3977 3978/** 3979 * @ngdoc object 3980 * @name ng.$templateCache 3981 * 3982 * @description 3983 * The first time a template is used, it is loaded in the template cache for quick retrieval. You can 3984 * load templates directly into the cache in a `script` tag, or by consuming the `$templateCache` 3985 * service directly. 3986 * 3987 * Adding via the `script` tag: 3988 * <pre> 3989 * <html ng-app> 3990 * <head> 3991 * <script type="text/ng-template" id="templateId.html"> 3992 * This is the content of the template 3993 * </script> 3994 * </head> 3995 * ... 3996 * </html> 3997 * </pre> 3998 * 3999 * **Note:** the `script` tag containing the template does not need to be included in the `head` of the document, but 4000 * it must be below the `ng-app` definition. 4001 * 4002 * Adding via the $templateCache service: 4003 * 4004 * <pre> 4005 * var myApp = angular.module('myApp', []); 4006 * myApp.run(function($templateCache) { 4007 * $templateCache.put('templateId.html', 'This is the content of the template'); 4008 * }); 4009 * </pre> 4010 * 4011 * To retrieve the template later, simply use it in your HTML: 4012 * <pre> 4013 * <div ng-include=" 'templateId.html' "></div> 4014 * </pre> 4015 * 4016 * or get it via Javascript: 4017 * <pre> 4018 * $templateCache.get('templateId.html') 4019 * </pre> 4020 * 4021 * See {@link ng.$cacheFactory $cacheFactory}. 4022 * 4023 */ 4024function $TemplateCacheProvider() { 4025 this.$get = ['$cacheFactory', function($cacheFactory) { 4026 return $cacheFactory('templates'); 4027 }]; 4028} 4029 4030/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 4031 * 4032 * DOM-related variables: 4033 * 4034 * - "node" - DOM Node 4035 * - "element" - DOM Element or Node 4036 * - "$node" or "$element" - jqLite-wrapped node or element 4037 * 4038 * 4039 * Compiler related stuff: 4040 * 4041 * - "linkFn" - linking fn of a single directive 4042 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 4043 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 4044 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 4045 */ 4046 4047 4048/** 4049 * @ngdoc function 4050 * @name ng.$compile 4051 * @function 4052 * 4053 * @description 4054 * Compiles a piece of HTML string or DOM into a template and produces a template function, which 4055 * can then be used to link {@link ng.$rootScope.Scope scope} and the template together. 4056 * 4057 * The compilation is a process of walking the DOM tree and trying to match DOM elements to 4058 * {@link ng.$compileProvider#directive directives}. For each match it 4059 * executes corresponding template function and collects the 4060 * instance functions into a single template function which is then returned. 4061 * 4062 * The template function can then be used once to produce the view or as it is the case with 4063 * {@link ng.directive:ngRepeat repeater} many-times, in which 4064 * case each call results in a view that is a DOM clone of the original template. 4065 * 4066 <doc:example module="compile"> 4067 <doc:source> 4068 <script> 4069 // declare a new module, and inject the $compileProvider
4070 angular.module('compile', [], function($compileProvider) { 4071 // configure new 'compile' directive by passing a directive 4072 // factory function. The factory function injects the '$compile' 4073 $compileProvider.directive('compile', function($compile) { 4074 // directive factory creates a link function 4075 return function(scope, element, attrs) { 4076 scope.$watch( 4077 function(scope) { 4078 // watch the 'compile' expression for changes 4079 return scope.$eval(attrs.compile); 4080 }, 4081 function(value) { 4082 // when the 'compile' expression changes 4083 // assign it into the current DOM 4084 element.html(value); 4085 4086 // compile the new DOM and link it to the current 4087 // scope. 4088 // NOTE: we only compile .childNodes so that 4089 // we don't get into infinite loop compiling ourselves 4090 $compile(element.contents())(scope); 4091 } 4092 ); 4093 }; 4094 }) 4095 }); 4096 4097 function Ctrl($scope) { 4098 $scope.name = 'Angular'; 4099 $scope.html = 'Hello {{name}}'; 4100 } 4101 </script> 4102 <div ng-controller="Ctrl"> 4103 <input ng-model="name"> <br> 4104 <textarea ng-model="html"></textarea> <br> 4105 <div compile="html"></div> 4106 </div> 4107 </doc:source> 4108 <doc:scenario> 4109 it('should auto compile', function() { 4110 expect(element('div[compile]').text()).toBe('Hello Angular'); 4111 input('html').enter('{{name}}!'); 4112 expect(element('div[compile]').text()).toBe('Angular!'); 4113 }); 4114 </doc:scenario> 4115 </doc:example> 4116 4117 * 4118 * 4119 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 4120 * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives. 4121 * @param {number} maxPriority only apply directives lower then given priority (Only effects the 4122 * root element(s), not their children) 4123 * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template 4124 * (a DOM element/tree) to a scope. Where: 4125 * 4126 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 4127 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 4128 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 4129 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 4130 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 4131 * 4132 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 4133 * * `scope` - is the current scope with which the linking function is working with. 4134 * 4135 * Calling the linking function returns the element of the template. It is either the original element 4136 * passed in, or the clone of the element if the `cloneAttachFn` is provided. 4137 * 4138 * After linking the view is not updated until after a call to $digest which typically is done by 4139 * Angular automatically. 4140 * 4141 * If you need access to the bound view, there are two ways to do it: 4142 * 4143 * - If you are not asking the linking function to clone the template, create the DOM element(s) 4144 * before you send them to the compiler and keep this reference around. 4145 * <pre> 4146 * var element = $compile('<p>{{total}}</p>')(scope); 4147 * </pre> 4148 * 4149 * - if on the other hand, you need the element to be cloned, the view reference from the original 4150 * example would not point to the clone, but rather to the original template that was cloned. In 4151 * this case, you can access the clone via the cloneAttachFn: 4152 * <pre> 4153 * var templateHTML = angular.element('<p>{{total}}</p>'), 4154 * scope = ....; 4155 * 4156 * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) { 4157 * //attach the clone to DOM document at the right place 4158 * }); 4159 * 4160 * //now we have reference to the cloned DOM via `clone` 4161 * </pre> 4162 * 4163 * 4164 * For information on how the compiler works, see the 4165 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 4166 */ 4167 4168var $compileMinErr = minErr('$compile'); 4169 4170/** 4171 * @ngdoc service 4172 * @name ng.$compileProvider 4173 * @function 4174 * 4175 * @description 4176 */ 4177$CompileProvider.$inject = ['$provide']; 4178function $CompileProvider($provide) { 4179 var hasDirectives = {}, 4180 Suffix = 'Directive', 4181 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, 4182 CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, 4183 aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/, 4184 imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; 4185 4186 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 4187 // The assumption is that future DOM event attribute names will begin with 4188 // 'on' and be composed of only English letters. 4189 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]*|formaction)$/; 4190 4191 /** 4192 * @ngdoc function 4193 * @name ng.$compileProvider#directive 4194 * @methodOf ng.$compileProvider 4195 * @function 4196 * 4197 * @description 4198 * Register a new directive with the compiler. 4199 * 4200 * @param {string} name Name of the directive in camel-case. (ie <code>ngBind</code> which will match as 4201 * <code>ng-bind</code>). 4202 * @param {function|Array} directiveFactory An injectable directive factory function. See {@link guide/directive} for more 4203 * info. 4204 * @returns {ng.$compileProvider} Self for chaining. 4205 */ 4206 this.directive = function registerDirective(name, directiveFactory) { 4207 if (isString(name)) { 4208 assertArg(directiveFactory, 'directiveFactory'); 4209 if (!hasDirectives.hasOwnProperty(name)) { 4210 hasDirectives[name] = []; 4211 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 4212 function($injector, $exceptionHandler) { 4213 var directives = [];
4214 forEach(hasDirectives[name], function(directiveFactory) { 4215 try { 4216 var directive = $injector.invoke(directiveFactory); 4217 if (isFunction(directive)) { 4218 directive = { compile: valueFn(directive) }; 4219 } else if (!directive.compile && directive.link) { 4220 directive.compile = valueFn(directive.link); 4221 } 4222 directive.priority = directive.priority || 0; 4223 directive.name = directive.name || name; 4224 directive.require = directive.require || (directive.controller && directive.name); 4225 directive.restrict = directive.restrict || 'A'; 4226 directives.push(directive); 4227 } catch (e) { 4228 $exceptionHandler(e); 4229 } 4230 }); 4231 return directives; 4232 }]); 4233 } 4234 hasDirectives[name].push(directiveFactory); 4235 } else { 4236 forEach(name, reverseParams(registerDirective)); 4237 } 4238 return this; 4239 }; 4240 4241 4242 /** 4243 * @ngdoc function 4244 * @name ng.$compileProvider#aHrefSanitizationWhitelist 4245 * @methodOf ng.$compileProvider 4246 * @function 4247 * 4248 * @description 4249 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 4250 * urls during a[href] sanitization. 4251 * 4252 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 4253 * 4254 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 4255 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 4256 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 4257 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 4258 * 4259 * @param {RegExp=} regexp New regexp to whitelist urls with. 4260 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 4261 * chaining otherwise. 4262 */ 4263 this.aHrefSanitizationWhitelist = function(regexp) { 4264 if (isDefined(regexp)) { 4265 aHrefSanitizationWhitelist = regexp; 4266 return this; 4267 } 4268 return aHrefSanitizationWhitelist; 4269 }; 4270 4271 4272 /** 4273 * @ngdoc function 4274 * @name ng.$compileProvider#imgSrcSanitizationWhitelist 4275 * @methodOf ng.$compileProvider 4276 * @function 4277 * 4278 * @description 4279 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 4280 * urls during img[src] sanitization. 4281 * 4282 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 4283 * 4284 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into an 4285 * absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` regular 4286 * expression. If a match is found, the original url is written into the dom. Otherwise, the 4287 * absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 4288 * 4289 * @param {RegExp=} regexp New regexp to whitelist urls with. 4290 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 4291 * chaining otherwise. 4292 */ 4293 this.imgSrcSanitizationWhitelist = function(regexp) { 4294 if (isDefined(regexp)) { 4295 imgSrcSanitizationWhitelist = regexp; 4296 return this; 4297 } 4298 return imgSrcSanitizationWhitelist; 4299 }; 4300 4301 4302 this.$get = [ 4303 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', 4304 '$controller', '$rootScope', '$document', '$sce', '$$urlUtils', '$animate', 4305 function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, 4306 $controller, $rootScope, $document, $sce, $$urlUtils, $animate) { 4307 4308 var Attributes = function(element, attr) { 4309 this.$$element = element; 4310 this.$attr = attr || {}; 4311 }; 4312 4313 Attributes.prototype = { 4314 $normalize: directiveNormalize, 4315 4316 4317 /** 4318 * @ngdoc function 4319 * @name ng.$compile.directive.Attributes#$addClass 4320 * @methodOf ng.$compile.directive.Attributes 4321 * @function 4322 *
4323 * @description 4324 * Adds the CSS class value specified by the classVal parameter to the element. If animations 4325 * are enabled then an animation will be triggered for the class addition. 4326 * 4327 * @param {string} classVal The className value that will be added to the element 4328 */ 4329 $addClass : function(classVal) { 4330 if(classVal && classVal.length > 0) { 4331 $animate.addClass(this.$$element, classVal); 4332 } 4333 }, 4334 4335 /** 4336 * @ngdoc function 4337 * @name ng.$compile.directive.Attributes#$removeClass 4338 * @methodOf ng.$compile.directive.Attributes 4339 * @function 4340 * 4341 * @description 4342 * Removes the CSS class value specified by the classVal parameter from the element. If animations 4343 * are enabled then an animation will be triggered for the class removal. 4344 * 4345 * @param {string} classVal The className value that will be removed from the element 4346 */ 4347 $removeClass : function(classVal) { 4348 if(classVal && classVal.length > 0) { 4349 $animate.removeClass(this.$$element, classVal); 4350 } 4351 }, 4352 4353 /** 4354 * Set a normalized attribute on the element in a way such that all directives 4355 * can share the attribute. This function properly handles boolean attributes. 4356 * @param {string} key Normalized key. (ie ngAttribute) 4357 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 4358 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 4359 * Defaults to true. 4360 * @param {string=} attrName Optional none normalized name. Defaults to key. 4361 */ 4362 $set: function(key, value, writeAttr, attrName) { 4363 //special case for class attribute addition + removal 4364 //so that class changes can tap into the animation 4365 //hooks provided by the $animate service 4366 if(key == 'class') { 4367 value = value || ''; 4368 var current = this.$$element.attr('class') || ''; 4369 this.$removeClass(tokenDifference(current, value).join(' ')); 4370 this.$addClass(tokenDifference(value, current).join(' ')); 4371 } else { 4372 var booleanKey = getBooleanAttrName(this.$$element[0], key), 4373 normalizedVal, 4374 nodeName; 4375 4376 if (booleanKey) { 4377 this.$$element.prop(key, value); 4378 attrName = booleanKey; 4379 } 4380 4381 this[key] = value; 4382 4383 // translate normalized key to actual key 4384 if (attrName) { 4385 this.$attr[key] = attrName; 4386 } else { 4387 attrName = this.$attr[key]; 4388 if (!attrName) { 4389 this.$attr[key] = attrName = snake_case(key, '-'); 4390 } 4391 } 4392 4393 nodeName = nodeName_(this.$$element); 4394 4395 // sanitize a[href] and img[src] values 4396 if ((nodeName === 'A' && key === 'href') || 4397 (nodeName === 'IMG' && key === 'src')) { 4398 // NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case. 4399 if (!msie || msie >= 8 ) { 4400 normalizedVal = $$urlUtils.resolve(value); 4401 if (normalizedVal !== '') { 4402 if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) || 4403 (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) { 4404 this[key] = value = 'unsafe:' + normalizedVal; 4405 } 4406 } 4407 } 4408 } 4409 4410 if (writeAttr !== false) { 4411 if (value === null || value === undefined) { 4412 this.$$element.removeAttr(attrName); 4413 } else { 4414 this.$$element.attr(attrName, value); 4415 } 4416 } 4417 } 4418 4419 // fire observers 4420 var $$observers = this.$$observers; 4421 $$observers && forEach($$observers[key], function(fn) { 4422 try { 4423 fn(value); 4424 } catch (e) { 4425 $exceptionHandler(e); 4426 } 4427 }); 4428 4429 function tokenDifference(str1, str2) { 4430 var values = [], 4431 tokens1 = str1.split(/\s+/), 4432 tokens2 = str2.split(/\s+/); 4433 4434 outer: 4435 for(var i=0;i<tokens1.length;i++) { 4436 var token = tokens1[i]; 4437 for(var j=0;j<tokens2.length;j++) { 4438 if(token == tokens2[j]) continue outer; 4439 } 4440 values.push(token); 4441 } 4442 return values; 4443 }; 4444 }, 4445 4446 4447 /** 4448 * Observe an interpolated attribute. 4449 * The observer will never be called, if given attribute is not interpolated. 4450 * 4451 * @param {string} key Normalized key. (ie ngAttribute) . 4452 * @param {function(*)} fn Function that will be called whenever the attribute value changes. 4453 * @returns {function(*)} the `fn` Function passed in. 4454 */ 4455 $observe: function(key, fn) { 4456 var attrs = this, 4457 $$observers = (attrs.$$observers || (attrs.$$observers = {})), 4458 listeners = ($$observers[key] || ($$observers[key] = [])); 4459 4460 listeners.push(fn); 4461 $rootScope.$evalAsync(function() { 4462 if (!listeners.$$inter) { 4463 // no one registered attribute interpolation function, so lets call it manually 4464 fn(attrs[key]); 4465 } 4466 }); 4467 return fn; 4468 } 4469 }; 4470 4471 var urlSanitizationNode = $document[0].createElement('a'), 4472 startSymbol = $interpolate.startSymbol(), 4473 endSymbol = $interpolate.endSymbol(), 4474 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 4475 ? identity 4476 : function denormalizeTemplate(template) { 4477 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 4478 }, 4479 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 4480 4481 4482 return compile; 4483 4484 //================================ 4485 4486 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective) { 4487 if (!($compileNodes instanceof jqLite)) { 4488 // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it. 4489 $compileNodes = jqLite($compileNodes); 4490 } 4491 // We can not compile top level text elements since text nodes can be merged and we will 4492 // not be able to attach scope data to them, so we will wrap them in <span>
4493 forEach($compileNodes, function(node, index){ 4494 if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { 4495 $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; 4496 } 4497 }); 4498 var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective); 4499 return function publicLinkFn(scope, cloneConnectFn){ 4500 assertArg(scope, 'scope'); 4501 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 4502 // and sometimes changes the structure of the DOM. 4503 var $linkNode = cloneConnectFn 4504 ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! 4505 : $compileNodes; 4506 4507 // Attach scope only to non-text nodes. 4508 for(var i = 0, ii = $linkNode.length; i<ii; i++) { 4509 var node = $linkNode[i]; 4510 if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) { 4511 $linkNode.eq(i).data('$scope', scope); 4512 } 4513 } 4514 safeAddClass($linkNode, 'ng-scope'); 4515 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 4516 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); 4517 return $linkNode; 4518 }; 4519 } 4520 4521 function safeAddClass($element, className) { 4522 try { 4523 $element.addClass(className); 4524 } catch(e) { 4525 // ignore, since it means that we are trying to set class on 4526 // SVG element, where class name is read-only. 4527 } 4528 } 4529 4530 /** 4531 * Compile function matches each node in nodeList against the directives. Once all directives 4532 * for a particular node are collected their compile functions are executed. The compile 4533 * functions return values - the linking functions - are combined into a composite linking 4534 * function, which is the a linking function for the node. 4535 * 4536 * @param {NodeList} nodeList an array of nodes or NodeList to compile 4537 * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the 4538 * scope argument is auto-generated to the new child of the transcluded parent scope. 4539 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the 4540 * rootElement must be set the jqLite collection of the compile root. This is 4541 * needed so that the jqLite collection items can be replaced with widgets. 4542 * @param {number=} max directive priority 4543 * @returns {?function} A composite linking function of all of the matched directives or null. 4544 */ 4545 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective) { 4546 var linkFns = [], 4547 nodeLinkFn, childLinkFn, directives, attrs, linkFnFound; 4548 4549 for(var i = 0; i < nodeList.length; i++) { 4550 attrs = new Attributes(); 4551 4552 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 4553 directives = collectDirectives(nodeList[i], [], attrs, i == 0 ? maxPriority : undefined, ignoreDirective); 4554 4555 nodeLinkFn = (directives.length) 4556 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement) 4557 : null; 4558 4559 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length) 4560 ? null 4561 : compileNodes(nodeList[i].childNodes, 4562 nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); 4563 4564 linkFns.push(nodeLinkFn); 4565 linkFns.push(childLinkFn); 4566 linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn); 4567 } 4568 4569 // return a linking function if we have found anything, null otherwise 4570 return linkFnFound ? compositeLinkFn : null; 4571 4572 function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { 4573 var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn, i, ii, n; 4574 4575 // copy nodeList so that linking doesn't break due to live list updates. 4576 var stableNodeList = []; 4577 for (i = 0, ii = nodeList.length; i < ii; i++) { 4578 stableNodeList.push(nodeList[i]); 4579 } 4580 4581 for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) { 4582 node = stableNodeList[n]; 4583 nodeLinkFn = linkFns[i++]; 4584 childLinkFn = linkFns[i++]; 4585 4586 if (nodeLinkFn) { 4587 if (nodeLinkFn.scope) { 4588 childScope = scope.$new(isObject(nodeLinkFn.scope)); 4589 jqLite(node).data('$scope', childScope); 4590 } else { 4591 childScope = scope; 4592 } 4593 childTranscludeFn = nodeLinkFn.transclude; 4594 if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) { 4595 nodeLinkFn(childLinkFn, childScope, node, $rootElement, 4596 (function(transcludeFn) { 4597 return function(cloneFn) { 4598 var transcludeScope = scope.$new(); 4599 transcludeScope.$$transcluded = true; 4600 4601 return transcludeFn(transcludeScope, cloneFn). 4602 on('$destroy', bind(transcludeScope, transcludeScope.$destroy)); 4603 }; 4604 })(childTranscludeFn || transcludeFn) 4605 ); 4606 } else { 4607 nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn); 4608 } 4609 } else if (childLinkFn) { 4610 childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn); 4611 } 4612 } 4613 } 4614 } 4615 4616 4617 /** 4618 * Looks for directives on the given node and adds them to the directive collection which is 4619 * sorted. 4620 * 4621 * @param node Node to search. 4622 * @param directives An array to which the directives are added to. This array is sorted before 4623 * the function returns. 4624 * @param attrs The shared attrs object which is used to populate the normalized attributes. 4625 * @param {number=} maxPriority Max directive priority. 4626 */ 4627 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 4628 var nodeType = node.nodeType, 4629 attrsMap = attrs.$attr, 4630 match, 4631 className; 4632 4633 switch(nodeType) { 4634 case 1: /* Element */ 4635 // use the node name: <directive> 4636 addDirective(directives, 4637 directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); 4638 4639 // iterate over the attributes 4640 for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, 4641 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 4642 var attrStartName; 4643 var attrEndName; 4644 var index; 4645 4646 attr = nAttrs[j]; 4647 if (!msie || msie >= 8 || attr.specified) { 4648 name = attr.name; 4649 // support ngAttr attribute binding 4650 ngAttrName = directiveNormalize(name); 4651 if (NG_ATTR_BINDING.test(ngAttrName)) { 4652 name = ngAttrName.substr(6).toLowerCase(); 4653 } 4654 if ((index = ngAttrName.lastIndexOf('Start')) != -1 && index == ngAttrName.length - 5) { 4655 attrStartName = name; 4656 attrEndName = name.substr(0, name.length - 5) + 'end'; 4657 name = name.substr(0, name.length - 6); 4658 } 4659 nName = directiveNormalize(name.toLowerCase()); 4660 attrsMap[nName] = name; 4661 attrs[nName] = value = trim((msie && name == 'href') 4662 ? decodeURIComponent(node.getAttribute(name, 2)) 4663 : attr.value); 4664 if (getBooleanAttrName(node, nName)) { 4665 attrs[nName] = true; // presence means true 4666 } 4667 addAttrInterpolateDirective(node, directives, value, nName); 4668 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName); 4669 } 4670 } 4671 4672 // use class as directive
4673 className = node.className; 4674 if (isString(className) && className !== '') { 4675 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 4676 nName = directiveNormalize(match[2]); 4677 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 4678 attrs[nName] = trim(match[3]); 4679 } 4680 className = className.substr(match.index + match[0].length); 4681 } 4682 } 4683 break; 4684 case 3: /* Text Node */ 4685 addTextInterpolateDirective(directives, node.nodeValue); 4686 break; 4687 case 8: /* Comment */ 4688 try { 4689 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 4690 if (match) { 4691 nName = directiveNormalize(match[1]); 4692 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 4693 attrs[nName] = trim(match[2]); 4694 } 4695 } 4696 } catch (e) { 4697 // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value. 4698 // Just ignore it and continue. (Can't seem to reproduce in test case.) 4699 } 4700 break; 4701 } 4702 4703 directives.sort(byPriority); 4704 return directives; 4705 } 4706 4707 /** 4708 * Given a node with an directive-start it collects all of the siblings until it find directive-end. 4709 * @param node 4710 * @param attrStart 4711 * @param attrEnd 4712 * @returns {*} 4713 */ 4714 function groupScan(node, attrStart, attrEnd) { 4715 var nodes = []; 4716 var depth = 0; 4717 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 4718 var startNode = node; 4719 do { 4720 if (!node) { 4721 throw $compileMinErr('uterdir', "Unterminated attribute, found '{0}' but no matching '{1}' found.", attrStart, attrEnd); 4722 } 4723 if (node.nodeType == 1 /** Element **/) { 4724 if (node.hasAttribute(attrStart)) depth++; 4725 if (node.hasAttribute(attrEnd)) depth--; 4726 } 4727 nodes.push(node); 4728 node = node.nextSibling; 4729 } while (depth > 0); 4730 } else { 4731 nodes.push(node); 4732 } 4733 return jqLite(nodes); 4734 } 4735 4736 /** 4737 * Wrapper for linking function which converts normal linking function into a grouped 4738 * linking function. 4739 * @param linkFn 4740 * @param attrStart 4741 * @param attrEnd 4742 * @returns {Function} 4743 */ 4744 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 4745 return function(scope, element, attrs, controllers) { 4746 element = groupScan(element[0], attrStart, attrEnd); 4747 return linkFn(scope, element, attrs, controllers); 4748 } 4749 } 4750 4751 /** 4752 * Once the directives have been collected, their compile functions are executed. This method 4753 * is responsible for inlining directive templates as well as terminating the application 4754 * of the directives if the terminal directive has been reached. 4755 * 4756 * @param {Array} directives Array of collected directives to execute their compile function. 4757 * this needs to be pre-sorted by priority order. 4758 * @param {Node} compileNode The raw DOM node to apply the compile functions to 4759 * @param {Object} templateAttrs The shared attribute function 4760 * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the 4761 * scope argument is auto-generated to the new child of the transcluded parent scope. 4762 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 4763 * argument has the root jqLite array so that we can replace nodes on it. 4764 * @returns linkFn 4765 */ 4766 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective) { 4767 var terminalPriority = -Number.MAX_VALUE, 4768 preLinkFns = [], 4769 postLinkFns = [], 4770 newScopeDirective = null, 4771 newIsolateScopeDirective = null, 4772 templateDirective = null, 4773 $compileNode = templateAttrs.$$element = jqLite(compileNode), 4774 directive, 4775 directiveName, 4776 $template, 4777 transcludeDirective, 4778 replaceDirective = originalReplaceDirective, 4779 childTranscludeFn = transcludeFn, 4780 controllerDirectives, 4781 linkFn, 4782 directiveValue; 4783 4784 // executes all directives on the current element 4785 for(var i = 0, ii = directives.length; i < ii; i++) { 4786 directive = directives[i]; 4787 var attrStart = directive.$$start; 4788 var attrEnd = directive.$$end; 4789 4790 // collect multiblock sections 4791 if (attrStart) { 4792 $compileNode = groupScan(compileNode, attrStart, attrEnd) 4793 } 4794 $template = undefined; 4795 4796 if (terminalPriority > directive.priority) { 4797 break; // prevent further processing of directives 4798 } 4799 4800 if (directiveValue = directive.scope) { 4801 assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode); 4802 if (isObject(directiveValue)) { 4803 safeAddClass($compileNode, 'ng-isolate-scope'); 4804 newIsolateScopeDirective = directive; 4805 } 4806 safeAddClass($compileNode, 'ng-scope'); 4807 newScopeDirective = newScopeDirective || directive; 4808 } 4809 4810 directiveName = directive.name; 4811 4812 if (directiveValue = directive.controller) { 4813 controllerDirectives = controllerDirectives || {}; 4814 assertNoDuplicate("'" + directiveName + "' controller", 4815 controllerDirectives[directiveName], directive, $compileNode); 4816 controllerDirectives[directiveName] = directive; 4817 } 4818 4819 if (directiveValue = directive.transclude) { 4820 assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); 4821 transcludeDirective = directive; 4822 terminalPriority = directive.priority; 4823 if (directiveValue == 'element') { 4824 $template = groupScan(compileNode, attrStart, attrEnd) 4825 $compileNode = templateAttrs.$$element = 4826 jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); 4827 compileNode = $compileNode[0]; 4828 replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); 4829 4830 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 4831 replaceDirective && replaceDirective.name); 4832 } else { 4833 $template = jqLite(JQLiteClone(compileNode)).contents(); 4834 $compileNode.html(''); // clear contents 4835 childTranscludeFn = compile($template, transcludeFn); 4836 } 4837 } 4838 4839 if (directive.template) { 4840 assertNoDuplicate('template', templateDirective, directive, $compileNode); 4841 templateDirective = directive; 4842 4843 directiveValue = (isFunction(directive.template)) 4844 ? directive.template($compileNode, templateAttrs) 4845 : directive.template; 4846 4847 directiveValue = denormalizeTemplate(directiveValue); 4848 4849 if (directive.replace) { 4850 replaceDirective = directive; 4851 $template = jqLite('<div>' + 4852 trim(directiveValue) + 4853 '</div>').contents(); 4854 compileNode = $template[0]; 4855 4856 if ($template.length != 1 || compileNode.nodeType !== 1) { 4857 throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", directiveName, ''); 4858 } 4859 4860 replaceWith(jqCollection, $compileNode, compileNode); 4861 4862 var newTemplateAttrs = {$attr: {}}; 4863
4864 // combine directives from the original node and from the template: 4865 // - take the array of directives for this element 4866 // - split it into two parts, those that were already applied and those that weren't 4867 // - collect directives from the template, add them to the second group and sort them 4868 // - append the second group with new directives to the first group 4869 directives = directives.concat( 4870 collectDirectives( 4871 compileNode, 4872 directives.splice(i + 1, directives.length - (i + 1)), 4873 newTemplateAttrs 4874 ) 4875 ); 4876 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 4877 4878 ii = directives.length; 4879 } else { 4880 $compileNode.html(directiveValue); 4881 } 4882 } 4883 4884 if (directive.templateUrl) { 4885 assertNoDuplicate('template', templateDirective, directive, $compileNode); 4886 templateDirective = directive; 4887 4888 if (directive.replace) { 4889 replaceDirective = directive; 4890 } 4891 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), 4892 nodeLinkFn, $compileNode, templateAttrs, jqCollection, childTranscludeFn); 4893 ii = directives.length; 4894 } else if (directive.compile) { 4895 try { 4896 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 4897 if (isFunction(linkFn)) { 4898 addLinkFns(null, linkFn, attrStart, attrEnd); 4899 } else if (linkFn) { 4900 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 4901 } 4902 } catch (e) { 4903 $exceptionHandler(e, startingTag($compileNode)); 4904 } 4905 } 4906 4907 if (directive.terminal) { 4908 nodeLinkFn.terminal = true; 4909 terminalPriority = Math.max(terminalPriority, directive.priority); 4910 } 4911 4912 } 4913 4914 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope; 4915 nodeLinkFn.transclude = transcludeDirective && childTranscludeFn; 4916 4917 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 4918 return nodeLinkFn; 4919 4920 //////////////////// 4921 4922 function addLinkFns(pre, post, attrStart, attrEnd) { 4923 if (pre) { 4924 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 4925 pre.require = directive.require; 4926 preLinkFns.push(pre); 4927 } 4928 if (post) { 4929 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 4930 post.require = directive.require; 4931 postLinkFns.push(post); 4932 } 4933 } 4934 4935 4936 function getControllers(require, $element) { 4937 var value, retrievalMethod = 'data', optional = false;
4938 if (isString(require)) { 4939 while((value = require.charAt(0)) == '^' || value == '?') { 4940 require = require.substr(1); 4941 if (value == '^') { 4942 retrievalMethod = 'inheritedData'; 4943 } 4944 optional = optional || value == '?'; 4945 } 4946 value = $element[retrievalMethod]('$' + require + 'Controller'); 4947 if (!value && !optional) { 4948 throw $compileMinErr('ctreq', "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName); 4949 } 4950 return value; 4951 } else if (isArray(require)) { 4952 value = []; 4953 forEach(require, function(require) { 4954 value.push(getControllers(require, $element)); 4955 }); 4956 } 4957 return value; 4958 } 4959 4960 4961 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 4962 var attrs, $element, i, ii, linkFn, controller; 4963 4964 if (compileNode === linkNode) { 4965 attrs = templateAttrs; 4966 } else { 4967 attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); 4968 } 4969 $element = attrs.$$element; 4970 4971 if (newIsolateScopeDirective) { 4972 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; 4973 4974 var parentScope = scope.$parent || scope; 4975 4976 forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { 4977 var match = definition.match(LOCAL_REGEXP) || [], 4978 attrName = match[3] || scopeName, 4979 optional = (match[2] == '?'), 4980 mode = match[1], // @, =, or & 4981 lastValue, 4982 parentGet, parentSet; 4983 4984 scope.$$isolateBindings[scopeName] = mode + attrName; 4985 4986 switch (mode) { 4987 4988 case '@': { 4989 attrs.$observe(attrName, function(value) { 4990 scope[scopeName] = value; 4991 }); 4992 attrs.$$observers[attrName].$$scope = parentScope; 4993 if( attrs[attrName] ) { 4994 // If the attribute has been provided then we trigger an interpolation to ensure the value is there for use in the link fn 4995 scope[scopeName] = $interpolate(attrs[attrName])(parentScope); 4996 } 4997 break; 4998 } 4999 5000 case '=': { 5001 if (optional && !attrs[attrName]) { 5002 return; 5003 } 5004 parentGet = $parse(attrs[attrName]); 5005 parentSet = parentGet.assign || function() { 5006 // reset the change, or we will throw this exception on every $digest 5007 lastValue = scope[scopeName] = parentGet(parentScope); 5008 throw $compileMinErr('nonassign', "Expression '{0}' used with directive '{1}' is non-assignable!", 5009 attrs[attrName], newIsolateScopeDirective.name); 5010 }; 5011 lastValue = scope[scopeName] = parentGet(parentScope); 5012 scope.$watch(function parentValueWatch() { 5013 var parentValue = parentGet(parentScope); 5014 5015 if (parentValue !== scope[scopeName]) { 5016 // we are out of sync and need to copy 5017 if (parentValue !== lastValue) { 5018 // parent changed and it has precedence 5019 lastValue = scope[scopeName] = parentValue; 5020 } else { 5021 // if the parent can be assigned then do so 5022 parentSet(parentScope, parentValue = lastValue = scope[scopeName]); 5023 } 5024 } 5025 return parentValue; 5026 }); 5027 break; 5028 } 5029 5030 case '&': { 5031 parentGet = $parse(attrs[attrName]); 5032 scope[scopeName] = function(locals) { 5033 return parentGet(parentScope, locals); 5034 }; 5035 break; 5036 } 5037 5038 default: { 5039 throw $compileMinErr('iscp', "Invalid isolate scope definition for directive '{0}'. Definition: {... {1}: '{2}' ...}", 5040 newIsolateScopeDirective.name, scopeName, definition); 5041 } 5042 } 5043 }); 5044 } 5045 5046 if (controllerDirectives) {
5047 forEach(controllerDirectives, function(directive) { 5048 var locals = { 5049 $scope: scope, 5050 $element: $element, 5051 $attrs: attrs, 5052 $transclude: boundTranscludeFn 5053 }, controllerInstance; 5054 5055 controller = directive.controller; 5056 if (controller == '@') { 5057 controller = attrs[directive.name]; 5058 } 5059 5060 controllerInstance = $controller(controller, locals); 5061 $element.data( 5062 '$' + directive.name + 'Controller', 5063 controllerInstance); 5064 if (directive.controllerAs) { 5065 locals.$scope[directive.controllerAs] = controllerInstance; 5066 } 5067 }); 5068 } 5069 5070 // PRELINKING 5071 for(i = 0, ii = preLinkFns.length; i < ii; i++) { 5072 try { 5073 linkFn = preLinkFns[i]; 5074 linkFn(scope, $element, attrs, 5075 linkFn.require && getControllers(linkFn.require, $element)); 5076 } catch (e) { 5077 $exceptionHandler(e, startingTag($element)); 5078 } 5079 } 5080 5081 // RECURSION 5082 childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn); 5083 5084 // POSTLINKING 5085 for(i = 0, ii = postLinkFns.length; i < ii; i++) { 5086 try { 5087 linkFn = postLinkFns[i]; 5088 linkFn(scope, $element, attrs, 5089 linkFn.require && getControllers(linkFn.require, $element)); 5090 } catch (e) { 5091 $exceptionHandler(e, startingTag($element)); 5092 } 5093 } 5094 } 5095 } 5096 5097 5098 /** 5099 * looks up the directive and decorates it with exception handling and proper parameters. We 5100 * call this the boundDirective. 5101 * 5102 * @param {string} name name of the directive to look up. 5103 * @param {string} location The directive must be found in specific format. 5104 * String containing any of theses characters: 5105 * 5106 * * `E`: element name 5107 * * `A': attribute 5108 * * `C`: class 5109 * * `M`: comment 5110 * @returns true if directive was added. 5111 */ 5112 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) { 5113 if (name === ignoreDirective) return null; 5114 var match = null; 5115 if (hasDirectives.hasOwnProperty(name)) { 5116 for(var directive, directives = $injector.get(name + Suffix), 5117 i = 0, ii = directives.length; i<ii; i++) { 5118 try { 5119 directive = directives[i]; 5120 if ( (maxPriority === undefined || maxPriority > directive.priority) && 5121 directive.restrict.indexOf(location) != -1) { 5122 if (startAttrName) { 5123 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 5124 } 5125 tDirectives.push(directive); 5126 match = directive; 5127 } 5128 } catch(e) { $exceptionHandler(e); } 5129 } 5130 } 5131 return match; 5132 } 5133 5134 5135 /** 5136 * When the element is replaced with HTML template then the new attributes 5137 * on the template need to be merged with the existing attributes in the DOM. 5138 * The desired effect is to have both of the attributes present. 5139 * 5140 * @param {object} dst destination attributes (original DOM) 5141 * @param {object} src source attributes (from the directive template) 5142 */ 5143 function mergeTemplateAttributes(dst, src) { 5144 var srcAttr = src.$attr, 5145 dstAttr = dst.$attr, 5146 $element = dst.$$element; 5147 5148 // reapply the old attributes to the new element 5149 forEach(dst, function(value, key) { 5150 if (key.charAt(0) != '$') { 5151 if (src[key]) { 5152 value += (key === 'style' ? ';' : ' ') + src[key]; 5153 } 5154 dst.$set(key, value, true, srcAttr[key]); 5155 } 5156 }); 5157 5158 // copy the new attributes on the old attrs object 5159 forEach(src, function(value, key) { 5160 if (key == 'class') { 5161 safeAddClass($element, value); 5162 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 5163 } else if (key == 'style') { 5164 $element.attr('style', $element.attr('style') + ';' + value); 5165 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 5166 dst[key] = value; 5167 dstAttr[key] = srcAttr[key]; 5168 } 5169 }); 5170 } 5171 5172 5173 function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs, 5174 $rootElement, childTranscludeFn) { 5175 var linkQueue = [], 5176 afterTemplateNodeLinkFn, 5177 afterTemplateChildLinkFn, 5178 beforeTemplateCompileNode = $compileNode[0], 5179 origAsyncDirective = directives.shift(), 5180 // The fact that we have to copy and patch the directive seems wrong! 5181 derivedSyncDirective = extend({}, origAsyncDirective, { 5182 controller: null, templateUrl: null, transclude: null, scope: null, replace: null 5183 }), 5184 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 5185 ? origAsyncDirective.templateUrl($compileNode, tAttrs
5185) 5186 : origAsyncDirective.templateUrl; 5187 5188 $compileNode.html(''); 5189 5190 $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). 5191 success(function(content) { 5192 var compileNode, tempTemplateAttrs, $template; 5193 5194 content = denormalizeTemplate(content); 5195 5196 if (origAsyncDirective.replace) { 5197 $template = jqLite('<div>' + trim(content) + '</div>').contents(); 5198 compileNode = $template[0]; 5199 5200 if ($template.length != 1 || compileNode.nodeType !== 1) { 5201 throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", 5202 origAsyncDirective.name, templateUrl); 5203 } 5204 5205 tempTemplateAttrs = {$attr: {}}; 5206 replaceWith($rootElement, $compileNode, compileNode); 5207 collectDirectives(compileNode, directives, tempTemplateAttrs); 5208 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 5209 } else { 5210 compileNode = beforeTemplateCompileNode; 5211 $compileNode.html(content); 5212 } 5213 5214 directives.unshift(derivedSyncDirective); 5215 5216 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn, $compileNode, origAsyncDirective); 5217 forEach($rootElement, function(node, i) { 5218 if (node == compileNode) { 5219 $rootElement[i] = $compileNode[0]; 5220 } 5221 }); 5222 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 5223 5224 5225 while(linkQueue.length) { 5226 var scope = linkQueue.shift(), 5227 beforeTemplateLinkNode = linkQueue.shift(), 5228 linkRootElement = linkQueue.shift(), 5229 controller = linkQueue.shift(), 5230 linkNode = $compileNode[0]; 5231 5232 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 5233 // it was cloned therefore we have to clone as well. 5234 linkNode = JQLiteClone(compileNode); 5235 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 5236 } 5237 5238 afterTemplateNodeLinkFn( 5239 beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller), 5240 scope, linkNode, $rootElement, controller 5241 ); 5242 } 5243 linkQueue = null; 5244 }). 5245 error(function(response, code, headers, config) { 5246 throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); 5247 }); 5248 5249 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) { 5250 if (linkQueue) { 5251 linkQueue.push(scope); 5252 linkQueue.push(node); 5253 linkQueue.push(rootElement); 5254 linkQueue.push(controller); 5255 } else { 5256 afterTemplateNodeLinkFn(function() { 5257 beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller); 5258 }, scope, node, rootElement, controller); 5259 } 5260 }; 5261 } 5262 5263 5264 /** 5265 * Sorting function for bound directives. 5266 */ 5267 function byPriority(a, b) { 5268 return b.priority - a.priority; 5269 } 5270 5271 5272 function assertNoDuplicate(what, previousDirective, directive, element) { 5273 if (previousDirective) { 5274 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 5275 previousDirective.name, directive.name, what, startingTag(element)); 5276 } 5277 } 5278 5279 5280 function addTextInterpolateDirective(directives, text) { 5281 var interpolateFn = $interpolate(text, true); 5282 if (interpolateFn) { 5283 directives.push({ 5284 priority: 0, 5285 compile: valueFn(function textInterpolateLinkFn(scope, node) { 5286 var parent = node.parent(), 5287 bindings = parent.data('$binding') || []; 5288 bindings.push(interpolateFn); 5289 safeAddClass(parent.data('$binding', bindings), 'ng-binding'); 5290 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 5291 node[0].nodeValue = value; 5292 }); 5293 }) 5294 }); 5295 } 5296 } 5297 5298 5299 function getTrustedContext(node, attrNormalizedName) { 5300 // maction[xlink:href] can source SVG. It's not limited to <maction>. 5301 if (attrNormalizedName == "xlinkHref" || 5302 (nodeName_(node) != "IMG" && (attrNormalizedName == "src" || 5303 attrNormalizedName == "ngSrc"))) { 5304 return $sce.RESOURCE_URL; 5305 } 5306 } 5307 5308 5309 function addAttrInterpolateDirective(node, directives, value, name) { 5310 var interpolateFn = $interpolate(value, true); 5311 5312 // no interpolation found -> ignore 5313 if (!interpolateFn) return; 5314 5315 5316 if (name === "multiple" && nodeName_(node) === "SELECT") { 5317 throw $compileMinErr("selmulti", "Binding to the 'multiple' attribute is not supported. Element: {0}", 5318 startingTag(node)); 5319 } 5320 5321 directives.push({ 5322 priority: 100, 5323 compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) { 5324 var $$observers = (attr.$$observers || (attr.$$observers = {})); 5325 5326 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 5327 throw $compileMinErr('nodomevents', 5328 "Interpolations for HTML DOM event attributes are disallowed. Please use the ng- " + 5329 "versions (such as ng-click instead of onclick) instead."); 5330 } 5331 5332 // we need to interpolate again, in case the attribute value has been updated 5333 // (e.g. by another directive's compile function) 5334 interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); 5335 5336 // if attribute was updated so that there is no interpolation going on we don't want to 5337 // register any observers 5338 if (!interpolateFn) return; 5339 5340 attr[name] = interpolateFn(scope); 5341 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 5342 (attr.$$observers && attr.$$observers[name].$$scope || scope). 5343 $watch(interpolateFn, function interpolateFnWatchAction(value) { 5344 attr.$set(name, value); 5345 }); 5346 }) 5347 }); 5348 } 5349 5350 5351 /** 5352 * This is a special jqLite.replaceWith, which can replace items which 5353 * have no parents, provided that the containing jqLite collection is provided. 5354 * 5355 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 5356 * in the root of the tree. 5357 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep the shell, 5358 * but replace its DOM node reference. 5359 * @param {Node} newNode The new DOM node. 5360 */ 5361 function replaceWith($rootElement, elementsToRemove, newNode) { 5362 var firstElementToRemove = elementsToRemove[0], 5363 removeCount = elementsToRemove.length, 5364 parent = firstElementToRemove.parentNode, 5365 i, ii; 5366 5367 if ($rootElement) { 5368 for(i = 0, ii = $rootElement.length; i < ii; i++) { 5369 if ($rootElement[i] == firstElementToRemove) { 5370 $rootElement[i++] = newNode; 5371 for (var j = i, j2 = j + removeCount - 1, 5372 jj = $rootElement.length; 5373 j < jj; j++, j2++) { 5374 if (j2 < jj) { 5375 $rootElement[j] = $rootElement[j2]; 5376 } else { 5377 delete $rootElement[j]; 5378 } 5379 } 5380 $rootElement.length -= removeCount - 1; 5381 break; 5382 } 5383 } 5384 } 5385 5386 if (parent) { 5387 parent.replaceChild(newNode, firstElementToRemove); 5388 } 5389 var fragment = document.createDocumentFragment(); 5390 fragment.appendChild(firstElementToRemove); 5391 newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; 5392 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 5393 var element = elementsToRemove[k]; 5394 jqLite(element).remove(); // must do this way to clean up expando 5395 fragment.appendChild(element); 5396 delete elementsToRemove[k]; 5397 } 5398 5399 elementsToRemove[0] = newNode; 5400 elementsToRemove.length = 1 5401 } 5402 }]; 5403} 5404 5405var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; 5406/** 5407 * Converts all accepted directives format into proper directive name. 5408 * All of these will become 'myDirective': 5409 * my:Directive 5410 * my-directive 5411 * x-my-directive 5412 * data-my:directive 5413 * 5414 * Also there is special case for Moz prefix starting with upper case letter. 5415 * @param name Name to normalize 5416 */ 5417function directiveNormalize(name) { 5418 return camelCase(name.replace(PREFIX_REGEXP, '')); 5419} 5420 5421/** 5422 * @ngdoc object 5423 * @name ng.$compile.directive.Attributes 5424 * @description 5425 * 5426 * A shared object between directive compile / linking functions which contains
5426normalized DOM element 5427 * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed 5428 * since all of these are treated as equivalent in Angular: 5429 * 5430 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 5431 */ 5432 5433/** 5434 * @ngdoc property 5435 * @name ng.$compile.directive.Attributes#$attr 5436 * @propertyOf ng.$compile.directive.Attributes 5437 * @returns {object} A map of DOM element attribute names to the normalized name. This is 5438 * needed to do reverse lookup from normalized name back to actual name. 5439 */ 5440 5441 5442/** 5443 * @ngdoc function 5444 * @name ng.$compile.directive.Attributes#$set 5445 * @methodOf ng.$compile.directive.Attributes 5446 * @function 5447 * 5448 * @description 5449 * Set DOM element attribute value. 5450 * 5451 * 5452 * @param {string} name Normalized element attribute name of the property to modify. The name is 5453 * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 5454 * property to the original name. 5455 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 5456 */ 5457 5458 5459 5460/** 5461 * Closure compiler type information 5462 */ 5463 5464function nodesetLinkingFn( 5465 /* angular.Scope */ scope, 5466 /* NodeList */ nodeList, 5467 /* Element */ rootElement, 5468 /* function(Function) */ boundTranscludeFn 5469){} 5470 5471function directiveLinkingFn( 5472 /* nodesetLinkingFn */ nodesetLinkingFn, 5473 /* angular.Scope */ scope, 5474 /* Node */ node, 5475 /* Element */ rootElement, 5476 /* function(Function) */ boundTranscludeFn 5477){} 5478 5479/** 5480 * @ngdoc object 5481 * @name ng.$controllerProvider 5482 * @description 5483 * The {@link ng.$controller $controller service} is used by Angular to create new 5484 * controllers. 5485 * 5486 * This provider allows controller registration via the 5487 * {@link ng.$controllerProvider#register register} method. 5488 */ 5489function $ControllerProvider() { 5490 var controllers = {}, 5491 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 5492 5493 5494 /** 5495 * @ngdoc function 5496 * @name ng.$controllerProvider#register 5497 * @methodOf ng.$controllerProvider 5498 * @param {string} name Controller name 5499 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 5500 * annotations in the array notation). 5501 */ 5502 this.register = function(name, constructor) { 5503 if (isObject(name)) { 5504 extend(controllers, name) 5505 } else { 5506 controllers[name] = constructor; 5507 } 5508 }; 5509 5510 5511 this.$get = ['$injector', '$window', function($injector, $window) { 5512 5513 /** 5514 * @ngdoc function 5515 * @name ng.$controller 5516 * @requires $injector 5517 * 5518 * @param {Function|string} constructor If called with a function then it's considered to be the 5519 * controller constructor function. Otherwise it's considered to be a string which is used 5520 * to retrieve the controller constructor using the following steps: 5521 * 5522 * * check if a controller with given name is registered via `$controllerProvider` 5523 * * check if evaluating the string on the current scope returns a constructor 5524 * * check `window[constructor]` on the global `window` object 5525 * 5526 * @param {Object} locals Injection locals for Controller. 5527 * @return {Object} Instance of given controller. 5528 * 5529 * @description 5530 * `$controller` service is responsible for instantiating controllers. 5531 * 5532 * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into 5533 * a service, so that one can override this service with {@link https://gist.github.com/1649788 5534 * BC version}. 5535 */ 5536 return function(expression, locals) { 5537 var instance, match, constructor, identifier; 5538 5539 if(isString(expression)) { 5540 match = expression.match(CNTRL_REG), 5541 constructor = match[1], 5542 identifier = match[3]; 5543 expression = controllers.hasOwnProperty(constructor) 5544 ? controllers[constructor] 5545 : getter(locals.$scope, constructor, true) || getter($window, constructor, true); 5546 5547 assertArgFn(expression, constructor, true); 5548 } 5549 5550 instance = $injector.instantiate(expression, locals); 5551 5552 if (identifier) { 5553 if (!(locals && typeof locals.$scope == 'object')) { 5554 throw minErr('$controller')('noscp', "Cannot export controller '{0}' as '{1}
5554'! No $scope object provided via `locals`.", constructor || expression.name, identifier); 5555 } 5556 5557 locals.$scope[identifier] = instance; 5558 } 5559 5560 return instance; 5561 }; 5562 }]; 5563} 5564 5565/** 5566 * @ngdoc object 5567 * @name ng.$document 5568 * @requires $window 5569 * 5570 * @description 5571 * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document` 5572 * element. 5573 */ 5574function $DocumentProvider(){ 5575 this.$get = ['$window', function(window){ 5576 return jqLite(window.document); 5577 }]; 5578} 5579 5580/** 5581 * @ngdoc function 5582 * @name ng.$exceptionHandler 5583 * @requires $log 5584 * 5585 * @description 5586 * Any uncaught exception in angular expressions is delegated to this service. 5587 * The default implementation simply delegates to `$log.error` which logs it into 5588 * the browser console. 5589 * 5590 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 5591 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 5592 * 5593 * @param {Error} exception Exception associated with the error. 5594 * @param {string=} cause optional information about the context in which 5595 * the error was thrown. 5596 * 5597 */ 5598function $ExceptionHandlerProvider() { 5599 this.$get = ['$log', function($log) { 5600 return function(exception, cause) { 5601 $log.error.apply($log, arguments); 5602 }; 5603 }]; 5604} 5605 5606/** 5607 * Parse headers into key value object 5608 * 5609 * @param {string} headers Raw headers as a string 5610 * @returns {Object} Parsed headers as key value object 5611 */ 5612function parseHeaders(headers) { 5613 var parsed = {}, key, val, i; 5614 5615 if (!headers) return parsed; 5616 5617 forEach(headers.split('\n'), function(line) { 5618 i = line.indexOf(':');
5619 key = lowercase(trim(line.substr(0, i))); 5620 val = trim(line.substr(i + 1)); 5621 5622 if (key) { 5623 if (parsed[key]) { 5624 parsed[key] += ', ' + val; 5625 } else { 5626 parsed[key] = val; 5627 } 5628 } 5629 }); 5630 5631 return parsed; 5632} 5633 5634 5635/** 5636 * Returns a function that provides access to parsed headers. 5637 * 5638 * Headers are lazy parsed when first requested. 5639 * @see parseHeaders 5640 * 5641 * @param {(string|Object)} headers Headers to provide access to. 5642 * @returns {function(string=)} Returns a getter function which if called with: 5643 * 5644 * - if called with single an argument returns a single header value or null 5645 * - if called with no arguments returns an object containing all headers. 5646 */ 5647function headersGetter(headers) { 5648 var headersObj = isObject(headers) ? headers : undefined; 5649 5650 return function(name) { 5651 if (!headersObj) headersObj = parseHeaders(headers); 5652 5653 if (name) { 5654 return headersObj[lowercase(name)] || null; 5655 } 5656 5657 return headersObj; 5658 }; 5659} 5660 5661 5662/** 5663 * Chain all given functions 5664 * 5665 * This function is used for both request and response transforming 5666 * 5667 * @param {*} data Data to transform. 5668 * @param {function(string=)} headers Http headers getter fn. 5669 * @param {(function|Array.<function>)} fns Function or an array of functions. 5670 * @returns {*} Transformed data. 5671 */ 5672function transformData(data, headers, fns) { 5673 if (isFunction(fns)) 5674 return fns(data, headers); 5675 5676 forEach(fns, function(fn) { 5677 data = fn(data, headers); 5678 }); 5679 5680 return data; 5681} 5682 5683 5684function isSuccess(status) { 5685 return 200 <= status && status < 300; 5686} 5687 5688 5689function $HttpProvider() { 5690 var JSON_START = /^\s*(\[|\{[^\{])/, 5691 JSON_END = /[\}\]]\s*$/, 5692 PROTECTION_PREFIX = /^\)\]\}',?\n/, 5693 CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; 5694 5695 var defaults = this.defaults = { 5696 // transform incoming response data 5697 transformResponse: [function(data) { 5698 if (isString(data)) { 5699 // strip json vulnerability protection prefix 5700 data = data.replace(PROTECTION_PREFIX, ''); 5701 if (JSON_START.test(data) && JSON_END.test(data)) 5702 data = fromJson(data, true); 5703 } 5704 return data; 5705 }], 5706 5707 // transform outgoing request data 5708 transformRequest: [function(d) { 5709 return isObject(d) && !isFile(d) ? toJson(d) : d; 5710 }], 5711 5712 // default headers 5713 headers: { 5714 common: { 5715 'Accept': 'application/json, text/plain, */*' 5716 }, 5717 post: CONTENT_TYPE_APPLICATION_JSON, 5718 put: CONTENT_TYPE_APPLICATION_JSON, 5719 patch: CONTENT_TYPE_APPLICATION_JSON 5720 }, 5721 5722 xsrfCookieName: 'XSRF-TOKEN', 5723 xsrfHeaderName: 'X-XSRF-TOKEN' 5724 }; 5725 5726 /** 5727 * Are order by request. I.E. they are applied in the same order as 5728 * array on request, but revers order on response. 5729 */ 5730 var interceptorFactories = this.interceptors = []; 5731 /** 5732 * For historical reasons, response interceptors ordered by the order in which 5733 * they are applied to response. (This is in revers to interceptorFactories) 5734 */ 5735 var responseInterceptorFactories = this.responseInterceptors = []; 5736 5737 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', '$$urlUtils', 5738 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector, $$urlUtils) { 5739 5740 var defaultCache = $cacheFactory('$http'); 5741 5742 /** 5743 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 5744 * The reversal is needed so that we can build up the interception chain around the 5745 * server request. 5746 */ 5747 var reversedInterceptors = []; 5748 5749 forEach(interceptorFactories, function(interceptorFactory) { 5750 reversedInterceptors.unshift(isString(interceptorFactory) 5751 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 5752 }); 5753 5754 forEach(responseInterceptorFactories, function(interceptorFactory, index) { 5755 var responseFn = isString(interceptorFactory) 5756 ? $injector.get(interceptorFactory) 5757 : $injector.invoke(interceptorFactory); 5758 5759 /** 5760 * Response interceptors go before "around" interceptors (no real reason, just 5761 * had to pick one.) But they are already reversed, so we can't use unshift, hence 5762 * the splice. 5763 */ 5764 reversedInterceptors.splice(index, 0, { 5765 response: function(response) { 5766 return responseFn($q.when(response)); 5767 }, 5768 responseError: function(response) { 5769 return responseFn($q.reject(response)); 5770 } 5771 }); 5772 }); 5773 5774 5775 /** 5776 * @ngdoc function 5777 * @name ng.$http 5778 * @requires $httpBackend 5779 * @requires $browser 5780 * @requires $cacheFactory 5781 * @requires $rootScope 5782 * @requires $q 5783 * @requires $injector 5784 *
5785 * @description 5786 * The `$http` service is a core Angular service that facilitates communication with the remote 5787 * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest 5788 * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. 5789 * 5790 * For unit testing applications that use `$http` service, see 5791 * {@link ngMock.$httpBackend $httpBackend mock}. 5792 * 5793 * For a higher level of abstraction, please check out the {@link ngResource.$resource 5794 * $resource} service. 5795 * 5796 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 5797 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 5798 * it is important to familiarize yourself with these APIs and the guarantees they provide. 5799 * 5800 * 5801 * # General usage 5802 * The `$http` service is a function which takes a single argument ââ¬â a configuration object ââ¬â 5803 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 5804 * with two $http specific methods: `success` and `error`. 5805 * 5806 * <pre> 5807 * $http({method: 'GET', url: '/someUrl'}). 5808 * success(function(data, status, headers, config) { 5809 * // this callback will be called asynchronously 5810 * // when the response is available 5811 * }). 5812 * error(function(data, status, headers, config) { 5813 * // called asynchronously if an error occurs 5814 * // or server returns response with an error status. 5815 * }); 5816 * </pre> 5817 * 5818 * Since the returned value of calling the $http function is a `promise`, you can also use 5819 * the `then` method to register callbacks, and these callbacks will receive a single argument ââ¬â 5820 * an object representing the response. See the API signature and type info below for more 5821 * details. 5822 * 5823 * A response status code between 200 and 299 is considered a success status and 5824 * will result in the success callback being called. Note that if the response is a redirect, 5825 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 5826 * called for such responses. 5827 * 5828 * # Shortcut methods 5829 * 5830 * Since all invocations of the $http service require passing in an HTTP method and URL, and 5831 * POST/PUT requests require request data to be provided as well, shortcut methods 5832 * were created: 5833 * 5834 * <pre> 5835 * $http.get('/someUrl').success(successCallback); 5836 * $http.post('/someUrl', data).success(successCallback); 5837 * </pre> 5838 * 5839 * Complete list of shortcut methods: 5840 * 5841 * - {@link ng.$http#get $http.get} 5842 * - {@link ng.$http#head $http.head} 5843 * - {@link ng.$http#post $http.post} 5844 * - {@link ng.$http#put $http.put} 5845 * - {@link ng.$http#delete $http.delete} 5846 * - {@link ng.$http#jsonp $http.jsonp} 5847 * 5848 * 5849 * # Setting HTTP Headers 5850 * 5851 * The $http service will automatically add certain HTTP headers to all requests. These defaults 5852 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 5853 * object, which currently contains this default configuration: 5854 * 5855 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 5856 * - `Accept: application/json, text/plain, * / *` 5857 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 5858 * - `Content-Type: application/json` 5859 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 5860 * - `Content-Type: application/json` 5861 * 5862 * To add or overwrite these defaults, simply add or remove a property from these configuration 5863 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 5864 * with the lowercased HTTP method name as the key, e.g. 5865 * `$httpProvider.defaults.headers.get['My-Header']='value'`. 5866 * 5867 * Additionally, the defaults can be set at runtime via the `$http.defaults` object in the same 5868 * fashion. 5869 * 5870 * 5871 * # Transforming Requests and Responses 5872 * 5873 * Both requests and responses can be transformed using transform functions. By default, Angular 5874 * applies these transformations: 5875 * 5876 * Request transformations: 5877 * 5878 * - If the `data` property of the request configuration object c
5878ontains an object, serialize it into 5879 * JSON format. 5880 * 5881 * Response transformations: 5882 * 5883 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 5884 * - If JSON response is detected, deserialize it using a JSON parser. 5885 * 5886 * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and 5887 * `$httpProvider.defaults.transformResponse` properties. These properties are by default an 5888 * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the 5889 * transformation chain. You can also decide to completely override any default transformations by assigning your 5890 * transformation functions to these properties directly without the array wrapper. 5891 * 5892 * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or 5893 * `transformResponse` properties of the configuration object passed into `$http`. 5894 * 5895 * 5896 * # Caching 5897 * 5898 * To enable caching, set the configuration property `cache` to `true`. When the cache is 5899 * enabled, `$http` stores the response from the server in local cache. Next time the 5900 * response is served from the cache without sending a request to the server. 5901 * 5902 * Note that even if the response is served from cache, delivery of the data is asynchronous in 5903 * the same way that real requests are. 5904 * 5905 * If there are multiple GET requests for the same URL that should be cached using the same 5906 * cache, but the cache is not populated yet, only one request to the server will be made and 5907 * the remaining requests will be fulfilled using the response from the first request. 5908 * 5909 * A custom default cache built with $cacheFactory can be provided in $http.defaults.cache. 5910 * To skip it, set configuration property `cache` to `false`. 5911 * 5912 * 5913 * # Interceptors 5914 * 5915 * Before you start creating interceptors, be sure to understand the 5916 * {@link ng.$q $q and deferred/promise APIs}. 5917 * 5918 * For purposes of global error handling, authentication, or any kind of synchronous or 5919 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 5920 * able to intercept requests before they are handed to the server and 5921 * responses before they are handed over to the application code that 5922 * initiated these requests. The interceptors leverage the {@link ng.$q 5923 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 5924 * 5925 * The interceptors are service factories that are registered with the `$httpProvider` by 5926 * adding them to the `$httpProvider.interceptors` array. The factory is called and 5927 * injected with dependencies (if specified) and returns the interceptor. 5928 * 5929 * There are two kinds of interceptors (and two kinds of rejection interceptors): 5930 * 5931 * * `request`: interceptors get called with http `config` object. The function is free to modify 5932 * the `config` or create a new one. The function needs to return the `config` directly or as a 5933 * promise. 5934 * * `requestError`: interceptor gets called when a previous interceptor threw an error or resolved 5935 * with a rejection. 5936 * * `response`: interceptors get called with http `response` object. The function is free to modify 5937 * the `response` or create a new one. The function needs to return the `response` directly or as a 5938 * promise. 5939 * * `responseError`: interceptor gets called when a previous interceptor threw an error or resolved 5940 * with a rejection. 5941 * 5942 * 5943 * <pre> 5944 * // register the interceptor as a service 5945 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 5946 * return { 5947 * // optional method 5948 * 'request': function(config) { 5949 * // do something on success 5950 * return config || $q.when(config); 5951 * }, 5952 * 5953 * // optional method 5954 * 'requestError': function(rejection) {
5955 * // do something on error 5956 * if (canRecover(rejection)) { 5957 * return responseOrNewPromise 5958 * } 5959 * return $q.reject(rejection); 5960 * }, 5961 * 5962 * 5963 * 5964 * // optional method 5965 * 'response': function(response) { 5966 * // do something on success 5967 * return response || $q.when(response); 5968 * }, 5969 * 5970 * // optional method 5971 * 'responseError': function(rejection) { 5972 * // do something on error 5973 * if (canRecover(rejection)) { 5974 * return responseOrNewPromise 5975 * } 5976 * return $q.reject(rejection); 5977 * }; 5978 * } 5979 * }); 5980 * 5981 * $httpProvider.interceptors.push('myHttpInterceptor'); 5982 * 5983 * 5984 * // register the interceptor via an anonymous factory 5985 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 5986 * return { 5987 * 'request': function(config) { 5988 * // same as above 5989 * }, 5990 * 'response': function(response) { 5991 * // same as above 5992 * } 5993 * }); 5994 * </pre> 5995 * 5996 * # Response interceptors (DEPRECATED) 5997 * 5998 * Before you start creating interceptors, be sure to understand the 5999 * {@link ng.$q $q and deferred/promise APIs}. 6000 * 6001 * For purposes of global error handling, authentication or any kind of synchronous or 6002 * asynchronous preprocessing of received responses, it is desirable to be able to intercept 6003 * responses for http requests before they are handed over to the application code that 6004 * initiated these requests. The response interceptors leverage the {@link ng.$q 6005 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. 6006 * 6007 * The interceptors are service factories that are registered with the $httpProvider by 6008 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and 6009 * injected with dependencies (if specified) and returns the interceptor ââ¬â a function that 6010 * takes a {@link ng.$q promise} and returns the original or a new promise. 6011 * 6012 * <pre> 6013 * // register the interceptor as a service 6014 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 6015 * return function(promise) { 6016 * return promise.then(function(response) { 6017 * // do something on success 6018 * }, function(response) { 6019 * // do something on error 6020 * if (canRecover(response)) { 6021 * return responseOrNewPromise 6022 * } 6023 * return $q.reject(response); 6024 * }); 6025 * } 6026 * }); 6027 * 6028 * $httpProvider.responseInterceptors.push('myHttpInterceptor'); 6029 * 6030 * 6031 * // register the interceptor via an anonymous factory 6032 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { 6033 * return function(promise) { 6034 * // same as above 6035 * } 6036 * }); 6037 * </pre> 6038 * 6039 * 6040 * # Security Considerations 6041 * 6042 * When designing web applications, consider security threats from: 6043 * 6044 * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx 6045 * JSON vulnerability} 6046 * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} 6047 * 6048 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 6049 * pre-configured with strategies that address these issues, but for this to work backend server 6050 * cooperation is required. 6051 * 6052 * ## JSON Vulnerability Protection 6053 * 6054 * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx 6055 * JSON vulnerability} allows third party website to turn your JSON resource URL into 6056 * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To 6057 * counter this your server can prefix all JSON requests with following str
6057ing `")]}',\n"`. 6058 * Angular will automatically strip the prefix before processing it as JSON. 6059 * 6060 * For example if your server needs to return: 6061 * <pre> 6062 * ['one','two'] 6063 * </pre> 6064 * 6065 * which is vulnerable to attack, your server can return: 6066 * <pre> 6067 * )]}', 6068 * ['one','two'] 6069 * </pre> 6070 * 6071 * Angular will strip the prefix, before processing the JSON. 6072 * 6073 * 6074 * ## Cross Site Request Forgery (XSRF) Protection 6075 * 6076 * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which 6077 * an unauthorized site can gain your user's private data. Angular provides a mechanism 6078 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 6079 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 6080 * JavaScript that runs on your domain could read the cookie, your server can be assured that 6081 * the XHR came from JavaScript running on your domain. The header will not be set for 6082 * cross-domain requests. 6083 * 6084 * To take advantage of this, your server needs to set a token in a JavaScript readable session 6085 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 6086 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 6087 * that only JavaScript running on your domain could have sent the request. The token must be 6088 * unique for each user and must be verifiable by the server (to prevent the JavaScript from making 6089 * up its own tokens). We recommend that the token is a digest of your site's authentication 6090 * cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} for added security. 6091 * 6092 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 6093 * properties of either $httpProvider.defaults, or the per-request config object. 6094 * 6095 * 6096 * @param {object} config Object describing the request to be made and how it should be 6097 * processed. The object has following properties: 6098 * 6099 * - **method** ââ¬â `{string}` ââ¬â HTTP method (e.g. 'GET', 'POST', etc) 6100 * - **url** ââ¬â `{string}` ââ¬â Absolute or relative URL of the resource that is being requested. 6101 * - **params** ââ¬â `{Object.<string|Object>}` ââ¬â Map of strings or objects which will be turned to 6102 * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified. 6103 * - **data** ââ¬â `{string|Object}` ââ¬â Data to be sent as the request message data. 6104 * - **headers** ââ¬â `{Object}` ââ¬â Map of strings or functions which return strings representing 6105 * HTTP headers to send to the server. If the return value of a function is null, the header will 6106 * not be sent. 6107 * - **xsrfHeaderName** ââ¬â `{string}` ââ¬â Name of HTTP header to populate with the XSRF token. 6108 * - **xsrfCookieName** ââ¬â `{string}` ââ¬â Name of cookie containing the XSRF token. 6109 * - **transformRequest** ââ¬â `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` ââ¬â 6110 * transform function or an array of such functions. The transform function takes the http 6111 * request body and headers and returns its transformed (typically serialized) version. 6112 * - **transformResponse** ââ¬â `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` ââ¬â 6113 * transform function or an array of such functions. The transform function takes the http 6114 * response body and headers and returns its transformed (typically deserialized) version. 6115 * - **cache** ââ¬â `{boolean|Cache}` ââ¬â If true, a default $http cache will be used to cache the 6116 * GET request, otherwise if a cache instance built with 6117 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 6118 * caching. 6119 * - **timeout** ââ¬â `{number|Promise}` ââ¬â timeout in milliseconds, or {@link ng.$q promise} 6120 * that should abort the request when resolved. 6121 * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the 6122 * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 6123 * requests with credentials} for more information. 6124 * - **responseType** - `{string}` - see {@link 6125 * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. 6126 * 6127 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 6128 * standard `then` method and two http specific methods: `success` and `error`. The `then` 6129 * method takes two arguments a success and an error callback which will be called with a 6130 * response object. The `success` and `error` methods take a single argument - a function that 6131 * will be called when the request succeeds or fails respectively. The arguments passed into 6132 * these functions are destructured representation of the response object passed into the 6133 * `then` method. The response object has these properties: 6134 * 6135 * - **data** ââ¬â `{string|Object}` ââ¬â The response body transformed with the transform functions. 6136 * - **status** ââ¬â `{number}` ââ¬â HTTP status code of the response. 6137 * - **headers** ââ¬â `{function([headerName])}` ââ¬â Header getter function. 6138 * - **config** ââ¬â `{Object}` ââ¬â The configuration object that was used to generate the request. 6139 * 6140 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 6141 * requests. This is primarily meant to be used for debugging purposes. 6142 * 6143 * 6144 * @example 6145 <example> 6146 <file name="index.html"> 6147 <div ng-controller="FetchCtrl"> 6148 <select ng-model="method"> 6149 <option>GET</option> 6150 <option>JSONP</option> 6151 </select> 6152 <input type="text" ng-model="url" size="80"/> 6153 <button ng-click="fetch()">fetch</button><br> 6154 <button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 6155 <button ng-click="updateModel('JSONP', 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">Sample JSONP</button> 6156 <button ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">Invalid JSONP</button> 6157 <pre>http status code: {{status}}</pre> 6158 <pre>http response data: {{data}}</pre> 6159 </div> 6160 </file> 6161 <file name="script.js"> 6162 function FetchCtrl($scope, $http, $templateCache) { 6163 $scope.method = 'GET'; 6164 $scope.url = 'http-hello.html'; 6165 6166 $scope.fetch = function() { 6167 $scope.code = null; 6168 $scope.response = null; 6169 6170 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 6171 success(function(data, status) { 6172 $scope.status = status; 6173 $scope.data = data; 6174 }). 6175 error(function(data, status) { 6176 $scope.data = data || "Request failed"; 6177 $scope.status = status; 6178 }); 6179 }; 6180 6181 $scope.updateModel = function(method, url) { 6182 $scope.method = method; 6183 $scope.url = url; 6184 }; 6185 } 6186 </file> 6187 <file name="http-hello.html"> 6188 Hello, $http! 6189 </file> 6190 <file name="scenario.js"> 6191 it('should make an xhr GET request', function() {
6192 element(':button:contains("Sample GET")').click(); 6193 element(':button:contains("fetch")').click(); 6194 expect(binding('status')).toBe('200'); 6195 expect(binding('data')).toMatch(/Hello, \$http!/); 6196 }); 6197 6198 it('should make a JSONP request to angularjs.org', function() { 6199 element(':button:contains("Sample JSONP")').click(); 6200 element(':button:contains("fetch")').click(); 6201 expect(binding('status')).toBe('200'); 6202 expect(binding('data')).toMatch(/Super Hero!/); 6203 }); 6204 6205 it('should make JSONP request to invalid URL and invoke the error handler', 6206 function() { 6207 element(':button:contains("Invalid JSONP")').click(); 6208 element(':button:contains("fetch")').click(); 6209 expect(binding('status')).toBe('0'); 6210 expect(binding('data')).toBe('Request failed'); 6211 }); 6212 </file> 6213 </example> 6214 */ 6215 function $http(requestConfig) { 6216 var config = { 6217 transformRequest: defaults.transformRequest, 6218 transformResponse: defaults.transformResponse 6219 }; 6220 var headers = mergeHeaders(requestConfig); 6221 6222 extend(config, requestConfig); 6223 config.headers = headers; 6224 config.method = uppercase(config.method); 6225 6226 var xsrfValue = $$urlUtils.isSameOrigin(config.url) 6227 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 6228 : undefined; 6229 if (xsrfValue) { 6230 headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 6231 } 6232 6233 6234 var serverRequest = function(config) { 6235 headers = config.headers; 6236 var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); 6237 6238 // strip content-type if data is undefined 6239 if (isUndefined(config.data)) { 6240 forEach(headers, function(value, header) { 6241 if (lowercase(header) === 'content-type') { 6242 delete headers[header]; 6243 } 6244 }); 6245 } 6246 6247 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 6248 config.withCredentials = defaults.withCredentials; 6249 } 6250 6251 // send request 6252 return sendReq(config, reqData, headers).then(transformResponse, transformResponse); 6253 }; 6254 6255 var chain = [serverRequest, undefined]; 6256 var promise = $q.when(config); 6257 6258 // apply interceptors 6259 forEach(reversedInterceptors, function(interceptor) { 6260 if (interceptor.request || interceptor.requestError) { 6261 chain.unshift(interceptor.request, interceptor.requestError); 6262 } 6263 if (interceptor.response || interceptor.responseError) { 6264 chain.push(interceptor.response, interceptor.responseError); 6265 } 6266 }); 6267 6268 while(chain.length) { 6269 var thenFn = chain.shift(); 6270 var rejectFn = chain.shift(); 6271 6272 promise = promise.then(thenFn, rejectFn); 6273 } 6274 6275 promise.success = function(fn) { 6276 promise.then(function(response) { 6277 fn(response.data, response.status, response.headers, config); 6278 }); 6279 return promise; 6280 }; 6281 6282 promise.error = function(fn) { 6283 promise.then(null, function(response) { 6284 fn(response.data, response.status, response.headers, config); 6285 }); 6286 return promise; 6287 }; 6288 6289 return promise; 6290 6291 function transformResponse(response) { 6292 // make a copy since the response must be cacheable 6293 var resp = extend({}, response, { 6294 data: transformData(response.data, response.headers, config.transformResponse) 6295 }); 6296 return (isSuccess(response.status)) 6297 ? resp 6298 : $q.reject(resp); 6299 } 6300 6301 function mergeHeaders(config) { 6302 var defHeaders = defaults.headers, 6303 reqHeaders = extend({}, config.headers), 6304 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 6305 6306 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 6307 6308 // execute if header value is function 6309 execHeaders(defHeaders); 6310 execHeaders(reqHeaders); 6311
6312 // using for-in instead of forEach to avoid unecessary iteration after header has been found 6313 defaultHeadersIteration: 6314 for (defHeaderName in defHeaders) { 6315 lowercaseDefHeaderName = lowercase(defHeaderName); 6316 6317 for (reqHeaderName in reqHeaders) { 6318 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 6319 continue defaultHeadersIteration; 6320 } 6321 } 6322 6323 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 6324 } 6325 6326 return reqHeaders; 6327 6328 function execHeaders(headers) { 6329 var headerContent; 6330 6331 forEach(headers, function(headerFn, header) { 6332 if (isFunction(headerFn)) { 6333 headerContent = headerFn(); 6334 if (headerContent != null) { 6335 headers[header] = headerContent; 6336 } else { 6337 delete headers[header]; 6338 } 6339 } 6340 }); 6341 } 6342 } 6343 } 6344 6345 $http.pendingRequests = []; 6346 6347 /** 6348 * @ngdoc method 6349 * @name ng.$http#get 6350 * @methodOf ng.$http 6351 * 6352 * @description 6353 * Shortcut method to perform `GET` request. 6354 * 6355 * @param {string} url Relative or absolute URL specifying the destination of the request 6356 * @param {Object=} config Optional configuration object 6357 * @returns {HttpPromise} Future object 6358 */ 6359 6360 /** 6361 * @ngdoc method 6362 * @name ng.$http#delete 6363 * @methodOf ng.$http 6364 * 6365 * @description 6366 * Shortcut method to perform `DELETE` request. 6367 * 6368 * @param {string} url Relative or absolute URL specifying the destination of the request 6369 * @param {Object=} config Optional configuration object 6370 * @returns {HttpPromise} Future object 6371 */ 6372 6373 /** 6374 * @ngdoc method 6375 * @name ng.$http#head 6376 * @methodOf ng.$http 6377 * 6378 * @description 6379 * Shortcut method to perform `HEAD` request. 6380 * 6381 * @param {string} url Relative or absolute URL specifying the destination of the request 6382 * @param {Object=} config Optional configuration object 6383 * @returns {HttpPromise} Future object 6384 */ 6385 6386 /** 6387 * @ngdoc method 6388 * @name ng.$http#jsonp 6389 * @methodOf ng.$http 6390 * 6391 * @description 6392 * Shortcut method to perform `JSONP` request. 6393 * 6394 * @param {string} url Relative or absolute URL specifying the destination of the request. 6395 * Should contain `JSON_CALLBACK` string. 6396 * @param {Object=} config Optional configuration object 6397 * @returns {HttpPromise} Future object 6398 */ 6399 createShortMethods('get', 'delete', 'head', 'jsonp'); 6400 6401 /** 6402 * @ngdoc method 6403 * @name ng.$http#post 6404 * @methodOf ng.$http 6405 * 6406 * @description 6407 * Shortcut method to perform `POST` request. 6408 * 6409 * @param {string} url Relative or absolute URL specifying the destination of the request 6410 * @param {*} data Request content 6411 * @param {Object=} config Optional configuration object 6412 * @returns {HttpPromise} Future object 6413 */ 6414 6415 /** 6416 * @ngdoc method 6417 * @name ng.$http#put 6418 * @methodOf ng.$http 6419 * 6420 * @description 6421 * Shortcut method to perform `PUT` request. 6422 * 6423 * @param {string} url Relative or absolute URL specifying the destination of the request 6424 * @param {*} data Request content 6425 * @param {Object=} config Optional configuration object 6426 * @returns {HttpPromise} Future object 6427 */ 6428 createShortMethodsWithData('post', 'put'); 6429 6430 /** 6431 * @ngdoc property 6432 * @name ng.$http#defaults 6433 * @propertyOf ng.$http 6434 * 6435 * @description 6436 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 6437 * default headers, withCredentials as well as request and response transformations. 6438 * 6439 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 6440 */ 6441 $http.defaults = defaults; 6442 6443 6444 return $http; 6445 6446 6447 function createShortMethods(names) { 6448 forEach(arguments, function(name) { 6449 $http[name] = function(url, config) { 6450 return $http(extend(config || {}, { 6451 method: name, 6452 url: url 6453 })); 6454 }; 6455 }); 6456 } 6457 6458 6459 function createShortMethodsWithData(name) {
6460 forEach(arguments, function(name) { 6461 $http[name] = function(url, data, config) { 6462 return $http(extend(config || {}, { 6463 method: name, 6464 url: url, 6465 data: data 6466 })); 6467 }; 6468 }); 6469 } 6470 6471 6472 /** 6473 * Makes the request. 6474 * 6475 * !!! ACCESSES CLOSURE VARS: 6476 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 6477 */ 6478 function sendReq(config, reqData, reqHeaders) { 6479 var deferred = $q.defer(), 6480 promise = deferred.promise, 6481 cache, 6482 cachedResp, 6483 url = buildUrl(config.url, config.params); 6484 6485 $http.pendingRequests.push(config); 6486 promise.then(removePendingReq, removePendingReq); 6487 6488 6489 if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { 6490 cache = isObject(config.cache) ? config.cache 6491 : isObject(defaults.cache) ? defaults.cache 6492 : defaultCache; 6493 } 6494 6495 if (cache) { 6496 cachedResp = cache.get(url); 6497 if (cachedResp) { 6498 if (cachedResp.then) { 6499 // cached request has already been sent, but there is no response yet 6500 cachedResp.then(removePendingReq, removePendingReq); 6501 return cachedResp; 6502 } else { 6503 // serving from cache 6504 if (isArray(cachedResp)) { 6505 resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2])); 6506 } else { 6507 resolvePromise(cachedResp, 200, {}); 6508 } 6509 } 6510 } else { 6511 // put the promise for the non-transformed response into cache as a placeholder 6512 cache.put(url, promise); 6513 } 6514 } 6515 6516 // if we won't have the response in cache, send the request to the backend 6517 if (!cachedResp) { 6518 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 6519 config.withCredentials, config.responseType); 6520 } 6521 6522 return promise; 6523 6524 6525 /** 6526 * Callback registered to $httpBackend(): 6527 * - caches the response if desired 6528 * - resolves the raw $http promise 6529 * - calls $apply 6530 */ 6531 function done(status, response, headersString) { 6532 if (cache) { 6533 if (isSuccess(status)) { 6534 cache.put(url, [status, response, parseHeaders(headersString)]); 6535 } else { 6536 // remove promise from the cache 6537 cache.remove(url); 6538 } 6539 } 6540 6541 resolvePromise(response, status, headersString); 6542 if (!$rootScope.$$phase) $rootScope.$apply(); 6543 } 6544 6545 6546 /** 6547 * Resolves the raw $http promise. 6548 */ 6549 function resolvePromise(response, status, headers) { 6550 // normalize internal statuses to 0 6551 status = Math.max(status, 0); 6552 6553 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 6554 data: response, 6555 status: status, 6556 headers: headersGetter(headers), 6557 config: config 6558 }); 6559 } 6560 6561 6562 function removePendingReq() { 6563 var idx = indexOf($http.pendingRequests, config); 6564 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 6565 } 6566 } 6567 6568 6569 function buildUrl(url, params) { 6570 if (!params) return url; 6571 var parts = []; 6572 forEachSorted(params, function(value, key) { 6573 if (value == null || value == undefined) return; 6574 if (!isArray(value)) value = [value]; 6575 6576 forEach(value, function(v) { 6577 if (isObject(v)) { 6578 v = toJson(v); 6579 } 6580 parts.push(encodeUriQuery(key) + '=' + 6581 encodeUriQuery(v)); 6582 }); 6583 }); 6584 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 6585 } 6586 6587 6588 }]; 6589} 6590 6591var XHR = window.XMLHttpRequest || function() { 6592 try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {} 6593 try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {} 6594 try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {} 6595 throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); 6596}; 6597 6598 6599/** 6600 * @ngdoc object 6601 * @name ng.$httpBackend 6602 * @requires $browser 6603 * @requires $window 6604 * @requires $document 6605 * 6606 * @description 6607 * HTTP backend used by the {@link ng.$http service} that delegates to 6608 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 6609 * 6610 * You should never need to use this service directly, instead use the higher-level abstractions: 6611 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 6612 * 6613 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 6614 * $httpBackend} which can be trained with responses. 6615 */ 6616function $HttpBackendProvider() { 6617 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 6618 return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, 6619 $document[0], $window.location.protocol.replace(':', '')); 6620 }]; 6621} 6622 6623function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) { 6624 // TODO(vojta): fix the signature 6625 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 6626 var status; 6627 $browser.$$incOutstandingRequestCount(); 6628 url = url || $browser.url(); 6629 6630 if (lowercase(method) == 'jsonp') { 6631 var callbackId = '_' + (callbacks.counter++).toString(36); 6632 callbacks[callbackId] = function(data) { 6633 callbacks[callbackId].data = data; 6634 }; 6635 6636 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 6637 function() { 6638 if (callbacks[callbackId].data) { 6639 completeRequest(callback, 200, callbacks[callbackId].data); 6640 } else { 6641 completeRequest(callback, status || -2); 6642 } 6643 delete callbacks[callbackId]; 6644 }); 6645 } else { 6646 var xhr = new XHR(); 6647 xhr.open(method, url, true);
6648 forEach(headers, function(value, key) { 6649 if (value) xhr.setRequestHeader(key, value); 6650 }); 6651 6652 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the 6653 // response is in the cache. the promise api will ensure that to the app code the api is 6654 // always async 6655 xhr.onreadystatechange = function() { 6656 if (xhr.readyState == 4) { 6657 var responseHeaders = xhr.getAllResponseHeaders(); 6658 6659 // TODO(vojta): remove once Firefox 21 gets released. 6660 // begin: workaround to overcome Firefox CORS http response headers bug 6661 // https://bugzilla.mozilla.org/show_bug.cgi?id=608735 6662 // Firefox already patched in nightly. Should land in Firefox 21. 6663 6664 // CORS "simple response headers" http://www.w3.org/TR/cors/ 6665 var value, 6666 simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type", 6667 "Expires", "Last-Modified", "Pragma"]; 6668 if (!responseHeaders) { 6669 responseHeaders = ""; 6670 forEach(simpleHeaders, function (header) { 6671 var value = xhr.getResponseHeader(header); 6672 if (value) { 6673 responseHeaders += header + ": " + value + "\n"; 6674 } 6675 }); 6676 } 6677 // end of the workaround. 6678 6679 // responseText is the old-school way of retrieving response (supported by IE8 & 9) 6680 // response and responseType properties were introduced in XHR Level2 spec (supported by IE10) 6681 completeRequest(callback, 6682 status || xhr.status, 6683 (xhr.responseType ? xhr.response : xhr.responseText), 6684 responseHeaders); 6685 } 6686 }; 6687 6688 if (withCredentials) { 6689 xhr.withCredentials = true; 6690 } 6691 6692 if (responseType) { 6693 xhr.responseType = responseType; 6694 } 6695 6696 xhr.send(post || ''); 6697 } 6698 6699 if (timeout > 0) { 6700 var timeoutId = $browserDefer(timeoutRequest, timeout); 6701 } else if (timeout && timeout.then) { 6702 timeout.then(timeoutRequest); 6703 } 6704 6705 6706 function timeoutRequest() { 6707 status = -1; 6708 jsonpDone && jsonpDone(); 6709 xhr && xhr.abort(); 6710 } 6711 6712 function completeRequest(callback, status, response, headersString) { 6713 // URL_MATCH is defined in src/service/location.js 6714 var protocol = (url.match(SERVER_MATCH) || ['', locationProtocol])[1]; 6715 6716 // cancel timeout and subsequent timeout promise resolution 6717 timeoutId && $browserDefer.cancel(timeoutId); 6718 jsonpDone = xhr = null; 6719 6720 // fix status code for file protocol (it's always 0) 6721 status = (protocol == 'file') ? (response ? 200 : 404) : status; 6722 6723 // normalize IE bug (http://bugs.jquery.com/ticket/1450) 6724 status = status == 1223 ? 204 : status; 6725 6726 callback(status, response, headersString); 6727 $browser.$$completeOutstandingRequest(noop); 6728 } 6729 }; 6730 6731 function jsonpReq(url, done) { 6732 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 6733 // - fetches local scripts via XHR and evals them 6734 // - adds and immediately removes script elements from the document 6735 var script = rawDocument.createElement('script'), 6736 doneWrapper = function() { 6737 rawDocument.body.removeChild(script); 6738 if (done) done(); 6739 }; 6740 6741 script.type = 'text/javascript'; 6742 script.src = url; 6743 6744 if (msie) { 6745 script.onreadystatechange = function() { 6746 if (/loaded|complete/.test(script.readyState)) doneWrapper(); 6747 }; 6748 } else { 6749 script.onload = script.onerror = doneWrapper; 6750 } 6751 6752 rawDocument.body.appendChild(script); 6753 return doneWrapper; 6754 } 6755} 6756 6757var $interpolateMinErr = minErr('$interpolate'); 6758 6759/** 6760 * @ngdoc object 6761 * @name ng.$interpolateProvider 6762 * @function 6763 * 6764 * @description 6765 *
6766 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 6767 * 6768 * @example 6769 <doc:example> 6770 <doc:source> 6771 <script> 6772 var myApp = angular.module('App', [], function($interpolateProvider) { 6773 $interpolateProvider.startSymbol('//'); 6774 $interpolateProvider.endSymbol('//'); 6775 }); 6776 function Controller($scope) { 6777 $scope.label = "Interpolation Provider Sample"; 6778 } 6779 </script> 6780 <div ng-app="App" ng-controller="Controller"> 6781 //label// 6782 </div> 6783 </doc:source> 6784 </doc:example> 6785 */ 6786function $InterpolateProvider() { 6787 var startSymbol = '{{'; 6788 var endSymbol = '}}'; 6789 6790 /** 6791 * @ngdoc method 6792 * @name ng.$interpolateProvider#startSymbol 6793 * @methodOf ng.$interpolateProvider 6794 * @description 6795 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 6796 * 6797 * @param {string=} value new value to set the starting symbol to. 6798 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 6799 */ 6800 this.startSymbol = function(value){ 6801 if (value) { 6802 startSymbol = value; 6803 return this; 6804 } else { 6805 return startSymbol; 6806 } 6807 }; 6808 6809 /** 6810 * @ngdoc method 6811 * @name ng.$interpolateProvider#endSymbol 6812 * @methodOf ng.$interpolateProvider 6813 * @description 6814 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 6815 * 6816 * @param {string=} value new value to set the ending symbol to. 6817 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 6818 */ 6819 this.endSymbol = function(value){ 6820 if (value) { 6821 endSymbol = value; 6822 return this; 6823 } else { 6824 return endSymbol; 6825 } 6826 }; 6827 6828 6829 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 6830 var startSymbolLength = startSymbol.length, 6831 endSymbolLength = endSymbol.length; 6832 6833 /** 6834 * @ngdoc function 6835 * @name ng.$interpolate 6836 * @function 6837 * 6838 * @requires $parse 6839 * @requires $sce 6840 * 6841 * @description 6842 * 6843 * Compiles a string with markup into an interpolation function. This service is used by the 6844 * HTML {@link ng.$compile $compile} service for data binding. See 6845 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 6846 * interpolation markup. 6847 * 6848 * 6849 <pre> 6850 var $interpolate = ...; // injected 6851 var exp = $interpolate('Hello {{name}}!'); 6852 expect(exp({name:'Angular'}).toEqual('Hello Angular!'); 6853 </pre> 6854 * 6855 * 6856 * @param {string} text The text with markup to interpolate. 6857 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 6858 * embedded expression in order to return an interpolation function. Strings with no 6859 * embedded expression will return null for the interpolation function. 6860 * @param {string=} trustedContext when provided, the returned function passes the interpolated 6861 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 6862 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 6863 * provides Strict Contextual Escaping for details. 6864 * @returns {function(context)} an interpolation function which is used to compute the interpolated 6865 * string. The function has these parameters: 6866 * 6867 * * `context`: an object against which any expressions embedded in the strings are evaluated 6868 * against. 6869 * 6870 */ 6871 function $interpolate(text, mustHaveExpression, trustedContext) { 6872 var startIndex, 6873 endIndex, 6874 index = 0, 6875 parts = [], 6876 length = text.length, 6877 hasInterpolation = false, 6878 fn, 6879 exp, 6880 concat = []; 6881 6882 while(index < length) { 6883 if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && 6884 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { 6885 (index != startIndex) && parts.push(text.substring(index, startIndex)); 6886 parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); 6887 fn.exp = exp;
6888 index = endIndex + endSymbolLength; 6889 hasInterpolation = true; 6890 } else { 6891 // we did not find anything, so we have to add the remainder to the parts array 6892 (index != length) && parts.push(text.substring(index)); 6893 index = length; 6894 } 6895 } 6896 6897 if (!(length = parts.length)) { 6898 // we added, nothing, must have been an empty string. 6899 parts.push(''); 6900 length = 1; 6901 } 6902 6903 // Concatenating expressions makes it hard to reason about whether some combination of concatenated 6904 // values are unsafe to use and could easily lead to XSS. By requiring that a single 6905 // expression be used for iframe[src], object[src], etc., we ensure that the value that's used 6906 // is assigned or constructed by some JS code somewhere that is more testable or make it 6907 // obvious that you bound the value to some user controlled value. This helps reduce the load 6908 // when auditing for XSS issues. 6909 if (trustedContext && parts.length > 1) { 6910 throw $interpolateMinErr('noconcat', 6911 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 6912 "interpolations that concatenate multiple expressions when a trusted value is " + 6913 "required. See http://docs.angularjs.org/api/ng.$sce", text); 6914 } 6915 6916 if (!mustHaveExpression || hasInterpolation) { 6917 concat.length = length; 6918 fn = function(context) { 6919 try { 6920 for(var i = 0, ii = length, part; i<ii; i++) { 6921 if (typeof (part = parts[i]) == 'function') { 6922 part = part(context); 6923 if (trustedContext) { 6924 part = $sce.getTrusted(trustedContext, part); 6925 } else { 6926 part = $sce.valueOf(part); 6927 } 6928 if (part == null || part == undefined) { 6929 part = ''; 6930 } else if (typeof part != 'string') { 6931 part = toJson(part); 6932 } 6933 } 6934 concat[i] = part; 6935 } 6936 return concat.join(''); 6937 } 6938 catch(err) { 6939 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 6940 $exceptionHandler(newErr); 6941 } 6942 }; 6943 fn.exp = text; 6944 fn.parts = parts; 6945 return fn; 6946 } 6947 } 6948 6949 6950 /** 6951 * @ngdoc method 6952 * @name ng.$interpolate#startSymbol 6953 * @methodOf ng.$interpolate 6954 * @description 6955 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 6956 * 6957 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change 6958 * the symbol. 6959 * 6960 * @returns {string} start symbol. 6961 */ 6962 $interpolate.startSymbol = function() { 6963 return startSymbol; 6964 } 6965 6966 6967 /** 6968 * @ngdoc method 6969 * @name ng.$interpolate#endSymbol 6970 * @methodOf ng.$interpolate 6971 * @description 6972 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 6973 * 6974 * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change 6975 * the symbol. 6976 * 6977 * @returns {string} start symbol. 6978 */ 6979 $interpolate.endSymbol = function() { 6980 return endSymbol; 6981 } 6982 6983 return $interpolate; 6984 }]; 6985} 6986 6987/** 6988 * @ngdoc object 6989 * @name ng.$locale 6990 * 6991 * @description 6992 * $locale service provides localization rules for various Angular components. As of right now the 6993 * only public api is: 6994 * 6995 * * `id` ââ¬â `{string}` ââ¬â locale id formatted as `languageId-countryId` (e.g. `en-us`) 6996 */ 6997function $LocaleProvider(){ 6998 this.$get = function() { 6999 return { 7000 id: 'en-us', 7001 7002 NUMBER_FORMATS: { 7003 DECIMAL_SEP: '.', 7004 GROUP_SEP: ',', 7005 PATTERNS: [ 7006 { // Decimal Pattern 7007 minInt: 1, 7008 minFrac: 0, 7009 maxFrac: 3, 7010 posPre: '', 7011 posSuf: '', 7012 negPre: '-', 7013 negSuf: '', 7014 gSize: 3, 7015 lgSize: 3 7016 },{ //Currency Pattern 7017 minInt: 1, 7018 minFrac: 2, 7019 maxFrac: 2, 7020 posPre: '\u00A4', 7021 posSuf: '', 7022 negPre: '(\u00A4', 7023 negSuf: ')', 7024 gSize: 3, 7025 lgSize: 3 7026 } 7027 ], 7028 CURRENCY_SYM: '$' 7029 }, 7030 7031 DATETIME_FORMATS: { 7032 MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December' 7033 .split(','), 7034 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 7035 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 7036 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 7037 AMPMS: ['AM','PM'], 7038 medium: 'MMM d, y h:mm:ss a', 7039 short: 'M/d/yy h:mm a', 7040 fullDate: 'EEEE, MMMM d, y', 7041 longDate: 'MMMM d, y', 7042 mediumDate: 'MMM d, y', 7043 shortDate: 'M/d/yy', 7044 mediumTime: 'h:mm:ss a', 7045 shortTime: 'h:mm a' 7046 }, 7047 7048 pluralCat: function(num) { 7049 if (num === 1) { 7050 return 'one'; 7051 } 7052 return 'other'; 7053 } 7054 }; 7055 }; 7056} 7057 7058var SERVER_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/, 7059 PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 7060 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 7061var $locationMinErr = minErr('$location'); 7062 7063 7064/** 7065 * Encode path using encodeUriSegment, ignoring forward slashes 7066 * 7067 * @param {string} path Path to encode 7068 * @returns {string} 7069 */ 7070function encodePath(path) { 7071 var segments = path.split('/'), 7072 i = segments.length; 7073 7074 while (i--) { 7075 segments[i] = encodeUriSegment(segments[i]); 7076 } 7077 7078 return segments.join('/'); 7079} 7080 7081function matchUrl(url, obj) { 7082 var match = SERVER_MATCH.exec(url); 7083 7084 obj.$$protocol = match[1]; 7085 obj.$$host = match[3]; 7086 obj.$$port = int(match[5]) || DEFAULT_PORTS[match[1]] || null; 7087} 7088 7089function matchAppUrl(url, obj) { 7090 var match = PATH_MATCH.exec(url); 7091 7092 obj.$$path = decodeURIComponent(match[1]); 7093 obj.$$search = parseKeyValue(match[3]); 7094 obj.$$hash = decodeURIComponent(match[5] || ''); 7095 7096 // make sure path starts with '/'; 7097 if (obj.$$path && obj.$$path.charAt(0) != '/') obj.$$path = '/' + obj.$$path; 7098} 7099 7100 7101function composeProtocolHostPort(protocol, host, port) {
7102 return protocol + '://' + host + (port == DEFAULT_PORTS[protocol] ? '' : ':' + port); 7103} 7104 7105/** 7106 * 7107 * @param {string} begin 7108 * @param {string} whole 7109 * @param {string} otherwise 7110 * @returns {string} returns text from whole after begin or otherwise if it does not begin with expected string. 7111 */ 7112function beginsWith(begin, whole, otherwise) { 7113 return whole.indexOf(begin) == 0 ? whole.substr(begin.length) : otherwise; 7114} 7115 7116 7117function stripHash(url) { 7118 var index = url.indexOf('#'); 7119 return index == -1 ? url : url.substr(0, index); 7120} 7121 7122 7123function stripFile(url) { 7124 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 7125} 7126 7127/* return the server only (scheme://host:port) */ 7128function serverBase(url) { 7129 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 7130} 7131 7132 7133/** 7134 * LocationHtml5Url represents an url 7135 * This object is exposed as $location service when HTML5 mode is enabled and supported 7136 * 7137 * @constructor 7138 * @param {string} appBase application base URL 7139 * @param {string} basePrefix url path prefix 7140 */ 7141function LocationHtml5Url(appBase, basePrefix) { 7142 this.$$html5 = true; 7143 basePrefix = basePrefix || ''; 7144 var appBaseNoFile = stripFile(appBase); 7145 /** 7146 * Parse given html5 (regular) url string into properties 7147 * @param {string} newAbsoluteUrl HTML5 url 7148 * @private 7149 */ 7150 this.$$parse = function(url) { 7151 var parsed = {} 7152 matchUrl(url, parsed); 7153 var pathUrl = beginsWith(appBaseNoFile, url); 7154 if (!isString(pathUrl)) { 7155 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, appBaseNoFile); 7156 } 7157 matchAppUrl(pathUrl, parsed); 7158 extend(this, parsed); 7159 if (!this.$$path) { 7160 this.$$path = '/'; 7161 } 7162 7163 this.$$compose(); 7164 }; 7165 7166 /** 7167 * Compose url and update `absUrl` property 7168 * @private 7169 */ 7170 this.$$compose = function() { 7171 var search = toKeyValue(this.$$search), 7172 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 7173 7174 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 7175 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 7176 }; 7177 7178 this.$$rewrite = function(url) { 7179 var appUrl, prevAppUrl; 7180 7181 if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { 7182 prevAppUrl = appUrl; 7183 if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { 7184 return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 7185 } else { 7186 return appBase + prevAppUrl; 7187 } 7188 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { 7189 return appBaseNoFile + appUrl; 7190 } else if (appBaseNoFile == url + '/') { 7191 return appBaseNoFile; 7192 } 7193 } 7194} 7195 7196 7197/** 7198 * LocationHashbangUrl represents url 7199 * This object is exposed as $location service when developer doesn't opt into html5 mode. 7200 * It also serves as the base class for html5 mode fallback on legacy browsers. 7201 * 7202 * @constructor 7203 * @param {string} appBase application base URL 7204 * @param {string} hashPrefix hashbang prefix 7205 */ 7206function LocationHashbangUrl(appBase, hashPrefix) { 7207 var appBaseNoFile = stripFile(appBase); 7208 7209 matchUrl(appBase, this); 7210 7211 7212 /** 7213 * Parse given hashbang url into properties 7214 * @param {string} url Hashbang url 7215 * @private 7216 */ 7217 this.$$parse = function(url) { 7218 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 7219 var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' 7220 ? beginsWith(hashPrefix, withoutBaseUrl) 7221 : (this.$$html5) 7222 ? withoutBaseUrl 7223 : ''; 7224 7225 if (!isString(withoutHashUrl)) { 7226 throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, hashPrefix); 7227 } 7228 matchAppUrl(withoutHashUrl, this); 7229 this.$$compose(); 7230 }; 7231 7232 /** 7233 * Compose hashbang url and update `absUrl` property 7234 * @private 7235 */ 7236 this.$$compose = function() { 7237 var search = toKeyValue(this.$$search), 7238 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 7239 7240 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 7241 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 7242 }; 7243 7244 this.$$rewrite = function(url) { 7245 if(stripHash(appBase) == stripHash(url)) { 7246 return url; 7247 } 7248 } 7249} 7250 7251 7252/** 7253 * LocationHashbangUrl represents url 7254 * This object is exposed as $location service when html5 history api is enabled but the browser 7255 * does not support it. 7256 * 7257 * @constructor 7258 * @param {string} appBase application base URL 7259 * @param {string} hashPrefix hashbang prefix 7260 */ 7261function LocationHashbangInHtml5Url(appBase, hashPrefix) { 7262 this.$$html5 = true; 7263 LocationHashbangUrl.apply(this, arguments); 7264 7265 var appBaseNoFile = stripFile(appBase); 7266 7267 this.$$rewrite = function(url) { 7268 var appUrl; 7269 7270 if ( appBase == stripHash(url) ) { 7271 return url; 7272 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { 7273 return appBase + hashPrefix + appUrl; 7274 } else if ( appBaseNoFile === url + '/') { 7275 return appBaseNoFile; 7276 } 7277 } 7278} 7279 7280 7281LocationHashbangInHtml5Url.prototype = 7282 LocationHashbangUrl.prototype = 7283 LocationHtml5Url.prototype = { 7284 7285 /** 7286 * Are we in html5 mode? 7287 * @private 7288 */ 7289 $$html5: false, 7290 7291 /** 7292 * Has any change been replacing ? 7293 * @private 7294 */ 7295 $$replace: false, 7296 7297 /** 7298 * @ngdoc method 7299 * @name ng.$location#absUrl 7300 * @methodOf ng.$location 7301 * 7302 * @description 7303 * This method is getter only. 7304 * 7305 * Return full url representation with all segments encoded according to rules specified in 7306 * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. 7307 * 7308 * @return {string} full url 7309 */ 7310 absUrl: locationGetter('$$absUrl'), 7311 7312 /** 7313 * @ngdoc method 7314 * @name ng.$location#url 7315 * @methodOf ng.$location 7316 * 7317 * @description 7318 * This method is getter / setter. 7319 * 7320 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 7321 * 7322 * Change path, search and hash, when called with parameter and return `$location`. 7323 * 7324 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 7325 * @param {string=} replace The path that will be changed 7326 * @return {string} url 7327 */ 7328 url: function(url, replace) { 7329 if (isUndefined(url)) 7330 return this.$$url; 7331 7332 var match = PATH_MATCH.exec(url); 7333 if (match[1]) this.path(decodeURIComponent(match[1])); 7334 if (match[2] || match[1]) this.search(match[3] || ''); 7335 this.hash(match[5] || '', replace); 7336 7337 return this; 7338 }, 7339 7340 /** 7341 * @ngdoc method 7342 * @name ng.$location#protocol 7343 * @methodOf ng.$location 7344 * 7345 * @description 7346 * This method is getter only. 7347 * 7348 * Return protocol of current url. 7349 * 7350 * @return {string} protocol of current url 7351 */ 7352 protocol: locationGetter('$$protocol'), 7353 7354 /** 7355 * @ngdoc method 7356 * @name ng.$location#host 7357 * @methodOf ng.$location 7358 * 7359 * @description 7360 * This method is getter only. 7361 * 7362 * Return host of current url. 7363 * 7364 * @return {string} host of current url. 7365 */ 7366 host: locationGetter('$$host'), 7367 7368 /** 7369 * @ngdoc method 7370 * @name ng.$location#port 7371 * @methodOf ng.$location 7372 * 7373 * @description 7374 * This method is getter only. 7375 * 7376 * Return port of current url. 7377 * 7378 * @return {Number} port 7379 */ 7380 port: locationGetter('$$port'), 7381 7382 /** 7383 * @ngdoc method 7384 * @name ng.$location#path 7385 * @methodOf ng.$location 7386 * 7387 * @description 7388 * This method is getter / setter. 7389 * 7390 * Return path of current url when called without any parameter. 7391 * 7392 * Change path when called with parameter and return `$location`. 7393 * 7394 * Note: Path should always begin with forward slash (/), this method will add the forward slash 7395 * if it is missing. 7396 * 7397 * @param {string=} path New path 7398 * @return {string} path 7399 */ 7400 path: locationGetterSetter('$$path', function(path) { 7401 return path.charAt(0) == '/' ? path : '/' + path; 7402 }), 7403 7404 /** 7405 * @ngdoc method 7406 * @name ng.$location#search 7407 * @methodOf ng.$location 7408 * 7409 * @description 7410 * This method is getter / setter. 7411 * 7412 * Return search part (as object) of current url when called without any parameter. 7413 * 7414 * Change search part when called with parameter and return `$location`. 7415 * 7416 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or hash object. Hash object 7417 * may contain an array of values, which will be decoded as duplicates in the url. 7418 * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a 7419 * single search parameter. If the value is `null`, the parameter will be deleted. 7420 * 7421 * @return {string} search 7422 */ 7423 search: function(search, paramValue) { 7424 switch (arguments.length) { 7425 case 0: 7426 return this.$$search; 7427 case 1: 7428 if (isString(search)) { 7429 this.$$search = parseKeyValue(search); 7430 } else if (isObject(search)) { 7431 this.$$search = search; 7432 } else { 7433 throw $locationMinErr('isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); 7434 } 7435 break; 7436 default: 7437 if (paramValue == undefined || paramValue == null) { 7438 delete this.$$search[search]; 7439 } else { 7440 this.$$search[search] = paramValue; 7441 } 7442 } 7443 7444 this.$$compose(); 7445 return this; 7446 }, 7447 7448 /** 7449 * @ngdoc method 7450 * @name ng.$location#hash 7451 * @methodOf ng.$location 7452 * 7453 * @description 7454 * This method is getter / setter. 7455 * 7456 * Return hash fragment when called without any parameter. 7457 * 7458 * Change hash fragment when called with parameter and return `$location`. 7459 * 7460 * @param {string=} hash New hash fragment 7461 * @return {string} hash 7462 */ 7463 hash: locationGetterSetter('$$hash', identity), 7464 7465 /** 7466 * @ngdoc method 7467 * @name ng.$location#replace 7468 * @methodOf ng.$location 7469 * 7470 * @description 7471 * If called, all changes to $location during current `$digest` will be replacing current history 7472 * record, instead of adding new one. 7473 */ 7474 replace: function() { 7475 this.$$replace = true; 7476 return this; 7477 } 7478}; 7479 7480function locationGetter(property) { 7481 return function() { 7482 return this[property]; 7483 }; 7484} 7485 7486 7487function locationGetterSetter(property, preprocess) { 7488 return function(value) { 7489 if (isUndefined(value)) 7490 return this[property]; 7491 7492 this[property] = preprocess(value); 7493 this.$$compose(); 7494 7495 return this; 7496 }; 7497} 7498 7499 7500/** 7501 * @ngdoc object 7502 * @name ng.$location 7503 * 7504 * @requires $browser 7505 * @requires $sniffer 7506 * @requires $rootElement 7507 * 7508 * @description 7509 * The $location service parses the URL in the browser address bar (based on the 7510 * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL 7511 * available to your application. Changes to the URL in the address bar are reflected into 7512 * $location service and changes to $location are reflected into the browser address bar. 7513 * 7514 * **The $location service:** 7515 * 7516 * - Exposes the current URL in the browser address bar, so you can 7517 * - Watch and observe the URL. 7518 * - Change the URL. 7519 * - Synchronizes the URL with the browser when the user 7520 * - Changes the address bar. 7521 * - Clicks the back or forward button (or clicks a History link). 7522 * - Clicks on a link. 7523 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 7524 * 7525 * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular 7526 * Services: Using $location} 7527 */ 7528
7529/** 7530 * @ngdoc object 7531 * @name ng.$locationProvider 7532 * @description 7533 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 7534 */ 7535function $LocationProvider(){ 7536 var hashPrefix = '', 7537 html5Mode = false; 7538 7539 /** 7540 * @ngdoc property 7541 * @name ng.$locationProvider#hashPrefix 7542 * @methodOf ng.$locationProvider 7543 * @description 7544 * @param {string=} prefix Prefix for hash part (containing path and search) 7545 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7546 */ 7547 this.hashPrefix = function(prefix) { 7548 if (isDefined(prefix)) { 7549 hashPrefix = prefix; 7550 return this; 7551 } else { 7552 return hashPrefix; 7553 } 7554 }; 7555 7556 /** 7557 * @ngdoc property 7558 * @name ng.$locationProvider#html5Mode 7559 * @methodOf ng.$locationProvider 7560 * @description 7561 * @param {string=} mode Use HTML5 strategy if available. 7562 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7563 */ 7564 this.html5Mode = function(mode) { 7565 if (isDefined(mode)) { 7566 html5Mode = mode; 7567 return this; 7568 } else { 7569 return html5Mode; 7570 } 7571 }; 7572 7573 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', 7574 function( $rootScope, $browser, $sniffer, $rootElement) { 7575 var $location, 7576 LocationMode, 7577 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 7578 initialUrl = $browser.url(), 7579 appBase; 7580 7581 if (html5Mode) { 7582 appBase = serverBase(initialUrl) + (baseHref || '/'); 7583 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 7584 } else { 7585 appBase = stripHash(initialUrl); 7586 LocationMode = LocationHashbangUrl; 7587 } 7588 $location = new LocationMode(appBase, '#' + hashPrefix); 7589 $location.$$parse($location.$$rewrite(initialUrl)); 7590 7591 $rootElement.on('click', function(event) { 7592 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 7593 // currently we open nice url link and redirect then 7594 7595 if (event.ctrlKey || event.metaKey || event.which == 2) return; 7596 7597 var elm = jqLite(event.target); 7598 7599 // traverse the DOM up to find first A tag 7600 while (lowercase(elm[0].nodeName) !== 'a') { 7601 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 7602 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 7603 } 7604 7605 var absHref = elm.prop('href'); 7606 var rewrittenUrl = $location.$$rewrite(absHref); 7607 7608 if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { 7609 event.preventDefault(); 7610 if (rewrittenUrl != $browser.url()) { 7611 // update location manually 7612 $location.$$parse(rewrittenUrl); 7613 $rootScope.$apply(); 7614 // hack to work around FF6 bug 684208 when scenario runner clicks on links 7615 window.angular['ff-684208-preventDefault'] = true; 7616 } 7617 } 7618 }); 7619 7620 7621 // rewrite hashbang url <> html5 url 7622 if ($location.absUrl() != initialUrl) { 7623 $browser.url($location.absUrl(), true); 7624 } 7625 7626 // update $location when $browser url changes 7627 $browser.onUrlChange(function(newUrl) { 7628 if ($location.absUrl() != newUrl) { 7629 if ($rootScope.$broadcast('$locationChangeStart', newUrl, $location.absUrl()).defaultPrevented) { 7630 $browser.url($location.absUrl()); 7631 return; 7632 } 7633 $rootScope.$evalAsync(function() { 7634 var oldUrl = $location.absUrl(); 7635 7636 $location.$$parse(newUrl); 7637 afterLocationChange(oldUrl); 7638 }); 7639 if (!$rootScope.$$phase) $rootScope.$digest(); 7640 } 7641 }); 7642 7643 // update browser 7644 var changeCounter = 0; 7645 $rootScope.$watch(function $locationWatch() { 7646 var oldUrl = $browser.url(); 7647 var currentReplace = $location.$$replace; 7648 7649 if (!changeCounter || oldUrl != $location.absUrl()) { 7650 changeCounter++; 7651 $rootScope.$evalAsync(function() { 7652 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). 7653 defaultPrevented) { 7654 $location.$$parse(oldUrl); 7655 } else { 7656 $browser.url($location.absUrl(), currentReplace); 7657 afterLocationChange(oldUrl); 7658 } 7659 }); 7660 } 7661 $location.$$replace = false;
7662 7663 return changeCounter; 7664 }); 7665 7666 return $location; 7667 7668 function afterLocationChange(oldUrl) { 7669 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); 7670 } 7671}]; 7672} 7673 7674/** 7675 * @ngdoc object 7676 * @name ng.$log 7677 * @requires $window 7678 * 7679 * @description 7680 * Simple service for logging. Default implementation writes the message 7681 * into the browser's console (if present). 7682 * 7683 * The main purpose of this service is to simplify debugging and troubleshooting. 7684 * 7685 * @example 7686 <example> 7687 <file name="script.js"> 7688 function LogCtrl($scope, $log) { 7689 $scope.$log = $log; 7690 $scope.message = 'Hello World!'; 7691 } 7692 </file> 7693 <file name="index.html"> 7694 <div ng-controller="LogCtrl"> 7695 <p>Reload this page with open console, enter text and hit the log button...</p> 7696 Message: 7697 <input type="text" ng-model="message"/> 7698 <button ng-click="$log.log(message)">log</button> 7699 <button ng-click="$log.warn(message)">warn</button> 7700 <button ng-click="$log.info(message)">info</button> 7701 <button ng-click="$log.error(message)">error</button> 7702 </div> 7703 </file> 7704 </example> 7705 */ 7706 7707/** 7708 * @ngdoc object 7709 * @name ng.$logProvider 7710 * @description 7711 * Use the `$logProvider` to configure how the application logs messages 7712 */ 7713function $LogProvider(){ 7714 var debug = true, 7715 self = this; 7716 7717 /** 7718 * @ngdoc property 7719 * @name ng.$logProvider#debugEnabled 7720 * @methodOf ng.$logProvider 7721 * @description 7722 * @param {string=} flag enable or disable debug level messages 7723 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7724 */ 7725 this.debugEnabled = function(flag) { 7726 if (isDefined(flag)) { 7727 debug = flag; 7728 return this; 7729 } else { 7730 return debug; 7731 } 7732 }; 7733 7734 this.$get = ['$window', function($window){ 7735 return { 7736 /** 7737 * @ngdoc method 7738 * @name ng.$log#log 7739 * @methodOf ng.$log 7740 * 7741 * @description 7742 * Write a log message 7743 */ 7744 log: consoleLog('log'), 7745 7746 /** 7747 * @ngdoc method 7748 * @name ng.$log#info 7749 * @methodOf ng.$log 7750 * 7751 * @description 7752 * Write an information message 7753 */ 7754 info: consoleLog('info'), 7755 7756 /** 7757 * @ngdoc method 7758 * @name ng.$log#warn 7759 * @methodOf ng.$log 7760 * 7761 * @description 7762 * Write a warning message 7763 */ 7764 warn: consoleLog('warn'), 7765 7766 /** 7767 * @ngdoc method 7768 * @name ng.$log#error 7769 * @methodOf ng.$log 7770 * 7771 * @description 7772 * Write an error message 7773 */ 7774 error: consoleLog('error'), 7775 7776 /** 7777 * @ngdoc method 7778 * @name ng.$log#debug 7779 * @methodOf ng.$log 7780 * 7781 * @description 7782 * Write a debug message 7783 */ 7784 debug: (function () { 7785 var fn = consoleLog('debug'); 7786 7787 return function() { 7788 if (debug) { 7789 fn.apply(self, arguments); 7790 } 7791 } 7792 }()) 7793 }; 7794 7795 function formatError(arg) { 7796 if (arg instanceof Error) { 7797 if (arg.stack) { 7798 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 7799 ? 'Error: ' + arg.message + '\n' + arg.stack 7800 : arg.stack; 7801 } else if (arg.sourceURL) { 7802 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 7803 } 7804 } 7805 return arg; 7806 } 7807 7808 function consoleLog(type) { 7809 var console = $window.console || {}, 7810 logFn = console[type] || console.log || noop; 7811 7812 if (logFn.apply) { 7813 return function() { 7814 var args = []; 7815 forEach(arguments, function(arg) { 7816 args.push(formatError(arg)); 7817 }); 7818 return logFn.apply(console, args); 7819 }; 7820 } 7821 7822 // we are IE which either doesn't have window.console => this is noop and we do nothing, 7823 // or we are IE where console.log doesn't have apply so we log at least first 2 args 7824 return function(arg1, arg2) { 7825 logFn(arg1, arg2); 7826 } 7827 } 7828 }]; 7829} 7830 7831var $parseMinErr = minErr('$parse'); 7832 7833// Sandboxing Angular Expressions 7834// ------------------------------ 7835// Angular expressions are generally considered safe because these expressions only have direct access to $scope and 7836// locals. However, one can obtain the ability to execute arbitrary JS code by obtaining a reference to native JS 7837// functions such as the Function constructor. 7838// 7839// As an example, consider the following Angular expression: 7840// 7841// {}.toString.constructor(alert("evil JS code")) 7842// 7843// We want to prevent this type of access. For the sake of performance, during the lexing phase we disallow any "dotted" 7844// access to any member named "constructor". 7845// 7846// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor while evaluating 7847// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor while evaluating 7848// the expression, which is a stronger but more expensive test. Since reflective calls are expensive anyway, this is not 7849// such a big deal compared to static dereferencing. 7850// 7851// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits against the 7852// expression language, but not to prevent exploits that were enabled by exposing sensitive JavaScript or browser apis 7853// on Scope. Exposing such objects on a Scope is never a good practice and therefore we are not even trying to protect 7854// against interaction with an object explicitly exposed in this way. 7855// 7856// A developer could foil the name check by aliasing the Function constructor under a different name on the scope. 7857// 7858// In general, it is not possible to access a Window object from an angular expression unless a window or some DOM 7859// object that has a reference to window is published onto a Scope. 7860 7861function ensureSafeMemberName(name, fullExpression) { 7862 if (name === "constructor") { 7863 throw $parseMinErr('isecfld', 7864 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', fullExpression); 7865 } 7866 return name; 7867}; 7868 7869function ensureSafeObject(obj, fullExpression) { 7870 // nifty check if obj is Function that is fast and works across iframes and other contexts 7871 if (obj && obj.constructor === obj) { 7872 throw $parseMinErr('isecfn', 7873 'Referencing Function in Angular expressions is disallowed! Expression: {0}', fullExpression); 7874 } else { 7875 return obj; 7876 } 7877} 7878 7879 7880var OPERATORS = { 7881 'null':function(){return null;}, 7882 'true':function(){return true;}, 7883 'false':function(){return false;}, 7884 undefined:noop, 7885 '+':function(self, locals, a,b){ 7886 a=a(self, locals); b=b(self, locals); 7887 if (isDefined(a)) { 7888 if (isDefined(b)) { 7889 return a + b; 7890 } 7891 return a; 7892 } 7893 return isDefined(b)?b:undefined;}, 7894 '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);}, 7895 '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, 7896 '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, 7897 '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, 7898 '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, 7899 '=':noop, 7900 '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, 7901 '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, 7902 '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, 7903 '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, 7904 '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, 7905 '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, 7906 '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, 7907 '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, 7908 '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, 7909 '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, 7910 '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, 7911// '|':function(self, locals, a,b){return a|b;}, 7912 '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, 7913 '!':function(self, locals, a){return !a(self, locals);} 7914}; 7915var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 7916 7917function lex(text, csp){ 7918 var tokens = [], 7919 token, 7920 index = 0, 7921 json = [], 7922 ch, 7923 lastCh = ':'; // can start regexp 7924 7925 while (index < text.length) { 7926 ch = text.charAt(index); 7927 if (is('"\'')) { 7928 readString(ch); 7929 } else if (isNumber(ch) || is('.') && isNumber(peek())) { 7930 readNumber(); 7931 } else if (isIdent(ch)) { 7932 readIdent(); 7933 // identifiers can only be if the preceding char was a { or , 7934 if (was('{,') && json[0]=='{' && 7935 (token=tokens[tokens.length-1])) { 7936 token.json = token.text.indexOf('.') == -1; 7937 } 7938 } else if (is('(){}[].,;:?')) { 7939 tokens.push({ 7940 index:index, 7941 text:ch, 7942 json:(was(':[,') && is('{[')) || is('}]:,') 7943 }); 7944 if (is('{[')) json.unshift(ch); 7945 if (is('}]')) json.shift(); 7946 index++; 7947 } else if (isWhitespace(ch)) { 7948 index++; 7949 continue; 7950 } else { 7951 var ch2 = ch + peek(), 7952 ch3 = ch2 + peek(2), 7953 fn = OPERATORS[ch],
7954 fn2 = OPERATORS[ch2], 7955 fn3 = OPERATORS[ch3]; 7956 if (fn3) { 7957 tokens.push({index:index, text:ch3, fn:fn3}); 7958 index += 3; 7959 } else if (fn2) { 7960 tokens.push({index:index, text:ch2, fn:fn2}); 7961 index += 2; 7962 } else if (fn) { 7963 tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')}); 7964 index += 1; 7965 } else { 7966 throwError("Unexpected next character ", index, index+1); 7967 } 7968 } 7969 lastCh = ch; 7970 } 7971 return tokens; 7972 7973 function is(chars) { 7974 return chars.indexOf(ch) != -1; 7975 } 7976 7977 function was(chars) { 7978 return chars.indexOf(lastCh) != -1; 7979 } 7980 7981 function peek(i) { 7982 var num = i || 1; 7983 return index + num < text.length ? text.charAt(index + num) : false; 7984 } 7985 function isNumber(ch) { 7986 return '0' <= ch && ch <= '9'; 7987 } 7988 function isWhitespace(ch) { 7989 return ch == ' ' || ch == '\r' || ch == '\t' || 7990 ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0 7991 } 7992 function isIdent(ch) { 7993 return 'a' <= ch && ch <= 'z' || 7994 'A' <= ch && ch <= 'Z' || 7995 '_' == ch || ch == '$'; 7996 } 7997 function isExpOperator(ch) { 7998 return ch == '-' || ch == '+' || isNumber(ch); 7999 } 8000 8001 function throwError(error, start, end) { 8002 end = end || index; 8003 var colStr = (isDefined(start) ? 8004 "s " + start + "-" + index + " [" + text.substring(start, end) + "]" 8005 : " " + end); 8006 throw $parseMinErr('lexerr', "Lexer Error: {0} at column{1} in expression [{2}].", 8007 error, colStr, text); 8008 } 8009 8010 function readNumber() { 8011 var number = ""; 8012 var start = index; 8013 while (index < text.length) { 8014 var ch = lowercase(text.charAt(index)); 8015 if (ch == '.' || isNumber(ch)) { 8016 number += ch; 8017 } else { 8018 var peekCh = peek(); 8019 if (ch == 'e' && isExpOperator(peekCh)) { 8020 number += ch; 8021 } else if (isExpOperator(ch) && 8022 peekCh && isNumber(peekCh) && 8023 number.charAt(number.length - 1) == 'e') { 8024 number += ch; 8025 } else if (isExpOperator(ch) && 8026 (!peekCh || !isNumber(peekCh)) && 8027 number.charAt(number.length - 1) == 'e') { 8028 throwError('Invalid exponent'); 8029 } else { 8030 break; 8031 } 8032 } 8033 index++; 8034 } 8035 number = 1 * number; 8036 tokens.push({index:start, text:number, json:true, 8037 fn:function() {return number;}}); 8038 } 8039 function readIdent() { 8040 var ident = "", 8041 start = index, 8042 lastDot, peekIndex, methodName, ch; 8043 8044 while (index < text.length) { 8045 ch = text.charAt(index); 8046 if (ch == '.' || isIdent(ch) || isNumber(ch)) { 8047 if (ch == '.') lastDot = index; 8048 ident += ch; 8049 } else { 8050 break; 8051 } 8052 index++; 8053 } 8054 8055 //check if this is not a method invocation and if it is back out to last dot 8056 if (lastDot) { 8057 peekIndex = index; 8058 while(peekIndex < text.length) { 8059 ch = text.charAt(peekIndex); 8060 if (ch == '(') { 8061 methodName = ident.substr(lastDot - start + 1); 8062 ident = ident.substr(0, lastDot - start); 8063 index = peekIndex; 8064 break; 8065 } 8066 if(isWhitespace(ch)) { 8067 peekIndex++; 8068 } else { 8069 break; 8070 } 8071 } 8072 } 8073 8074 8075 var token = { 8076 index:start, 8077 text:ident 8078 }; 8079 8080 if (OPERATORS.hasOwnProperty(ident)) { 8081 token.fn = token.json = OPERATORS[ident]; 8082 } else { 8083 var getter = getterFn(ident, csp, text); 8084 token.fn = extend(function(self, locals) { 8085 return (getter(self, locals)); 8086 }, { 8087 assign: function(self, value) { 8088 return setter(self, ident, value, text); 8089 } 8090 }); 8091 } 8092 8093 tokens.push(token); 8094 8095 if (methodName) { 8096 tokens.push({ 8097 index:lastDot, 8098 text: '.', 8099 json: false 8100 }); 8101 tokens.push({ 8102 index: lastDot + 1, 8103 text: methodName, 8104 json: false 8105 }); 8106 } 8107 } 8108 8109 function readString(quote) { 8110 var start = index; 8111 index++; 8112 var string = ""; 8113 var rawString = quote; 8114 var escape = false;
8115 while (index < text.length) { 8116 var ch = text.charAt(index); 8117 rawString += ch; 8118 if (escape) { 8119 if (ch == 'u') { 8120 var hex = text.substring(index + 1, index + 5); 8121 if (!hex.match(/[\da-f]{4}/i)) 8122 throwError( "Invalid unicode escape [\\u" + hex + "]"); 8123 index += 4; 8124 string += String.fromCharCode(parseInt(hex, 16)); 8125 } else { 8126 var rep = ESCAPE[ch]; 8127 if (rep) { 8128 string += rep; 8129 } else { 8130 string += ch; 8131 } 8132 } 8133 escape = false; 8134 } else if (ch == '\\') { 8135 escape = true; 8136 } else if (ch == quote) { 8137 index++; 8138 tokens.push({ 8139 index:start, 8140 text:rawString, 8141 string:string, 8142 json:true, 8143 fn:function() { return string; } 8144 }); 8145 return; 8146 } else { 8147 string += ch; 8148 } 8149 index++; 8150 } 8151 throwError("Unterminated quote", start); 8152 } 8153} 8154 8155///////////////////////////////////////// 8156 8157function parser(text, json, $filter, csp){ 8158 var ZERO = valueFn(0), 8159 value, 8160 tokens = lex(text, csp), 8161 assignment = _assignment, 8162 functionCall = _functionCall, 8163 fieldAccess = _fieldAccess, 8164 objectIndex = _objectIndex, 8165 filterChain = _filterChain; 8166 8167 if(json){ 8168 // The extra level of aliasing is here, just in case the lexer misses something, so that 8169 // we prevent any accidental execution in JSON. 8170 assignment = logicalOR; 8171 functionCall = 8172 fieldAccess = 8173 objectIndex = 8174 filterChain = 8175 function() { throwError("is not valid json", {text:text, index:0}); }; 8176 value = primary(); 8177 } else { 8178 value = statements(); 8179 } 8180 if (tokens.length !== 0) { 8181 throwError("is an unexpected token", tokens[0]); 8182 } 8183 value.literal = !!value.literal; 8184 value.constant = !!value.constant; 8185 return value; 8186 8187 /////////////////////////////////// 8188 function throwError(msg, token) { 8189 throw $parseMinErr('syntax', 8190 "Syntax Error: Token '{0}' {1} at column {2} of the expression [{3}] starting at [{4}].", 8191 token.text, msg, (token.index + 1), text, text.substring(token.index)); 8192 } 8193 8194 function peekToken() { 8195 if (tokens.length === 0) 8196 throw $parseMinErr('ueoe', "Unexpected end of expression: {0}", text); 8197 return tokens[0]; 8198 } 8199 8200 function peek(e1, e2, e3, e4) { 8201 if (tokens.length > 0) { 8202 var token = tokens[0]; 8203 var t = token.text; 8204 if (t==e1 || t==e2 || t==e3 || t==e4 || 8205 (!e1 && !e2 && !e3 && !e4)) { 8206 return token; 8207 } 8208 } 8209 return false; 8210 } 8211 8212 function expect(e1, e2, e3, e4){ 8213 var token = peek(e1, e2, e3, e4); 8214 if (token) { 8215 if (json && !token.json) { 8216 throwError("is not valid json", token); 8217 } 8218 tokens.shift(); 8219 return token; 8220 } 8221 return false; 8222 } 8223 8224 function consume(e1){ 8225 if (!expect(e1)) { 8226 throwError("is unexpected, expecting [" + e1 + "]", peek()); 8227 } 8228 } 8229 8230 function unaryFn(fn, right) { 8231 return extend(function(self, locals) { 8232 return fn(self, locals, right); 8233 }, { 8234 constant:right.constant 8235 }); 8236 } 8237 8238 function ternaryFn(left, middle, right){ 8239 return extend(function(self, locals){ 8240 return left(self, locals) ? middle(self, locals) : right(self, locals); 8241 }, {
8242 constant: left.constant && middle.constant && right.constant 8243 }); 8244 } 8245 8246 function binaryFn(left, fn, right) { 8247 return extend(function(self, locals) { 8248 return fn(self, locals, left, right); 8249 }, { 8250 constant:left.constant && right.constant 8251 }); 8252 } 8253 8254 function statements() { 8255 var statements = []; 8256 while(true) { 8257 if (tokens.length > 0 && !peek('}', ')', ';', ']')) 8258 statements.push(filterChain()); 8259 if (!expect(';')) { 8260 // optimize for the common case where there is only one statement. 8261 // TODO(size): maybe we should not support multiple statements? 8262 return statements.length == 1 8263 ? statements[0] 8264 : function(self, locals){ 8265 var value; 8266 for ( var i = 0; i < statements.length; i++) { 8267 var statement = statements[i]; 8268 if (statement) 8269 value = statement(self, locals); 8270 } 8271 return value; 8272 }; 8273 } 8274 } 8275 } 8276 8277 function _filterChain() { 8278 var left = expression(); 8279 var token; 8280 while(true) { 8281 if ((token = expect('|'))) { 8282 left = binaryFn(left, token.fn, filter()); 8283 } else { 8284 return left; 8285 } 8286 } 8287 } 8288 8289 function filter() { 8290 var token = expect(); 8291 var fn = $filter(token.text); 8292 var argsFn = []; 8293 while(true) { 8294 if ((token = expect(':'))) { 8295 argsFn.push(expression()); 8296 } else { 8297 var fnInvoke = function(self, locals, input){ 8298 var args = [input]; 8299 for ( var i = 0; i < argsFn.length; i++) { 8300 args.push(argsFn[i](self, locals)); 8301 } 8302 return fn.apply(self, args); 8303 }; 8304 return function() { 8305 return fnInvoke; 8306 }; 8307 } 8308 } 8309 } 8310 8311 function expression() { 8312 return assignment(); 8313 } 8314 8315 function _assignment() { 8316 var left = ternary(); 8317 var right; 8318 var token; 8319 if ((token = expect('='))) { 8320 if (!left.assign) { 8321 throwError("implies assignment but [" + 8322 text.substring(0, token.index) + "] can not be assigned to", token); 8323 } 8324 right = ternary(); 8325 return function(scope, locals){ 8326 return left.assign(scope, right(scope, locals), locals); 8327 }; 8328 } else { 8329 return left; 8330 } 8331 } 8332 8333 function ternary() { 8334 var left = logicalOR(); 8335 var middle; 8336 var token; 8337 if((token = expect('?'))){ 8338 middle = ternary(); 8339 if((token = expect(':'))){ 8340 return ternaryFn(left, middle, ternary()); 8341 } 8342 else { 8343 throwError('expected :', token); 8344 } 8345 } 8346 else { 8347 return left; 8348 } 8349 } 8350 8351 function logicalOR() { 8352 var left = logicalAND(); 8353 var token; 8354 while(true) { 8355 if ((token = expect('||'))) { 8356 left = binaryFn(left, token.fn, logicalAND()); 8357 } else { 8358 return left; 8359 } 8360 } 8361 } 8362 8363 function logicalAND() { 8364 var left = equality(); 8365 var token; 8366 if ((token = expect('&&'))) { 8367 left = binaryFn(left, token.fn, logicalAND()); 8368 } 8369 return left; 8370 } 8371 8372 function equality() { 8373 var left = relational(); 8374 var token; 8375 if ((token = expect('==','!=','===','!=='))) { 8376 left = binaryFn(left, token.fn, equality()); 8377 } 8378 return left; 8379 } 8380 8381 function relational() { 8382 var left = additive(); 8383 var token; 8384 if ((token = expect('<', '>', '<=', '>='))) { 8385 left = binaryFn(left, token.fn, relational()); 8386 } 8387 return left; 8388 } 8389 8390 function additive() { 8391 var left = multiplicative(); 8392 var token; 8393 while ((token = expect('+','-'))) { 8394 left = binaryFn(left, token.fn, multiplicative()); 8395 } 8396 return left; 8397 } 8398 8399 function multiplicative() { 8400 var left = unary(); 8401 var token; 8402 while ((token = expect('*','/','%'))) { 8403 left = binaryFn(left, token.fn, unary()); 8404 } 8405 return left; 8406 } 8407 8408 function unary() { 8409 var token; 8410 if (expect('+')) { 8411 return primary(); 8412 } else if ((token = expect('-'))) { 8413 return binaryFn(ZERO, token.fn, unary()); 8414 } else if ((token = expect('!'))) { 8415 return unaryFn(token.fn, unary()); 8416 } else { 8417 return primary(); 8418 } 8419 } 8420 8421 8422 function primary() { 8423 var primary; 8424 if (expect('(')) { 8425 primary = filterChain(); 8426 consume(')'); 8427 } else if (expect('[')) { 8428 primary = arrayDeclaration(); 8429 } else if (expect('{')) { 8430 primary = object(); 8431 } else { 8432 var token = expect(); 8433 primary = token.fn; 8434 if (!primary) { 8435 throwError("not a primary expression", token); 8436 } 8437 if (token.json) { 8438 primary.constant = primary.literal = true; 8439 } 8440 } 8441 8442 var next, context; 8443 while ((next = expect('(', '[', '.'))) { 8444 if (next.text === '(') { 8445 primary = functionCall(primary, context); 8446 context = null; 8447 } else if (next.text === '[') { 8448 context = primary; 8449 primary = objectIndex(primary); 8450 } else if (next.text === '.') { 8451 context = primary; 8452 primary = fieldAccess(primary); 8453 } else { 8454 throwError("IMPOSSIBLE"); 8455 } 8456 } 8457 return primary; 8458 } 8459 8460 function _fieldAccess(object) { 8461 var field = expect().text; 8462 var getter = getterFn(field, csp, text); 8463 return extend( 8464 function(scope, locals, self) { 8465 return getter(self || object(scope, locals), locals); 8466 }, 8467 { 8468 assign:function(scope, value, locals) { 8469 return setter(object(scope, locals), field, value, text); 8470 } 8471 } 8472 ); 8473 } 8474 8475 function _objectIndex(obj) { 8476 var indexFn = expression(); 8477 consume(']'); 8478 return extend( 8479 function(self, locals){ 8480 var o = obj(self, locals), 8481 i = indexFn(self, locals), 8482 v, p; 8483 8484 if (!o) return undefined; 8485 v = ensureSafeObject(o[i], text); 8486 if (v && v.then) { 8487 p = v; 8488 if (!('$$v' in v)) { 8489 p.$$v = undefined; 8490 p.then(function(val) { p.$$v = val; }); 8491 } 8492 v = v.$$v; 8493 } 8494 return v; 8495 }, { 8496 assign:function(self, value, locals){ 8497 var key = indexFn(self, locals);
8498 // prevent overwriting of Function.constructor which would break ensureSafeObject check 8499 return ensureSafeObject(obj(self, locals), text)[key] = value; 8500 } 8501 }); 8502 } 8503 8504 function _functionCall(fn, contextGetter) { 8505 var argsFn = []; 8506 if (peekToken().text != ')') { 8507 do { 8508 argsFn.push(expression()); 8509 } while (expect(',')); 8510 } 8511 consume(')'); 8512 return function(scope, locals){ 8513 var args = [], 8514 context = contextGetter ? contextGetter(scope, locals) : scope; 8515 8516 for ( var i = 0; i < argsFn.length; i++) { 8517 args.push(argsFn[i](scope, locals)); 8518 } 8519 var fnPtr = fn(scope, locals, context) || noop; 8520 // IE stupidity! 8521 return fnPtr.apply 8522 ? fnPtr.apply(context, args) 8523 : fnPtr(args[0], args[1], args[2], args[3], args[4]); 8524 }; 8525 } 8526 8527 // This is used with json array declaration 8528 function arrayDeclaration () { 8529 var elementFns = []; 8530 var allConstant = true; 8531 if (peekToken().text != ']') { 8532 do { 8533 var elementFn = expression(); 8534 elementFns.push(elementFn); 8535 if (!elementFn.constant) { 8536 allConstant = false; 8537 } 8538 } while (expect(',')); 8539 } 8540 consume(']'); 8541 return extend(function(self, locals){ 8542 var array = []; 8543 for ( var i = 0; i < elementFns.length; i++) { 8544 array.push(elementFns[i](self, locals)); 8545 } 8546 return array; 8547 }, { 8548 literal:true, 8549 constant:allConstant 8550 }); 8551 } 8552 8553 function object () { 8554 var keyValues = []; 8555 var allConstant = true; 8556 if (peekToken().text != '}') { 8557 do { 8558 var token = expect(), 8559 key = token.string || token.text; 8560 consume(":"); 8561 var value = expression(); 8562 keyValues.push({key:key, value:value}); 8563 if (!value.constant) { 8564 allConstant = false; 8565 } 8566 } while (expect(',')); 8567 } 8568 consume('}'); 8569 return extend(function(self, locals){ 8570 var object = {}; 8571 for ( var i = 0; i < keyValues.length; i++) { 8572 var keyValue = keyValues[i]; 8573 object[keyValue.key] = keyValue.value(self, locals); 8574 } 8575 return object; 8576 }, { 8577 literal:true, 8578 constant:allConstant 8579 }); 8580 } 8581} 8582 8583////////////////////////////////////////////////// 8584// Parser helper functions 8585////////////////////////////////////////////////// 8586 8587function setter(obj, path, setValue, fullExp) { 8588 var element = path.split('.'), key; 8589 for (var i = 0; element.length > 1; i++) { 8590 key = ensureSafeMemberName(element.shift(), fullExp); 8591 var propertyObj = obj[key]; 8592 if (!propertyObj) { 8593 propertyObj = {}; 8594 obj[key] = propertyObj; 8595 } 8596 obj = propertyObj; 8597 if (obj.then) { 8598 if (!("$$v" in obj)) { 8599 (function(promise) { 8600 promise.then(function(val) { promise.$$v = val; }); } 8601 )(obj); 8602 } 8603 if (obj.$$v === undefined) { 8604 obj.$$v = {}; 8605 } 8606 obj = obj.$$v; 8607 } 8608 } 8609 key = ensureSafeMemberName(element.shift(), fullExp); 8610 obj[key] = setValue; 8611 return setValue; 8612} 8613 8614var getterFnCache = {}; 8615 8616/** 8617 * Implementation of the "Black Hole" variant from: 8618 * - http://jsperf.com/angularjs-parse-getter/4 8619 * - http://jsperf.com/path-evaluation-simplified/7 8620 */ 8621function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) { 8622 ensureSafeMemberName(key0, fullExp); 8623 ensureSafeMemberName(key1, fullExp); 8624 ensureSafeMemberName(key2, fullExp); 8625 ensureSafeMemberName(key3, fullExp); 8626 ensureSafeMemberName(key4, fullExp); 8627 return function(scope, locals) { 8628 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, 8629 promise; 8630 8631 if (pathVal === null || pathVal === undefined) return pathVal; 8632 8633 pathVal = pathVal[key0]; 8634 if (pathVal && pathVal.then) { 8635 if (!("$$v" in pathVal)) { 8636 promise = pathVal; 8637 promise.$$v = undefined; 8638 promise.then(function(val) { promise.$$v = val; }); 8639 } 8640 pathVal = pathVal.$$v; 8641 } 8642 if (!key1 || pathVal === null || pathVal === undefined) return pathVal; 8643 8644 pathVal = pathVal[key1]; 8645 if (pathVal && pathVal.then) { 8646 if (!("$$v" in pathVal)) { 8647 promise = pathVal; 8648 promise.$$v = undefined; 8649 promise.then(function(val) { promise.$$v = val; }); 8650 } 8651 pathVal = pathVal.$$v; 8652 } 8653 if (!key2 || pathVal === null || pathVal === undefined) return pathVal; 8654 8655 pathVal = pathVal[key2]; 8656 if (pathVal && pathVal.then) { 8657 if (!("$$v" in pathVal)) { 8658 promise = pathVal; 8659 promise.$$v = undefined; 8660 promise.then(function(val) { promise.$$v = val; }); 8661 } 8662 pathVal = pathVal.$$v; 8663 } 8664 if (!key3 || pathVal === null || pathVal === undefined) return pathVal; 8665 8666 pathVal = pathVal[key3]; 8667 if (pathVal && pathVal.then) { 8668 if (!("$$v" in pathVal)) { 8669 promise = pathVal; 8670 promise.$$v = undefined; 8671 promise.then(function(val) { promise.$$v = val; }); 8672 } 8673 pathVal = pathVal.$$v; 8674 } 8675 if (!key4 || pathVal === null || pathVal === undefined) return pathVal; 8676 8677 pathVal = pathVal[key4]; 8678 if (pathVal && pathVal.then) { 8679 if (!("$$v" in pathVal)) { 8680 promise = pathVal; 8681 promise.$$v = undefined; 8682 promise.then(function(val) { promise.$$v = val; }); 8683 } 8684 pathVal = pathVal.$$v; 8685 } 8686 return pathVal; 8687 }; 8688} 8689 8690function getterFn(path, csp, fullExp) { 8691 if (getterFnCache.hasOwnProperty(path)) { 8692 return getterFnCache[path]; 8693 } 8694 8695 var pathKeys = path.split('.'), 8696 pathKeysLength = pathKeys.length, 8697 fn; 8698 8699 if (csp) { 8700 fn = (pathKeysLength < 6) 8701 ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp) 8702 : function(scope, locals) { 8703 var i = 0, val; 8704 do { 8705 val = cspSafeGetterFn( 8706 pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], fullExp 8707 )(scope, locals); 8708 8709 locals = undefined; // clear after first iteration 8710 scope = val; 8711 } while (i < pathKeysLength); 8712 return val; 8713 } 8714 } else { 8715 var code = 'var l, fn, p;\n';
8716 forEach(pathKeys, function(key, index) { 8717 ensureSafeMemberName(key, fullExp); 8718 code += 'if(s === null || s === undefined) return s;\n' + 8719 'l=s;\n' + 8720 's='+ (index 8721 // we simply dereference 's' on any .dot notation 8722 ? 's' 8723 // but if we are first then we check locals first, and if so read it first 8724 : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + 8725 'if (s && s.then) {\n' + 8726 ' if (!("$$v" in s)) {\n' + 8727 ' p=s;\n' + 8728 ' p.$$v = undefined;\n' + 8729 ' p.then(function(v) {p.$$v=v;});\n' + 8730 '}\n' + 8731 ' s=s.$$v\n' + 8732 '}\n'; 8733 }); 8734 code += 'return s;'; 8735 fn = Function('s', 'k', code); // s=scope, k=locals 8736 fn.toString = function() { return code; }; 8737 } 8738 8739 return getterFnCache[path] = fn; 8740} 8741 8742/////////////////////////////////// 8743 8744/** 8745 * @ngdoc function 8746 * @name ng.$parse 8747 * @function 8748 * 8749 * @description 8750 * 8751 * Converts Angular {@link guide/expression expression} into a function. 8752 * 8753 * <pre> 8754 * var getter = $parse('user.name'); 8755 * var setter = getter.assign; 8756 * var context = {user:{name:'angular'}}; 8757 * var locals = {user:{name:'local'}}; 8758 * 8759 * expect(getter(context)).toEqual('angular'); 8760 * setter(context, 'newValue'); 8761 * expect(context.user.name).toEqual('newValue'); 8762 * expect(getter(context, locals)).toEqual('local'); 8763 * </pre> 8764 * 8765 * 8766 * @param {string} expression String expression to compile. 8767 * @returns {function(context, locals)} a function which represents the compiled expression: 8768 * 8769 * * `context` ââ¬â `{object}` ââ¬â an object against which any expressions embedded in the strings 8770 * are evaluated against (typically a scope object). 8771 * * `locals` ââ¬â `{object=}` ââ¬â local variables context object, useful for overriding values in 8772 * `context`. 8773 * 8774 * The returned function also has the following properties: 8775 * * `literal` ââ¬â `{boolean}` ââ¬â whether the expression's top-level node is a JavaScript 8776 * literal. 8777 * * `constant` ââ¬â `{boolean}` ââ¬â whether the expression is made entirely of JavaScript 8778 * constant literals. 8779 * * `assign` ââ¬â `{?function(context, value)}` ââ¬â if the expression is assignable, this will be 8780 * set to a function to change its value on the given context. 8781 * 8782 */ 8783function $ParseProvider() { 8784 var cache = {}; 8785 this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { 8786 return function(exp) { 8787 switch(typeof exp) { 8788 case 'string': 8789 return cache.hasOwnProperty(exp) 8790 ? cache[exp] 8791 : cache[exp] = parser(exp, false, $filter, $sniffer.csp); 8792 case 'function': 8793 return exp; 8794 default: 8795 return noop; 8796 } 8797 }; 8798 }]; 8799} 8800 8801/** 8802 * @ngdoc service 8803 * @name ng.$q 8804 * @requires $rootScope 8805 * 8806 * @description 8807 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 8808 * 8809 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 8810 * interface for interacting with an object that represents the result of an action that is 8811 * performed asynchronously, and may or may not be finished at any given point in time. 8812 * 8813 * From the perspective of dealing with error handling, deferred and promise APIs are to 8814 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 8815 * 8816 * <pre> 8817 * // for the purpose of this example let's assume that variables `$q` and `scope` are 8818 * // available in the current lexical scope (they could have been injected or passed in). 8819 * 8820 * function asyncGreet(name) { 8821 * var deferred = $q.defer(); 8822 * 8823 * setTimeout(function() { 8824 * // since this fn executes async in a future turn of the event loop, we need to wrap 8825 * // our code into an $apply call so that the model changes are properly observed. 8826 * scope.$apply(function() { 8827 * if (okToGreet(name)) { 8828 * deferred.resolve('Hello, ' + name + '!'); 8829 * } else { 8830 * deferred.reject('Greeting ' + name + ' is not allowed.'); 8831 * } 8832 * }); 8833 * }, 1000); 8834 * 8835 * return deferred.promise; 8836 * } 8837 * 8838 * var promise = asyncGreet('Robin Hood'); 8839 * promise.then(function(greeting) { 8840 * alert('Success: ' + greeting); 8841 * }, function(reason) { 8842 * alert('Failed: ' + reason); 8843 * }); 8844 * </pre> 8845 * 8846 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 8847 * comes in the way of 8848 * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md). 8849 *
8850 * Additionally the promise api allows for composition that is very hard to do with the 8851 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 8852 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 8853 * section on serial or parallel joining of promises. 8854 * 8855 * 8856 * # The Deferred API 8857 * 8858 * A new instance of deferred is constructed by calling `$q.defer()`. 8859 * 8860 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 8861 * that can be used for signaling the successful or unsuccessful completion of the task. 8862 * 8863 * **Methods** 8864 * 8865 * - `resolve(value)` ââ¬â resolves the derived promise with the `value`. If the value is a rejection 8866 * constructed via `$q.reject`, the promise will be rejected instead. 8867 * - `reject(reason)` ââ¬â rejects the derived promise with the `reason`. This is equivalent to 8868 * resolving it with a rejection constructed via `$q.reject`. 8869 * 8870 * **Properties** 8871 * 8872 * - promise ââ¬â `{Promise}` ââ¬â promise object associated with this deferred. 8873 * 8874 * 8875 * # The Promise API 8876 * 8877 * A new promise instance is created when a deferred instance is created and can be retrieved by 8878 * calling `deferred.promise`. 8879 * 8880 * The purpose of the promise object is to allow for interested parties to get access to the result 8881 * of the deferred task when it completes. 8882 * 8883 * **Methods** 8884 * 8885 * - `then(successCallback, errorCallback)` ââ¬â regardless of when the promise was or will be resolved 8886 * or rejected, `then` calls one of the success or error callbacks asynchronously as soon as the result 8887 * is available. The callbacks are called with a single argument: the result or rejection reason. 8888 * 8889 * This method *returns a new promise* which is resolved or rejected via the return value of the 8890 * `successCallback` or `errorCallback`. 8891 * 8892 * - `catch(errorCallback)` ââ¬â shorthand for `promise.then(null, errorCallback)` 8893 * 8894 * - `finally(callback)` ââ¬â allows you to observe either the fulfillment or rejection of a promise, 8895 * but to do so without modifying the final value. This is useful to release resources or do some 8896 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 8897 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 8898 * more information. 8899 * 8900 * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as 8901 * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to 8902 * make your code IE8 compatible. 8903 * 8904 * # Chaining promises 8905 * 8906 * Because calling the `then` method of a promise returns a new derived promise, it is easily possible 8907 * to create a chain of promises: 8908 * 8909 * <pre> 8910 * promiseB = promiseA.then(function(result) { 8911 * return result + 1; 8912 * }); 8913 * 8914 * // promiseB will be resolved immediately after promiseA is resolved and its value 8915 * // will be the result of promiseA incremented by 1 8916 * </pre> 8917 * 8918 * It is possible to create chains of any length and since a promise can be resolved with another 8919 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 8920 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 8921 * $http's response interceptors. 8922 * 8923 * 8924 * # Differences between Kris Kowal's Q and $q 8925 * 8926 * There are three main differences: 8927 * 8928 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 8929 * mechanism in angular, which means faster propagation of resolution or rejection into your 8930 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 8931 * - $q promises are recognized by the templating engine in angular, which means that in templates 8932 * you can treat promises attached to a scope as if they were the resulting values. 8933 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
8934 * all the important functionality needed for common async tasks. 8935 * 8936 * # Testing 8937 * 8938 * <pre> 8939 * it('should simulate promise', inject(function($q, $rootScope) { 8940 * var deferred = $q.defer(); 8941 * var promise = deferred.promise; 8942 * var resolvedValue; 8943 * 8944 * promise.then(function(value) { resolvedValue = value; }); 8945 * expect(resolvedValue).toBeUndefined(); 8946 * 8947 * // Simulate resolving of promise 8948 * deferred.resolve(123); 8949 * // Note that the 'then' function does not get called synchronously. 8950 * // This is because we want the promise API to always be async, whether or not 8951 * // it got called synchronously or asynchronously. 8952 * expect(resolvedValue).toBeUndefined(); 8953 * 8954 * // Propagate promise resolution to 'then' functions using $apply(). 8955 * $rootScope.$apply(); 8956 * expect(resolvedValue).toEqual(123); 8957 * }); 8958 * </pre> 8959 */ 8960function $QProvider() { 8961 8962 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 8963 return qFactory(function(callback) { 8964 $rootScope.$evalAsync(callback); 8965 }, $exceptionHandler); 8966 }]; 8967} 8968 8969 8970/** 8971 * Constructs a promise manager. 8972 * 8973 * @param {function(function)} nextTick Function for executing functions in the next turn. 8974 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 8975 * debugging purposes. 8976 * @returns {object} Promise manager. 8977 */ 8978function qFactory(nextTick, exceptionHandler) { 8979 8980 /** 8981 * @ngdoc 8982 * @name ng.$q#defer 8983 * @methodOf ng.$q 8984 * @description 8985 * Creates a `Deferred` object which represents a task which will finish in the future. 8986 * 8987 * @returns {Deferred} Returns a new instance of deferred. 8988 */ 8989 var defer = function() { 8990 var pending = [], 8991 value, deferred; 8992 8993 deferred = { 8994 8995 resolve: function(val) { 8996 if (pending) { 8997 var callbacks = pending; 8998 pending = undefined; 8999 value = ref(val); 9000 9001 if (callbacks.length) { 9002 nextTick(function() { 9003 var callback; 9004 for (var i = 0, ii = callbacks.length; i < ii; i++) { 9005 callback = callbacks[i]; 9006 value.then(callback[0], callback[1], callback[2]); 9007 } 9008 }); 9009 } 9010 } 9011 }, 9012 9013 9014 reject: function(reason) { 9015 deferred.resolve(reject(reason)); 9016 }, 9017 9018 9019 notify: function(progress) { 9020 if (pending) { 9021 var callbacks = pending; 9022 9023 if (pending.length) { 9024 nextTick(function() { 9025 var callback; 9026 for (var i = 0, ii = callbacks.length; i < ii; i++) { 9027 callback = callbacks[i]; 9028 callback[2](progress); 9029 } 9030 }); 9031 } 9032 } 9033 }, 9034 9035 9036 promise: { 9037 then: function(callback, errback, progressback) { 9038 var result = defer(); 9039 9040 var wrappedCallback = function(value) { 9041 try { 9042 result.resolve((callback || defaultCallback)(value)); 9043 } catch(e) { 9044 result.reject(e); 9045 exceptionHandler(e); 9046 } 9047 }; 9048 9049 var wrappedErrback = function(reason) { 9050 try { 9051 result.resolve((errback || defaultErrback)(reason)); 9052 } catch(e) { 9053 result.reject(e); 9054 exceptionHandler(e); 9055 } 9056 }; 9057 9058 var wrappedProgressback = function(progress) { 9059 try { 9060 result.notify((progressback || defaultCallback)(progress)); 9061 } catch(e) { 9062 exceptionHandler(e); 9063 } 9064 }; 9065 9066 if (pending) { 9067 pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); 9068 } else { 9069 value.then(wrappedCallback, wrappedErrback, wrappedProgressback); 9070 } 9071 9072 return result.promise; 9073 }, 9074 9075 "catch": function(callback) { 9076 return this.then(null, callback); 9077 }, 9078 9079 "finally": function(callback) { 9080 9081 function makePromise(value, resolved) { 9082 var result = defer(); 9083 if (resolved) { 9084 result.resolve(value); 9085 } else { 9086 result.reject(value); 9087 } 9088 return result.promise; 9089 } 9090 9091 function handleCallback(value, isResolved) { 9092 var callbackOutput = null; 9093 try { 9094 callbackOutput = (callback ||defaultCallback)(); 9095 } catch(e) { 9096 return makePromise(e, false); 9097 } 9098 if (callbackOutput && callbackOutput.then) { 9099 return callbackOutput.then(function() { 9100 return makePromise(value, isResolved); 9101 }, function(error) { 9102 return makePromise(error, false); 9103 }); 9104 } else { 9105 return makePromise(value, isResolved); 9106 } 9107 } 9108 9109 return this.then(function(value) { 9110 return handleCallback(value, true); 9111 }, function(error) { 9112 return handleCallback(error, false); 9113 }); 9114 } 9115 } 9116 }; 9117 9118 return deferred; 9119 }; 9120 9121 9122 var ref = function(value) { 9123 if (value && value.then) return value; 9124 return { 9125 then: function(callback) { 9126 var result = defer(); 9127 nextTick(function() { 9128 result.resolve(callback(value)); 9129 }); 9130 return result.promise; 9131 } 9132 }; 9133 }; 9134 9135 9136 /** 9137 * @ngdoc 9138 * @name ng.$q#reject 9139 * @methodOf ng.$q 9140 * @description 9141 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 9142 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 9143 * a promise chain, you don't need to worry about it. 9144 * 9145 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 9146 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 9147 * a promise error callback and you want to forward the error to the promise derived from the 9148 * current promise, you have to "rethrow" the error by returning a rejection constructed via 9149 * `reject`. 9150 * 9151 * <pre> 9152 * promiseB = promiseA.then(function(result) { 9153 * // success: do something and resolve promiseB 9154 * // with the old or a new result 9155 * return result; 9156 * }, function(reason) { 9157 * // error: handle the error if possible and
9158 * // resolve promiseB with newPromiseOrValue, 9159 * // otherwise forward the rejection to promiseB 9160 * if (canHandle(reason)) { 9161 * // handle the error and recover 9162 * return newPromiseOrValue; 9163 * } 9164 * return $q.reject(reason); 9165 * }); 9166 * </pre> 9167 * 9168 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 9169 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 9170 */ 9171 var reject = function(reason) { 9172 return { 9173 then: function(callback, errback) { 9174 var result = defer(); 9175 nextTick(function() { 9176 result.resolve((errback || defaultErrback)(reason)); 9177 }); 9178 return result.promise; 9179 } 9180 }; 9181 }; 9182 9183 9184 /** 9185 * @ngdoc 9186 * @name ng.$q#when 9187 * @methodOf ng.$q 9188 * @description 9189 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 9190 * This is useful when you are dealing with an object that might or might not be a promise, or if 9191 * the promise comes from a source that can't be trusted. 9192 * 9193 * @param {*} value Value or a promise 9194 * @returns {Promise} Returns a promise of the passed value or promise 9195 */ 9196 var when = function(value, callback, errback, progressback) { 9197 var result = defer(), 9198 done; 9199 9200 var wrappedCallback = function(value) { 9201 try { 9202 return (callback || defaultCallback)(value); 9203 } catch (e) { 9204 exceptionHandler(e); 9205 return reject(e); 9206 } 9207 }; 9208 9209 var wrappedErrback = function(reason) { 9210 try { 9211 return (errback || defaultErrback)(reason); 9212 } catch (e) { 9213 exceptionHandler(e); 9214 return reject(e); 9215 } 9216 }; 9217 9218 var wrappedProgressback = function(progress) { 9219 try { 9220 return (progressback || defaultCallback)(progress); 9221 } catch (e) { 9222 exceptionHandler(e); 9223 } 9224 }; 9225 9226 nextTick(function() { 9227 ref(value).then(function(value) { 9228 if (done) return; 9229 done = true; 9230 result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); 9231 }, function(reason) { 9232 if (done) return; 9233 done = true; 9234 result.resolve(wrappedErrback(reason)); 9235 }, function(progress) { 9236 if (done) return; 9237 result.notify(wrappedProgressback(progress)); 9238 }); 9239 }); 9240 9241 return result.promise; 9242 }; 9243 9244 9245 function defaultCallback(value) { 9246 return value; 9247 } 9248 9249 9250 function defaultErrback(reason) { 9251 return reject(reason); 9252 } 9253 9254 9255 /** 9256 * @ngdoc 9257 * @name ng.$q#all 9258 * @methodOf ng.$q 9259 * @description 9260 * Combines multiple promises into a single promise that is resolved when all of the input 9261 * promises are resolved. 9262 * 9263 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 9264 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 9265 * each value corresponding to the promise at the same index/key in the `promises` array/hash. If any of 9266 * the promises is resolved with a rejection, this resulting promise will be resolved with the 9267 * same rejection. 9268 */ 9269 function all(promises) { 9270 var deferred = defer(), 9271 counter = 0, 9272 results = isArray(promises) ? [] : {}; 9273 9274 forEach(promises, function(promise, key) { 9275 counter++; 9276 ref(promise).then(function(value) { 9277 if (results.hasOwnProperty(key)) return; 9278 results[key] = value; 9279 if (!(--counter)) deferred.resolve(results); 9280 }, function(reason) { 9281 if (results.hasOwnProperty(key)) return; 9282 deferred.reject(reason); 9283 }); 9284 }); 9285 9286 if (counter === 0) { 9287 deferred.resolve(results); 9288 } 9289 9290 return deferred.promise; 9291 } 9292 9293 return { 9294 defer: defer, 9295 reject: reject, 9296 when: when, 9297 all: all 9298 }; 9299} 9300 9301/** 9302 * DESIGN NOTES 9303 * 9304 * The design decisions behind the scope are heavily favored for speed and memory consumption. 9305 * 9306 * The typical use of scope is to watch the expressions, which most of the time return the same 9307 * value as last time so we optimize the operation. 9308 * 9309 * Closures construction is expensive in terms of speed as well as memory: 9310 * - No closures, instead use prototypical inheritance for API 9311 * - Internal state needs to be stored on scope directly, which means that private state is 9312 * exposed as $$____ properties 9313 * 9314 * Loop operations are optimized by using while(count--) { ... } 9315 * - this means that in order to keep the same order of execution as addition we have to add 9316 * items to the array at the beginning (shift) instead of at the end (push) 9317 * 9318 * Child scopes are created and removed often 9319 * - Using an array would be slow since inserts in middle are expensive so we use linked list 9320 *
9321 * There are few watches then a lot of observers. This is why you don't want the observer to be 9322 * implemented in the same way as watch. Watch requires return of initialization function which 9323 * are expensive to construct. 9324 */ 9325 9326 9327/** 9328 * @ngdoc object 9329 * @name ng.$rootScopeProvider 9330 * @description 9331 * 9332 * Provider for the $rootScope service. 9333 */ 9334 9335/** 9336 * @ngdoc function 9337 * @name ng.$rootScopeProvider#digestTtl 9338 * @methodOf ng.$rootScopeProvider 9339 * @description 9340 * 9341 * Sets the number of digest iterations the scope should attempt to execute before giving up and 9342 * assuming that the model is unstable. 9343 * 9344 * The current default is 10 iterations. 9345 * 9346 * @param {number} limit The number of digest iterations. 9347 */ 9348 9349 9350/** 9351 * @ngdoc object 9352 * @name ng.$rootScope 9353 * @description 9354 * 9355 * Every application has a single root {@link ng.$rootScope.Scope scope}. 9356 * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide 9357 * event processing life-cycle. See {@link guide/scope developer guide on scopes}. 9358 */ 9359function $RootScopeProvider(){ 9360 var TTL = 10; 9361 var $rootScopeMinErr = minErr('$rootScope'); 9362 9363 this.digestTtl = function(value) { 9364 if (arguments.length) { 9365 TTL = value; 9366 } 9367 return TTL; 9368 }; 9369 9370 this.$get = ['$injector', '$exceptionHandler', '$parse', 9371 function( $injector, $exceptionHandler, $parse) { 9372 9373 /** 9374 * @ngdoc function 9375 * @name ng.$rootScope.Scope 9376 * 9377 * @description 9378 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 9379 * {@link AUTO.$injector $injector}. Child scopes are created using the 9380 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 9381 * compiled HTML template is executed.) 9382 * 9383 * Here is a simple scope snippet to show how you can interact with the scope. 9384 * <pre> 9385 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 9386 * </pre> 9387 * 9388 * # Inheritance 9389 * A scope can inherit from a parent scope, as in this example: 9390 * <pre> 9391 var parent = $rootScope; 9392 var child = parent.$new(); 9393 9394 parent.salutation = "Hello"; 9395 child.name = "World"; 9396 expect(child.salutation).toEqual('Hello'); 9397 9398 child.salutation = "Welcome"; 9399 expect(child.salutation).toEqual('Welcome'); 9400 expect(parent.salutation).toEqual('Hello'); 9401 * </pre> 9402 * 9403 * 9404 * @param {Object.<string, function()>=} providers Map of service factory which need to be provided 9405 * for the current scope. Defaults to {@link ng}. 9406 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 9407 * append/override services provided by `providers`. This is handy when unit-testing and having 9408 * the need to override a default service. 9409 * @returns {Object} Newly created scope. 9410 * 9411 */ 9412 function Scope() { 9413 this.$id = nextUid(); 9414 this.$$phase = this.$parent = this.$$watchers = 9415 this.$$nextSibling = this.$$prevSibling = 9416 this.$$childHead = this.$$childTail = null; 9417 this['this'] = this.$root = this; 9418 this.$$destroyed = false; 9419 this.$$asyncQueue = []; 9420 this.$$listeners = {}; 9421 this.$$isolateBindings = {}; 9422 } 9423 9424 /** 9425 * @ngdoc property 9426 * @name ng.$rootScope.Scope#$id 9427 * @propertyOf ng.$rootScope.Scope 9428 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for 9429 * debugging. 9430 */ 9431 9432 9433 Scope.prototype = { 9434 /** 9435 * @ngdoc function 9436 * @name ng.$rootScope.Scope#$new 9437 * @methodOf ng.$rootScope.Scope 9438 * @function 9439 * 9440 * @description 9441 * Creates a new child {@link ng.$rootScope.Scope scope}. 9442 * 9443 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and 9444 * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope 9445 * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 9446 * 9447 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for 9448 * the scope and its child scopes to be permanently detached from the parent and thus stop 9449 * participating in model change detection and listener notification by invoking. 9450 * 9451 * @param {boolean} isolate if true then the scope does not prototypically inherit from the
9452 * parent scope. The scope is isolated, as it can not see parent scope properties. 9453 * When creating widgets it is useful for the widget to not accidentally read parent 9454 * state. 9455 * 9456 * @returns {Object} The newly created child scope. 9457 * 9458 */ 9459 $new: function(isolate) { 9460 var Child, 9461 child; 9462 9463 if (isolate) { 9464 child = new Scope(); 9465 child.$root = this.$root; 9466 // ensure that there is just one async queue per $rootScope and it's children 9467 child.$$asyncQueue = this.$$asyncQueue; 9468 } else { 9469 Child = function() {}; // should be anonymous; This is so that when the minifier munges 9470 // the name it does not become random set of chars. These will then show up as class 9471 // name in the debugger. 9472 Child.prototype = this; 9473 child = new Child(); 9474 child.$id = nextUid(); 9475 } 9476 child['this'] = child; 9477 child.$$listeners = {}; 9478 child.$parent = this; 9479 child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; 9480 child.$$prevSibling = this.$$childTail; 9481 if (this.$$childHead) { 9482 this.$$childTail.$$nextSibling = child; 9483 this.$$childTail = child; 9484 } else { 9485 this.$$childHead = this.$$childTail = child; 9486 } 9487 return child; 9488 }, 9489 9490 /** 9491 * @ngdoc function 9492 * @name ng.$rootScope.Scope#$watch 9493 * @methodOf ng.$rootScope.Scope 9494 * @function 9495 * 9496 * @description 9497 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 9498 * 9499 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and 9500 * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()} 9501 * reruns when it detects changes the `watchExpression` can execute multiple times per 9502 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 9503 * - The `listener` is called only when the value from the current `watchExpression` and the 9504 * previous call to `watchExpression` are not equal (with the exception of the initial run, 9505 * see below). The inequality is determined according to 9506 * {@link angular.equals} function. To save the value of the object for later comparison, the 9507 * {@link angular.copy} function is used. It also means that watching complex options will 9508 * have adverse memory and performance implications. 9509 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This 9510 * is achieved by rerunning the watchers until no changes are detected. The rerun iteration 9511 * limit is 10 to prevent an infinite loop deadlock. 9512 * 9513 * 9514 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 9515 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 9516 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is 9517 * detected, be prepared for multiple calls to your listener.) 9518 * 9519 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 9520 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 9521 * watcher. In rare cases, this is undesirable because the listener is called when the result 9522 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 9523 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 9524 * listener was called due to initialization. 9525 * 9526 * 9527 * # Example 9528 * <pre> 9529 // let's assume that scope was dependency injected as the $rootScope 9530 var scope = $rootScope; 9531 scope.name = 'misko'; 9532 scope.counter = 0; 9533 9534 expect(scope.counter).toEqual(0); 9535 scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; }); 9536 expect(scope.counter).toEqual(0); 9537 9538 scope.$digest(); 9539 // no variable change 9540 expect(scope.counter).toEqual(0); 9541 9542 scope.name = 'adam'; 9543 scope.$digest(); 9544 expect(scope.counter).toEqual(1); 9545 * </pre> 9546 * 9547 * 9548 * 9549 * @param {(function()|string)} watchExpression Expression that is evaluated on each 9550 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a 9551 * call to the `listener`. 9552 * 9553 * - `string`: Evaluated as {@link guide/expression expression} 9554 * - `function(scope)`: called with current `scope` as a parameter. 9555 * @param {(function()|string)=} listener Callback called whenever the return value of 9556 * the `watchExpression` changes. 9557 * 9558 * - `string`: Evaluated as {@link guide/expression expression} 9559 * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters. 9560 * 9561 * @param {boolean=} objectEquality Compare object for equality rather than for reference. 9562 * @returns {function()} Returns a deregistration function for this listener. 9563 */ 9564 $watch: function(watchExp, listener, objectEquality) { 9565 var scope = this, 9566 get = compileToFn(watchExp, 'watch'), 9567 array = scope.$$watchers, 9568 watcher = { 9569 fn: listener, 9570 last: initWatchVal, 9571 get: get, 9572 exp: watchExp, 9573 eq: !!objectEquality 9574 }; 9575 9576 // in the case user pass string, we need to compile it, do we really need this ? 9577 if (!isFunction(listener)) { 9578 var listenFn = compileToFn(listener || noop, 'listener'); 9579 watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; 9580 } 9581 9582 if (typeof watchExp == 'string' && get.constant) { 9583 var originalFn = watcher.fn; 9584 watcher.fn = function(newVal, oldVal, scope) { 9585 originalFn.call(this, newVal, oldVal, scope); 9586 arrayRemove(array, watcher); 9587 }; 9588 } 9589 9590 if (!array) { 9591 array = scope.$$watchers = []; 9592 } 9593 // we use unshift since we use a while loop in $digest for speed. 9594 // the while loop reads in reverse order. 9595 array.unshift(watcher); 9596 9597 return function() { 9598 arrayRemove(array, watcher); 9599 }; 9600 }, 9601 9602 9603 /** 9604 * @ngdoc function 9605 * @name ng.$rootScope.Scope#$watchCollection 9606 * @methodOf ng.$rootScope.Scope 9607 * @function 9608 *
9609 * @description 9610 * Shallow watches the properties of an object and fires whenever any of the properties change 9611 * (for arrays this implies watching the array items, for object maps this implies watching the properties). 9612 * If a change is detected the `listener` callback is fired. 9613 * 9614 * - The `obj` collection is observed via standard $watch operation and is examined on every call to $digest() to 9615 * see if any items have been added, removed, or moved. 9616 * - The `listener` is called whenever anything within the `obj` has changed. Examples include adding new items 9617 * into the object or array, removing and moving items around. 9618 * 9619 * 9620 * # Example 9621 * <pre> 9622 $scope.names = ['igor', 'matias', 'misko', 'james']; 9623 $scope.dataCount = 4; 9624 9625 $scope.$watchCollection('names', function(newNames, oldNames) { 9626 $scope.dataCount = newNames.length; 9627 }); 9628 9629 expect($scope.dataCount).toEqual(4); 9630 $scope.$digest(); 9631 9632 //still at 4 ... no changes 9633 expect($scope.dataCount).toEqual(4); 9634 9635 $scope.names.pop(); 9636 $scope.$digest(); 9637 9638 //now there's been a change 9639 expect($scope.dataCount).toEqual(3); 9640 * </pre> 9641 * 9642 * 9643 * @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The expression value 9644 * should evaluate to an object or an array which is observed on each 9645 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the collection will trigger 9646 * a call to the `listener`. 9647 * 9648 * @param {function(newCollection, oldCollection, scope)} listener a callback function that is fired with both 9649 * the `newCollection` and `oldCollection` as parameters. 9650 * The `newCollection` object is the newly modified data obtained from the `obj` expression and the 9651 * `oldCollection` object is a copy of the former collection data. 9652 * The `scope` refers to the current scope. 9653 * 9654 * @returns {function()} Returns a de-registration function for this listener. When the de-registration function is executed 9655 * then the internal watch operation is terminated. 9656 */ 9657 $watchCollection: function(obj, listener) { 9658 var self = this; 9659 var oldValue; 9660 var newValue; 9661 var changeDetected = 0; 9662 var objGetter = $parse(obj); 9663 var internalArray = []; 9664 var internalObject = {}; 9665 var oldLength = 0; 9666 9667 function $watchCollectionWatch() { 9668 newValue = objGetter(self); 9669 var newLength, key; 9670 9671 if (!isObject(newValue)) { 9672 if (oldValue !== newValue) { 9673 oldValue = newValue; 9674 changeDetected++; 9675 } 9676 } else if (isArrayLike(newValue)) { 9677 if (oldValue !== internalArray) { 9678 // we are transitioning from something which was not an array into array. 9679 oldValue = internalArray; 9680 oldLength = oldValue.length = 0; 9681 changeDetected++; 9682 } 9683 9684 newLength = newValue.length; 9685 9686 if (oldLength !== newLength) { 9687 // if lengths do not match we need to trigger change notification 9688 changeDetected++; 9689 oldValue.length = oldLength = newLength; 9690 } 9691 // copy the items to oldValue and look for changes. 9692 for (var i = 0; i < newLength; i++) { 9693 if (oldValue[i] !== newValue[i]) { 9694 changeDetected++; 9695 oldValue[i] = newValue[i]; 9696 } 9697 } 9698 } else { 9699 if (oldValue !== internalObject) { 9700 // we are transitioning from something which was not an object into object. 9701 oldValue = internalObject = {}; 9702 oldLength = 0; 9703 changeDetected++; 9704 } 9705 // copy the items to oldValue and look for changes. 9706 newLength = 0; 9707 for (key in newValue) { 9708 if (newValue.hasOwnProperty(key)) { 9709 newLength++; 9710 if (oldValue.hasOwnProperty(key)) { 9711 if (oldValue[key] !== newValue[key]) { 9712 changeDetected++; 9713 oldValue[key] = newValue[key]; 9714 } 9715 } else { 9716 oldLength++; 9717 oldValue[key] = newValue[key]; 9718 changeDetected++; 9719 } 9720 } 9721 } 9722 if (oldLength > newLength) { 9723 // we used to have more keys, need to find them and destroy them. 9724 changeDetected++; 9725 for(key in oldValue) { 9726 if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { 9727 oldLength--; 9728 delete oldValue[key]; 9729 } 9730 } 9731 } 9732 } 9733 return changeDetected; 9734 } 9735 9736 function $watchCollectionAction() { 9737 listener(newValue, oldValue, self); 9738 } 9739 9740 return this.$watch($watchCollectionWatch, $watchCollectionAction); 9741 }, 9742 9743 /** 9744 * @ngdoc function 9745 * @name ng.$rootScope.Scope#$digest 9746 * @methodOf ng.$rootScope.Scope 9747 * @function 9748 *
9749 * @description 9750 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. 9751 * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the 9752 * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are 9753 * firing. This means that it is possible to get into an infinite loop. This function will throw 9754 * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10. 9755 * 9756 * Usually you don't call `$digest()` directly in 9757 * {@link ng.directive:ngController controllers} or in 9758 * {@link ng.$compileProvider#directive directives}. 9759 * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a 9760 * {@link ng.$compileProvider#directive directives}) will force a `$digest()`. 9761 * 9762 * If you want to be notified whenever `$digest()` is called, 9763 * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()} 9764 * with no `listener`. 9765 * 9766 * You may have a need to call `$digest()` from within unit-tests, to simulate the scope 9767 * life-cycle. 9768 * 9769 * # Example 9770 * <pre> 9771 var scope = ...; 9772 scope.name = 'misko'; 9773 scope.counter = 0; 9774 9775 expect(scope.counter).toEqual(0); 9776 scope.$watch('name', function(newValue, oldValue) { 9777 scope.counter = scope.counter + 1; 9778 }); 9779 expect(scope.counter).toEqual(0); 9780 9781 scope.$digest(); 9782 // no variable change 9783 expect(scope.counter).toEqual(0); 9784 9785 scope.name = 'adam'; 9786 scope.$digest(); 9787 expect(scope.counter).toEqual(1); 9788 * </pre> 9789 * 9790 */ 9791 $digest: function() { 9792 var watch, value, last, 9793 watchers, 9794 asyncQueue = this.$$asyncQueue, 9795 length, 9796 dirty, ttl = TTL, 9797 next, current, target = this, 9798 watchLog = [], 9799 logIdx, logMsg; 9800 9801 beginPhase('$digest'); 9802 9803 do { // "while dirty" loop 9804 dirty = false; 9805 current = target; 9806 9807 while(asyncQueue.length) { 9808 try { 9809 current.$eval(asyncQueue.shift()); 9810 } catch (e) { 9811 $exceptionHandler(e); 9812 } 9813 } 9814 9815 do { // "traverse the scopes" loop 9816 if ((watchers = current.$$watchers)) { 9817 // process our watches 9818 length = watchers.length; 9819 while (length--) { 9820 try { 9821 watch = watchers[length]; 9822 // Most common watches are on primitives, in which case we can short 9823 // circuit it with === operator, only when === fails do we use .equals 9824 if (watch && (value = watch.get(current)) !== (last = watch.last) && 9825 !(watch.eq 9826 ? equals(value, last) 9827 : (typeof value == 'number' && typeof last == 'number' 9828 && isNaN(value) && isNaN(last)))) { 9829 dirty = true; 9830 watch.last = watch.eq ? copy(value) : value; 9831 watch.fn(value, ((last === initWatchVal) ? value : last), current); 9832 if (ttl < 5) { 9833 logIdx = 4 - ttl; 9834 if (!watchLog[logIdx]) watchLog[logIdx] = []; 9835 logMsg = (isFunction(watch.exp)) 9836 ? 'fn: ' + (watch.exp.name || watch.exp.toString()) 9837 : watch.exp; 9838 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); 9839 watchLog[logIdx].push(logMsg); 9840 } 9841 } 9842 } catch (e) { 9843 $exceptionHandler(e); 9844 } 9845 } 9846 } 9847 9848 // Insanity Warning: scope depth-first traversal 9849 // yes, this code is a bit crazy, but it works and we have tests to prove it! 9850 // this piece should be kept in sync with the traversal in $broadcast 9851 if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { 9852 while(current !== target && !(next = current.$$nextSibling)) { 9853 current = current.$parent; 9854 } 9855 } 9856 } while ((current = next)); 9857 9858 if(dirty && !(ttl--)) { 9859 clearPhase(); 9860 throw $rootScopeMinErr('infdig', 9861 '{0} $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: {1}', 9862 TTL, toJson(watchLog)); 9863 } 9864 }
9864 while (dirty || asyncQueue.length); 9865 9866 clearPhase(); 9867 }, 9868 9869 9870 /** 9871 * @ngdoc event 9872 * @name ng.$rootScope.Scope#$destroy 9873 * @eventOf ng.$rootScope.Scope 9874 * @eventType broadcast on scope being destroyed 9875 * 9876 * @description 9877 * Broadcasted when a scope and its children are being destroyed. 9878 * 9879 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 9880 * clean up DOM bindings before an element is removed from the DOM. 9881 */ 9882 9883 /** 9884 * @ngdoc function 9885 * @name ng.$rootScope.Scope#$destroy 9886 * @methodOf ng.$rootScope.Scope 9887 * @function 9888 * 9889 * @description 9890 * Removes the current scope (and all of its children) from the parent scope. Removal implies 9891 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 9892 * propagate to the current scope and its children. Removal also implies that the current 9893 * scope is eligible for garbage collection. 9894 * 9895 * The `$destroy()` is usually used by directives such as 9896 * {@link ng.directive:ngRepeat ngRepeat} for managing the 9897 * unrolling of the loop. 9898 * 9899 * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope. 9900 * Application code can register a `$destroy` event handler that will give it chance to 9901 * perform any necessary cleanup. 9902 * 9903 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 9904 * clean up DOM bindings before an element is removed from the DOM. 9905 */ 9906 $destroy: function() { 9907 // we can't destroy the root scope or a scope that has been already destroyed 9908 if ($rootScope == this || this.$$destroyed) return; 9909 var parent = this.$parent; 9910 9911 this.$broadcast('$destroy'); 9912 this.$$destroyed = true; 9913 9914 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 9915 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 9916 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 9917 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 9918 9919 // This is bogus code that works around Chrome's GC leak 9920 // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 9921 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 9922 this.$$childTail = null; 9923 }, 9924 9925 /** 9926 * @ngdoc function 9927 * @name ng.$rootScope.Scope#$eval 9928 * @methodOf ng.$rootScope.Scope 9929 * @function 9930 * 9931 * @description 9932 * Executes the `expression` on the current scope returning the result. Any exceptions in the 9933 * expression are propagated (uncaught). This is useful when evaluating Angular expressions. 9934 * 9935 * # Example 9936 * <pre> 9937 var scope = ng.$rootScope.Scope(); 9938 scope.a = 1; 9939 scope.b = 2; 9940 9941 expect(scope.$eval('a+b')).toEqual(3); 9942 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 9943 * </pre> 9944 * 9945 * @param {(string|function())=} expression An angular expression to be executed. 9946 * 9947 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 9948 * - `function(scope)`: execute the function with the current `scope` parameter. 9949 * 9950 * @returns {*} The result of evaluating the expression. 9951 */ 9952 $eval: function(expr, locals) { 9953 return $parse(expr)(this, locals); 9954 }, 9955 9956 /** 9957 * @ngdoc function 9958 * @name ng.$rootScope.Scope#$evalAsync 9959 * @methodOf ng.$rootScope.Scope 9960 * @function 9961 * 9962 * @description 9963 * Executes the expression on the current scope at a later point in time. 9964 * 9965 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that: 9966 * 9967 * - it will execute in the current script execution context (before any DOM rendering). 9968 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 9969 * `expression` execution. 9970 * 9971 * Any exceptions from the execution of the expression are forwarded to the 9972 * {@link ng.$exceptionHandler $exceptionHandler} service. 9973 * 9974 * @param {(string|function())=} expression An angular expression to be executed. 9975 * 9976 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 9977 * - `function(scope)`: execute the function with the current `scope` parameter. 9978 * 9979 */ 9980 $evalAsync: function(expr) { 9981 this.$$asyncQueue.push(expr); 9982 }, 9983 9984 /** 9985 * @ngdoc function 9986 * @name ng.$rootScope.Scope#$apply 9987 * @methodOf ng.$rootScope.Scope 9988 * @function 9989 *
9990 * @description 9991 * `$apply()` is used to execute an expression in angular from outside of the angular framework. 9992 * (For example from browser DOM events, setTimeout, XHR or third party libraries). 9993 * Because we are calling into the angular framework we need to perform proper scope life-cycle 9994 * of {@link ng.$exceptionHandler exception handling}, 9995 * {@link ng.$rootScope.Scope#$digest executing watches}. 9996 * 9997 * ## Life cycle 9998 * 9999 * # Pseudo-Code of `$apply()` 10000 * <pre> 10001 function $apply(expr) { 10002 try { 10003 return $eval(expr); 10004 } catch (e) { 10005 $exceptionHandler(e); 10006 } finally { 10007 $root.$digest(); 10008 } 10009 } 10010 * </pre> 10011 * 10012 * 10013 * Scope's `$apply()` method transitions through the following stages: 10014 * 10015 * 1. The {@link guide/expression expression} is executed using the 10016 * {@link ng.$rootScope.Scope#$eval $eval()} method. 10017 * 2. Any exceptions from the execution of the expression are forwarded to the 10018 * {@link ng.$exceptionHandler $exceptionHandler} service. 10019 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression 10020 * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 10021 * 10022 * 10023 * @param {(string|function())=} exp An angular expression to be executed. 10024 * 10025 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 10026 * - `function(scope)`: execute the function with current `scope` parameter. 10027 * 10028 * @returns {*} The result of evaluating the expression. 10029 */ 10030 $apply: function(expr) { 10031 try { 10032 beginPhase('$apply'); 10033 return this.$eval(expr); 10034 } catch (e) { 10035 $exceptionHandler(e); 10036 } finally { 10037 clearPhase(); 10038 try { 10039 $rootScope.$digest(); 10040 } catch (e) { 10041 $exceptionHandler(e); 10042 throw e; 10043 } 10044 } 10045 }, 10046 10047 /** 10048 * @ngdoc function 10049 * @name ng.$rootScope.Scope#$on 10050 * @methodOf ng.$rootScope.Scope 10051 * @function 10052 * 10053 * @description 10054 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of 10055 * event life cycle. 10056 * 10057 * The event listener function format is: `function(event, args...)`. The `event` object 10058 * passed into the listener has the following attributes: 10059 * 10060 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed. 10061 * - `currentScope` - `{Scope}`: the current scope which is handling the event. 10062 * - `name` - `{string}`: Name of the event. 10063 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event 10064 * propagation (available only for events that were `$emit`-ed). 10065 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true. 10066 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 10067 * 10068 * @param {string} name Event name to listen on. 10069 * @param {function(event, args...)} listener Function to call when the event is emitted. 10070 * @returns {function()} Returns a deregistration function for this listener. 10071 */ 10072 $on: function(name, listener) { 10073 var namedListeners = this.$$listeners[name]; 10074 if (!namedListeners) { 10075 this.$$listeners[name] = namedListeners = []; 10076 } 10077 namedListeners.push(listener); 10078 10079 return function() { 10080 namedListeners[indexOf(namedListeners, listener)] = null; 10081 }; 10082 }, 10083 10084 10085 /** 10086 * @ngdoc function 10087 * @name ng.$rootScope.Scope#$emit 10088 * @methodOf ng.$rootScope.Scope 10089 * @function 10090 * 10091 * @description 10092 * Dispatches an event `name` upwards through the scope hierarchy notifying the 10093 * registered {@link ng.$rootScope.Scope#$on} listeners. 10094 * 10095 * The event life cycle starts at the scope on which `$emit` was called. All 10096 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified. 10097 * Afterwards, the event traverses upwards toward the root scope and calls all registered 10098 * listeners along the way. The event will stop propagating if one of the listeners cancels it. 10099 * 10100 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 10101 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 10102 * 10103 * @param {string} name Event name to emit. 10104 * @param {...*} args Optional set of arguments which will be passed onto the event listeners. 10105 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 10106 */ 10107 $emit: function(name, args) { 10108 var empty = [], 10109 namedListeners, 10110 scope = this, 10111 stopPropagation = false, 10112 event = { 10113 name: name, 10114 targetScope: scope, 10115 stopPropagation: function() {stopPropagation = true;}, 10116 preventDefault: function() { 10117 event.defaultPrevented = true; 10118 }, 10119 defaultPrevented: false 10120 }, 10121 listenerArgs = concat([event], arguments, 1), 10122 i, length; 10123 10124 do { 10125 namedListeners = scope.$$listeners[name] || empty; 10126 event.currentScope = scope; 10127 for (i=0, length=namedListeners.length; i<length; i++) { 10128 10129 // if listeners were deregistered, defragment the array 10130 if (!namedListeners[i]) { 10131 namedListeners.splice(i, 1); 10132 i--; 10133 length--; 10134 continue; 10135 } 10136 try { 10137 namedListeners[i].apply(null, listenerArgs); 10138 if (stopPropagation) return event; 10139 } catch (e) { 10140 $exceptionHandler(e); 10141 } 10142 } 10143 //traverse upwards 10144 scope = scope.$parent; 10145 } while (scope); 10146 10147 return event; 10148 }, 10149 10150 10151 /** 10152 * @ngdoc function 10153 * @name ng.$rootScope.Scope#$broadcast 10154 * @methodOf ng.$rootScope.Scope 10155 * @function 10156 *
10157 * @description 10158 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 10159 * registered {@link ng.$rootScope.Scope#$on} listeners. 10160 * 10161 * The event life cycle starts at the scope on which `$broadcast` was called. All 10162 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified. 10163 * Afterwards, the event propagates to all direct and indirect scopes of the current scope and 10164 * calls all registered listeners along the way. The event cannot be canceled. 10165 * 10166 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 10167 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 10168 * 10169 * @param {string} name Event name to broadcast. 10170 * @param {...*} args Optional set of arguments which will be passed onto the event listeners. 10171 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 10172 */ 10173 $broadcast: function(name, args) { 10174 var target = this, 10175 current = target, 10176 next = target, 10177 event = { 10178 name: name, 10179 targetScope: target, 10180 preventDefault: function() { 10181 event.defaultPrevented = true; 10182 }, 10183 defaultPrevented: false 10184 }, 10185 listenerArgs = concat([event], arguments, 1), 10186 listeners, i, length; 10187 10188 //down while you can, then up and next sibling or up and next sibling until back at root 10189 do { 10190 current = next; 10191 event.currentScope = current; 10192 listeners = current.$$listeners[name] || []; 10193 for (i=0, length = listeners.length; i<length; i++) { 10194 // if listeners were deregistered, defragment the array 10195 if (!listeners[i]) { 10196 listeners.splice(i, 1); 10197 i--; 10198 length--; 10199 continue; 10200 } 10201 10202 try { 10203 listeners[i].apply(null, listenerArgs); 10204 } catch(e) { 10205 $exceptionHandler(e); 10206 } 10207 } 10208 10209 // Insanity Warning: scope depth-first traversal 10210 // yes, this code is a bit crazy, but it works and we have tests to prove it! 10211 // this piece should be kept in sync with the traversal in $digest 10212 if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { 10213 while(current !== target && !(next = current.$$nextSibling)) { 10214 current = current.$parent; 10215 } 10216 } 10217 } while ((current = next)); 10218 10219 return event; 10220 } 10221 }; 10222 10223 var $rootScope = new Scope(); 10224 10225 return $rootScope; 10226 10227 10228 function beginPhase(phase) { 10229 if ($rootScope.$$phase) { 10230 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 10231 } 10232 10233 $rootScope.$$phase = phase; 10234 } 10235 10236 function clearPhase() { 10237 $rootScope.$$phase = null; 10238 } 10239 10240 function compileToFn(exp, name) { 10241 var fn = $parse(exp); 10242 assertArgFn(fn, name); 10243 return fn; 10244 } 10245 10246 /** 10247 * function used as an initial value for watchers. 10248 * because it's unique we can easily tell it apart from other values 10249 */ 10250 function initWatchVal() {} 10251 }]; 10252} 10253 10254var $sceMinErr = minErr('$sce'); 10255 10256var SCE_CONTEXTS = { 10257 HTML: 'html', 10258 CSS: 'css', 10259 URL: 'url', 10260 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 10261 // url. (e.g. ng-include, script src, templateUrl) 10262 RESOURCE_URL: 'resourceUrl', 10263 JS: 'js' 10264}; 10265 10266 10267/** 10268 * @ngdoc service 10269 * @name ng.$sceDelegate 10270 * @function 10271 * 10272 * @description 10273 * 10274 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 10275 * Contextual Escaping (SCE)} services to AngularJS. 10276 * 10277 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 10278 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 10279 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 10280 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 10281 * work because `$sce` delegates to `$sceDelegate` for these operations. 10282 * 10283 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 10284 * 10285 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
10286 * can override it completely to change the behavior of `$sce`, the common case would 10287 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 10288 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 10289 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 10290 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 10291 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 10292 */ 10293 10294/** 10295 * @ngdoc object 10296 * @name ng.$sceDelegateProvider 10297 * @description 10298 * 10299 * The $sceDelegateProvider provider allows developers to configure the {@link ng.$sceDelegate 10300 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 10301 * that URLs used for sourcing Angular templates are safe. Refer {@link 10302 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 10303 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 10304 * 10305 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 10306 */ 10307 10308function $SceDelegateProvider() { 10309 this.SCE_CONTEXTS = SCE_CONTEXTS; 10310 10311 // Resource URLs can also be trusted by policy. 10312 var resourceUrlWhitelist = ['self'], 10313 resourceUrlBlacklist = []; 10314 10315 /** 10316 * @ngdoc function 10317 * @name ng.sceDelegateProvider#resourceUrlWhitelist 10318 * @methodOf ng.$sceDelegateProvider 10319 * @function 10320 * 10321 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 10322 * provided. This must be an array. 10323 * 10324 * Each element of this array must either be a regex or the special string `'self'`. 10325 * 10326 * When a regex is used, it is matched against the normalized / absolute URL of the resource 10327 * being tested. 10328 * 10329 * The **special string** `'self'` can be used to match against all URLs of the same domain as the 10330 * application document with the same protocol (allows sourcing https resources from http documents.) 10331 * 10332 * Please note that **an empty whitelist array will block all URLs**! 10333 * 10334 * @return {Array} the currently set whitelist array. 10335 * 10336 * The **default value** when no whitelist has been explicitly set is `['self']`. 10337 * 10338 * @description 10339 * Sets/Gets the whitelist of trusted resource URLs. 10340 */ 10341 this.resourceUrlWhitelist = function (value) { 10342 if (arguments.length) { 10343 resourceUrlWhitelist = value; 10344 } 10345 return resourceUrlWhitelist; 10346 }; 10347 10348 /** 10349 * @ngdoc function 10350 * @name ng.sceDelegateProvider#resourceUrlBlacklist 10351 * @methodOf ng.$sceDelegateProvider 10352 * @function 10353 * 10354 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 10355 * provided. This must be an array. 10356 * 10357 * Each element of this array must either be a regex or the special string `'self'` (see 10358 * `resourceUrlWhitelist` for meaning - it's only really useful there.) 10359 * 10360 * When a regex is used, it is matched against the normalized / absolute URL of the resource 10361 * being tested. 10362 * 10363 * The typical usage for the blacklist is to **block [open redirects](http://cwe.mitre.org/data/definitions/601.html)** 10364 * served by your domain as these would otherwise be trusted but actually return content from the redirected 10365 * domain. 10366 * 10367 * Finally, **the blacklist overrides the whitelist** and has the final say. 10368 * 10369 * @return {Array} the currently set blacklist array. 10370 * 10371 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there is 10372 * no blacklist.) 10373 * 10374 * @description 10375 * Sets/Gets the blacklist of trusted resource URLs. 10376 */ 10377 10378 this.resourceUrlBlacklist = function (value) { 10379 if (arguments.length) { 10380 resourceUrlBlacklist = value; 10381 } 10382 return resourceUrlBlacklist; 10383 }; 10384 10385 // Helper functions for matching resource urls by policy. 10386 function isCompatibleProtocol(documentProtocol, resourceProtocol) { 10387 return ((documentProtocol === resourceProtocol) || 10388 (documentProtocol === "http:" && resourceProtocol === "https:")); 10389 } 10390 10391 this.$get = ['$log', '$document', '$injector', '$$urlUtils', function( 10392 $log, $document, $injector, $$urlUtils) { 10393 10394 var htmlSanitizer = function htmlSanitizer(html) { 10395 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 10396 }; 10397 10398 if ($injector.has('$sanitize')) { 10399 htmlSanitizer = $injector.get('$sanitize'); 10400 } 10401 10402 10403 function matchUrl(matcher, parsedUrl) { 10404 if (matcher === 'self') {
10405 return $$urlUtils.isSameOrigin(parsedUrl); 10406 } else { 10407 return !!parsedUrl.href.match(matcher); 10408 } 10409 } 10410 10411 function isResourceUrlAllowedByPolicy(url) { 10412 var parsedUrl = $$urlUtils.resolve(url.toString(), true); 10413 var i, n, allowed = false; 10414 // Ensure that at least one item from the whitelist allows this url. 10415 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 10416 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 10417 allowed = true; 10418 break; 10419 } 10420 } 10421 if (allowed) { 10422 // Ensure that no item from the blacklist blocked this url. 10423 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 10424 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 10425 allowed = false; 10426 break; 10427 } 10428 } 10429 } 10430 return allowed; 10431 } 10432 10433 function generateHolderType(base) { 10434 var holderType = function TrustedValueHolderType(trustedValue) { 10435 this.$$unwrapTrustedValue = function() { 10436 return trustedValue; 10437 }; 10438 }; 10439 if (base) { 10440 holderType.prototype = new base(); 10441 } 10442 holderType.prototype.valueOf = function sceValueOf() { 10443 return this.$$unwrapTrustedValue(); 10444 } 10445 holderType.prototype.toString = function sceToString() { 10446 return this.$$unwrapTrustedValue().toString(); 10447 } 10448 return holderType; 10449 } 10450 10451 var trustedValueHolderBase = generateHolderType(), 10452 byType = {}; 10453 10454 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 10455 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 10456 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 10457 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 10458 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 10459 10460 /** 10461 * @ngdoc method 10462 * @name ng.$sceDelegate#trustAs 10463 * @methodOf ng.$sceDelegate 10464 * 10465 * @description 10466 * Returns an object that is trusted by angular for use in specified strict 10467 * contextual escaping contexts (such as ng-html-bind-unsafe, ng-include, any src 10468 * attribute interpolation, any dom event binding attribute interpolation 10469 * such as for onclick, etc.) that uses the provided value. 10470 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 10471 * 10472 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 10473 * resourceUrl, html, js and css. 10474 * @param {*} value The value that that should be considered trusted/safe. 10475 * @returns {*} A value that can be used to stand in for the provided `value` in places 10476 * where Angular expects a $sce.trustAs() return value. 10477 */ 10478 function trustAs(type, trustedValue) { 10479 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 10480 if (!constructor) { 10481 throw $sceMinErr('icontext', 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 10482 type, trustedValue); 10483 } 10484 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 10485 return trustedValue; 10486 } 10487 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 10488 // mutable objects, we ensure here that the value passed in is actually a string. 10489 if (typeof trustedValue !== 'string') { 10490 throw $sceMinErr('itype', 10491 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 10492 type); 10493 } 10494 return new constructor(trustedValue); 10495 } 10496 10497 /** 10498 * @ngdoc method 10499 * @name ng.$sceDelegate#valueOf 10500 * @methodOf ng.$sceDelegate 10501 * 10502 * @description 10503 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 10504 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 10505 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 10506 * 10507 * If the passed parameter is not a value that had been returned by {@link 10508 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 10509 * 10510 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 10511 * call or anything else. 10512 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 10513 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns `value` 10514 * unchanged. 10515 */ 10516 function valueOf(maybeTrusted) { 10517 if (maybeTrusted instanceof trustedValueHolderBase) { 10518 return maybeTrusted.$$unwrapTrustedValue(); 10519 } else { 10520 return maybeTrusted; 10521 } 10522 } 10523 10524 /** 10525 * @ngdoc method 10526 * @name ng.$sceDelegate#getTrusted 10527 * @methodOf ng.$sceDelegate 10528 *
10529 * @description 10530 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and returns the 10531 * originally supplied value if the queried context type is a supertype of the created type. If 10532 * this condition isn't satisfied, throws an exception. 10533 * 10534 * @param {string} type The kind of context in which this value is to be used. 10535 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 10536 * `$sceDelegate.trustAs`} call. 10537 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 10538 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 10539 */ 10540 function getTrusted(type, maybeTrusted) { 10541 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 10542 return maybeTrusted; 10543 } 10544 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 10545 if (constructor && maybeTrusted instanceof constructor) { 10546 return maybeTrusted.$$unwrapTrustedValue(); 10547 } 10548 // If we get here, then we may only take one of two actions. 10549 // 1. sanitize the value for the requested type, or 10550 // 2. throw an exception. 10551 if (type === SCE_CONTEXTS.RESOURCE_URL) { 10552 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 10553 return maybeTrusted; 10554 } else { 10555 throw $sceMinErr('insecurl', 10556 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', maybeTrusted.toString()); 10557 } 10558 } else if (type === SCE_CONTEXTS.HTML) { 10559 return htmlSanitizer(maybeTrusted); 10560 } 10561 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 10562 } 10563 10564 return { trustAs: trustAs, 10565 getTrusted: getTrusted, 10566 valueOf: valueOf }; 10567 }]; 10568} 10569 10570 10571/** 10572 * @ngdoc object 10573 * @name ng.$sceProvider 10574 * @description 10575 * 10576 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 10577 * - enable/disable Strict Contextual Escaping (SCE) in a module 10578 * - override the default implementation with a custom delegate 10579 * 10580 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 10581 */ 10582 10583/** 10584 * @ngdoc service 10585 * @name ng.$sce 10586 * @function 10587 * 10588 * @description 10589 * 10590 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 10591 * 10592 * # Strict Contextual Escaping 10593 * 10594 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 10595 * contexts to result in a value that is marked as safe to use for that context One example of such 10596 * a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer to these 10597 * contexts as privileged or SCE contexts. 10598 * 10599 * As of version 1.2, Angular ships with SCE enabled by default. 10600 * 10601 * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows 10602 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 10603 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
10604 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 10605 * to the top of your HTML document. 10606 * 10607 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 10608 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 10609 * 10610 * Here's an example of a binding in a privileged context: 10611 * 10612 * <pre class="prettyprint"> 10613 * <input ng-model="userHtml"> 10614 * <div ng-bind-html="{{userHtml}}"> 10615 * </pre> 10616 * 10617 * Notice that `ng-bind-html` is bound to `{{userHtml}}` controlled by the user. With SCE 10618 * disabled, this application allows the user to render arbitrary HTML into the DIV. 10619 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 10620 * bindings. (HTML is just one example of a context where rendering user controlled input creates 10621 * security vulnerabilities.) 10622 * 10623 * For the case of HTML, you might use a library, either on the client side, or on the server side, 10624 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 10625 * 10626 * How would you ensure that every place that used these types of bindings was bound to a value that 10627 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 10628 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 10629 * properties/fields and forgot to update the binding to the sanitized value? 10630 * 10631 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 10632 * determine that something explicitly says it's safe to use a value for binding in that
10633 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 10634 * for those values that you can easily tell are safe - because they were received from your server, 10635 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 10636 * allowing only the files in a specific directory to do this. Ensuring that the internal API 10637 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 10638 * 10639 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} (and shorthand 10640 * methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to obtain values that will be 10641 * accepted by SCE / privileged contexts. 10642 * 10643 * 10644 * ## How does it work? 10645 * 10646 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 10647 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 10648 * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 10649 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 10650 * 10651 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 10652 * ng.$sce#parseHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 10653 * simplified): 10654 * 10655 * <pre class="prettyprint"> 10656 * var ngBindHtmlDirective = ['$sce', function($sce) { 10657 * return function(scope, element, attr) { 10658 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 10659 * element.html(value || ''); 10660 * }); 10661 * }; 10662 * }]; 10663 * </pre> 10664 * 10665 * ## Impact on loading templates 10666 * 10667 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 10668 * `templateUrl`'s specified by {@link guide/directive directives}. 10669 * 10670 * By default, Angular only loads templates from the same domain and protocol as the application 10671 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 10672 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 10673 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 10674 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 10675 * 10676 * *Please note*: 10677 * The browser's 10678 * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest 10679 * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing (CORS)} 10680 * policy apply in addition to this and may further restrict whether the template is successfully 10681 * loaded. This means that without the right CORS policy, loading templates from a different domain 10682 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 10683 * browsers. 10684 * 10685 * ## This feels like too much overhead for the developer? 10686 * 10687 * It's important to remember that SCE only applies to interpolation expressions. 10688 * 10689 * If your expressions are constant literals, they're automatically trusted and you don't need to 10690 * call `$sce.trustAs` on them. (e.g. 10691 * `<div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div>`) just works. 10692 * 10693 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 10694 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 10695 * 10696 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 10697 * templates in `ng-include` from your application's domain without having to even know about SCE. 10698 * It blocks loading templates from other domains or loading templates over http from an https 10699 * served document. You can change these by setting your own custom {@link 10700 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 10701 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 10702 * 10703 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 10704 * application that's secure and can be audited to verify that with much more ease than bolting 10705 * security onto an application later. 10706 *
10707 * ## What trusted context types are supported?<a name="contexts"></a> 10708 * 10709 * | Context | Notes | 10710 * |=====================|================| 10711 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | 10712 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 10713 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't consititute an SCE context. | 10714 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contens 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. | 10715 * | `$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. | 10716 * 10717 * ## Show me an example. 10718 * 10719 * 10720 * 10721 * @example 10722 <example module="mySceApp"> 10723 <file name="index.html"> 10724 <div ng-controller="myAppController as myCtrl"> 10725 <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 10726 <b>User comments</b><br> 10727 By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when $sanitize is available. If $sanitize isn't available, this results in an error instead of an exploit. 10728 <div class="well"> 10729 <div ng-repeat="userComment in myCtrl.userComments"> 10730 <b>{{userComment.name}}</b>: 10731 <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 10732 <br> 10733 </div> 10734 </div> 10735 </div> 10736 </file> 10737 10738 <file name="script.js"> 10739 var mySceApp = angular.module('mySceApp', ['ngSanitize']); 10740 10741 mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { 10742 var self = this; 10743 $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 10744 self.userComments = userComments; 10745 }); 10746 self.explicitlyTrustedHtml = $sce.trustAsHtml( 10747 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
10748 'sanitization."">Hover over this text.</span>'); 10749 }); 10750 </file> 10751 10752 <file name="test_data.json"> 10753 [ 10754 { "name": "Alice", 10755 "htmlComment": "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 10756 }, 10757 { "name": "Bob", 10758 "htmlComment": "<i>Yes!</i> Am I the only other one?" 10759 } 10760 ] 10761 </file> 10762 10763 <file name="scenario.js"> 10764 describe('SCE doc demo', function() { 10765 it('should sanitize untrusted values', function() { 10766 expect(element('.htmlComment').html()).toBe('<span>Is <i>anyone</i> reading this?</span>'); 10767 }); 10768 it('should NOT sanitize explicitly trusted values', function() { 10769 expect(element('#explicitlyTrustedHtml').html()).toBe( 10770 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 10771 'sanitization."">Hover over this text.</span>'); 10772 }); 10773 }); 10774 </file> 10775 </example> 10776 * 10777 * 10778 * 10779 * ## Can I disable SCE completely? 10780 * 10781 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 10782 * for little coding overhead. It will be much harder to take an SCE disabled application and 10783 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 10784 * for cases where you have a lot of existing code that was written before SCE was introduced and 10785 * you're migrating them a module at a time. 10786 * 10787 * That said, here's how you can completely disable SCE: 10788 * 10789 * <pre class="prettyprint"> 10790 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 10791 * // Completely disable SCE. For demonstration purposes only! 10792 * // Do not use in new projects. 10793 * $sceProvider.enabled(false); 10794 * }); 10795 * </pre> 10796 * 10797 */ 10798 10799function $SceProvider() { 10800 var enabled = true; 10801 10802 /** 10803 * @ngdoc function 10804 * @name ng.sceProvider#enabled 10805 * @methodOf ng.$sceProvider 10806 * @function 10807 * 10808 * @param {boolean=} value If provided, then enables/disables SCE. 10809 * @return {boolean} true if SCE is enabled, false otherwise. 10810 * 10811 * @description 10812 * Enables/disables SCE and returns the current value. 10813 */ 10814 this.enabled = function (value) { 10815 if (arguments.length) { 10816 enabled = !!value; 10817 } 10818 return enabled; 10819 }; 10820 10821 10822 /* Design notes on the default implementation for SCE. 10823 * 10824 * The API contract for the SCE delegate 10825 * ------------------------------------- 10826 * The SCE delegate object must provide the following 3 methods: 10827 * 10828 * - trustAs(contextEnum, value) 10829 * This method is used to tell the SCE service that the provided value is OK to use in the 10830 * contexts specified by contextEnum. It must return an object that will be accepted by 10831 * getTrusted() for a compatible contextEnum and return this value. 10832 * 10833 * - valueOf(value) 10834 * For values that were not produced by trustAs(), return them as is. For values that were 10835 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 10836 * trustAs is wrapping the given values into some type, this operation unwraps it when given 10837 * such a value. 10838 * 10839 * - getTrusted(contextEnum, value) 10840 * This function should return the a value that is safe to use in the context specified by 10841 * contextEnum or throw and exception otherwise. 10842 * 10843 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be opaque 10844 * or wrapped in some holder object. That happens to be an implementation detail. For instance, 10845 * an implementation could maintain a registry of all trusted objects by context. In such a case, 10846 * trustAs() would return the same object that was passed in. getTrusted() would return the same 10847 * object passed in if it was found in the registry under a compatible context or throw an 10848 * exception otherwise. An implementation might only wrap values some of the time based on 10849 * some criteria. getTrusted() might return a value and not throw an exception for special 10850 * constants or objects even if not wrapped. All such implementations fulfill this contract. 10851 * 10852 * 10853 * A note on the inheritance model for SCE contexts 10854 * ------------------------------------------------ 10855 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 10856 * is purely an implementation details. 10857 * 10858 * The contract is simply this: 10859 * 10860 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 10861 * will also succeed. 10862 * 10863 * Inheritance happens to capture this in a natural way. In some future, we
10864 * may not use inheritance anymore. That is OK because no code outside of 10865 * sce.js and sceSpecs.js would need to be aware of this detail. 10866 */ 10867 10868 this.$get = ['$parse', '$document', '$sceDelegate', function( 10869 $parse, $document, $sceDelegate) { 10870 // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows 10871 // the "expression(javascript expression)" syntax which is insecure. 10872 if (enabled && msie) { 10873 var documentMode = $document[0].documentMode; 10874 if (documentMode !== undefined && documentMode < 8) { 10875 throw $sceMinErr('iequirks', 10876 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 10877 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 10878 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 10879 } 10880 } 10881 10882 var sce = copy(SCE_CONTEXTS); 10883 10884 /** 10885 * @ngdoc function 10886 * @name ng.sce#isEnabled 10887 * @methodOf ng.$sce 10888 * @function 10889 * 10890 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 10891 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 10892 * 10893 * @description 10894 * Returns a boolean indicating if SCE is enabled. 10895 */ 10896 sce.isEnabled = function () { 10897 return enabled; 10898 }; 10899 sce.trustAs = $sceDelegate.trustAs; 10900 sce.getTrusted = $sceDelegate.getTrusted; 10901 sce.valueOf = $sceDelegate.valueOf; 10902 10903 if (!enabled) { 10904 sce.trustAs = sce.getTrusted = function(type, value) { return value; }, 10905 sce.valueOf = identity 10906 } 10907 10908 /** 10909 * @ngdoc method 10910 * @name ng.$sce#parse 10911 * @methodOf ng.$sce 10912 * 10913 * @description 10914 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 10915 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 10916 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 10917 * *result*)} 10918 * 10919 * @param {string} type The kind of SCE context in which this result will be used. 10920 * @param {string} expression String expression to compile. 10921 * @returns {function(context, locals)} a function which represents the compiled expression: 10922 * 10923 * * `context` ââ¬â `{object}` ââ¬â an object against which any expressions embedded in the strings 10924 * are evaluated against (typically a scope object). 10925 * * `locals` ââ¬â `{object=}` ââ¬â local variables context object, useful for overriding values in 10926 * `context`. 10927 */ 10928 sce.parseAs = function sceParseAs(type, expr) { 10929 var parsed = $parse(expr); 10930 if (parsed.literal && parsed.constant) { 10931 return parsed; 10932 } else { 10933 return function sceParseAsTrusted(self, locals) { 10934 return sce.getTrusted(type, parsed(self, locals)); 10935 } 10936 } 10937 }; 10938 10939 /** 10940 * @ngdoc method 10941 * @name ng.$sce#trustAs 10942 * @methodOf ng.$sce 10943 * 10944 * @description 10945 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns an object 10946 * that is trusted by angular for use in specified strict contextual escaping contexts (such as 10947 * ng-html-bind-unsafe, ng-include, any src attribute interpolation, any dom event binding 10948 * attribute interpolation such as for onclick, etc.) that uses the provided value. See * 10949 * {@link ng.$sce $sce} for enabling strict contextual escaping. 10950 * 10951 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 10952 * resource_url, html, js and css. 10953 * @param {*} value The value that that should be considered trusted/safe. 10954 * @returns {*} A value that can be used to stand in for the provided `value` in places 10955 * where Angular expects a $sce.trustAs() return value. 10956 */ 10957 10958 /** 10959 * @ngdoc method 10960 * @name ng.$sce#trustAsHtml 10961 * @methodOf ng.$sce 10962 * 10963 * @description 10964 * Shorthand method. `$sce.trustAsHtml(value)` ââ â {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 10965 * 10966 * @param {*} value The value to trustAs. 10967 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 10968 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 10969 * only accept expressions that are either literal constants or are the 10970 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 10971 */ 10972 10973 /** 10974 * @ngdoc method 10975 * @name ng.$sce#trustAsUrl 10976 * @methodOf ng.$sce 10977 *
10978 * @description 10979 * Shorthand method. `$sce.trustAsUrl(value)` ââ â {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 10980 * 10981 * @param {*} value The value to trustAs. 10982 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 10983 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 10984 * only accept expressions that are either literal constants or are the 10985 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 10986 */ 10987 10988 /** 10989 * @ngdoc method 10990 * @name ng.$sce#trustAsResourceUrl 10991 * @methodOf ng.$sce 10992 * 10993 * @description 10994 * Shorthand method. `$sce.trustAsResourceUrl(value)` ââ â {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 10995 * 10996 * @param {*} value The value to trustAs. 10997 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 10998 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 10999 * only accept expressions that are either literal constants or are the return 11000 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 11001 */ 11002 11003 /** 11004 * @ngdoc method 11005 * @name ng.$sce#trustAsJs 11006 * @methodOf ng.$sce 11007 * 11008 * @description 11009 * Shorthand method. `$sce.trustAsJs(value)` ââ â {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 11010 * 11011 * @param {*} value The value to trustAs. 11012 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 11013 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 11014 * only accept expressions that are either literal constants or are the 11015 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 11016 */ 11017 11018 /** 11019 * @ngdoc method 11020 * @name ng.$sce#getTrusted 11021 * @methodOf ng.$sce 11022 * 11023 * @description 11024 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, takes 11025 * the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the originally supplied 11026 * value if the queried context type is a supertype of the created type. If this condition 11027 * isn't satisfied, throws an exception. 11028 * 11029 * @param {string} type The kind of context in which this value is to be used. 11030 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} call. 11031 * @returns {*} The value the was originally provided to {@link ng.$sce#trustAs `$sce.trustAs`} if 11032 * valid in this context. Otherwise, throws an exception. 11033 */ 11034 11035 /** 11036 * @ngdoc method 11037 * @name ng.$sce#getTrustedHtml 11038 * @methodOf ng.$sce 11039 * 11040 * @description 11041 * Shorthand method. `$sce.getTrustedHtml(value)` ââ â {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 11042 * 11043 * @param {*} value The value to pass to `$sce.getTrusted`. 11044 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 11045 */ 11046 11047 /** 11048 * @ngdoc method 11049 * @name ng.$sce#getTrustedCss 11050 * @methodOf ng.$sce 11051 * 11052 * @description 11053 * Shorthand method. `$sce.getTrustedCss(value)` ââ â {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 11054 * 11055 * @param {*} value The value to pass to `$sce.getTrusted`. 11056 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 11057 */ 11058 11059 /** 11060 * @ngdoc method 11061 * @name ng.$sce#getTrustedUrl 11062 * @methodOf ng.$sce 11063 * 11064 * @description 11065 * Shorthand method. `$sce.getTrustedUrl(value)` ââ â {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 11066 * 11067 * @param {*} value The value to pass to `$sce.getTrusted`. 11068 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 11069 */ 11070 11071 /** 11072 * @ngdoc method 11073 * @name ng.$sce#getTrustedResourceUrl 11074 * @methodOf ng.$sce 11075 * 11076 * @description 11077 * Shorthand method. `$sce.getTrustedResourceUrl(value)` ââ â {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 11078 * 11079 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 11080 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 11081 */ 11082 11083 /** 11084 * @ngdoc method 11085 * @name ng.$sce#getTrustedJs 11086 * @methodOf ng.$sce 11087 *
11088 * @description 11089 * Shorthand method. `$sce.getTrustedJs(value)` ââ â {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 11090 * 11091 * @param {*} value The value to pass to `$sce.getTrusted`. 11092 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 11093 */ 11094 11095 /** 11096 * @ngdoc method 11097 * @name ng.$sce#parseAsHtml 11098 * @methodOf ng.$sce 11099 * 11100 * @description 11101 * Shorthand method. `$sce.parseAsHtml(expression string)` ââ â {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} 11102 * 11103 * @param {string} expression String expression to compile. 11104 * @returns {function(context, locals)} a function which represents the compiled expression: 11105 * 11106 * * `context` ââ¬â `{object}` ââ¬â an object against which any expressions embedded in the strings 11107 * are evaluated against (typically a scope object). 11108 * * `locals` ââ¬â `{object=}` ââ¬â local variables context object, useful for overriding values in 11109 * `context`. 11110 */ 11111 11112 /** 11113 * @ngdoc method 11114 * @name ng.$sce#parseAsCss 11115 * @methodOf ng.$sce 11116 * 11117 * @description 11118 * Shorthand method. `$sce.parseAsCss(value)` ââ â {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} 11119 * 11120 * @param {string} expression String expression to compile. 11121 * @returns {function(context, locals)} a function which represents the compiled expression: 11122 * 11123 * * `context` ââ¬â `{object}` ââ¬â an object against which any expressions embedded in the strings 11124 * are evaluated against (typically a scope object). 11125 * * `locals` ââ¬â `{object=}` ââ¬â local variables context object, useful for overriding values in 11126 * `context`. 11127 */ 11128 11129 /** 11130 * @ngdoc method 11131 * @name ng.$sce#parseAsUrl 11132 * @methodOf ng.$sce 11133 * 11134 * @description 11135 * Shorthand method. `$sce.parseAsUrl(value)` ââ â {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} 11136 * 11137 * @param {string} expression String expression to compile. 11138 * @returns {function(context, locals)} a function which represents the compiled expression: 11139 * 11140 * * `context` ââ¬â `{object}` ââ¬â an object against which any expressions embedded in the strings 11141 * are evaluated against (typically a scope object). 11142 * * `locals` ââ¬â `{object=}` ââ¬â local variables context object, useful for overriding values in 11143 * `context`. 11144 */ 11145 11146 /** 11147 * @ngdoc method 11148 * @name ng.$sce#parseAsResourceUrl 11149 * @methodOf ng.$sce 11150 * 11151 * @description 11152 * Shorthand method. `$sce.parseAsResourceUrl(value)` ââ â {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} 11153 * 11154 * @param {string} expression String expression to compile. 11155 * @returns {function(context, locals)} a function which represents the compiled expression: 11156 * 11157 * * `context` ââ¬â `{object}` ââ¬â an object against which any expressions embedded in the strings 11158 * are evaluated against (typically a scope object). 11159 * * `locals` ââ¬â `{object=}` ââ¬â local variables context object, useful for overriding values in 11160 * `context`. 11161 */ 11162 11163 /** 11164 * @ngdoc method 11165 * @name ng.$sce#parseAsJs 11166 * @methodOf ng.$sce 11167 * 11168 * @description 11169 * Shorthand method. `$sce.parseAsJs(value)` ââ â {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} 11170 * 11171 * @param {string} expression String expression to compile. 11172 * @returns {function(context, locals)} a function which represents the compiled expression: 11173 * 11174 * * `context` ââ¬â `{object}` ââ¬â an object against which any expressions embedded in the strings 11175 * are evaluated against (typically a scope object). 11176 * * `locals` ââ¬â `{object=}` ââ¬â local variables context object, useful for overriding values in 11177 * `context`. 11178 */ 11179 11180 // Shorthand delegations. 11181 var parse = sce.parseAs, 11182 getTrusted = sce.getTrusted, 11183 trustAs = sce.trustAs; 11184 11185 angular.forEach(SCE_CONTEXTS, function (enumValue, name) { 11186 var lName = lowercase(name); 11187 sce[camelCase("parse_as_" + lName)] = function (expr) { 11188 return parse(enumValue, expr); 11189 } 11190 sce[camelCase("get_trusted_" + lName)] = function (value) { 11191 return getTrusted(enumValue, value); 11192 } 11193 sce[camelCase("trust_as_" + lName)] = function (value) { 11194 return trustAs(enumValue, value); 11195 } 11196 }); 11197 11198 return sce; 11199 }]; 11200} 11201 11202/** 11203 * !!! This is an undocumented "private" service !!! 11204 * 11205 * @name ng.$sniffer 11206 * @requires $window 11207 * @requires $document 11208 * 11209 * @property {boolean} history Does the browser support html5 history api ? 11210 * @property {boolean} hashchange Does the browser support hashchange event ? 11211 * @property {boolean} transitions Does the browser support CSS transition events ? 11212 * @property {boolean} animations Does the browser support CSS animation events ? 11213 * 11214 * @description 11215 * This is very simple implementation of testing browser's features. 11216 */ 11217function $SnifferProvider() { 11218 this.$get = ['$window', '$document', function($window, $document) { 11219 var eventSupport = {}, 11220 android = int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 11221 document = $document[0] || {}, 11222 vendorPrefix, 11223 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, 11224 bodyStyle = document.body && document.body.style, 11225 transitions = false, 11226 animations = false, 11227 match; 11228 11229 if (bodyStyle) { 11230 for(var prop in bodyStyle) {
11231 if(match = vendorRegex.exec(prop)) { 11232 vendorPrefix = match[0]; 11233 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 11234 break; 11235 } 11236 } 11237 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 11238 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 11239 11240 if (android && (!transitions||!animations)) { 11241 transitions = isString(document.body.style.webkitTransition); 11242 animations = isString(document.body.style.webkitAnimation); 11243 } 11244 } 11245 11246 11247 return { 11248 // Android has history.pushState, but it does not update location correctly 11249 // so let's not use the history API at all. 11250 // http://code.google.com/p/android/issues/detail?id=17471 11251 // https://github.com/angular/angular.js/issues/904 11252 history: !!($window.history && $window.history.pushState && !(android < 4)), 11253 hashchange: 'onhashchange' in $window && 11254 // IE8 compatible mode lies 11255 (!document.documentMode || document.documentMode > 7), 11256 hasEvent: function(event) { 11257 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 11258 // it. In particular the event is not fired when backspace or delete key are pressed or 11259 // when cut operation is performed. 11260 if (event == 'input' && msie == 9) return false; 11261 11262 if (isUndefined(eventSupport[event])) { 11263 var divElm = document.createElement('div'); 11264 eventSupport[event] = 'on' + event in divElm; 11265 } 11266 11267 return eventSupport[event]; 11268 }, 11269 csp: document.securityPolicy ? document.securityPolicy.isActive : false, 11270 vendorPrefix: vendorPrefix, 11271 transitions : transitions, 11272 animations : animations 11273 }; 11274 }]; 11275} 11276 11277function $TimeoutProvider() { 11278 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 11279 function($rootScope, $browser, $q, $exceptionHandler) { 11280 var deferreds = {}; 11281 11282 11283 /** 11284 * @ngdoc function 11285 * @name ng.$timeout 11286 * @requires $browser 11287 * 11288 * @description 11289 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 11290 * block and delegates any exceptions to 11291 * {@link ng.$exceptionHandler $exceptionHandler} service. 11292 * 11293 * The return value of registering a timeout function is a promise, which will be resolved when 11294 * the timeout is reached and the timeout function is executed. 11295 * 11296 * To cancel a timeout request, call `$timeout.cancel(promise)`. 11297 * 11298 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 11299 * synchronously flush the queue of deferred functions. 11300 * 11301 * @param {function()} fn A function, whose execution should be delayed. 11302 * @param {number=} [delay=0] Delay in milliseconds. 11303 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 11304 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 11305 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 11306 * promise will be resolved with is the return value of the `fn` function. 11307 */ 11308 function timeout(fn, delay, invokeApply) { 11309 var deferred = $q.defer(), 11310 promise = deferred.promise, 11311 skipApply = (isDefined(invokeApply) && !invokeApply), 11312 timeoutId, cleanup; 11313 11314 timeoutId = $browser.defer(function() { 11315 try { 11316 deferred.resolve(fn()); 11317 } catch(e) { 11318 deferred.reject(e); 11319 $exceptionHandler(e); 11320 } 11321 11322 if (!skipApply) $rootScope.$apply(); 11323 }, delay); 11324 11325 cleanup = function() { 11326 delete deferreds[promise.$$timeoutId]; 11327 }; 11328 11329 promise.$$timeoutId = timeoutId; 11330 deferreds[timeoutId] = deferred; 11331 promise.then(cleanup, cleanup); 11332 11333 return promise; 11334 } 11335 11336 11337 /** 11338 * @ngdoc function 11339 * @name ng.$timeout#cancel 11340 * @methodOf ng.$timeout 11341 *
11342 * @description 11343 * Cancels a task associated with the `promise`. As a result of this, the promise will be 11344 * resolved with a rejection. 11345 * 11346 * @param {Promise=} promise Promise returned by the `$timeout` function. 11347 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 11348 * canceled. 11349 */ 11350 timeout.cancel = function(promise) { 11351 if (promise && promise.$$timeoutId in deferreds) { 11352 deferreds[promise.$$timeoutId].reject('canceled'); 11353 return $browser.defer.cancel(promise.$$timeoutId); 11354 } 11355 return false; 11356 }; 11357 11358 return timeout; 11359 }]; 11360} 11361 11362function $$UrlUtilsProvider() { 11363 this.$get = [function() { 11364 var urlParsingNode = document.createElement("a"), 11365 // NOTE: The usage of window and document instead of $window and $document here is 11366 // deliberate. This service depends on the specific behavior of anchor nodes created by the 11367 // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 11368 // cause us to break tests. In addition, when the browser resolves a URL for XHR, it 11369 // doesn't know about mocked locations and resolves URLs to the real document - which is 11370 // exactly the behavior needed here. There is little value is mocking these our for this 11371 // service. 11372 originUrl = resolve(window.location.href, true); 11373 11374 /** 11375 * @description 11376 * Normalizes and optionally parses a URL. 11377 * 11378 * NOTE: This is a private service. The API is subject to change unpredictably in any commit. 11379 * 11380 * Implementation Notes for non-IE browsers 11381 * ---------------------------------------- 11382 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 11383 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 11384 * URL will be resolved into an absolute URL in the context of the application document. 11385 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 11386 * properties are all populated to reflect the normalized URL. This approach has wide 11387 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 11388 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 11389 * 11390 * Implementation Notes for IE 11391 * --------------------------- 11392 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 11393 * browsers. However, the parsed components will not be set if the URL assigned did not specify 11394 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 11395 * work around that by performing the parsing in a 2nd step by taking a previously normalized 11396 * URL (e.g. by assining to a.href) and assigning it a.href again. This correctly populates the 11397 * properties such as protocol, hostname, port, etc. 11398 * 11399 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 11400 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 11401 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 11402 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 11403 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 11404 * method and IE < 8 is unsupported. 11405 * 11406 * References: 11407 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 11408 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 11409 * http://url.spec.whatwg.org/#urlutils 11410 * https://github.com/angular/angular.js/pull/2902 11411 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 11412 * 11413 * @param {string} url The URL to be parsed. 11414 * @param {boolean=} parse When true, returns an object for the parsed URL. Otherwise, returns 11415 * a single string that is the normalized URL. 11416 * @returns {object|string} When parse is true, returns the normalized URL as a string. 11417 * Otherwise, returns an object with the following members. 11418 * 11419 * | member name | Description | 11420 * |===============|================| 11421 * | href | A normalized version of the provided URL if it was not an absolute URL | 11422 * | protocol | The protocol including the trailing colon | 11423 * | host | The host and port (if the port is non-default) of the normalizedUrl | 11424 * 11425 * These fields from the UrlUtils interface are currently not needed and hence not returned. 11426 * 11427 * | member name | Description | 11428 * |===============|================| 11429 * | hostname | The host without the port of the normalizedUrl | 11430 * | pathname | The path following the host in the normalizedUrl | 11431 * | hash | The URL hash if present | 11432 * | search | The query string | 11433 * 11434 */ 11435 function resolve(url, parse) { 11436 var href = url; 11437 if (msie) { 11438 // Normalize before parse. Refer Implementation Notes on why this is 11439 // done in two steps on IE. 11440 urlParsingNode.setAttribute("href", href); 11441 href = urlParsingNode.href; 11442 } 11443 urlParsingNode.setAttribute('href', href); 11444 11445 if (!parse) { 11446 return urlParsingNode.href; 11447 } 11448 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 11449 return { 11450 href: urlParsingNode.href, 11451 protocol: urlParsingNode.protocol, 11452 host: urlParsingNode.host 11453 // Currently unused and hence commented out. 11454 // hostname: urlParsingNode.hostname, 11455 // port: urlParsingNode.port, 11456 // pathname: urlParsingNode.pathname, 11457 // hash: urlParsingNode.hash, 11458 // search: urlParsingNode.search 11459 }; 11460 } 11461 11462 return { 11463 resolve: resolve, 11464 /** 11465 * Parse a request URL and determine whether this is a same-origin request as the application document. 11466 * 11467 * @param {string|object} requestUrl The url of the request as a string that will be resolved 11468 * or a parsed URL object. 11469 * @returns {boolean} Whether the request is for the same origin as the application document. 11470 */ 11471 isSameOrigin: function isSameOrigin(requestUrl) { 11472 var parsed = (typeof requestUrl === 'string') ? resolve(requestUrl, true) : requestUrl; 11473 return (parsed.protocol === originUrl.protocol && 11474 parsed.host === originUrl.host); 11475 } 11476 }; 11477 }]; 11478} 11479 11480/** 11481 * @ngdoc object 11482 * @name ng.$window 11483 * 11484 * @description 11485 * A reference to the browser's `window` object. While `window` 11486 * is globally available in JavaScript, it causes testability problems, because 11487 * it is a global variable. In angular we always refer to it through the 11488 * `$window` service, so it may be overridden, removed or mocked for testing. 11489 * 11490 * Expressions, like the one defined for the `ngClick` directive in the example 11491 * below, are evaluated with respect to the current scope. Therefore, there is 11492 * no risk of inadvertently coding in a dependency on a global value in such an 11493 * expression. 11494 * 11495 * @example 11496 <doc:example> 11497 <doc:source> 11498 <script> 11499 function Ctrl($scope, $window) { 11500 $scope.$window = $window; 11501 $scope.greeting = 'Hello, World!'; 11502 } 11503 </script> 11504 <div ng-controller="Ctrl"> 11505 <input type="text" ng-model="greeting" /> 11506 <button ng-click="$window.alert(greeting)">ALERT</button> 11507 </div>
11508 </doc:source> 11509 <doc:scenario> 11510 it('should display the greeting in the input box', function() { 11511 input('greeting').enter('Hello, E2E Tests'); 11512 // If we click the button it will block the test runner 11513 // element(':button').click(); 11514 }); 11515 </doc:scenario> 11516 </doc:example> 11517 */ 11518function $WindowProvider(){ 11519 this.$get = valueFn(window); 11520} 11521 11522/** 11523 * @ngdoc object 11524 * @name ng.$filterProvider 11525 * @description 11526 * 11527 * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To 11528 * achieve this a filter definition consists of a factory function which is annotated with dependencies and is 11529 * responsible for creating a filter function. 11530 * 11531 * <pre> 11532 * // Filter registration 11533 * function MyModule($provide, $filterProvider) { 11534 * // create a service to demonstrate injection (not always needed) 11535 * $provide.value('greet', function(name){ 11536 * return 'Hello ' + name + '!'; 11537 * }); 11538 * 11539 * // register a filter factory which uses the 11540 * // greet service to demonstrate DI. 11541 * $filterProvider.register('greet', function(greet){ 11542 * // return the filter function which uses the greet service 11543 * // to generate salutation 11544 * return function(text) { 11545 * // filters need to be forgiving so check input validity 11546 * return text && greet(text) || text; 11547 * }; 11548 * }); 11549 * } 11550 * </pre> 11551 * 11552 * The filter function is registered with the `$injector` under the filter name suffix with `Filter`. 11553 * <pre> 11554 * it('should be the same instance', inject( 11555 * function($filterProvider) { 11556 * $filterProvider.register('reverse', function(){ 11557 * return ...; 11558 * }); 11559 * }, 11560 * function($filter, reverseFilter) { 11561 * expect($filter('reverse')).toBe(reverseFilter); 11562 * }); 11563 * </pre> 11564 * 11565 * 11566 * For more information about how angular filters work, and how to create your own filters, see 11567 * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer 11568 * Guide. 11569 */ 11570/** 11571 * @ngdoc method 11572 * @name ng.$filterProvider#register 11573 * @methodOf ng.$filterProvider 11574 * @description 11575 * Register filter factory function. 11576 * 11577 * @param {String} name Name of the filter. 11578 * @param {function} fn The filter factory function which is injectable. 11579 */ 11580 11581 11582/** 11583 * @ngdoc function 11584 * @name ng.$filter 11585 * @function 11586 * @description 11587 * Filters are used for formatting data displayed to the user. 11588 * 11589 * The general syntax in templates is as follows: 11590 * 11591 * {{ expression [| filter_name[:parameter_value] ... ] }} 11592 * 11593 * @param {String} name Name of the filter function to retrieve 11594 * @return {Function} the filter function 11595 */ 11596$FilterProvider.$inject = ['$provide']; 11597function $FilterProvider($provide) { 11598 var suffix = 'Filter'; 11599 11600 function register(name, factory) { 11601 return $provide.factory(name + suffix, factory); 11602 } 11603 this.register = register; 11604 11605 this.$get = ['$injector', function($injector) { 11606 return function(name) { 11607 return $injector.get(name + suffix); 11608 } 11609 }]; 11610 11611 //////////////////////////////////////// 11612 11613 register('currency', currencyFilter); 11614 register('date', dateFilter); 11615 register('filter', filterFilter); 11616 register('json', jsonFilter); 11617 register('limitTo', limitToFilter); 11618 register('lowercase', lowercaseFilter); 11619 register('number', numberFilter); 11620 register('orderBy', orderByFilter); 11621 register('uppercase', uppercaseFilter); 11622} 11623 11624/** 11625 * @ngdoc filter 11626 * @name ng.filter:filter 11627 * @function 11628 * 11629 * @description 11630 * Selects a subset of items from `array` and returns it as a new array. 11631 * 11632 * Note: This function is used to augment the `Array` type in Angular expressions. See 11633 * {@link ng.$filter} for more information about Angular arrays. 11634 * 11635 * @param {Array} array The source array. 11636 * @param {string|Object|function()} expression The predicate to be used for selecting items from 11637 * `array`. 11638 * 11639 * Can be one of: 11640 * 11641 * - `string`: Predicate that results in a substring match using the value of `expression` 11642 * string. All strings or objects with string properties in `array` that c
11642ontain this string 11643 * will be returned. The predicate can be negated by prefixing the string with `!`. 11644 * 11645 * - `Object`: A pattern object can be used to filter specific properties on objects contained 11646 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 11647 * which have property `name` containing "M" and property `phone` containing "1". A special 11648 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 11649 * property of the object. That's equivalent to the simple substring match with a `string` 11650 * as described above. 11651 * 11652 * - `function`: A predicate function can be used to write arbitrary filters. The function is 11653 * called for each element of `array`. The final result is an array of those elements that 11654 * the predicate returned true for. 11655 * 11656 * @param {function(expected, actual)|true|undefined} comparator Comparator which is used in 11657 * determining if the expected value (from the filter expression) and actual value (from 11658 * the object in the array) should be considered a match. 11659 * 11660 * Can be one of: 11661 * 11662 * - `function(expected, actual)`: 11663 * The function will be given the object value and the predicate value to compare and 11664 * should return true if the item should be included in filtered result. 11665 * 11666 * - `true`: A shorthand for `function(expected, actual) { return angular.equals(expected, actual)}`. 11667 * this is essentially strict comparison of expected and actual. 11668 * 11669 * - `false|undefined`: A short hand for a function which will look for a substring match in case 11670 * insensitive way. 11671 * 11672 * @example 11673 <doc:example> 11674 <doc:source> 11675 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 11676 {name:'Mary', phone:'800-BIG-MARY'}, 11677 {name:'Mike', phone:'555-4321'}, 11678 {name:'Adam', phone:'555-5678'}, 11679 {name:'Julie', phone:'555-8765'}, 11680 {name:'Juliette', phone:'555-5678'}]"></div> 11681 11682 Search: <input ng-model="searchText"> 11683 <table id="searchTextResults"> 11684 <tr><th>Name</th><th>Phone</th></tr> 11685 <tr ng-repeat="friend in friends | filter:searchText"> 11686 <td>{{friend.name}}</td> 11687 <td>{{friend.phone}}</td> 11688 </tr> 11689 </table> 11690 <hr> 11691 Any: <input ng-model="search.$"> <br> 11692 Name only <input ng-model="search.name"><br> 11693 Phone only <input ng-model="search.phone"><br> 11694 Equality <input type="checkbox" ng-model="strict"><br> 11695 <table id="searchObjResults"> 11696 <tr><th>Name</th><th>Phone</th></tr> 11697 <tr ng-repeat="friend in friends | filter:search:strict"> 11698 <td>{{friend.name}}</td> 11699 <td>{{friend.phone}}</td> 11700 </tr> 11701 </table>
11702 </doc:source> 11703 <doc:scenario> 11704 it('should search across all fields when filtering with a string', function() { 11705 input('searchText').enter('m'); 11706 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). 11707 toEqual(['Mary', 'Mike', 'Adam']); 11708 11709 input('searchText').enter('76'); 11710 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). 11711 toEqual(['John', 'Julie']); 11712 }); 11713 11714 it('should search in specific fields when filtering with a predicate object', function() { 11715 input('search.$').enter('i'); 11716 expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). 11717 toEqual(['Mary', 'Mike', 'Julie', 'Juliette']); 11718 }); 11719 it('should use a equal comparison when comparator is true', function() { 11720 input('search.name').enter('Julie'); 11721 input('strict').check(); 11722 expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). 11723 toEqual(['Julie']); 11724 }); 11725 </doc:scenario> 11726 </doc:example> 11727 */ 11728function filterFilter() { 11729 return function(array, expression, comperator) { 11730 if (!isArray(array)) return array; 11731 var predicates = []; 11732 predicates.check = function(value) { 11733 for (var j = 0; j < predicates.length; j++) { 11734 if(!predicates[j](value)) { 11735 return false; 11736 } 11737 } 11738 return true; 11739 }; 11740 switch(typeof comperator) { 11741 case "function": 11742 break; 11743 case "boolean": 11744 if(comperator == true) { 11745 comperator = function(obj, text) { 11746 return angular.equals(obj, text); 11747 } 11748 break; 11749 } 11750 default: 11751 comperator = function(obj, text) { 11752 text = (''+text).toLowerCase(); 11753 return (''+obj).toLowerCase().indexOf(text) > -1 11754 }; 11755 } 11756 var search = function(obj, text){ 11757 if (typeof text == 'string' && text.charAt(0) === '!') { 11758 return !search(obj, text.substr(1)); 11759 } 11760 switch (typeof obj) { 11761 case "boolean": 11762 case "number": 11763 case "string": 11764 return comperator(obj, text); 11765 case "object": 11766 switch (typeof text) { 11767 case "object": 11768 return comperator(obj, text); 11769 break; 11770 default: 11771 for ( var objKey in obj) { 11772 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { 11773 return true; 11774 } 11775 } 11776 break; 11777 } 11778 return false; 11779 case "array": 11780 for ( var i = 0; i < obj.length; i++) { 11781 if (search(obj[i], text)) { 11782 return true; 11783 } 11784 } 11785 return false; 11786 default: 11787 return false; 11788 } 11789 }; 11790 switch (typeof expression) { 11791 case "boolean": 11792 case "number": 11793 case "string": 11794 expression = {$:expression}; 11795 case "object": 11796 for (var key in expression) { 11797 if (key == '$') { 11798 (function() { 11799 if (!expression[key]) return; 11800 var path = key 11801 predicates.push(function(value) { 11802 return search(value, expression[path]); 11803 }); 11804 })(); 11805 } else { 11806 (function() { 11807 if (!expression[key]) return; 11808 var path = key; 11809 predicates.push(function(value) { 11810 return search(getter(value,path), expression[path]); 11811 }); 11812 })(); 11813 } 11814 } 11815 break; 11816 case 'function': 11817 predicates.push(expression); 11818 break; 11819 default: 11820 return array; 11821 } 11822 var filtered = []; 11823 for ( var j = 0; j < array.length; j++) { 11824 var value = array[j]; 11825 if (predicates.check(value)) { 11826 filtered.push(value); 11827 } 11828 } 11829 return filtered; 11830 } 11831} 11832 11833/** 11834 * @ngdoc filter 11835 * @name ng.filter:currency 11836 * @function 11837 * 11838 * @description 11839 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 11840 * symbol for current locale is used. 11841 * 11842 * @param {number} amount Input to filter. 11843 * @param {string=} symbol Currency symbol or identifier to be displayed. 11844 * @returns {string} Formatted number. 11845 * 11846 * 11847 * @example 11848 <doc:example> 11849 <doc:source> 11850 <script> 11851 function Ctrl($scope) { 11852 $scope.amount = 1234.56; 11853 } 11854 </script> 11855 <div ng-controller="Ctrl"> 11856 <input type="number" ng-model="amount"> <br> 11857 default currency symbol ($): {{amount | currency}}<br> 11858 custom currency identifier (USD$): {{amount | currency:"USD$"}} 11859 </div>
11860 </doc:source> 11861 <doc:scenario> 11862 it('should init with 1234.56', function() { 11863 expect(binding('amount | currency')).toBe('$1,234.56'); 11864 expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56'); 11865 }); 11866 it('should update', function() { 11867 input('amount').enter('-1234'); 11868 expect(binding('amount | currency')).toBe('($1,234.00)'); 11869 expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)'); 11870 }); 11871 </doc:scenario> 11872 </doc:example> 11873 */ 11874currencyFilter.$inject = ['$locale']; 11875function currencyFilter($locale) { 11876 var formats = $locale.NUMBER_FORMATS; 11877 return function(amount, currencySymbol){ 11878 if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; 11879 return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). 11880 replace(/\u00A4/g, currencySymbol); 11881 }; 11882} 11883 11884/** 11885 * @ngdoc filter 11886 * @name ng.filter:number 11887 * @function 11888 * 11889 * @description 11890 * Formats a number as text. 11891 * 11892 * If the input is not a number an empty string is returned. 11893 * 11894 * @param {number|string} number Number to format. 11895 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 11896 * If this is not provided then the fraction size is computed from the current locale's number 11897 * formatting pattern. In the case of the default locale, it will be 3. 11898 * @returns {string} Number rounded to decimalPlaces and places a ââ¬Å,ââ¬Â after each third digit. 11899 * 11900 * @example 11901 <doc:example> 11902 <doc:source> 11903 <script> 11904 function Ctrl($scope) { 11905 $scope.val = 1234.56789; 11906 } 11907 </script> 11908 <div ng-controller="Ctrl"> 11909 Enter number: <input ng-model='val'><br> 11910 Default formatting: {{val | number}}<br> 11911 No fractions: {{val | number:0}}<br> 11912 Negative number: {{-val | number:4}} 11913 </div> 11914 </doc:source> 11915 <doc:scenario> 11916 it('should format numbers', function() { 11917 expect(binding('val | number')).toBe('1,234.568'); 11918 expect(binding('val | number:0')).toBe('1,235'); 11919 expect(binding('-val | number:4')).toBe('-1,234.5679'); 11920 }); 11921 11922 it('should update', function() { 11923 input('val').enter('3374.333'); 11924 expect(binding('val | number')).toBe('3,374.333'); 11925 expect(binding('val | number:0')).toBe('3,374'); 11926 expect(binding('-val | number:4')).toBe('-3,374.3330'); 11927 }); 11928 </doc:scenario> 11929 </doc:example> 11930 */ 11931 11932 11933numberFilter.$inject = ['$locale']; 11934function numberFilter($locale) { 11935 var formats = $locale.NUMBER_FORMATS; 11936 return function(number, fractionSize) { 11937 return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 11938 fractionSize); 11939 }; 11940} 11941 11942var DECIMAL_SEP = '.'; 11943function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 11944 if (isNaN(number) || !isFinite(number)) return ''; 11945 11946 var isNegative = number < 0; 11947 number = Math.abs(number); 11948 var numStr = number + '', 11949 formatedText = '', 11950 parts = []; 11951 11952 var hasExponent = false; 11953 if (numStr.indexOf('e') !== -1) { 11954 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 11955 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 11956 numStr = '0'; 11957 } else { 11958 formatedText = numStr; 11959 hasExponent = true; 11960 } 11961 } 11962 11963 if (!hasExponent) { 11964 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 11965 11966 // determine fractionSize if it is not specified 11967 if (isUndefined(fractionSize)) { 11968 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 11969 } 11970 11971 var pow = Math.pow(10, fractionSize); 11972 number = Math.round(number * pow) / pow; 11973 var fraction = ('' + number).split(DECIMAL_SEP); 11974 var whole = fraction[0]; 11975 fraction = fraction[1] || ''; 11976 11977 var pos = 0, 11978 lgroup = pattern.lgSize, 11979 group = pattern.gSize; 11980 11981 if (whole.length >= (lgroup + group)) { 11982 pos = whole.length - lgroup; 11983 for (var i = 0; i < pos; i++) { 11984 if ((pos - i)%group === 0 && i !== 0) { 11985 formatedText += groupSep; 11986 } 11987 formatedText += whole.charAt(i); 11988 } 11989 } 11990 11991 for (i = pos; i < whole.length; i++) { 11992 if ((whole.length - i)%lgroup === 0 && i !== 0) { 11993 formatedText += groupSep; 11994 } 11995 formatedText += whole.charAt(i); 11996 } 11997 11998 // format fraction part. 11999 while(fraction.length < fractionSize) { 12000 fraction += '0'; 12001 } 12002 12003 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 12004 } else { 12005 12006 if (fractionSize > 0 && number > -1 && number < 1) { 12007 formatedText = number.toFixed(fractionSize); 12008 } 12009 } 12010 12011 parts.push(isNegative ? pattern.negPre : pattern.posPre); 12012 parts.push(formatedText); 12013 parts.push(isNegative ? pattern.negSuf : pattern.posSuf); 12014 return parts.join(''); 12015} 12016 12017function padNumber(num, digits, trim) { 12018 var neg = ''; 12019 if (num < 0) { 12020 neg = '-'; 12021 num = -num; 12022 } 12023 num = '' + num; 12024 while(num.length < digits) num = '0' + num; 12025 if (trim) 12026 num = num.substr(num.length - digits); 12027 return neg + num; 12028} 12029 12030 12031function dateGetter(name, size, offset, trim) { 12032 offset = offset || 0; 12033 return function(date) { 12034 var value = date['get' + name](); 12035 if (offset > 0 || value > -offset) 12036 value += offset; 12037 if (value === 0 && offset == -12 ) value = 12; 12038 return padNumber(value, size, trim); 12039 }; 12040} 12041 12042function dateStrGetter(name, shortForm) { 12043 return function(date, formats) { 12044 var value = date['get' + name](); 12045 var get = uppercase(shortForm ? ('SHORT' + name) : name); 12046 12047 return formats[get][value]; 12048 }; 12049} 12050 12051function timeZoneGetter(date) { 12052 var zone = -1 * date.getTimezoneOffset(); 12053 var paddedZone = (zone >= 0) ? "+" : ""; 12054 12055 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 12056 padNumber(Math.abs(zone % 60), 2); 12057 12058 return paddedZone; 12059} 12060 12061function ampmGetter(date, formats) { 12062 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 12063} 12064 12065var DATE_FORMATS = { 12066 yyyy: dateGetter('FullYear', 4), 12067 yy: dateGetter('FullYear', 2, 0, true), 12068 y: dateGetter('FullYear', 1), 12069 MMMM: dateStrGetter('Month'),
12070 MMM: dateStrGetter('Month', true), 12071 MM: dateGetter('Month', 2, 1), 12072 M: dateGetter('Month', 1, 1), 12073 dd: dateGetter('Date', 2), 12074 d: dateGetter('Date', 1), 12075 HH: dateGetter('Hours', 2), 12076 H: dateGetter('Hours', 1), 12077 hh: dateGetter('Hours', 2, -12), 12078 h: dateGetter('Hours', 1, -12), 12079 mm: dateGetter('Minutes', 2), 12080 m: dateGetter('Minutes', 1), 12081 ss: dateGetter('Seconds', 2), 12082 s: dateGetter('Seconds', 1), 12083 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 12084 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 12085 sss: dateGetter('Milliseconds', 3), 12086 EEEE: dateStrGetter('Day'), 12087 EEE: dateStrGetter('Day', true), 12088 a: ampmGetter, 12089 Z: timeZoneGetter 12090}; 12091 12092var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, 12093 NUMBER_STRING = /^\d+$/; 12094 12095/** 12096 * @ngdoc filter 12097 * @name ng.filter:date 12098 * @function 12099 * 12100 * @description 12101 * Formats `date` to a string based on the requested `format`. 12102 * 12103 * `format` string can be composed of the following elements: 12104 * 12105 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 12106 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 12107 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 12108 * * `'MMMM'`: Month in year (January-December) 12109 * * `'MMM'`: Month in year (Jan-Dec) 12110 * * `'MM'`: Month in year, padded (01-12) 12111 * * `'M'`: Month in year (1-12) 12112 * * `'dd'`: Day in month, padded (01-31) 12113 * * `'d'`: Day in month (1-31) 12114 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 12115 * * `'EEE'`: Day in Week, (Sun-Sat) 12116 * * `'HH'`: Hour in day, padded (00-23) 12117 * * `'H'`: Hour in day (0-23) 12118 * * `'hh'`: Hour in am/pm, padded (01-12) 12119 * * `'h'`: Hour in am/pm, (1-12) 12120 * * `'mm'`: Minute in hour, padded (00-59) 12121 * * `'m'`: Minute in hour (0-59) 12122 * * `'ss'`: Second in minute, padded (00-59) 12123 * * `'s'`: Second in minute (0-59) 12124 * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) 12125 * * `'a'`: am/pm marker 12126 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 12127 * 12128 * `format` string can also be one of the following predefined 12129 * {@link guide/i18n localizable formats}: 12130 * 12131 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 12132 * (e.g. Sep 3, 2010 12:05:08 pm) 12133 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) 12134 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale 12135 * (e.g. Friday, September 3, 2010) 12136 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 12137 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 12138 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 12139 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) 12140 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) 12141 * 12142 * `format` string can contain literal values. These need to be quoted with single quotes (e.g. 12143 * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence 12144 * (e.g. `"h 'o''clock'"`). 12145 * 12146 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 12147 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its 12148 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 12149 * specified in the string input, the time is considered to be in the local timezone. 12150 * @param {string=} format Formatting rules (see Description). If not specified, 12151 * `mediumDate` is used. 12152 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 12153 * 12154 * @example 12155 <doc:example> 12156 <doc:source> 12157 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 12158 {{1288323623006 | date:'medium'}}<br> 12159 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 12160 {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
12161 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 12162 {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br> 12163 </doc:source> 12164 <doc:scenario> 12165 it('should format date', function() { 12166 expect(binding("1288323623006 | date:'medium'")). 12167 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 12168 expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). 12169 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 12170 expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). 12171 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 12172 }); 12173 </doc:scenario> 12174 </doc:example> 12175 */ 12176dateFilter.$inject = ['$locale']; 12177function dateFilter($locale) { 12178 12179 12180 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 12181 // 1 2 3 4 5 6 7 8 9 10 11 12182 function jsonStringToDate(string) { 12183 var match; 12184 if (match = string.match(R_ISO8601_STR)) { 12185 var date = new Date(0), 12186 tzHour = 0, 12187 tzMin = 0, 12188 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 12189 timeSetter = match[8] ? date.setUTCHours : date.setHours; 12190 12191 if (match[9]) { 12192 tzHour = int(match[9] + match[10]); 12193 tzMin = int(match[9] + match[11]); 12194 } 12195 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 12196 var h = int(match[4]||0) - tzHour; 12197 var m = int(match[5]||0) - tzMin 12198 var s = int(match[6]||0); 12199 var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); 12200 timeSetter.call(date, h, m, s, ms); 12201 return date; 12202 } 12203 return string; 12204 } 12205 12206 12207 return function(date, format) { 12208 var text = '', 12209 parts = [], 12210 fn, match; 12211 12212 format = format || 'mediumDate'; 12213 format = $locale.DATETIME_FORMATS[format] || format; 12214 if (isString(date)) { 12215 if (NUMBER_STRING.test(date)) { 12216 date = int(date); 12217 } else { 12218 date = jsonStringToDate(date); 12219 } 12220 } 12221 12222 if (isNumber(date)) { 12223 date = new Date(date); 12224 } 12225 12226 if (!isDate(date)) { 12227 return date; 12228 } 12229 12230 while(format) { 12231 match = DATE_FORMATS_SPLIT.exec(format); 12232 if (match) { 12233 parts = concat(parts, match, 1); 12234 format = parts.pop(); 12235 } else { 12236 parts.push(format); 12237 format = null; 12238 } 12239 } 12240 12241 forEach(parts, function(value){ 12242 fn = DATE_FORMATS[value]; 12243 text += fn ? fn(date, $locale.DATETIME_FORMATS) 12244 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 12245 }); 12246 12247 return text; 12248 }; 12249} 12250 12251 12252/** 12253 * @ngdoc filter 12254 * @name ng.filter:json 12255 * @function 12256 * 12257 * @description 12258 * Allows you to convert a JavaScript object into JSON string. 12259 * 12260 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 12261 * the binding is automatically converted to JSON. 12262 * 12263 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 12264 * @returns {string} JSON string. 12265 * 12266 * 12267 * @example: 12268 <doc:example> 12269 <doc:source> 12270 <pre>{{ {'name':'value'} | json }}</pre>
12271 </doc:source> 12272 <doc:scenario> 12273 it('should jsonify filtered objects', function() { 12274 expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/); 12275 }); 12276 </doc:scenario> 12277 </doc:example> 12278 * 12279 */ 12280function jsonFilter() { 12281 return function(object) { 12282 return toJson(object, true); 12283 }; 12284} 12285 12286 12287/** 12288 * @ngdoc filter 12289 * @name ng.filter:lowercase 12290 * @function 12291 * @description 12292 * Converts string to lowercase. 12293 * @see angular.lowercase 12294 */ 12295var lowercaseFilter = valueFn(lowercase); 12296 12297 12298/** 12299 * @ngdoc filter 12300 * @name ng.filter:uppercase 12301 * @function 12302 * @description 12303 * Converts string to uppercase. 12304 * @see angular.uppercase 12305 */ 12306var uppercaseFilter = valueFn(uppercase); 12307 12308/** 12309 * @ngdoc function 12310 * @name ng.filter:limitTo 12311 * @function 12312 * 12313 * @description 12314 * Creates a new array or string containing only a specified number of elements. The elements 12315 * are taken from either the beginning or the end of the source array or string, as specified by 12316 * the value and sign (positive or negative) of `limit`. 12317 * 12318 * Note: This function is used to augment the `Array` type in Angular expressions. See 12319 * {@link ng.$filter} for more information about Angular arrays. 12320 * 12321 * @param {Array|string} input Source array or string to be limited. 12322 * @param {string|number} limit The length of the returned array or string. If the `limit` number 12323 * is positive, `limit` number of items from the beginning of the source array/string are copied. 12324 * If the number is negative, `limit` number of items from the end of the source array/string 12325 * are copied. The `limit` will be trimmed if it exceeds `array.length` 12326 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 12327 * had less than `limit` elements. 12328 * 12329 * @example 12330 <doc:example> 12331 <doc:source> 12332 <script> 12333 function Ctrl($scope) { 12334 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 12335 $scope.letters = "abcdefghi"; 12336 $scope.numLimit = 3; 12337 $scope.letterLimit = 3; 12338 } 12339 </script> 12340 <div ng-controller="Ctrl"> 12341 Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> 12342 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 12343 Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> 12344 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 12345 </div> 12346 </doc:source> 12347 <doc:scenario> 12348 it('should limit the number array to first three items', function() { 12349 expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3'); 12350 expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3'); 12351 expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]'); 12352 expect(binding('letters | limitTo:letterLimit')).toEqual('abc'); 12353 }); 12354 12355 it('should update the output when -3 is entered', function() { 12356 input('numLimit').enter(-3); 12357 input('letterLimit').enter(-3); 12358 expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]'); 12359 expect(binding('letters | limitTo:letterLimit')).toEqual('ghi'); 12360 }); 12361 12362 it('should not exceed the maximum size of input array', function() { 12363 input('numLimit').enter(100); 12364 input('letterLimit').enter(100); 12365 expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]'); 12366 expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi'); 12367 }); 12368 </doc:scenario> 12369 </doc:example> 12370 */ 12371function limitToFilter(){ 12372 return function(input, limit) { 12373 if (!isArray(input) && !isString(input)) return input; 12374 12375 limit = int(limit); 12376 12377 if (isString(input)) { 12378 //NaN check on limit 12379 if (limit) { 12380 return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); 12381 } else { 12382 return ""; 12383 } 12384 } 12385 12386 var out = [], 12387 i, n; 12388 12389 // if abs(limit) exceeds maximum length, trim it 12390 if (limit > input.length) 12391 limit = input.length; 12392 else if (limit < -input.length) 12393 limit = -input.length; 12394 12395 if (limit > 0) { 12396 i = 0; 12397 n = limit; 12398 } else { 12399 i = input.length + limit; 12400 n = input.length; 12401 } 12402 12403 for (; i<n; i++) { 12404 out.push(input[i]); 12405 } 12406 12407 return out; 12408 } 12409} 12410 12411/** 12412 * @ngdoc function 12413 * @name ng.filter:orderBy 12414 * @function 12415 * 12416 * @description 12417 * Orders a specified `array` by the `expression` predicate. 12418 *
12419 * Note: this function is used to augment the `Array` type in Angular expressions. See 12420 * {@link ng.$filter} for more information about Angular arrays. 12421 * 12422 * @param {Array} array The array to sort. 12423 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be 12424 * used by the comparator to determine the order of elements. 12425 * 12426 * Can be one of: 12427 * 12428 * - `function`: Getter function. The result of this function will be sorted using the 12429 * `<`, `=`, `>` operator. 12430 * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' 12431 * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control 12432 * ascending or descending sort order (for example, +name or -name). 12433 * - `Array`: An array of function or string predicates. The first predicate in the array 12434 * is used for sorting, but when two items are equivalent, the next predicate is used. 12435 * 12436 * @param {boolean=} reverse Reverse the order the array. 12437 * @returns {Array} Sorted copy of the source array. 12438 * 12439 * @example 12440 <doc:example> 12441 <doc:source> 12442 <script> 12443 function Ctrl($scope) { 12444 $scope.friends = 12445 [{name:'John', phone:'555-1212', age:10}, 12446 {name:'Mary', phone:'555-9876', age:19}, 12447 {name:'Mike', phone:'555-4321', age:21}, 12448 {name:'Adam', phone:'555-5678', age:35}, 12449 {name:'Julie', phone:'555-8765', age:29}] 12450 $scope.predicate = '-age'; 12451 } 12452 </script> 12453 <div ng-controller="Ctrl"> 12454 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 12455 <hr/> 12456 [ <a href="" ng-click="predicate=''">unsorted</a> ] 12457 <table class="friend"> 12458 <tr> 12459 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 12460 (<a href ng-click="predicate = '-name'; reverse=false">^</a>)</th> 12461 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 12462 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 12463 </tr> 12464 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 12465 <td>{{friend.name}}</td> 12466 <td>{{friend.phone}}</td> 12467 <td>{{friend.age}}</td> 12468 </tr> 12469 </table> 12470 </div> 12471 </doc:source> 12472 <doc:scenario> 12473 it('should be reverse ordered by aged', function() { 12474 expect(binding('predicate')).toBe('-age'); 12475 expect(repeater('table.friend', 'friend in friends').column('friend.age')). 12476 toEqual(['35', '29', '21', '19', '10']); 12477 expect(repeater('table.friend', 'friend in friends').column('friend.name')). 12478 toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); 12479 }); 12480 12481 it('should reorder the table when user selects different predicate', function() { 12482 element('.doc-example-live a:contains("Name")').click(); 12483 expect(repeater('table.friend', 'friend in friends').column('friend.name')). 12484 toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); 12485 expect(repeater('table.friend', 'friend in friends').column('friend.age')). 12486 toEqual(['35', '10', '29', '19', '21']); 12487 12488 element('.doc-example-live a:contains("Phone")').click(); 12489 expect(repeater('table.friend', 'friend in friends').column('friend.phone')). 12490 toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); 12491 expect(repeater('table.friend', 'friend in friends').column('friend.name')). 12492 toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); 12493 }); 12494 </doc:scenario> 12495 </doc:example> 12496 */ 12497orderByFilter.$inject = ['$parse']; 12498function orderByFilter($parse){ 12499 return function(array, sortPredicate, reverseOrder) { 12500 if (!isArray(array)) return array; 12501 if (!sortPredicate) return array; 12502 sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; 12503 sortPredicate = map(sortPredicate, function(predicate){ 12504 var descending = false, get = predicate || identity; 12505 if (isString(predicate)) { 12506 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 12507 descending = predicate.charAt(0) == '-'; 12508 predicate = predicate.substring(1); 12509 } 12510 get = $parse(predicate); 12511 } 12512 return reverseComparator(function(a,b){ 12513 return compare(get(a),get(b)); 12514 }, descending); 12515 }); 12516 var arrayCopy = []; 12517 for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } 12518 return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); 12519 12520 function comparator(o1, o2){ 12521 for ( var i = 0; i < sortPredicate.length; i++) { 12522 var comp = sortPredicate[i](o1, o2); 12523 if (comp !== 0) return comp; 12524 } 12525 return 0; 12526 } 12527 function reverseComparator(comp, descending) { 12528 return toBoolean(descending) 12529 ? function(a,b){return comp(b,a);} 12530 : comp; 12531 } 12532 function compare(v1, v2){ 12533 var t1 = typeof v1; 12534 var t2 = typeof v2; 12535 if (t1 == t2) { 12536 if (t1 == "string") v1 = v1.toLowerCase(); 12537 if (t1 == "string") v2 = v2.toLowerCase(); 12538 if (v1 === v2) return 0; 12539 return v1 < v2 ? -1 : 1; 12540 } else { 12541 return t1 < t2 ? -1 : 1; 12542 } 12543 } 12544 } 12545} 12546 12547function ngDirective(directive) { 12548 if (isFunction(directive)) { 12549 directive = { 12550 link: directive 12551 } 12552 } 12553 directive.restrict = directive.restrict || 'AC'; 12554 return valueFn(directive); 12555} 12556 12557/** 12558 * @ngdoc directive 12559 * @name ng.directive:a 12560 * @restrict E 12561 * 12562 * @description 12563 * Modifies the default behavior of html A tag, so that the default action is prevented when href 12564 * attribute is empty. 12565 * 12566 * The reasoning for this change is to allow easy creation of action links with `ngClick` directive 12567 * without changing the location or causing page reloads, e.g.: 12568 * `<a href="" ng-click="model.$save()">Save</a>` 12569 */ 12570var htmlAnchorDirective = valueFn({ 12571 restrict: 'E', 12572 compile: function(element, attr) { 12573 12574 if (msie <= 8) { 12575 12576 // turn <a href ng-click="..">link</a> into a stylable link in IE 12577 // but only if it doesn't have name attribute, in which case it's an anchor 12578 if (!attr.href && !attr.name) { 12579 attr.$set('href', ''); 12580 } 12581 12582 // add a comment node to anchors to workaround IE bug that causes element content to be reset 12583 // to new attribute content if attribute is updated with value containing @ and element also 12584 // contains value with @ 12585 // see issue #1949 12586 element.append(document.createComment('IE fix')); 12587 } 12588 12589 return function(scope, element) { 12590 element.on('click', function(event){ 12591 // if we have no href url, then don't navigate anywhere. 12592 if (!element.attr('href')) { 12593 event.preventDefault(); 12594 } 12595 }); 12596 } 12597 } 12598}); 12599 12600/** 12601 * @ngdoc directive 12602 * @name ng.directive:ngHref 12603 * @restrict A 12604 * 12605 * @description 12606 * Using Angular markup like {{hash}} in an href attribute makes 12607 * the page open to a wrong URL, if the user clicks that link before 12608 * angular has a chance to replace the {{hash}} with actual URL, the 12609 * link will be broken and will most likely return a 404 error. 12610 * The `ngHref` directive solves this problem. 12611 * 12612 * The buggy way to write it: 12613 * <pre> 12614 * <a href="http://www.gravatar.com/avatar/{{hash}}"/> 12615 * </pre> 12616 * 12617 * The correct way to write it: 12618 * <pre> 12619 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/> 12620 * </pre> 12621 * 12622 * @element A 12623 * @param {template} ngHref any string which can contain `{{}}` markup. 12624 * 12625 * @example 12626 * This example uses `link` variable inside `href` attribute: 12627 <doc:example> 12628 <doc:source> 12629 <input ng-model="value" /><br /> 12630 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 12631 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 12632 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 12633 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 12634 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 12635 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 12636 </doc:source> 12637 <doc:scenario> 12638 it('should execute ng-click but not reload when href without value', function() {
12639 element('#link-1').click(); 12640 expect(input('value').val()).toEqual('1'); 12641 expect(element('#link-1').attr('href')).toBe(""); 12642 }); 12643 12644 it('should execute ng-click but not reload when href empty string', function() { 12645 element('#link-2').click(); 12646 expect(input('value').val()).toEqual('2'); 12647 expect(element('#link-2').attr('href')).toBe(""); 12648 }); 12649 12650 it('should execute ng-click and change url when ng-href specified', function() { 12651 expect(element('#link-3').attr('href')).toBe("/123"); 12652 12653 element('#link-3').click(); 12654 expect(browser().window().path()).toEqual('/123'); 12655 }); 12656 12657 it('should execute ng-click but not reload when href empty string and name specified', function() { 12658 element('#link-4').click(); 12659 expect(input('value').val()).toEqual('4'); 12660 expect(element('#link-4').attr('href')).toBe(''); 12661 }); 12662 12663 it('should execute ng-click but not reload when no href but name specified', function() { 12664 element('#link-5').click(); 12665 expect(input('value').val()).toEqual('5'); 12666 expect(element('#link-5').attr('href')).toBe(undefined); 12667 }); 12668 12669 it('should only change url when only ng-href', function() { 12670 input('value').enter('6'); 12671 expect(element('#link-6').attr('href')).toBe('6'); 12672 12673 element('#link-6').click(); 12674 expect(browser().location().url()).toEqual('/6'); 12675 }); 12676 </doc:scenario> 12677 </doc:example> 12678 */ 12679 12680/** 12681 * @ngdoc directive 12682 * @name ng.directive:ngSrc 12683 * @restrict A 12684 * 12685 * @description 12686 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 12687 * work right: The browser will fetch from the URL with the literal 12688 * text `{{hash}}` until Angular replaces the expression inside 12689 * `{{hash}}`. The `ngSrc` directive solves this problem. 12690 * 12691 * The buggy way to write it: 12692 * <pre> 12693 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 12694 * </pre> 12695 * 12696 * The correct way to write it: 12697 * <pre> 12698 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 12699 * </pre> 12700 * 12701 * @element IMG 12702 * @param {template} ngSrc any string which can contain `{{}}` markup. 12703 */ 12704 12705/** 12706 * @ngdoc directive 12707 * @name ng.directive:ngSrcset 12708 * @restrict A 12709 * 12710 * @description 12711 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 12712 * work right: The browser will fetch from the URL with the literal 12713 * text `{{hash}}` until Angular replaces the expression inside 12714 * `{{hash}}`. The `ngSrcset` directive solves this problem. 12715 * 12716 * The buggy way to write it: 12717 * <pre> 12718 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 12719 * </pre> 12720 * 12721 * The correct way to write it: 12722 * <pre> 12723 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 12724 * </pre> 12725 * 12726 * @element IMG 12727 * @param {template} ngSrcset any string which can contain `{{}}` markup. 12728 */ 12729 12730/** 12731 * @ngdoc directive 12732 * @name ng.directive:ngDisabled 12733 * @restrict A 12734 * 12735 * @description 12736 * 12737 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 12738 * <pre> 12739 * <div ng-init="scope = { isDisabled: false }"> 12740 * <button disabled="{{scope.isDisabled}}">Disabled</button> 12741 * </div> 12742 * </pre> 12743 * 12744 * The HTML specs do not require browsers to preserve the special attributes such as disabled. 12745 * (The presence of them means true and absence means false) 12746 * This prevents the angular compiler from correctly retrieving the binding expression. 12747 * To solve this problem, we introduce the `ngDisabled` directive. 12748 * 12749 * @example 12750 <doc:example> 12751 <doc:source> 12752 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 12753 <button ng-model="button" ng-disabled="checked">Button</button> 12754 </doc:source> 12755 <doc:scenario> 12756 it('should toggle button', function() { 12757 expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy(); 12758 input('checked').check(); 12759 expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy(); 12760 }); 12761 </doc:scenario> 12762 </doc:example> 12763 * 12764 * @element INPUT 12765 * @param {expression} ngDisabled Angular expression that will be evaluated. 12766 */ 12767 12768 12769/** 12770 * @ngdoc directive 12771 * @name ng.directive:ngChecked 12772 * @restrict A 12773 * 12774 * @description 12775 * The HTML specs do not require browsers to preserve the special attributes such as checked. 12776 * (The presence of them means true and absence means false) 12777 * This prevents the angular compiler from correctly retrieving the binding expression. 12778 * To solve this problem, we introduce the `ngChecked` directive. 12779 * @example 12780 <doc:example> 12781 <doc:source> 12782 Check me to check both: <input type="checkbox" ng-model="master"><br/> 12783 <input id="checkSlave" type="checkbox" ng-checked="master"> 12784 </doc:source> 12785 <doc:scenario> 12786 it('should check both checkBoxes', function() { 12787 expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy(); 12788 input('master').check(); 12789 expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy(); 12790 }); 12791 </doc:scenario> 12792 </doc:example> 12793 * 12794 * @element INPUT 12795 * @param {expression} ngChecked Angular expression that will be evaluated. 12796 */ 12797 12798 12799/** 12800 * @ngdoc directive 12801 * @name ng.directive:ngReadonly 12802 * @restrict A 12803 * 12804 * @description 12805 * The HTML specs do not require browsers to preserve the special attributes such as readonly. 12806 * (The presence of them means true and absence means false) 12807 * This prevents the angular compiler from correctly retrieving the binding expression. 12808 * To solve this problem, we introduce the `ngReadonly` directive. 12809 * @example 12810 <doc:example> 12811 <doc:source> 12812 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 12813 <input type="text" ng-readonly="checked" value="I'm Angular"/> 12814 </doc:source> 12815 <doc:scenario> 12816 it('should toggle readonly attr', function() { 12817 expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy(); 12818 input('checked').check(); 12819 expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy(); 12820 }); 12821 </doc:scenario> 12822 </doc:example> 12823 * 12824 * @element INPUT 12825 * @param {string} expression Angular expression that will be evaluated. 12826 */ 12827 12828 12829/** 12830 * @ngdoc directive 12831 * @name ng.directive:ngSelected 12832 * @restrict A 12833 * 12834 * @description 12835 * The HTML specs do not require browsers to preserve the special attributes such as selected. 12836 * (The presence of them means true and absence means false) 12837 * This prevents the angular compiler from correctly retrieving the binding expression. 12838 * To solve this problem, we introduced the `ngSelected` directive. 12839 * @example 12840 <doc:example> 12841 <doc:source> 12842 Check me to select: <input type="checkbox" ng-model="selected"><br/> 12843 <select> 12844 <option>Hello!</option> 12845 <option id="greet" ng-selected="selected">Greetings!</option> 12846 </select> 12847 </doc:source> 12848 <doc:scenario> 12849 it('should select Greetings!', function() { 12850 expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy(); 12851 input('selected').check(); 12852 expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy(); 12853 }); 12854 </doc:scenario> 12855 </doc:example> 12856 * 12857 * @element OPTION 12858 * @param {string} expression Angular expression that will be evaluated. 12859 */ 12860 12861/** 12862 * @ngdoc directive 12863 * @name ng.directive:ngOpen 12864 * @restrict A 12865 * 12866 * @description 12867 * The HTML specs do not require browsers to preserve the special attributes such as open. 12868 * (The presence of them means true and absence means false) 12869 * This prevents the angular compiler from correctly retrieving the binding expression. 12870 * To solve this problem, we introduce the `ngOpen` directive. 12871 * 12872 * @example 12873 <doc:example> 12874 <doc:source> 12875 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 12876 <details id="details" ng-open="open"> 12877 <summary>Show/Hide me</summary> 12878 </details> 12879 </doc:source> 12880 <doc:scenario> 12881 it('should toggle open', function() { 12882 expect(element('#details').prop('open')).toBeFalsy(); 12883 input('open').check(); 12884 expect(element('#details').prop('open')).toBeTruthy(); 12885 }); 12886 </doc:scenario>
12887 </doc:example> 12888 * 12889 * @element DETAILS 12890 * @param {string} expression Angular expression that will be evaluated. 12891 */ 12892 12893var ngAttributeAliasDirectives = {}; 12894 12895 12896// boolean attrs are evaluated 12897forEach(BOOLEAN_ATTR, function(propName, attrName) { 12898 // binding to multiple is not supported 12899 if (propName == "multiple") return; 12900 12901 var normalized = directiveNormalize('ng-' + attrName); 12902 ngAttributeAliasDirectives[normalized] = function() { 12903 return { 12904 priority: 100, 12905 compile: function() { 12906 return function(scope, element, attr) { 12907 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 12908 attr.$set(attrName, !!value); 12909 }); 12910 }; 12911 } 12912 }; 12913 }; 12914}); 12915 12916 12917// ng-src, ng-srcset, ng-href are interpolated 12918forEach(['src', 'srcset', 'href'], function(attrName) { 12919 var normalized = directiveNormalize('ng-' + attrName); 12920 ngAttributeAliasDirectives[normalized] = function() { 12921 return { 12922 priority: 99, // it needs to run after the attributes are interpolated 12923 link: function(scope, element, attr) { 12924 attr.$observe(normalized, function(value) { 12925 if (!value) 12926 return; 12927 12928 attr.$set(attrName, value); 12929 12930 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 12931 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 12932 // to set the property as well to achieve the desired effect. 12933 // we use attr[attrName] value since $set can sanitize the url. 12934 if (msie) element.prop(attrName, attr[attrName]); 12935 }); 12936 } 12937 }; 12938 }; 12939}); 12940 12941var nullFormCtrl = { 12942 $addControl: noop, 12943 $removeControl: noop, 12944 $setValidity: noop, 12945 $setDirty: noop, 12946 $setPristine: noop 12947}; 12948 12949/** 12950 * @ngdoc object 12951 * @name ng.directive:form.FormController 12952 * 12953 * @property {boolean} $pristine True if user has not interacted with the form yet. 12954 * @property {boolean} $dirty True if user has already interacted with the form. 12955 * @property {boolean} $valid True if all of the containing forms and controls are valid. 12956 * @property {boolean} $invalid True if at least one containing control or form is invalid. 12957 * 12958 * @property {Object} $error Is an object hash, containing references to all invalid controls or 12959 * forms, where: 12960 * 12961 * - keys are validation tokens (error names) ââ¬â such as `required`, `url` or `email`), 12962 * - values are arrays of controls or forms that are invalid with given error. 12963 * 12964 * @description 12965 * `FormController` keeps track of all its controls and nested forms as well as state of them, 12966 * such as being valid/invalid or dirty/pristine. 12967 * 12968 * Each {@link ng.directive:form form} directive creates an instance 12969 * of `FormController`. 12970 * 12971 */ 12972//asks for $scope to fool the BC controller module 12973FormController.$inject = ['$element', '$attrs', '$scope']; 12974function FormController(element, attrs) { 12975 var form = this, 12976 parentForm = element.parent().controller('form') || nullFormCtrl, 12977 invalidCount = 0, // used to easily determine if we are valid 12978 errors = form.$error = {}, 12979 controls = []; 12980 12981 // init state 12982 form.$name = attrs.name || attrs.ngForm; 12983 form.$dirty = false; 12984 form.$pristine = true; 12985 form.$valid = true; 12986 form.$invalid = false; 12987 12988 parentForm.$addControl(form); 12989 12990 // Setup initial state of the control 12991 element.addClass(PRISTINE_CLASS); 12992 toggleValidCss(true); 12993 12994 // convenience method for easy toggling of classes 12995 function toggleValidCss(isValid, validationErrorKey) { 12996 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 12997 element. 12998 removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). 12999 addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 13000 } 13001 13002 /** 13003 * @ngdoc function 13004 * @name ng.directive:form.FormController#$addControl 13005 * @methodOf ng.directive:form.FormController 13006 * 13007 * @description 13008 * Register a control with the form. 13009 * 13010 * Input elements using ngModelController do this automatically when they are linked. 13011 */ 13012 form.$addControl = function(control) { 13013 controls.push(control); 13014 13015 if (control.$name && !form.hasOwnProperty(control.$name)) { 13016 form[control.$name] = control; 13017 } 13018 }; 13019 13020 /** 13021 * @ngdoc function
13022 * @name ng.directive:form.FormController#$removeControl 13023 * @methodOf ng.directive:form.FormController 13024 * 13025 * @description 13026 * Deregister a control from the form. 13027 * 13028 * Input elements using ngModelController do this automatically when they are destroyed. 13029 */ 13030 form.$removeControl = function(control) { 13031 if (control.$name && form[control.$name] === control) { 13032 delete form[control.$name]; 13033 } 13034 forEach(errors, function(queue, validationToken) { 13035 form.$setValidity(validationToken, true, control); 13036 }); 13037 13038 arrayRemove(controls, control); 13039 }; 13040 13041 /** 13042 * @ngdoc function 13043 * @name ng.directive:form.FormController#$setValidity 13044 * @methodOf ng.directive:form.FormController 13045 * 13046 * @description 13047 * Sets the validity of a form control. 13048 * 13049 * This method will also propagate to parent forms. 13050 */ 13051 form.$setValidity = function(validationToken, isValid, control) { 13052 var queue = errors[validationToken]; 13053 13054 if (isValid) { 13055 if (queue) { 13056 arrayRemove(queue, control); 13057 if (!queue.length) { 13058 invalidCount--; 13059 if (!invalidCount) { 13060 toggleValidCss(isValid); 13061 form.$valid = true; 13062 form.$invalid = false; 13063 } 13064 errors[validationToken] = false; 13065 toggleValidCss(true, validationToken); 13066 parentForm.$setValidity(validationToken, true, form); 13067 } 13068 } 13069 13070 } else { 13071 if (!invalidCount) { 13072 toggleValidCss(isValid); 13073 } 13074 if (queue) { 13075 if (includes(queue, control)) return; 13076 } else { 13077 errors[validationToken] = queue = []; 13078 invalidCount++; 13079 toggleValidCss(false, validationToken); 13080 parentForm.$setValidity(validationToken, false, form); 13081 } 13082 queue.push(control); 13083 13084 form.$valid = false; 13085 form.$invalid = true; 13086 } 13087 }; 13088 13089 /** 13090 * @ngdoc function 13091 * @name ng.directive:form.FormController#$setDirty 13092 * @methodOf ng.directive:form.FormController 13093 * 13094 * @description 13095 * Sets the form to a dirty state. 13096 * 13097 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 13098 * state (ng-dirty class). This method will also propagate to parent forms. 13099 */ 13100 form.$setDirty = function() { 13101 element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); 13102 form.$dirty = true; 13103 form.$pristine = false; 13104 parentForm.$setDirty(); 13105 }; 13106 13107 /** 13108 * @ngdoc function 13109 * @name ng.directive:form.FormController#$setPristine 13110 * @methodOf ng.directive:form.FormController 13111 * 13112 * @description 13113 * Sets the form to its pristine state. 13114 * 13115 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 13116 * state (ng-pristine class). This method will also propagate to all the controls contained 13117 * in this form. 13118 * 13119 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 13120 * saving or resetting it. 13121 */ 13122 form.$setPristine = function () { 13123 element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); 13124 form.$dirty = false; 13125 form.$pristine = true; 13126 forEach(controls, function(control) { 13127 control.$setPristine(); 13128 }); 13129 }; 13130} 13131 13132 13133/** 13134 * @ngdoc directive 13135 * @name ng.directive:ngForm 13136 * @restrict EAC 13137 * 13138 * @description 13139 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 13140 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 13141 * sub-group of controls needs to be determined. 13142 * 13143 * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into 13144 * related scope, under this name. 13145 * 13146 */ 13147 13148 /** 13149 * @ngdoc directive 13150 * @name ng.directive:form 13151 * @restrict E 13152 * 13153 * @description 13154 * Directive that instantiates 13155 * {@link ng.directive:form.FormController FormController}. 13156 * 13157 * If `name` attribute is specified, the form controller is published onto the current scope under 13158 * this name. 13159 * 13160 * # Alias: {@link ng.directive:ngForm `ngForm`} 13161 * 13162 * In angular forms can be nested. This means that the outer form is valid when all of the child 13163 * forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this 13164 * reason angular provides {@link ng.directive:ngForm `ngForm`} alias 13165 * which behaves identical to `<form>` but allows form nesting. 13166 * 13167 * 13168 * # CSS classes 13169 * - `ng-valid` Is set if the form is valid.
13170 * - `ng-invalid` Is set if the form is invalid. 13171 * - `ng-pristine` Is set if the form is pristine. 13172 * - `ng-dirty` Is set if the form is dirty. 13173 * 13174 * 13175 * # Submitting a form and preventing default action 13176 * 13177 * Since the role of forms in client-side Angular applications is different than in classical 13178 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 13179 * page reload that sends the data to the server. Instead some javascript logic should be triggered 13180 * to handle the form submission in application specific way. 13181 * 13182 * For this reason, Angular prevents the default action (form submission to the server) unless the 13183 * `<form>` element has an `action` attribute specified. 13184 * 13185 * You can use one of the following two ways to specify what javascript method should be called when 13186 * a form is submitted: 13187 * 13188 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 13189 * - {@link ng.directive:ngClick ngClick} directive on the first 13190 * button or input field of type submit (input[type=submit]) 13191 * 13192 * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This 13193 * is because of the following form submission rules coming from the html spec: 13194 * 13195 * - If a form has only one input field then hitting enter in this field triggers form submit 13196 * (`ngSubmit`) 13197 * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter 13198 * doesn't trigger submit 13199 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 13200 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 13201 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 13202 * 13203 * @param {string=} name Name of the form. If specified, the form controller will be published into 13204 * related scope, under this name. 13205 * 13206 * @example 13207 <doc:example> 13208 <doc:source> 13209 <script> 13210 function Ctrl($scope) { 13211 $scope.userType = 'guest'; 13212 } 13213 </script> 13214 <form name="myForm" ng-controller="Ctrl"> 13215 userType: <input name="input" ng-model="userType" required> 13216 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 13217 <tt>userType = {{userType}}</tt><br> 13218 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 13219 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 13220 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 13221 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 13222 </form> 13223 </doc:source> 13224 <doc:scenario> 13225 it('should initialize to model', function() { 13226 expect(binding('userType')).toEqual('guest'); 13227 expect(binding('myForm.input.$valid')).toEqual('true'); 13228 }); 13229 13230 it('should be invalid if empty', function() { 13231 input('userType').enter(''); 13232 expect(binding('userType')).toEqual(''); 13233 expect(binding('myForm.input.$valid')).toEqual('false'); 13234 }); 13235 </doc:scenario> 13236 </doc:example> 13237 */ 13238var formDirectiveFactory = function(isNgForm) { 13239 return ['$timeout', function($timeout) { 13240 var formDirective = { 13241 name: 'form', 13242 restrict: 'E', 13243 controller: FormController, 13244 compile: function() { 13245 return { 13246 pre: function(scope, formElement, attr, controller) { 13247 if (!attr.action) { 13248 // we can't use jq events because if a form is destroyed during submission the default 13249 // action is not prevented. see #1238 13250 // 13251 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 13252 // page reload if the form was destroyed by submission of the form via a click handler 13253 // on a button in the form. Looks like an IE9 specific bug. 13254 var preventDefaultListener = function(event) { 13255 event.preventDefault 13256 ? event.preventDefault() 13257 : event.returnValue = false; // IE 13258 }; 13259 13260 addEventListenerFn(formElement[0], 'submit', preventDefaultListener); 13261 13262 // unregister the preventDefault listener so that we don't not leak memory but in a 13263 // way that will achieve the prevention of the default action. 13264 formElement.on('$destroy', function() { 13265 $timeout(function() { 13266 removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); 13267 }, 0, false); 13268 }); 13269 } 13270 13271 var parentFormCtrl = formElement.parent().controller('form'), 13272 alias = attr.name || attr.ngForm; 13273 13274 if (alias) { 13275 setter(scope, alias, controller, alias); 13276 } 13277 if (parentFormCtrl) { 13278 formElement.on('$destroy', function() { 13279 parentFormCtrl.$removeControl(controller); 13280 if (alias) { 13281 setter(scope, alias, undefined, alias); 13282 } 13283 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 13284 }); 13285 } 13286 } 13287 }; 13288 } 13289 }; 13290 13291 return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective; 13292 }]; 13293}; 13294 13295var formDirective = formDirectiveFactory(); 13296var ngFormDirective = formDirectiveFactory(true); 13297 13298var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 13299var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;
13300var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 13301 13302var inputType = { 13303 13304 /** 13305 * @ngdoc inputType 13306 * @name ng.directive:input.text 13307 * 13308 * @description 13309 * Standard HTML text input with angular data binding. 13310 * 13311 * @param {string} ngModel Assignable angular expression to data-bind to. 13312 * @param {string=} name Property name of the form under which the control is published. 13313 * @param {string=} required Adds `required` validation error key if the value is not entered. 13314 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 13315 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 13316 * `required` when you want to data-bind to the `required` attribute. 13317 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 13318 * minlength. 13319 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 13320 * maxlength. 13321 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 13322 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 13323 * patterns defined as scope expressions. 13324 * @param {string=} ngChange Angular expression to be executed when input changes due to user 13325 * interaction with the input element. 13326 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trimming the 13327 * input. 13328 * 13329 * @example 13330 <doc:example> 13331 <doc:source> 13332 <script> 13333 function Ctrl($scope) { 13334 $scope.text = 'guest'; 13335 $scope.word = /^\s*\w*\s*$/; 13336 } 13337 </script> 13338 <form name="myForm" ng-controller="Ctrl"> 13339 Single word: <input type="text" name="input" ng-model="text" 13340 ng-pattern="word" required ng-trim="false"> 13341 <span class="error" ng-show="myForm.input.$error.required"> 13342 Required!</span> 13343 <span class="error" ng-show="myForm.input.$error.pattern"> 13344 Single word only!</span> 13345 13346 <tt>text = {{text}}</tt><br/> 13347 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 13348 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 13349 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 13350 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 13351 </form> 13352 </doc:source> 13353 <doc:scenario> 13354 it('should initialize to model', function() { 13355 expect(binding('text')).toEqual('guest'); 13356 expect(binding('myForm.input.$valid')).toEqual('true'); 13357 }); 13358 13359 it('should be invalid if empty', function() { 13360 input('text').enter(''); 13361 expect(binding('text')).toEqual(''); 13362 expect(binding('myForm.input.$valid')).toEqual('false'); 13363 }); 13364 13365 it('should be invalid if multi word', function() { 13366 input('text').enter('hello world'); 13367 expect(binding('myForm.input.$valid')).toEqual('false'); 13368 }); 13369 13370 it('should not be trimmed', function() { 13371 input('text').enter('untrimmed '); 13372 expect(binding('text')).toEqual('untrimmed '); 13373 expect(binding('myForm.input.$valid')).toEqual('true'); 13374 }); 13375 </doc:scenario> 13376 </doc:example> 13377 */ 13378 'text': textInputType, 13379 13380 13381 /** 13382 * @ngdoc inputType 13383 * @name ng.directive:input.number 13384 * 13385 * @description 13386 * Text input with number validation and transformation. Sets the `number` validation 13387 * error if not a valid number. 13388 * 13389 * @param {string} ngModel Assignable angular expression to data-bind to. 13390 * @param {string=} name Property name of the form under which the control is published. 13391 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 13392 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 13393 * @param {string=} required Sets `required` validation error key if the value is not entered. 13394 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 13395 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 13396 * `required` when you want to data-bind to the `required` attribute. 13397 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 13398 * minlength. 13399 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 13400 * maxlength. 13401 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 13402 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 13403 * patterns defined as scope expressions. 13404 * @param {string=} ngChange Angular expression to be executed when input changes due to user 13405 * interaction with the input element. 13406 * 13407 * @example 13408 <doc:example> 13409 <doc:source> 13410 <script> 13411 function Ctrl($scope) { 13412 $scope.value = 12; 13413 } 13414 </script> 13415 <form name="myForm" ng-controller="Ctrl"> 13416 Number: <input type="number" name="input" ng-model="value" 13417 min="0" max="99" required>
13418 <span class="error" ng-show="myForm.input.$error.required"> 13419 Required!</span> 13420 <span class="error" ng-show="myForm.input.$error.number"> 13421 Not valid number!</span> 13422 <tt>value = {{value}}</tt><br/> 13423 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 13424 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 13425 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 13426 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 13427 </form> 13428 </doc:source> 13429 <doc:scenario> 13430 it('should initialize to model', function() { 13431 expect(binding('value')).toEqual('12'); 13432 expect(binding('myForm.input.$valid')).toEqual('true'); 13433 }); 13434 13435 it('should be invalid if empty', function() { 13436 input('value').enter(''); 13437 expect(binding('value')).toEqual(''); 13438 expect(binding('myForm.input.$valid')).toEqual('false'); 13439 }); 13440 13441 it('should be invalid if over max', function() { 13442 input('value').enter('123'); 13443 expect(binding('value')).toEqual(''); 13444 expect(binding('myForm.input.$valid')).toEqual('false'); 13445 }); 13446 </doc:scenario> 13447 </doc:example> 13448 */ 13449 'number': numberInputType, 13450 13451 13452 /** 13453 * @ngdoc inputType 13454 * @name ng.directive:input.url 13455 * 13456 * @description 13457 * Text input with URL validation. Sets the `url` validation error key if the content is not a 13458 * valid URL. 13459 * 13460 * @param {string} ngModel Assignable angular expression to data-bind to. 13461 * @param {string=} name Property name of the form under which the control is published. 13462 * @param {string=} required Sets `required` validation error key if the value is not entered. 13463 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 13464 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 13465 * `required` when you want to data-bind to the `required` attribute. 13466 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 13467 * minlength. 13468 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 13469 * maxlength. 13470 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 13471 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 13472 * patterns defined as scope expressions. 13473 * @param {string=} ngChange Angular expression to be executed when input changes due to user 13474 * interaction with the input element. 13475 * 13476 * @example 13477 <doc:example> 13478 <doc:source> 13479 <script> 13480 function Ctrl($scope) { 13481 $scope.text = 'http://google.com'; 13482 } 13483 </script> 13484 <form name="myForm" ng-controller="Ctrl"> 13485 URL: <input type="url" name="input" ng-model="text" required> 13486 <span class="error" ng-show="myForm.input.$error.required"> 13487 Required!</span> 13488 <span class="error" ng-show="myForm.input.$error.url"> 13489 Not valid url!</span> 13490 <tt>text = {{text}}</tt><br/> 13491 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 13492 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 13493 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 13494 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 13495 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 13496 </form> 13497 </doc:source> 13498 <doc:scenario> 13499 it('should initialize to model', function() { 13500 expect(binding('text')).toEqual('http://google.com'); 13501 expect(binding('myForm.input.$valid')).toEqual('true'); 13502 }); 13503 13504 it('should be invalid if empty', function() { 13505 input('text').enter(''); 13506 expect(binding('text')).toEqual(''); 13507 expect(binding('myForm.input.$valid')).toEqual('false'); 13508 }); 13509 13510 it('should be invalid if not url', function() { 13511 input('text').enter('xxx'); 13512 expect(binding('myForm.input.$valid')).toEqual('false'); 13513 }); 13514 </doc:scenario> 13515 </doc:example> 13516 */ 13517 'url': urlInputType, 13518 13519 13520 /** 13521 * @ngdoc inputType 13522 * @name ng.directive:input.email 13523 * 13524 * @description 13525 * Text input with email validation. Sets the `email` validation error key if not a valid email 13526 * address. 13527 * 13528 * @param {string} ngModel Assignable angular expression to data-bind to. 13529 * @param {string=} name Property name of the form under which the control is published. 13530 * @param {string=} required Sets `required` validation error key if the value is not entered. 13531 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 13532 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 13533 * `required` when you want to data-bind to the `required` attribute. 13534 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 13535 * minlength. 13536 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 13537 * maxlength. 13538 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 13539 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 13540 * patterns defined as scope expressions. 13541 * @param {string=} ngChange Angular expression to be executed when input changes due to user 13542 * interaction with the input element. 13543 * 13544 * @example 13545 <doc:example> 13546 <doc:source> 13547 <script> 13548 function Ctrl($scope) { 13549 $scope.text = '[email protected]'; 13550 } 13551 </script> 13552 <form name="myForm" ng-controller="Ctrl"> 13553 Email: <input type="email" name="input" ng-model="text" required>
13554 <span class="error" ng-show="myForm.input.$error.required"> 13555 Required!</span> 13556 <span class="error" ng-show="myForm.input.$error.email"> 13557 Not valid email!</span> 13558 <tt>text = {{text}}</tt><br/> 13559 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 13560 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 13561 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 13562 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 13563 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 13564 </form> 13565 </doc:source> 13566 <doc:scenario> 13567 it('should initialize to model', function() { 13568 expect(binding('text')).toEqual('[email protected]'); 13569 expect(binding('myForm.input.$valid')).toEqual('true'); 13570 }); 13571 13572 it('should be invalid if empty', function() { 13573 input('text').enter(''); 13574 expect(binding('text')).toEqual(''); 13575 expect(binding('myForm.input.$valid')).toEqual('false'); 13576 }); 13577 13578 it('should be invalid if not email', function() { 13579 input('text').enter('xxx'); 13580 expect(binding('myForm.input.$valid')).toEqual('false'); 13581 }); 13582 </doc:scenario> 13583 </doc:example> 13584 */ 13585 'email': emailInputType, 13586 13587 13588 /** 13589 * @ngdoc inputType 13590 * @name ng.directive:input.radio 13591 * 13592 * @description 13593 * HTML radio button. 13594 * 13595 * @param {string} ngModel Assignable angular expression to data-bind to. 13596 * @param {string} value The value to which the expression should be set when selected. 13597 * @param {string=} name Property name of the form under which the control is published. 13598 * @param {string=} ngChange Angular expression to be executed when input changes due to user 13599 * interaction with the input element. 13600 * 13601 * @example 13602 <doc:example> 13603 <doc:source> 13604 <script> 13605 function Ctrl($scope) { 13606 $scope.color = 'blue'; 13607 } 13608 </script> 13609 <form name="myForm" ng-controller="Ctrl"> 13610 <input type="radio" ng-model="color" value="red"> Red <br/> 13611 <input type="radio" ng-model="color" value="green"> Green <br/> 13612 <input type="radio" ng-model="color" value="blue"> Blue <br/> 13613 <tt>color = {{color}}</tt><br/> 13614 </form> 13615 </doc:source> 13616 <doc:scenario> 13617 it('should change state', function() { 13618 expect(binding('color')).toEqual('blue'); 13619 13620 input('color').select('red'); 13621 expect(binding('color')).toEqual('red'); 13622 }); 13623 </doc:scenario> 13624 </doc:example> 13625 */ 13626 'radio': radioInputType, 13627 13628 13629 /** 13630 * @ngdoc inputType 13631 * @name ng.directive:input.checkbox 13632 * 13633 * @description 13634 * HTML checkbox. 13635 * 13636 * @param {string} ngModel Assignable angular expression to data-bind to. 13637 * @param {string=} name Property name of the form under which the control is published. 13638 * @param {string=} ngTrueValue The value to which the expression should be set when selected. 13639 * @param {string=} ngFalseValue The value to which the expression should be set when not selected. 13640 * @param {string=} ngChange Angular expression to be executed when input changes due to user 13641 * interaction with the input element. 13642 * 13643 * @example 13644 <doc:example> 13645 <doc:source> 13646 <script> 13647 function Ctrl($scope) { 13648 $scope.value1 = true; 13649 $scope.value2 = 'YES' 13650 } 13651 </script> 13652 <form name="myForm" ng-controller="Ctrl"> 13653 Value1: <input type="checkbox" ng-model="value1"> <br/> 13654 Value2: <input type="checkbox" ng-model="value2" 13655 ng-true-value="YES" ng-false-value="NO"> <br/> 13656 <tt>value1 = {{value1}}</tt><br/> 13657 <tt>value2 = {{value2}}</tt><br/> 13658 </form> 13659 </doc:source> 13660 <doc:scenario> 13661 it('should change state', function() { 13662 expect(binding('value1')).toEqual('true'); 13663 expect(binding('value2')).toEqual('YES'); 13664 13665 input('value1').check(); 13666 input('value2').check(); 13667 expect(binding('value1')).toEqual('false'); 13668 expect(binding('value2')).toEqual('NO'); 13669 }); 13670 </doc:scenario> 13671 </doc:example> 13672 */ 13673 'checkbox': checkboxInputType, 13674 13675 'hidden': noop, 13676 'button': noop, 13677 'submit': noop, 13678 'reset': noop 13679}; 13680 13681 13682function isEmpty(value) { 13683 return isUndefined(value) || value === '' || value === null || value !== value; 13684} 13685 13686 13687function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 13688 13689 var listener = function() { 13690 var value = element.val(); 13691 13692 // By default we will trim the value 13693 // If the attribute ng-trim exists we will avoid trimming 13694 // e.g. <input ng-model="foo" ng-trim="false"> 13695 if (toBoolean(attr.ngTrim || 'T')) { 13696 value = trim(value); 13697 } 13698 13699 if (ctrl.$viewValue !== value) { 13700 scope.$apply(function() { 13701 ctrl.$setViewValue(value); 13702 }); 13703 } 13704 }; 13705 13706 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 13707 // input event on backspace, delete or cut 13708 if ($sniffer.hasEvent('input')) { 13709 element.on('input', listener); 13710 } else { 13711 var timeout; 13712 13713 var deferListener = function() { 13714 if (!timeout) { 13715 timeout = $browser.defer(function() { 13716 listener(); 13717 timeout = null; 13718 }); 13719 } 13720 }; 13721 13722 element.on('keydown', function(event) { 13723 var key = event.keyCode; 13724 13725 // ignore 13726 // command modifiers arrows 13727 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 13728 13729 deferListener(); 13730 }); 13731 13732 // if user paste into input using mouse, we need "change" event to catch it 13733 element.on('change', listener); 13734 13735 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 13736 if ($sniffer.hasEvent('paste')) { 13737 element.on('paste cut', deferListener); 13738 } 13739 } 13740 13741 13742 ctrl.$render = function() { 13743 element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 13744 }; 13745 13746 // pattern validator 13747 var pattern = attr.ngPattern, 13748 patternValidator, 13749 match; 13750
13751 var validate = function(regexp, value) { 13752 if (isEmpty(value) || regexp.test(value)) { 13753 ctrl.$setValidity('pattern', true); 13754 return value; 13755 } else { 13756 ctrl.$setValidity('pattern', false); 13757 return undefined; 13758 } 13759 }; 13760 13761 if (pattern) { 13762 match = pattern.match(/^\/(.*)\/([gim]*)$/); 13763 if (match) { 13764 pattern = new RegExp(match[1], match[2]); 13765 patternValidator = function(value) { 13766 return validate(pattern, value) 13767 }; 13768 } else { 13769 patternValidator = function(value) { 13770 var patternObj = scope.$eval(pattern); 13771 13772 if (!patternObj || !patternObj.test) { 13773 throw minErr('ngPattern')('noregexp', 13774 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, 13775 patternObj, startingTag(element)); 13776 } 13777 return validate(patternObj, value); 13778 }; 13779 } 13780 13781 ctrl.$formatters.push(patternValidator); 13782 ctrl.$parsers.push(patternValidator); 13783 } 13784 13785 // min length validator 13786 if (attr.ngMinlength) { 13787 var minlength = int(attr.ngMinlength); 13788 var minLengthValidator = function(value) { 13789 if (!isEmpty(value) && value.length < minlength) { 13790 ctrl.$setValidity('minlength', false); 13791 return undefined; 13792 } else { 13793 ctrl.$setValidity('minlength', true); 13794 return value; 13795 } 13796 }; 13797 13798 ctrl.$parsers.push(minLengthValidator); 13799 ctrl.$formatters.push(minLengthValidator); 13800 } 13801 13802 // max length validator 13803 if (attr.ngMaxlength) { 13804 var maxlength = int(attr.ngMaxlength); 13805 var maxLengthValidator = function(value) { 13806 if (!isEmpty(value) && value.length > maxlength) { 13807 ctrl.$setValidity('maxlength', false); 13808 return undefined; 13809 } else { 13810 ctrl.$setValidity('maxlength', true); 13811 return value; 13812 } 13813 }; 13814 13815 ctrl.$parsers.push(maxLengthValidator); 13816 ctrl.$formatters.push(maxLengthValidator); 13817 } 13818} 13819 13820function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 13821 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 13822 13823 ctrl.$parsers.push(function(value) { 13824 var empty = isEmpty(value); 13825 if (empty || NUMBER_REGEXP.test(value)) { 13826 ctrl.$setValidity('number', true); 13827 return value === '' ? null : (empty ? value : parseFloat(value)); 13828 } else { 13829 ctrl.$setValidity('number', false); 13830 return undefined; 13831 } 13832 }); 13833 13834 ctrl.$formatters.push(function(value) { 13835 return isEmpty(value) ? '' : '' + value; 13836 }); 13837 13838 if (attr.min) { 13839 var min = parseFloat(attr.min); 13840 var minValidator = function(value) { 13841 if (!isEmpty(value) && value < min) { 13842 ctrl.$setValidity('min', false); 13843 return undefined; 13844 } else { 13845 ctrl.$setValidity('min', true); 13846 return value; 13847 } 13848 }; 13849 13850 ctrl.$parsers.push(minValidator); 13851 ctrl.$formatters.push(minValidator); 13852 } 13853 13854 if (attr.max) { 13855 var max = parseFloat(attr.max); 13856 var maxValidator = function(value) { 13857 if (!isEmpty(value) && value > max) { 13858 ctrl.$setValidity('max', false); 13859 return undefined; 13860 } else { 13861 ctrl.$setValidity('max', true); 13862 return value; 13863 } 13864 }; 13865 13866 ctrl.$parsers.push(maxValidator); 13867 ctrl.$formatters.push(maxValidator); 13868 } 13869 13870 ctrl.$formatters.push(function(value) { 13871 13872 if (isEmpty(value) || isNumber(value)) { 13873 ctrl.$setValidity('number', true); 13874 return value; 13875 } else { 13876 ctrl.$setValidity('number', false); 13877 return undefined; 13878 } 13879 }); 13880} 13881 13882function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 13883 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 13884 13885 var urlValidator = function(value) { 13886 if (isEmpty(value) || URL_REGEXP.test(value)) { 13887 ctrl.$setValidity('url', true); 13888 return value; 13889 } else { 13890 ctrl.$setValidity('url', false); 13891 return undefined; 13892 } 13893 }; 13894 13895 ctrl.$formatters.push(urlValidator); 13896 ctrl.$parsers.push(urlValidator); 13897} 13898 13899function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 13900 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 13901 13902 var emailValidator = function(value) { 13903 if (isEmpty(value) || EMAIL_REGEXP.test(value)) { 13904 ctrl.$setValidity('email', true); 13905 return value; 13906 } else { 13907 ctrl.$setValidity('email', false); 13908 return undefined; 13909 } 13910 }; 13911 13912 ctrl.$formatters.push(emailValidator); 13913 ctrl.$parsers.push(emailValidator); 13914} 13915 13916function radioInputType(scope, element, attr, ctrl) { 13917 // make the name unique, if not defined 13918 if (isUndefined(attr.name)) { 13919 element.attr('name', nextUid()); 13920 } 13921 13922 element.on('click', function() { 13923 if (element[0].checked) { 13924 scope.$apply(function() { 13925 ctrl.$setViewValue(attr.value); 13926 }); 13927 } 13928 }); 13929 13930 ctrl.$render = function() { 13931 var value = attr.value; 13932 element[0].checked = (value == ctrl.$viewValue); 13933 }; 13934 13935 attr.$observe('value', ctrl.$render); 13936} 13937 13938function checkboxInputType(scope, element, attr, ctrl) { 13939 var trueValue = attr.ngTrueValue, 13940 falseValue = attr.ngFalseValue; 13941 13942 if (!isString(trueValue)) trueValue = true; 13943 if (!isString(falseValue)) falseValue = false;
13944 13945 element.on('click', function() { 13946 scope.$apply(function() { 13947 ctrl.$setViewValue(element[0].checked); 13948 }); 13949 }); 13950 13951 ctrl.$render = function() { 13952 element[0].checked = ctrl.$viewValue; 13953 }; 13954 13955 ctrl.$formatters.push(function(value) { 13956 return value === trueValue; 13957 }); 13958 13959 ctrl.$parsers.push(function(value) { 13960 return value ? trueValue : falseValue; 13961 }); 13962} 13963 13964 13965/** 13966 * @ngdoc directive 13967 * @name ng.directive:textarea 13968 * @restrict E 13969 * 13970 * @description 13971 * HTML textarea element control with angular data-binding. The data-binding and validation 13972 * properties of this element are exactly the same as those of the 13973 * {@link ng.directive:input input element}. 13974 * 13975 * @param {string} ngModel Assignable angular expression to data-bind to. 13976 * @param {string=} name Property name of the form under which the control is published. 13977 * @param {string=} required Sets `required` validation error key if the value is not entered. 13978 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 13979 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 13980 * `required` when you want to data-bind to the `required` attribute. 13981 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 13982 * minlength. 13983 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 13984 * maxlength. 13985 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 13986 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 13987 * patterns defined as scope expressions. 13988 * @param {string=} ngChange Angular expression to be executed when input changes due to user 13989 * interaction with the input element. 13990 */ 13991 13992 13993/** 13994 * @ngdoc directive 13995 * @name ng.directive:input 13996 * @restrict E 13997 * 13998 * @description 13999 * HTML input element control with angular data-binding. Input control follows HTML5 input types 14000 * and polyfills the HTML5 validation behavior for older browsers. 14001 * 14002 * @param {string} ngModel Assignable angular expression to data-bind to. 14003 * @param {string=} name Property name of the form under which the control is published. 14004 * @param {string=} required Sets `required` validation error key if the value is not entered. 14005 * @param {boolean=} ngRequired Sets `required` attribute if set to true 14006 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 14007 * minlength. 14008 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 14009 * maxlength. 14010 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 14011 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 14012 * patterns defined as scope expressions. 14013 * @param {string=} ngChange Angular expression to be executed when input changes due to user 14014 * interaction with the input element. 14015 * 14016 * @example 14017 <doc:example> 14018 <doc:source> 14019 <script> 14020 function Ctrl($scope) { 14021 $scope.user = {name: 'guest', last: 'visitor'}; 14022 } 14023 </script> 14024 <div ng-controller="Ctrl"> 14025 <form name="myForm"> 14026 User name: <input type="text" name="userName" ng-model="user.name" required> 14027 <span class="error" ng-show="myForm.userName.$error.required"> 14028 Required!</span><br> 14029 Last name: <input type="text" name="lastName" ng-model="user.last" 14030 ng-minlength="3" ng-maxlength="10"> 14031 <span class="error" ng-show="myForm.lastName.$error.minlength"> 14032 Too short!</span> 14033 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 14034 Too long!</span><br> 14035 </form> 14036 <hr> 14037 <tt>user = {{user}}</tt><br/> 14038 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 14039 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 14040 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 14041 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 14042 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 14043 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 14044 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 14045 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br> 14046 </div> 14047 </doc:source> 14048 <doc:scenario> 14049 it('should initialize to model', function() { 14050 expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}'); 14051 expect(binding('myForm.userName.$valid')).toEqual('true'); 14052 expect(binding('myForm.$valid')).toEqual('true'); 14053 }); 14054 14055 it('should be invalid if empty when required', function() {
14056 input('user.name').enter(''); 14057 expect(binding('user')).toEqual('{"last":"visitor"}'); 14058 expect(binding('myForm.userName.$valid')).toEqual('false'); 14059 expect(binding('myForm.$valid')).toEqual('false'); 14060 }); 14061 14062 it('should be valid if empty when min length is set', function() { 14063 input('user.last').enter(''); 14064 expect(binding('user')).toEqual('{"name":"guest","last":""}'); 14065 expect(binding('myForm.lastName.$valid')).toEqual('true'); 14066 expect(binding('myForm.$valid')).toEqual('true'); 14067 }); 14068 14069 it('should be invalid if less than required min length', function() { 14070 input('user.last').enter('xx'); 14071 expect(binding('user')).toEqual('{"name":"guest"}'); 14072 expect(binding('myForm.lastName.$valid')).toEqual('false'); 14073 expect(binding('myForm.lastName.$error')).toMatch(/minlength/); 14074 expect(binding('myForm.$valid')).toEqual('false'); 14075 }); 14076 14077 it('should be invalid if longer than max length', function() { 14078 input('user.last').enter('some ridiculously long name'); 14079 expect(binding('user')) 14080 .toEqual('{"name":"guest"}'); 14081 expect(binding('myForm.lastName.$valid')).toEqual('false'); 14082 expect(binding('myForm.lastName.$error')).toMatch(/maxlength/); 14083 expect(binding('myForm.$valid')).toEqual('false'); 14084 }); 14085 </doc:scenario> 14086 </doc:example> 14087 */ 14088var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { 14089 return { 14090 restrict: 'E', 14091 require: '?ngModel', 14092 link: function(scope, element, attr, ctrl) { 14093 if (ctrl) { 14094 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, 14095 $browser); 14096 } 14097 } 14098 }; 14099}]; 14100 14101var VALID_CLASS = 'ng-valid', 14102 INVALID_CLASS = 'ng-invalid', 14103 PRISTINE_CLASS = 'ng-pristine', 14104 DIRTY_CLASS = 'ng-dirty'; 14105 14106/** 14107 * @ngdoc object 14108 * @name ng.directive:ngModel.NgModelController 14109 * 14110 * @property {string} $viewValue Actual string value in the view. 14111 * @property {*} $modelValue The value in the model, that the control is bound to. 14112 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 14113 the control reads value from the DOM. Each function is called, in turn, passing the value 14114 through to the next. Used to sanitize / convert the value as well as validation. 14115 For validation, the parsers should update the validity state using 14116 {@link ng.directive:ngModel.NgModelController#$setValidity $setValidity()}, 14117 and return `undefined` for invalid values. 14118 14119 * 14120 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 14121 the model value changes. Each function is called, in turn, passing the value through to the 14122 next. Used to format / convert values for display in the control and validation. 14123 * <pre> 14124 * function formatter(value) { 14125 * if (value) { 14126 * return value.toUpperCase(); 14127 * } 14128 * } 14129 * ngModel.$formatters.push(formatter); 14130 * </pre> 14131 * @property {Object} $error An object hash with all errors as keys. 14132 * 14133 * @property {boolean} $pristine True if user has not interacted with the control yet. 14134 * @property {boolean} $dirty True if user has already interacted with the control. 14135 * @property {boolean} $valid True if there is no error. 14136 * @property {boolean} $invalid True if at least one error on the control. 14137 * 14138 * @description 14139 * 14140 * `NgModelController` provides API for the `ng-model` directive. The controller contains 14141 * services for data-binding, validation, CSS update, value formatting and parsing. It 14142 * specifically does not contain any logic which deals with DOM rendering or listening to 14143 * DOM events. The `NgModelController` is meant to be extended by other directives where, the 14144 * directive provides DOM manipulation and the `NgModelController` provides the data-binding. 14145 * Note that you cannot use `NgModelController` in a directive with an isolated scope, 14146 * as, in that case, the `ng-model` value gets put into the isolated scope and does not get 14147 * propogated to the parent scope. 14148 * 14149 * 14150 * This example shows how to use `NgModelController` with a custom control to achieve 14151 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 14152 * collaborate together to achieve the desired result. 14153 * 14154 * <example module="customControl"> 14155 <file name="style.css"> 14156 [contenteditable] { 14157 border: 1px solid black; 14158 background-color: white; 14159 min-height: 20px; 14160 } 14161 14162 .ng-invalid { 14163 border: 1px solid red; 14164 } 14165 14166 </file> 14167 <file name="script.js">
14168 angular.module('customControl', []). 14169 directive('contenteditable', function() { 14170 return { 14171 restrict: 'A', // only activate on element attribute 14172 require: '?ngModel', // get a hold of NgModelController 14173 link: function(scope, element, attrs, ngModel) { 14174 if(!ngModel) return; // do nothing if no ng-model 14175 14176 // Specify how UI should be updated 14177 ngModel.$render = function() { 14178 element.html(ngModel.$viewValue || ''); 14179 }; 14180 14181 // Listen for change events to enable binding 14182 element.on('blur keyup change', function() { 14183 scope.$apply(read); 14184 }); 14185 read(); // initialize 14186 14187 // Write data to the model 14188 function read() { 14189 var html = element.html(); 14190 // When we clear the content editable the browser leaves a <br> behind 14191 // If strip-br attribute is provided then we strip this out 14192 if( attrs.stripBr && html == '<br>' ) { 14193 html = ''; 14194 } 14195 ngModel.$setViewValue(html); 14196 } 14197 } 14198 }; 14199 }); 14200 </file> 14201 <file name="index.html"> 14202 <form name="myForm"> 14203 <div contenteditable 14204 name="myWidget" ng-model="userContent" 14205 strip-br="true" 14206 required>Change me!</div> 14207 <span ng-show="myForm.myWidget.$error.required">Required!</span> 14208 <hr> 14209 <textarea ng-model="userContent"></textarea> 14210 </form> 14211 </file> 14212 <file name="scenario.js"> 14213 it('should data-bind and become invalid', function() { 14214 var contentEditable = element('[contenteditable]'); 14215 14216 expect(contentEditable.text()).toEqual('Change me!'); 14217 input('userContent').enter(''); 14218 expect(contentEditable.text()).toEqual(''); 14219 expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/); 14220 }); 14221 </file> 14222 * </example> 14223 * 14224 */ 14225var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', 14226 function($scope, $exceptionHandler, $attr, $element, $parse) { 14227 this.$viewValue = Number.NaN; 14228 this.$modelValue = Number.NaN; 14229 this.$parsers = []; 14230 this.$formatters = []; 14231 this.$viewChangeListeners = []; 14232 this.$pristine = true; 14233 this.$dirty = false; 14234 this.$valid = true; 14235 this.$invalid = false; 14236 this.$name = $attr.name; 14237 14238 var ngModelGet = $parse($attr.ngModel), 14239 ngModelSet = ngModelGet.assign; 14240 14241 if (!ngModelSet) { 14242 throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 14243 $attr.ngModel, startingTag($element)); 14244 } 14245 14246 /** 14247 * @ngdoc function 14248 * @name ng.directive:ngModel.NgModelController#$render 14249 * @methodOf ng.directive:ngModel.NgModelController 14250 * 14251 * @description 14252 * Called when the view needs to be updated. It is expected that the user of the ng-model 14253 * directive will implement this method. 14254 */ 14255 this.$render = noop; 14256 14257 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 14258 invalidCount = 0, // used to easily determine if we are valid 14259 $error = this.$error = {}; // keep invalid keys here 14260 14261 14262 // Setup initial state of the control 14263 $element.addClass(PRISTINE_CLASS); 14264 toggleValidCss(true); 14265 14266 // convenience method for easy toggling of classes 14267 function toggleValidCss(isValid, validationErrorKey) { 14268 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 14269 $element. 14270 removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). 14271 addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 14272 } 14273 14274 /** 14275 * @ngdoc function 14276 * @name ng.directive:ngModel.NgModelController#$setValidity 14277 * @methodOf ng.directive:ngModel.NgModelController 14278 * 14279 * @description 14280 * Change the validity state, and notifies the form when the control changes validity. (i.e. it 14281 * does not notify form if given validator is already marked as invalid). 14282 * 14283 * This method should be called by validators - i.e. the parser or formatter functions. 14284 * 14285 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign 14286 * to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
14287 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 14288 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 14289 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 14290 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). 14291 */ 14292 this.$setValidity = function(validationErrorKey, isValid) { 14293 if ($error[validationErrorKey] === !isValid) return; 14294 14295 if (isValid) { 14296 if ($error[validationErrorKey]) invalidCount--; 14297 if (!invalidCount) { 14298 toggleValidCss(true); 14299 this.$valid = true; 14300 this.$invalid = false; 14301 } 14302 } else { 14303 toggleValidCss(false); 14304 this.$invalid = true; 14305 this.$valid = false; 14306 invalidCount++; 14307 } 14308 14309 $error[validationErrorKey] = !isValid; 14310 toggleValidCss(isValid, validationErrorKey); 14311 14312 parentForm.$setValidity(validationErrorKey, isValid, this); 14313 }; 14314 14315 /** 14316 * @ngdoc function 14317 * @name ng.directive:ngModel.NgModelController#$setPristine 14318 * @methodOf ng.directive:ngModel.NgModelController 14319 * 14320 * @description 14321 * Sets the control to its pristine state. 14322 * 14323 * This method can be called to remove the 'ng-dirty' class and set the control to its pristine 14324 * state (ng-pristine class). 14325 */ 14326 this.$setPristine = function () { 14327 this.$dirty = false; 14328 this.$pristine = true; 14329 $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); 14330 }; 14331 14332 /** 14333 * @ngdoc function 14334 * @name ng.directive:ngModel.NgModelController#$setViewValue 14335 * @methodOf ng.directive:ngModel.NgModelController 14336 * 14337 * @description 14338 * Read a value from view. 14339 * 14340 * This method should be called from within a DOM event handler. 14341 * For example {@link ng.directive:input input} or 14342 * {@link ng.directive:select select} directives call it. 14343 * 14344 * It internally calls all `$parsers` (including validators) and updates the `$modelValue` and the actual model path. 14345 * Lastly it calls all registered change listeners. 14346 * 14347 * @param {string} value Value from the view. 14348 */ 14349 this.$setViewValue = function(value) { 14350 this.$viewValue = value; 14351 14352 // change to dirty 14353 if (this.$pristine) { 14354 this.$dirty = true; 14355 this.$pristine = false; 14356 $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); 14357 parentForm.$setDirty(); 14358 } 14359 14360 forEach(this.$parsers, function(fn) { 14361 value = fn(value); 14362 }); 14363 14364 if (this.$modelValue !== value) { 14365 this.$modelValue = value; 14366 ngModelSet($scope, value); 14367 forEach(this.$viewChangeListeners, function(listener) { 14368 try { 14369 listener(); 14370 } catch(e) { 14371 $exceptionHandler(e); 14372 } 14373 }) 14374 } 14375 }; 14376 14377 // model -> value 14378 var ctrl = this; 14379 14380 $scope.$watch(function ngModelWatch() { 14381 var value = ngModelGet($scope); 14382 14383 // if scope model value and ngModel value are out of sync 14384 if (ctrl.$modelValue !== value) { 14385 14386 var formatters = ctrl.$formatters, 14387 idx = formatters.length; 14388 14389 ctrl.$modelValue = value; 14390 while(idx--) { 14391 value = formatters[idx](value); 14392 } 14393 14394 if (ctrl.$viewValue !== value) { 14395 ctrl.$viewValue = value; 14396 ctrl.$render(); 14397 } 14398 } 14399 }); 14400}]; 14401 14402 14403/** 14404 * @ngdoc directive 14405 * @name ng.directive:ngModel 14406 * 14407 * @element input 14408 * 14409 * @description 14410 * Is a directive that tells Angular to do two-way data binding. It works together with `input`, 14411 * `select`, `textarea` and even custom form controls that use {@link ng.directive:ngModel.NgModelController 14412 * NgModelController} exposed by this directive. 14413 * 14414 * `ngModel` is responsible for: 14415 * 14416 * - binding the view into the model, which other directives such as `input`, `textarea` or `select` 14417 * require, 14418 * - providing validation behavior (i.e. required, number, email, url), 14419 * - keeping state of the control (valid/invalid, dirty/pristine, validation errors), 14420 * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`), 14421 * - register the control with parent {@link ng.directive:form form}. 14422 *
14423 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 14424 * current scope. If the property doesn't already exist on this scope, it will be created 14425 * implicitly and added to the scope. 14426 * 14427 * For basic examples, how to use `ngModel`, see: 14428 * 14429 * - {@link ng.directive:input input} 14430 * - {@link ng.directive:input.text text} 14431 * - {@link ng.directive:input.checkbox checkbox} 14432 * - {@link ng.directive:input.radio radio} 14433 * - {@link ng.directive:input.number number} 14434 * - {@link ng.directive:input.email email} 14435 * - {@link ng.directive:input.url url} 14436 * - {@link ng.directive:select select} 14437 * - {@link ng.directive:textarea textarea} 14438 * 14439 */ 14440var ngModelDirective = function() { 14441 return { 14442 require: ['ngModel', '^?form'], 14443 controller: NgModelController, 14444 link: function(scope, element, attr, ctrls) { 14445 // notify others, especially parent forms 14446 14447 var modelCtrl = ctrls[0], 14448 formCtrl = ctrls[1] || nullFormCtrl; 14449 14450 formCtrl.$addControl(modelCtrl); 14451 14452 element.on('$destroy', function() { 14453 formCtrl.$removeControl(modelCtrl); 14454 }); 14455 } 14456 }; 14457}; 14458 14459 14460/** 14461 * @ngdoc directive 14462 * @name ng.directive:ngChange 14463 * @restrict E 14464 * 14465 * @description 14466 * Evaluate given expression when user changes the input. 14467 * The expression is not evaluated when the value change is coming from the model. 14468 * 14469 * Note, this directive requires `ngModel` to be present. 14470 * 14471 * @element input 14472 * 14473 * @example 14474 * <doc:example> 14475 * <doc:source> 14476 * <script> 14477 * function Controller($scope) { 14478 * $scope.counter = 0; 14479 * $scope.change = function() { 14480 * $scope.counter++; 14481 * }; 14482 * } 14483 * </script> 14484 * <div ng-controller="Controller"> 14485 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 14486 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 14487 * <label for="ng-change-example2">Confirmed</label><br /> 14488 * debug = {{confirmed}}<br /> 14489 * counter = {{counter}} 14490 * </div> 14491 * </doc:source> 14492 * <doc:scenario> 14493 * it('should evaluate the expression if changing from view', function() { 14494 * expect(binding('counter')).toEqual('0'); 14495 * element('#ng-change-example1').click(); 14496 * expect(binding('counter')).toEqual('1'); 14497 * expect(binding('confirmed')).toEqual('true'); 14498 * }); 14499 * 14500 * it('should not evaluate the expression if changing from model', function() { 14501 * element('#ng-change-example2').click(); 14502 * expect(binding('counter')).toEqual('0'); 14503 * expect(binding('confirmed')).toEqual('true'); 14504 * }); 14505 * </doc:scenario> 14506 * </doc:example> 14507 */ 14508var ngChangeDirective = valueFn({ 14509 require: 'ngModel', 14510 link: function(scope, element, attr, ctrl) { 14511 ctrl.$viewChangeListeners.push(function() { 14512 scope.$eval(attr.ngChange); 14513 }); 14514 } 14515}); 14516 14517 14518var requiredDirective = function() { 14519 return { 14520 require: '?ngModel', 14521 link: function(scope, elm, attr, ctrl) { 14522 if (!ctrl) return; 14523 attr.required = true; // force truthy in case we are on non input element 14524 14525 var validator = function(value) { 14526 if (attr.required && (isEmpty(value) || value === false)) { 14527 ctrl.$setValidity('required', false); 14528 return; 14529 } else { 14530 ctrl.$setValidity('required', true); 14531 return value; 14532 } 14533 }; 14534 14535 ctrl.$formatters.push(validator); 14536 ctrl.$parsers.unshift(validator); 14537 14538 attr.$observe('required', function() { 14539 validator(ctrl.$viewValue); 14540 }); 14541 } 14542 }; 14543}; 14544 14545 14546/** 14547 * @ngdoc directive 14548 * @name ng.directive:ngList 14549 * 14550 * @description 14551 * Text input that converts between comma-separated string into an array of strings. 14552 * 14553 * @element input 14554 * @param {string=} ngList optional delimiter that should be used to split the value. If 14555 * specified in form `/something/` then the value will be converted into a regular expression. 14556 * 14557 * @example 14558 <doc:example> 14559 <doc:source> 14560 <script> 14561 function Ctrl($scope) { 14562 $scope.names = ['igor', 'misko', 'vojta']; 14563 } 14564 </script> 14565 <form name="myForm" ng-controller="Ctrl"> 14566 List: <input name="namesInput" ng-model="names" ng-list required>
14567 <span class="error" ng-show="myForm.namesInput.$error.required"> 14568 Required!</span> 14569 <br> 14570 <tt>names = {{names}}</tt><br/> 14571 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 14572 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 14573 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 14574 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 14575 </form> 14576 </doc:source> 14577 <doc:scenario> 14578 it('should initialize to model', function() { 14579 expect(binding('names')).toEqual('["igor","misko","vojta"]'); 14580 expect(binding('myForm.namesInput.$valid')).toEqual('true'); 14581 expect(element('span.error').css('display')).toBe('none'); 14582 }); 14583 14584 it('should be invalid if empty', function() { 14585 input('names').enter(''); 14586 expect(binding('names')).toEqual('[]'); 14587 expect(binding('myForm.namesInput.$valid')).toEqual('false'); 14588 expect(element('span.error').css('display')).not().toBe('none'); 14589 }); 14590 </doc:scenario> 14591 </doc:example> 14592 */ 14593var ngListDirective = function() { 14594 return { 14595 require: 'ngModel', 14596 link: function(scope, element, attr, ctrl) { 14597 var match = /\/(.*)\//.exec(attr.ngList), 14598 separator = match && new RegExp(match[1]) || attr.ngList || ','; 14599 14600 var parse = function(viewValue) { 14601 var list = []; 14602 14603 if (viewValue) { 14604 forEach(viewValue.split(separator), function(value) { 14605 if (value) list.push(trim(value)); 14606 }); 14607 } 14608 14609 return list; 14610 }; 14611 14612 ctrl.$parsers.push(parse); 14613 ctrl.$formatters.push(function(value) { 14614 if (isArray(value)) { 14615 return value.join(', '); 14616 } 14617 14618 return undefined; 14619 }); 14620 } 14621 }; 14622}; 14623 14624 14625var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 14626 14627var ngValueDirective = function() { 14628 return { 14629 priority: 100, 14630 compile: function(tpl, tplAttr) { 14631 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 14632 return function(scope, elm, attr) { 14633 attr.$set('value', scope.$eval(attr.ngValue)); 14634 }; 14635 } else { 14636 return function(scope, elm, attr) { 14637 scope.$watch(attr.ngValue, function valueWatchAction(value) { 14638 attr.$set('value', value); 14639 }); 14640 }; 14641 } 14642 } 14643 }; 14644}; 14645 14646/** 14647 * @ngdoc directive 14648 * @name ng.directive:ngBind 14649 * 14650 * @description 14651 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 14652 * with the value of a given expression, and to update the text content when the value of that 14653 * expression changes. 14654 * 14655 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 14656 * `{{ expression }}` which is similar but less verbose. 14657 * 14658 * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily 14659 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 14660 * element attribute, it makes the bindings invisible to the user while the page is loading. 14661 * 14662 * An alternative solution to this problem would be using the 14663 * {@link ng.directive:ngCloak ngCloak} directive. 14664 * 14665 * 14666 * @element ANY 14667 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 14668 * 14669 * @example 14670 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 14671 <doc:example> 14672 <doc:source> 14673 <script> 14674 function Ctrl($scope) { 14675 $scope.name = 'Whirled'; 14676 } 14677 </script> 14678 <div ng-controller="Ctrl"> 14679 Enter name: <input type="text" ng-model="name"><br> 14680 Hello <span ng-bind="name"></span>! 14681 </div> 14682 </doc:source> 14683 <doc:scenario> 14684 it('should check ng-bind', function() { 14685 expect(using('.doc-example-live').binding('name')).toBe('Whirled'); 14686 using('.doc-example-live').input('name').enter('world'); 14687 expect(using('.doc-example-live').binding('name')).toBe('world'); 14688 }); 14689 </doc:scenario> 14690 </doc:example> 14691 */ 14692var ngBindDirective = ngDirective(function(scope, element, attr) { 14693 element.addClass('ng-binding').data('$binding', attr.ngBind); 14694 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 14695 element.text(value == undefined ? '' : value); 14696 }); 14697}); 14698 14699 14700/** 14701 * @ngdoc directive 14702 * @name ng.directive:ngBindTemplate 14703 * 14704 * @description 14705 * The `ngBindTemplate` directive specifies that the element 14706 * text content should be replaced with the interpolation of the template 14707 * in the `ngBindTemplate` attribute.
14708 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 14709 * expressions. This directive is needed since some HTML elements 14710 * (such as TITLE and OPTION) cannot contain SPAN elements. 14711 * 14712 * @element ANY 14713 * @param {string} ngBindTemplate template of form 14714 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 14715 * 14716 * @example 14717 * Try it here: enter text in text box and watch the greeting change. 14718 <doc:example> 14719 <doc:source> 14720 <script> 14721 function Ctrl($scope) { 14722 $scope.salutation = 'Hello'; 14723 $scope.name = 'World'; 14724 } 14725 </script> 14726 <div ng-controller="Ctrl"> 14727 Salutation: <input type="text" ng-model="salutation"><br> 14728 Name: <input type="text" ng-model="name"><br> 14729 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 14730 </div> 14731 </doc:source> 14732 <doc:scenario> 14733 it('should check ng-bind', function() { 14734 expect(using('.doc-example-live').binding('salutation')). 14735 toBe('Hello'); 14736 expect(using('.doc-example-live').binding('name')). 14737 toBe('World'); 14738 using('.doc-example-live').input('salutation').enter('Greetings'); 14739 using('.doc-example-live').input('name').enter('user'); 14740 expect(using('.doc-example-live').binding('salutation')). 14741 toBe('Greetings'); 14742 expect(using('.doc-example-live').binding('name')). 14743 toBe('user'); 14744 }); 14745 </doc:scenario> 14746 </doc:example> 14747 */ 14748var ngBindTemplateDirective = ['$interpolate', function($interpolate) { 14749 return function(scope, element, attr) { 14750 // TODO: move this to scenario runner 14751 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 14752 element.addClass('ng-binding').data('$binding', interpolateFn); 14753 attr.$observe('ngBindTemplate', function(value) { 14754 element.text(value); 14755 }); 14756 } 14757}]; 14758 14759 14760/** 14761 * @ngdoc directive 14762 * @name ng.directive:ngBindHtml 14763 * 14764 * @description 14765 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current 14766 * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link 14767 * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` 14768 * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in 14769 * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to 14770 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 14771 * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. 14772 * 14773 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 14774 * will have an exception (instead of an exploit.) 14775 * 14776 * @element ANY 14777 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 14778 */ 14779var ngBindHtmlDirective = ['$sce', function($sce) { 14780 return function(scope, element, attr) { 14781 element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 14782 scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function ngBindHtmlWatchAction(value) { 14783 element.html(value || ''); 14784 }); 14785 }; 14786}]; 14787 14788function classDirective(name, selector) { 14789 name = 'ngClass' + name; 14790 return function() { 14791 return { 14792 restrict: 'AC', 14793 link: function(scope, element, attr) { 14794 var oldVal = undefined; 14795 14796 scope.$watch(attr[name], ngClassWatchAction, true); 14797 14798 attr.$observe('class', function(value) { 14799 ngClassWatchAction(scope.$eval(attr[name])); 14800 }); 14801 14802 14803 if (name !== 'ngClass') { 14804 scope.$watch('$index', function($index, old$index) { 14805 var mod = $index & 1; 14806 if (mod !== old$index & 1) { 14807 if (mod === selector) { 14808 addClass(scope.$eval(attr[name])); 14809 } else { 14810 removeClass(scope.$eval(attr[name])); 14811 } 14812 } 14813 }); 14814 } 14815 14816 14817 function ngClassWatchAction(newVal) { 14818 if (selector === true || scope.$index % 2 === selector) { 14819 if (oldVal && !equals(newVal,oldVal)) { 14820 removeClass(oldVal); 14821 } 14822 addClass(newVal); 14823 } 14824 oldVal = copy(newVal); 14825 } 14826 14827 14828 function removeClass(classVal) { 14829 attr.$removeClass(flattenClasses(classVal)); 14830 } 14831 14832 14833 function addClass(classVal) { 14834 attr.$addClass(flattenClasses(classVal)); 14835 } 14836 14837 function flattenClasses(classVal) { 14838 if(isArray(classVal)) { 14839 return classVal.join(' '); 14840 } else if (isObject(classVal)) { 14841 var classes = [], i = 0;
14842 forEach(classVal, function(v, k) { 14843 if (v) { 14844 classes.push(k); 14845 } 14846 }); 14847 return classes.join(' '); 14848 } 14849 14850 return classVal; 14851 }; 14852 } 14853 }; 14854 }; 14855} 14856 14857/** 14858 * @ngdoc directive 14859 * @name ng.directive:ngClass 14860 * 14861 * @description 14862 * The `ngClass` allows you to set CSS classes on HTML an element, dynamically, by databinding 14863 * an expression that represents all classes to be added. 14864 * 14865 * The directive won't add duplicate classes if a particular class was already set. 14866 * 14867 * When the expression changes, the previously added classes are removed and only then the 14868 * new classes are added. 14869 * 14870 * @animations 14871 * add - happens just before the class is applied to the element 14872 * remove - happens just before the class is removed from the element 14873 * 14874 * @element ANY 14875 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 14876 * of the evaluation can be a string representing space delimited class 14877 * names, an array, or a map of class names to boolean values. In the case of a map, the 14878 * names of the properties whose values are truthy will be added as css classes to the 14879 * element. 14880 * 14881 * @example Example that demostrates basic bindings via ngClass directive. 14882 <example> 14883 <file name="index.html"> 14884 <p ng-class="{strike: strike, bold: bold, red: red}">Map Syntax Example</p> 14885 <input type="checkbox" ng-model="bold"> bold 14886 <input type="checkbox" ng-model="strike"> strike 14887 <input type="checkbox" ng-model="red"> red 14888 <hr> 14889 <p ng-class="style">Using String Syntax</p> 14890 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 14891 <hr> 14892 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 14893 <input ng-model="style1" placeholder="Type: bold"><br> 14894 <input ng-model="style2" placeholder="Type: strike"><br> 14895 <input ng-model="style3" placeholder="Type: red"><br> 14896 </file> 14897 <file name="style.css"> 14898 .strike { 14899 text-decoration: line-through; 14900 } 14901 .bold { 14902 font-weight: bold; 14903 } 14904 .red { 14905 color: red; 14906 } 14907 </file> 14908 <file name="scenario.js"> 14909 it('should let you toggle the class', function() { 14910 14911 expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/bold/); 14912 expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/red/); 14913 14914 input('bold').check(); 14915 expect(element('.doc-example-live p:first').prop('className')).toMatch(/bold/); 14916 14917 input('red').check(); 14918 expect(element('.doc-example-live p:first').prop('className')).toMatch(/red/); 14919 }); 14920 14921 it('should let you toggle string example', function() { 14922 expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe(''); 14923 input('style').enter('red'); 14924 expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('red'); 14925 }); 14926 14927 it('array example should have 3 classes', function() { 14928 expect(element('.doc-example-live p:last').prop('className')).toBe(''); 14929 input('style1').enter('bold'); 14930 input('style2').enter('strike'); 14931 input('style3').enter('red'); 14932 expect(element('.doc-example-live p:last').prop('className')).toBe('bold strike red'); 14933 }); 14934 </file> 14935 </example> 14936 14937 ## Animations 14938 14939 Example that demostrates how addition and removal of classes can be animated. 14940 14941 <example animations="true"> 14942 <file name="index.html"> 14943 <input type="button" value="set" ng-click="myVar='my-class'"> 14944 <input type="button" value="clear" ng-click="myVar=''"> 14945 <br> 14946 <span ng-class="myVar">Sample Text</span> 14947 </file> 14948 <file name="style.css"> 14949 .my-class-add, .my-class-remove { 14950 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 14951 -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 14952 -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 14953 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 14954 } 14955 14956 .my-class, 14957 .my-class-add.my-class-add-active { 14958 color: red; 14959 font-size:3em; 14960 } 14961 14962 .my-class-remove.my-class-remove-active { 14963 font-size:1.0em; 14964 color:black; 14965 } 14966 </file> 14967 <file name="scenario.js"> 14968 it('should check ng-class', function() { 14969 expect(element('.doc-example-live span').prop('className')).not(). 14970 toMatch(/my-class/); 14971
14972 using('.doc-example-live').element(':button:first').click(); 14973 14974 expect(element('.doc-example-live span').prop('className')). 14975 toMatch(/my-class/); 14976 14977 using('.doc-example-live').element(':button:last').click(); 14978 14979 expect(element('.doc-example-live span').prop('className')).not(). 14980 toMatch(/my-class/); 14981 }); 14982 </file> 14983 </example> 14984 */ 14985var ngClassDirective = classDirective('', true); 14986 14987/** 14988 * @ngdoc directive 14989 * @name ng.directive:ngClassOdd 14990 * 14991 * @description 14992 * The `ngClassOdd` and `ngClassEven` directives work exactly as 14993 * {@link ng.directive:ngClass ngClass}, except it works in 14994 * conjunction with `ngRepeat` and takes affect only on odd (even) rows. 14995 * 14996 * This directive can be applied only within a scope of an 14997 * {@link ng.directive:ngRepeat ngRepeat}. 14998 * 14999 * @element ANY 15000 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 15001 * of the evaluation can be a string representing space delimited class names or an array. 15002 * 15003 * @example 15004 <example> 15005 <file name="index.html"> 15006 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 15007 <li ng-repeat="name in names"> 15008 <span ng-class-odd="'odd'" ng-class-even="'even'"> 15009 {{name}} 15010 </span> 15011 </li> 15012 </ol> 15013 </file> 15014 <file name="style.css"> 15015 .odd { 15016 color: red; 15017 } 15018 .even { 15019 color: blue; 15020 } 15021 </file> 15022 <file name="scenario.js"> 15023 it('should check ng-class-odd and ng-class-even', function() { 15024 expect(element('.doc-example-live li:first span').prop('className')). 15025 toMatch(/odd/); 15026 expect(element('.doc-example-live li:last span').prop('className')). 15027 toMatch(/even/); 15028 }); 15029 </file> 15030 </example> 15031 */ 15032var ngClassOddDirective = classDirective('Odd', 0); 15033 15034/** 15035 * @ngdoc directive 15036 * @name ng.directive:ngClassEven 15037 * 15038 * @description 15039 * The `ngClassOdd` and `ngClassEven` directives work exactly as 15040 * {@link ng.directive:ngClass ngClass}, except it works in 15041 * conjunction with `ngRepeat` and takes affect only on odd (even) rows. 15042 * 15043 * This directive can be applied only within a scope of an 15044 * {@link ng.directive:ngRepeat ngRepeat}. 15045 * 15046 * @element ANY 15047 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 15048 * result of the evaluation can be a string representing space delimited class names or an array. 15049 * 15050 * @example 15051 <example> 15052 <file name="index.html"> 15053 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 15054 <li ng-repeat="name in names"> 15055 <span ng-class-odd="'odd'" ng-class-even="'even'"> 15056 {{name}} 15057 </span> 15058 </li> 15059 </ol> 15060 </file> 15061 <file name="style.css"> 15062 .odd { 15063 color: red; 15064 } 15065 .even { 15066 color: blue; 15067 } 15068 </file> 15069 <file name="scenario.js"> 15070 it('should check ng-class-odd and ng-class-even', function() { 15071 expect(element('.doc-example-live li:first span').prop('className')). 15072 toMatch(/odd/); 15073 expect(element('.doc-example-live li:last span').prop('className')). 15074 toMatch(/even/); 15075 }); 15076 </file> 15077 </example> 15078 */ 15079var ngClassEvenDirective = classDirective('Even', 1); 15080 15081/** 15082 * @ngdoc directive 15083 * @name ng.directive:ngCloak 15084 * 15085 * @description 15086 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 15087 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 15088 * directive to avoid the undesirable flicker effect caused by the html template display. 15089 * 15090 * The directive can be applied to the `<body>` element, but typically a fine-grained application is 15091 * preferred in order to benefit from progressive rendering of the browser view. 15092 * 15093 * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and 15094 * `angular.min.js` files. Following is the css rule: 15095 * 15096 * <pre> 15097 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 15098 * display: none !important; 15099 * } 15100 * </pre> 15101 * 15102 * When this css rule is loaded by the browser, all html elements (including their children) that 15103 * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive 15104 * during the compilation of the template it deletes the `ngCloak` element attribute, which 15105 * makes the compiled element visible. 15106 * 15107 * For the best result, `angular.js` script must be loaded in the head section of the html file; 15108 * alternatively, the css rule (above) must be included in the external stylesheet of the 15109 * application. 15110 * 15111 * Legacy browsers, like IE7, do not provide attribute selector support (a
15111dded in CSS 2.1) so they 15112 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 15113 * class `ngCloak` in addition to `ngCloak` directive as shown in the example below. 15114 * 15115 * @element ANY 15116 * 15117 * @example 15118 <doc:example> 15119 <doc:source> 15120 <div id="template1" ng-cloak>{{ 'hello' }}</div> 15121 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 15122 </doc:source> 15123 <doc:scenario> 15124 it('should remove the template directive and css class', function() { 15125 expect(element('.doc-example-live #template1').attr('ng-cloak')). 15126 not().toBeDefined(); 15127 expect(element('.doc-example-live #template2').attr('ng-cloak')). 15128 not().toBeDefined(); 15129 }); 15130 </doc:scenario> 15131 </doc:example> 15132 * 15133 */ 15134var ngCloakDirective = ngDirective({ 15135 compile: function(element, attr) { 15136 attr.$set('ngCloak', undefined); 15137 element.removeClass('ng-cloak'); 15138 } 15139}); 15140 15141/** 15142 * @ngdoc directive 15143 * @name ng.directive:ngController 15144 * 15145 * @description 15146 * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular 15147 * supports the principles behind the Model-View-Controller design pattern. 15148 * 15149 * MVC components in angular: 15150 * 15151 * * Model ââ¬â The Model is data in scope properties; scopes are attached to the DOM. 15152 * * View ââ¬â The template (HTML with data bindings) is rendered into the View. 15153 * * Controller ââ¬â The `ngController` directive specifies a Controller class; the class has 15154 * methods that typically express the business logic behind the application. 15155 * 15156 * Note that an alternative way to define controllers is via the {@link ngRoute.$route $route} service. 15157 * 15158 * @element ANY 15159 * @scope 15160 * @param {expression} ngController Name of a globally accessible constructor function or an 15161 * {@link guide/expression expression} that on the current scope evaluates to a 15162 * constructor function. The controller instance can further be published into the scope 15163 * by adding `as localName` the controller name attribute. 15164 * 15165 * @example 15166 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 15167 * greeting are methods declared on the controller (see source tab). These methods can 15168 * easily be called from the angular markup. Notice that the scope becomes the `this` for the 15169 * controller's instance. This allows for easy access to the view data from the controller. Also 15170 * notice that any changes to the data are automatically reflected in the View without the need 15171 * for a manual update. The example is included in two different declaration styles based on 15172 * your style preferences. 15173 <doc:example> 15174 <doc:source> 15175 <script> 15176 function SettingsController1() { 15177 this.name = "John Smith"; 15178 this.contacts = [ 15179 {type: 'phone', value: '408 555 1212'}, 15180 {type: 'email', value: '[email protected]'} ]; 15181 }; 15182 15183 SettingsController1.prototype.greet = function() { 15184 alert(this.name); 15185 }; 15186 15187 SettingsController1.prototype.addContact = function() { 15188 this.contacts.push({type: 'email', value: '[email protected]'}); 15189 }; 15190 15191 SettingsController1.prototype.removeContact = function(contactToRemove) { 15192 var index = this.contacts.indexOf(contactToRemove); 15193 this.contacts.splice(index, 1); 15194 }; 15195 15196 SettingsController1.prototype.clearContact = function(contact) { 15197 contact.type = 'phone'; 15198 contact.value = ''; 15199 }; 15200 </script> 15201 <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 15202 Name: <input type="text" ng-model="settings.name"/> 15203 [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 15204 Contact: 15205 <ul> 15206 <li ng-repeat="contact in settings.contacts"> 15207 <select ng-model="contact.type"> 15208 <option>phone</option> 15209 <option>email</option> 15210 </select> 15211 <input type="text" ng-model="contact.value"/> 15212 [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 15213 | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 15214 </li> 15215 <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 15216 </ul> 15217 </div>
15218 </doc:source> 15219 <doc:scenario> 15220 it('should check controller as', function() { 15221 expect(element('#ctrl-as-exmpl>:input').val()).toBe('John Smith'); 15222 expect(element('#ctrl-as-exmpl li:nth-child(1) input').val()) 15223 .toBe('408 555 1212'); 15224 expect(element('#ctrl-as-exmpl li:nth-child(2) input').val()) 15225 .toBe('[email protected]'); 15226 15227 element('#ctrl-as-exmpl li:first a:contains("clear")').click(); 15228 expect(element('#ctrl-as-exmpl li:first input').val()).toBe(''); 15229 15230 element('#ctrl-as-exmpl li:last a:contains("add")').click(); 15231 expect(element('#ctrl-as-exmpl li:nth-child(3) input').val()) 15232 .toBe('[email protected]'); 15233 }); 15234 </doc:scenario> 15235 </doc:example> 15236 <doc:example> 15237 <doc:source> 15238 <script> 15239 function SettingsController2($scope) { 15240 $scope.name = "John Smith"; 15241 $scope.contacts = [ 15242 {type:'phone', value:'408 555 1212'}, 15243 {type:'email', value:'[email protected]'} ]; 15244 15245 $scope.greet = function() { 15246 alert(this.name); 15247 }; 15248 15249 $scope.addContact = function() { 15250 this.contacts.push({type:'email', value:'[email protected]'}); 15251 }; 15252 15253 $scope.removeContact = function(contactToRemove) { 15254 var index = this.contacts.indexOf(contactToRemove); 15255 this.contacts.splice(index, 1); 15256 }; 15257 15258 $scope.clearContact = function(contact) { 15259 contact.type = 'phone'; 15260 contact.value = ''; 15261 }; 15262 } 15263 </script> 15264 <div id="ctrl-exmpl" ng-controller="SettingsController2"> 15265 Name: <input type="text" ng-model="name"/> 15266 [ <a href="" ng-click="greet()">greet</a> ]<br/> 15267 Contact: 15268 <ul> 15269 <li ng-repeat="contact in contacts"> 15270 <select ng-model="contact.type"> 15271 <option>phone</option> 15272 <option>email</option> 15273 </select> 15274 <input type="text" ng-model="contact.value"/> 15275 [ <a href="" ng-click="clearContact(contact)">clear</a> 15276 | <a href="" ng-click="removeContact(contact)">X</a> ] 15277 </li> 15278 <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 15279 </ul> 15280 </div> 15281 </doc:source> 15282 <doc:scenario> 15283 it('should check controller', function() { 15284 expect(element('#ctrl-exmpl>:input').val()).toBe('John Smith'); 15285 expect(element('#ctrl-exmpl li:nth-child(1) input').val()) 15286 .toBe('408 555 1212'); 15287 expect(element('#ctrl-exmpl li:nth-child(2) input').val()) 15288 .toBe('[email protected]'); 15289 15290 element('#ctrl-exmpl li:first a:contains("clear")').click(); 15291 expect(element('#ctrl-exmpl li:first input').val()).toBe(''); 15292 15293 element('#ctrl-exmpl li:last a:contains("add")').click(); 15294 expect(element('#ctrl-exmpl li:nth-child(3) input').val()) 15295 .toBe('[email protected]'); 15296 }); 15297 </doc:scenario> 15298 </doc:example> 15299 15300 */ 15301var ngControllerDirective = [function() { 15302 return { 15303 scope: true, 15304 controller: '@' 15305 }; 15306}]; 15307 15308/** 15309 * @ngdoc directive 15310 * @name ng.directive:ngCsp 15311 * @priority 1000 15312 * 15313 * @element html 15314 * @description 15315 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 15316 * 15317 * This is necessary when developing things like Google Chrome Extensions. 15318 * 15319 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 15320 * For us to be compatible, we just need to implement the "getterFn" in $parse without violating 15321 * any of these restrictions. 15322 * 15323 * AngularJS uses `Function(string)` generated functions as a speed optimization. By applying `ngCsp` 15324 * it is be possible to opt into the CSP compatible mode. When this mode is on AngularJS will 15325 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 15326 * be raised. 15327 * 15328 * In order to use this feature put `ngCsp` directive on the root element of the application. 15329 * 15330 * @example 15331 * This example shows how to apply the `ngCsp` directive to the `html` tag. 15332 <pre> 15333 <!doctype html> 15334 <html ng-app ng-csp> 15335 ... 15336 ... 15337 </html> 15338 </pre> 15339 */ 15340 15341var ngCspDirective = ['$sniffer', function($sniffer) { 15342 return { 15343 priority: 1000, 15344 compile: function() { 15345 $sniffer.csp = true; 15346 } 15347 }; 15348}]; 15349 15350/** 15351 * @ngdoc directive 15352 * @name ng.directive:ngClick 15353 * 15354 * @description 15355 * The ngClick allows you to specify custom behavior when 15356 * element is clicked. 15357 * 15358 * @element ANY 15359 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 15360 * click. (Event object is available as `$event`) 15361 * 15362 * @example 15363 <doc:example> 15364 <doc:source> 15365 <button ng-click="count = count + 1" ng-init="count=0"> 15366 Increment 15367 </button> 15368 count: {{count}}
15369 </doc:source> 15370 <doc:scenario> 15371 it('should check ng-click', function() { 15372 expect(binding('count')).toBe('0'); 15373 element('.doc-example-live :button').click(); 15374 expect(binding('count')).toBe('1'); 15375 }); 15376 </doc:scenario> 15377 </doc:example> 15378 */ 15379/* 15380 * A directive that allows creation of custom onclick handlers that are defined as angular 15381 * expressions and are compiled and executed within the current scope. 15382 * 15383 * Events that are handled via these handler are always configured not to propagate further. 15384 */ 15385var ngEventDirectives = {}; 15386forEach( 15387 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur'.split(' '), 15388 function(name) { 15389 var directiveName = directiveNormalize('ng-' + name); 15390 ngEventDirectives[directiveName] = ['$parse', function($parse) { 15391 return function(scope, element, attr) { 15392 var fn = $parse(attr[directiveName]); 15393 element.on(lowercase(name), function(event) { 15394 scope.$apply(function() { 15395 fn(scope, {$event:event}); 15396 }); 15397 }); 15398 }; 15399 }]; 15400 } 15401); 15402 15403/** 15404 * @ngdoc directive 15405 * @name ng.directive:ngDblclick 15406 * 15407 * @description 15408 * The `ngDblclick` directive allows you to specify custom behavior on dblclick event. 15409 * 15410 * @element ANY 15411 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 15412 * dblclick. (Event object is available as `$event`) 15413 * 15414 * @example 15415 * See {@link ng.directive:ngClick ngClick} 15416 */ 15417 15418 15419/** 15420 * @ngdoc directive 15421 * @name ng.directive:ngMousedown 15422 * 15423 * @description 15424 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 15425 * 15426 * @element ANY 15427 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 15428 * mousedown. (Event object is available as `$event`) 15429 * 15430 * @example 15431 * See {@link ng.directive:ngClick ngClick} 15432 */ 15433 15434 15435/** 15436 * @ngdoc directive 15437 * @name ng.directive:ngMouseup 15438 * 15439 * @description 15440 * Specify custom behavior on mouseup event. 15441 * 15442 * @element ANY 15443 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 15444 * mouseup. (Event object is available as `$event`) 15445 * 15446 * @example 15447 * See {@link ng.directive:ngClick ngClick} 15448 */ 15449 15450/** 15451 * @ngdoc directive 15452 * @name ng.directive:ngMouseover 15453 * 15454 * @description 15455 * Specify custom behavior on mouseover event. 15456 * 15457 * @element ANY 15458 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 15459 * mouseover. (Event object is available as `$event`) 15460 * 15461 * @example 15462 * See {@link ng.directive:ngClick ngClick} 15463 */ 15464 15465 15466/** 15467 * @ngdoc directive 15468 * @name ng.directive:ngMouseenter 15469 * 15470 * @description 15471 * Specify custom behavior on mouseenter event. 15472 * 15473 * @element ANY 15474 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 15475 * mouseenter. (Event object is available as `$event`) 15476 * 15477 * @example 15478 * See {@link ng.directive:ngClick ngClick} 15479 */ 15480 15481 15482/** 15483 * @ngdoc directive 15484 * @name ng.directive:ngMouseleave 15485 * 15486 * @description 15487 * Specify custom behavior on mouseleave event. 15488 * 15489 * @element ANY 15490 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 15491 * mouseleave. (Event object is available as `$event`) 15492 * 15493 * @example 15494 * See {@link ng.directive:ngClick ngClick} 15495 */ 15496 15497 15498/** 15499 * @ngdoc directive 15500 * @name ng.directive:ngMousemove 15501 * 15502 * @description 15503 * Specify custom behavior on mousemove event. 15504 * 15505 * @element ANY 15506 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 15507 * mousemove. (Event object is available as `$event`) 15508 * 15509 * @example 15510 * See {@link ng.directive:ngClick ngClick} 15511 */ 15512 15513 15514/** 15515 * @ngdoc directive 15516 * @name ng.directive:ngKeydown 15517 * 15518 * @description 15519 * Specify custom behavior on keydown event. 15520 * 15521 * @element ANY 15522 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 15523 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 15524 * 15525 * @example 15526 * See {@link ng.directive:ngClick ngClick} 15527 */ 15528 15529 15530/** 15531 * @ngdoc directive 15532 * @name ng.directive:ngKeyup 15533 * 15534 * @description 15535 * Specify custom behavior on keyup event. 15536 * 15537 * @element ANY 15538 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 15539 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 15540 * 15541 * @example 15542 * See {@link ng.directive:ngClick ngClick}
15542 15543 */ 15544 15545 15546/** 15547 * @ngdoc directive 15548 * @name ng.directive:ngKeypress 15549 * 15550 * @description 15551 * Specify custom behavior on keypress event. 15552 * 15553 * @element ANY 15554 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 15555 * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 15556 * 15557 * @example 15558 * See {@link ng.directive:ngClick ngClick} 15559 */ 15560 15561 15562/** 15563 * @ngdoc directive 15564 * @name ng.directive:ngSubmit 15565 * 15566 * @description 15567 * Enables binding angular expressions to onsubmit events. 15568 * 15569 * Additionally it prevents the default action (which for form means sending the request to the 15570 * server and reloading the current page) **but only if the form does not contain an `action` 15571 * attribute**. 15572 * 15573 * @element form 15574 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`) 15575 * 15576 * @example 15577 <doc:example> 15578 <doc:source> 15579 <script> 15580 function Ctrl($scope) { 15581 $scope.list = []; 15582 $scope.text = 'hello'; 15583 $scope.submit = function() { 15584 if (this.text) { 15585 this.list.push(this.text); 15586 this.text = ''; 15587 } 15588 }; 15589 } 15590 </script> 15591 <form ng-submit="submit()" ng-controller="Ctrl"> 15592 Enter text and hit enter: 15593 <input type="text" ng-model="text" name="text" /> 15594 <input type="submit" id="submit" value="Submit" /> 15595 <pre>list={{list}}</pre> 15596 </form>
15597 </doc:source> 15598 <doc:scenario> 15599 it('should check ng-submit', function() { 15600 expect(binding('list')).toBe('[]'); 15601 element('.doc-example-live #submit').click(); 15602 expect(binding('list')).toBe('["hello"]'); 15603 expect(input('text').val()).toBe(''); 15604 }); 15605 it('should ignore empty strings', function() { 15606 expect(binding('list')).toBe('[]'); 15607 element('.doc-example-live #submit').click(); 15608 element('.doc-example-live #submit').click(); 15609 expect(binding('list')).toBe('["hello"]'); 15610 }); 15611 </doc:scenario> 15612 </doc:example> 15613 */ 15614 15615/** 15616 * @ngdoc directive 15617 * @name ng.directive:ngFocus 15618 * 15619 * @description 15620 * Specify custom behavior on focus event. 15621 * 15622 * @element window, input, select, textarea, a 15623 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 15624 * focus. (Event object is available as `$event`) 15625 * 15626 * @example 15627 * See {@link ng.directive:ngClick ngClick} 15628 */ 15629 15630/** 15631 * @ngdoc directive 15632 * @name ng.directive:ngBlur 15633 * 15634 * @description 15635 * Specify custom behavior on blur event. 15636 * 15637 * @element window, input, select, textarea, a 15638 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 15639 * blur. (Event object is available as `$event`) 15640 * 15641 * @example 15642 * See {@link ng.directive:ngClick ngClick} 15643 */ 15644 15645/** 15646 * @ngdoc directive 15647 * @name ng.directive:ngIf 15648 * @restrict A 15649 * 15650 * @description 15651 * The `ngIf` directive removes and recreates a portion of the DOM tree (HTML) 15652 * conditionally based on **"falsy"** and **"truthy"** values, respectively, evaluated within 15653 * an {expression}. In other words, if the expression assigned to **ngIf evaluates to a false 15654 * value** then **the element is removed from the DOM** and **if true** then **a clone of the 15655 * element is reinserted into the DOM**. 15656 * 15657 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 15658 * element in the DOM rather than changing its visibility via the `display` css property. A common 15659 * case when this difference is significant is when using css selectors that rely on an element's 15660 * position within the DOM (HTML), such as the `:first-child` or `:last-child` pseudo-classes. 15661 * 15662 * Note that **when an element is removed using ngIf its scope is destroyed** and **a new scope 15663 * is created when the element is restored**. The scope created within `ngIf` inherits from 15664 * its parent scope using 15665 * {@link https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance prototypal inheritance}. 15666 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 15667 * a javascript primitive defined in the parent scope. In this case any modifications made to the 15668 * variable within the child scope will override (hide) the value in the parent scope. 15669 * 15670 * Also, `ngIf` recreates elements using their compiled state. An example scenario of this behavior 15671 * is if an element's class attribute is directly modified after it's compiled, using something like 15672 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 15673 * the added class will be lost because the original compiled state is used to regenerate the element. 15674 * 15675 * Additionally, you can provide animations via the ngAnimate module to animate the **enter** 15676 * and **leave** effects. 15677 * 15678 * @animations 15679 * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container 15680 * leave - happens just before the ngIf contents are removed from the DOM 15681 * 15682 * @element ANY 15683 * @scope 15684 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 15685 * the element is removed from the DOM tree (HTML). 15686 * 15687 * @example 15688 <example animations="true"> 15689 <file name="index.html"> 15690 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 15691 Show when checked: 15692 <span ng-if="checked" class="animate-if"> 15693 I'm removed when the checkbox is unchecked. 15694 </span> 15695 </file> 15696 <file name="animations.css"> 15697 .animate-if { 15698 background:white; 15699 border:1px solid black; 15700 padding:10px; 15701 } 15702 15703 .animate-if.ng-enter, .animate-if.ng-leave { 15704 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15705 -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15706 -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15707 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15708 } 15709 15710 .animate-if.ng-enter, 15711 .animate-if.ng-leave.ng-leave-active { 15712 opacity:0; 15713 } 15714 15715 .animate-if.ng-enter.ng-enter-active, 15716 .animate-if.ng-leave { 15717 opacity:1; 15718 } 15719 </file> 15720 </example> 15721 */ 15722var ngIfDirective = ['$animate', function($animate) { 15723 return { 15724 transclude: 'element', 15725 priority: 1000, 15726 terminal: true, 15727 restrict: 'A', 15728 compile: function (element, attr, transclude) { 15729 return function ($scope, $element, $attr) { 15730 var childElement, childScope; 15731 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 15732 if (childElement) { 15733 $animate.leave(childElement); 15734 childElement = undefined; 15735 } 15736 if (childScope) { 15737 childScope.$destroy(); 15738 childScope = undefined; 15739 } 15740 if (toBoolean(value)) { 15741 childScope = $scope.$new(); 15742 transclude(childScope, function (clone) { 15743 childElement = clone; 15744 $animate.enter(clone, $element.parent(), $element); 15745 }); 15746 } 15747 }); 15748 } 15749 } 15750 } 15751}]; 15752 15753/** 15754 * @ngdoc directive 15755 * @name ng.directive:ngInclude 15756 * @restrict ECA 15757 * 15758 * @description 15759 * Fetches, compiles and includes an external HTML fragment. 15760 * 15761 * Keep in mind that: 15762 * 15763 * - by default, the template URL is restricted to the same domain and protocol as the 15764 * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 15765 * $sce.getTrustedResourceUrl} on it. To load templates from other domains and/or protocols, 15766 * you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 15767 * {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. Refer Angular's {@link 15768 * ng.$sce Strict Contextual Escaping}. 15769 * - in addition, the browser's 15770 * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest 15771 * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing 15772 * (CORS)} policy apply that may further restrict whether the template is successfully loaded. 15773 * (e.g. ngInclude won't work for cross-domain requests on all browsers and for `file://` 15774 * access on some browsers) 15775 * 15776 * @animations 15777 * enter - animation is used to bring new content into the browser. 15778 * leave - animation is used to animate existing content away. 15779 * 15780 * The enter and leave animation occur concurrently. 15781 * 15782 * @scope 15783 * 15784 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 15785 * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. 15786 * @param {string=} onload Expression to evaluate when a new partial is loaded. 15787 * 15788 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 15789 * $anchorScroll} to scroll the viewport after the content is loaded. 15790 * 15791 * - If the attribute is not set, disable scrolling. 15792 * - If the attribute is set without value, enable scrolling. 15793 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 15794 * 15795 * @example 15796 <example animations="true"> 15797 <file name="index.html"> 15798 <div ng-controller="Ctrl"> 15799 <select ng-model="template" ng-options="t.name for t in templates"> 15800 <option value="">(blank)</option> 15801 </select> 15802 url of the template: <tt>{{template.url}}</tt> 15803 <hr/> 15804 <div class="example-animate-container"> 15805 <div class="include-example" ng-include="template.url"></div> 15806 </div>
15807 </div> 15808 </file> 15809 <file name="script.js"> 15810 function Ctrl($scope) { 15811 $scope.templates = 15812 [ { name: 'template1.html', url: 'template1.html'} 15813 , { name: 'template2.html', url: 'template2.html'} ]; 15814 $scope.template = $scope.templates[0]; 15815 } 15816 </file> 15817 <file name="template1.html"> 15818 Content of template1.html 15819 </file> 15820 <file name="template2.html"> 15821 Content of template2.html 15822 </file> 15823 <file name="animations.css"> 15824 .example-animate-container { 15825 position:relative; 15826 background:white; 15827 border:1px solid black; 15828 height:40px; 15829 overflow:hidden; 15830 } 15831 15832 .example-animate-container > div { 15833 padding:10px; 15834 } 15835 15836 .include-example.ng-enter, .include-example.ng-leave { 15837 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15838 -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15839 -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15840 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 15841 15842 position:absolute; 15843 top:0; 15844 left:0; 15845 right:0; 15846 bottom:0; 15847 display:block; 15848 padding:10px; 15849 } 15850 15851 .include-example.ng-enter { 15852 top:-50px; 15853 } 15854 .include-example.ng-enter.ng-enter-active { 15855 top:0; 15856 } 15857 15858 .include-example.ng-leave { 15859 top:0; 15860 } 15861 .include-example.ng-leave.ng-leave-active { 15862 top:50px; 15863 } 15864 </file> 15865 <file name="scenario.js"> 15866 it('should load template1.html', function() { 15867 expect(element('.doc-example-live [ng-include]').text()). 15868 toMatch(/Content of template1.html/); 15869 }); 15870 it('should load template2.html', function() { 15871 select('template').option('1'); 15872 expect(element('.doc-example-live [ng-include]').text()). 15873 toMatch(/Content of template2.html/); 15874 }); 15875 it('should change to blank', function() { 15876 select('template').option(''); 15877 expect(element('.doc-example-live [ng-include]')).toBe(undefined); 15878 }); 15879 </file> 15880 </example> 15881 */ 15882 15883 15884/** 15885 * @ngdoc event 15886 * @name ng.directive:ngInclude#$includeContentRequested 15887 * @eventOf ng.directive:ngInclude 15888 * @eventType emit on the scope ngInclude was declared in 15889 * @description 15890 * Emitted every time the ngInclude content is requested. 15891 */ 15892 15893 15894/** 15895 * @ngdoc event 15896 * @name ng.directive:ngInclude#$includeContentLoaded 15897 * @eventOf ng.directive:ngInclude 15898 * @eventType emit on the current ngInclude scope 15899 * @description 15900 * Emitted every time the ngInclude content is reloaded. 15901 */ 15902var NG_INCLUDE_PRIORITY = 500; 15903var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animate', '$sce', 15904 function($http, $templateCache, $anchorScroll, $compile, $animate, $sce) { 15905 return { 15906 restrict: 'ECA', 15907 terminal: true, 15908 priority: NG_INCLUDE_PRIORITY, 15909 compile: function(element, attr) { 15910 var srcExp = attr.ngInclude || attr.src, 15911 onloadExp = attr.onload || '', 15912 autoScrollExp = attr.autoscroll; 15913 15914 element.html(''); 15915 var anchor = jqLite(document.createComment(' ngInclude: ' + srcExp + ' ')); 15916 element.replaceWith(anchor); 15917 15918 return function(scope) { 15919 var changeCounter = 0, 15920 currentScope, 15921 currentElement; 15922 15923 var cleanupLastIncludeContent = function() { 15924 if (currentScope) { 15925 currentScope.$destroy(); 15926 currentScope = null; 15927 } 15928 if(currentElement) { 15929 $animate.leave(currentElement); 15930 currentElement = null; 15931 } 15932 }; 15933 15934 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 15935 var thisChangeId = ++changeCounter; 15936 15937 if (src) { 15938 $http.get(src, {cache: $templateCache}).success(function(response) { 15939 if (thisChangeId !== changeCounter) return; 15940 var newScope = scope.$new(); 15941 15942 cleanupLastIncludeContent(); 15943 15944 currentScope = newScope; 15945 currentElement = element.clone(); 15946 currentElement.html(response); 15947 $animate.enter(currentElement, null, anchor); 15948 15949 $compile(currentElement, false, NG_INCLUDE_PRIORITY - 1)(currentScope); 15950 15951 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 15952 $anchorScroll(); 15953 } 15954 15955 currentScope.$emit('$includeContentLoaded'); 15956 scope.$eval(onloadExp); 15957 }).error(function() { 15958 if (thisChangeId === changeCounter) cleanupLastIncludeContent(); 15959 }); 15960 scope.$emit('$includeContentRequested'); 15961 } else { 15962 cleanupLastIncludeContent(); 15963 } 15964 }); 15965 }; 15966 } 15967 }; 15968}]; 15969 15970/** 15971 * @ngdoc directive 15972 * @name ng.directive:ngInit 15973 * 15974 * @description 15975 * The `ngInit` directive specifies initialization tasks to be executed 15976 * before the template enters execution mode during bootstrap. 15977 * 15978 * @element ANY 15979 * @param {expression} ngInit {@link guide/expression Expression} to eval. 15980 * 15981 * @example 15982 <doc:example> 15983 <doc:source> 15984 <div ng-init="greeting='Hello'; person='World'"> 15985 {{greeting}} {{person}}! 15986 </div>
15987 </doc:source> 15988 <doc:scenario> 15989 it('should check greeting', function() { 15990 expect(binding('greeting')).toBe('Hello'); 15991 expect(binding('person')).toBe('World'); 15992 }); 15993 </doc:scenario> 15994 </doc:example> 15995 */ 15996var ngInitDirective = ngDirective({ 15997 compile: function() { 15998 return { 15999 pre: function(scope, element, attrs) { 16000 scope.$eval(attrs.ngInit); 16001 } 16002 } 16003 } 16004}); 16005 16006/** 16007 * @ngdoc directive 16008 * @name ng.directive:ngNonBindable 16009 * @priority 1000 16010 * 16011 * @description 16012 * Sometimes it is necessary to write code which looks like bindings but which should be left alone 16013 * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML. 16014 * 16015 * @element ANY 16016 * 16017 * @example 16018 * In this example there are two location where a simple binding (`{{}}`) is present, but the one 16019 * wrapped in `ngNonBindable` is left alone. 16020 * 16021 * @example 16022 <doc:example> 16023 <doc:source> 16024 <div>Normal: {{1 + 2}}</div> 16025 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 16026 </doc:source> 16027 <doc:scenario> 16028 it('should check ng-non-bindable', function() { 16029 expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); 16030 expect(using('.doc-example-live').element('div:last').text()). 16031 toMatch(/1 \+ 2/); 16032 }); 16033 </doc:scenario> 16034 </doc:example> 16035 */ 16036var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 16037 16038/** 16039 * @ngdoc directive 16040 * @name ng.directive:ngPluralize 16041 * @restrict EA 16042 * 16043 * @description 16044 * # Overview 16045 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 16046 * These rules are bundled with angular.js, but can be overridden 16047 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 16048 * by specifying the mappings between 16049 * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html 16050 * plural categories} and the strings to be displayed. 16051 * 16052 * # Plural categories and explicit number rules 16053 * There are two 16054 * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html 16055 * plural categories} in Angular's default en-US locale: "one" and "other". 16056 * 16057 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 16058 * any number that is not 1), an explicit number rule can only match one number. For example, the 16059 * explicit number rule for "3" matches the number 3. There are examples of plural categories 16060 * and explicit number rules throughout the rest of this documentation. 16061 * 16062 * # Configuring ngPluralize 16063 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 16064 * You can also provide an optional attribute, `offset`. 16065 * 16066 * The value of the `count` attribute can be either a string or an {@link guide/expression 16067 * Angular expression}; these are evaluated on the current scope for its bound value. 16068 * 16069 * The `when` attribute specifies the mappings between plural categories and the actual 16070 * string to be displayed. The value of the attribute should be a JSON object. 16071 * 16072 * The following example shows how to configure ngPluralize: 16073 * 16074 * <pre> 16075 * <ng-pluralize count="personCount" 16076 when="{'0': 'Nobody is viewing.', 16077 * 'one': '1 person is viewing.', 16078 * 'other': '{} people are viewing.'}"> 16079 * </ng-pluralize> 16080 *</pre> 16081 * 16082 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 16083 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 16084 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 16085 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 16086 * show "a dozen people are viewing". 16087 * 16088 * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted 16089 * into pluralized strings. In the previous example, Angular will replace `{}` with 16090 * <span ng-non-bindable>`{{personCount}}
16090`</span>. The closed braces `{}` is a placeholder 16091 * for <span ng-non-bindable>{{numberExpression}}</span>. 16092 * 16093 * # Configuring ngPluralize with offset 16094 * The `offset` attribute allows further customization of pluralized text, which can result in 16095 * a better user experience. For example, instead of the message "4 people are viewing this document", 16096 * you might display "John, Kate and 2 others are viewing this document". 16097 * The offset attribute allows you to offset a number by any desired value. 16098 * Let's take a look at an example: 16099 * 16100 * <pre> 16101 * <ng-pluralize count="personCount" offset=2 16102 * when="{'0': 'Nobody is viewing.', 16103 * '1': '{{person1}} is viewing.', 16104 * '2': '{{person1}} and {{person2}} are viewing.', 16105 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 16106 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 16107 * </ng-pluralize> 16108 * </pre> 16109 * 16110 * Notice that we are still using two plural categories(one, other), but we added 16111 * three explicit number rules 0, 1 and 2. 16112 * When one person, perhaps John, views the document, "John is viewing" will be shown. 16113 * When three people view the document, no explicit number rule is found, so 16114 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 16115 * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" 16116 * is shown. 16117 * 16118 * Note that when you specify offsets, you must provide explicit number rules for 16119 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 16120 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 16121 * plural categories "one" and "other". 16122 * 16123 * @param {string|expression} count The variable to be bounded to. 16124 * @param {string} when The mapping between plural category to its corresponding strings. 16125 * @param {number=} offset Offset to deduct from the total number. 16126 * 16127 * @example 16128 <doc:example> 16129 <doc:source> 16130 <script> 16131 function Ctrl($scope) { 16132 $scope.person1 = 'Igor'; 16133 $scope.person2 = 'Misko'; 16134 $scope.personCount = 1; 16135 } 16136 </script> 16137 <div ng-controller="Ctrl"> 16138 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 16139 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 16140 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 16141 16142 <!--- Example with simple pluralization rules for en locale ---> 16143 Without Offset: 16144 <ng-pluralize count="personCount" 16145 when="{'0': 'Nobody is viewing.', 16146 'one': '1 person is viewing.', 16147 'other': '{} people are viewing.'}"> 16148 </ng-pluralize><br> 16149 16150 <!--- Example with offset ---> 16151 With Offset(2): 16152 <ng-pluralize count="personCount" offset=2 16153 when="{'0': 'Nobody is viewing.', 16154 '1': '{{person1}} is viewing.', 16155 '2': '{{person1}} and {{person2}} are viewing.', 16156 'one': '{{person1}}, {{person2}} and one other person are viewing.', 16157 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 16158 </ng-pluralize> 16159 </div> 16160 </doc:source> 16161 <doc:scenario> 16162 it('should show correct pluralized string', function() { 16163 expect(element('.doc-example-live ng-pluralize:first').text()). 16164 toBe('1 person is viewing.'); 16165 expect(element('.doc-example-live ng-pluralize:last').text()). 16166 toBe('Igor is viewing.'); 16167 16168 using('.doc-example-live').input('personCount').enter('0'); 16169 expect(element('.doc-example-live ng-pluralize:first').text()). 16170 toBe('Nobody is viewing.'); 16171 expect(element('.doc-example-live ng-pluralize:last').text()). 16172 toBe('Nobody is viewing.'); 16173 16174 using('.doc-example-live').input('personCount').enter('2'); 16175 expect(element('.doc-example-live ng-pluralize:first').text()). 16176 toBe('2 people are viewing.'); 16177 expect(element('.doc-example-live ng-pluralize:last').text()). 16178 toBe('Igor and Misko are viewing.'); 16179 16180 using('.doc-example-live').input('personCount').enter('3'); 16181 expect(element('.doc-example-live ng-pluralize:first').text()). 16182 toBe('3 people are viewing.'); 16183 expect(element('.doc-example-live ng-pluralize:last').text()). 16184 toBe('Igor, Misko and one other person are viewing.'); 16185 16186 using('.doc-example-live').input('personCount').enter('4'); 16187 expect(element('.doc-example-live ng-pluralize:first').text()). 16188 toBe('4 people are viewing.'); 16189 expect(element('.doc-example-live ng-pluralize:last').text()). 16190 toBe('Igor, Misko and 2 other people are viewing.'); 16191 }); 16192 16193 it('should show data-binded names', function() { 16194 using('.doc-example-live').input('personCount').enter('4'); 16195 expect(element('.doc-example-live ng-pluralize:last').text()). 16196 toBe('Igor, Misko and 2 other people are viewing.'); 16197 16198 using('.doc-example-live').input('person1').enter('Di'); 16199 using('.doc-example-live').input('person2').enter('Vojta'); 16200 expect(element('.doc-example-live ng-pluralize:last').text()). 16201 toBe('Di, Vojta and 2 other people are viewing.'); 16202 }); 16203 </doc:scenario> 16204 </doc:example> 16205 */ 16206var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { 16207 var BRACE = /{}/g; 16208 return { 16209 restrict: 'EA', 16210 link: function(scope, element, attr) { 16211 var numberExp = attr.count, 16212 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 16213 offset = attr.offset || 0, 16214 whens = scope.$eval(whenExp) || {}, 16215 whensExpFns = {}, 16216 startSymbol = $interpolate.startSymbol(), 16217 endSymbol = $interpolate.endSymbol(), 16218 isWhen = /^when(Minus)?(.+)$/; 16219
16220 forEach(attr, function(expression, attributeName) { 16221 if (isWhen.test(attributeName)) { 16222 whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = 16223 element.attr(attr.$attr[attributeName]); 16224 } 16225 }); 16226 forEach(whens, function(expression, key) { 16227 whensExpFns[key] = 16228 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + 16229 offset + endSymbol)); 16230 }); 16231 16232 scope.$watch(function ngPluralizeWatch() { 16233 var value = parseFloat(scope.$eval(numberExp)); 16234 16235 if (!isNaN(value)) { 16236 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, 16237 //check it against pluralization rules in $locale service 16238 if (!(value in whens)) value = $locale.pluralCat(value - offset); 16239 return whensExpFns[value](scope, element, true); 16240 } else { 16241 return ''; 16242 } 16243 }, function ngPluralizeWatchAction(newVal) { 16244 element.text(newVal); 16245 }); 16246 } 16247 }; 16248}]; 16249 16250/** 16251 * @ngdoc directive 16252 * @name ng.directive:ngRepeat 16253 * 16254 * @description 16255 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 16256 * instance gets its own scope, where the given loop variable is set to the current collection item, 16257 * and `$index` is set to the item index or key. 16258 * 16259 * Special properties are exposed on the local scope of each template instance, including: 16260 * 16261 * | Variable | Type | Details | 16262 * |-----------|-----------------|-----------------------------------------------------------------------------| 16263 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 16264 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 16265 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 16266 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 16267 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 16268 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 16269 * 16270 * 16271 * # Special repeat start and end points 16272 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 16273 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 16274 * 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) 16275 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 16276 * 16277 * The example below makes use of this feature: 16278 * <pre> 16279 * <header ng-repeat-start="item in items"> 16280 * Header {{ item }} 16281 * </header> 16282 * <div class="body"> 16283 * Body {{ item }} 16284 * </div> 16285 * <footer ng-repeat-end> 16286 * Footer {{ item }} 16287 * </footer> 16288 * </pre> 16289 * 16290 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 16291 * <pre> 16292 * <header> 16293 * Header A 16294 * </header> 16295 * <div class="body"> 16296 * Body A 16297 * </div> 16298 * <footer> 16299 * Footer A 16300 * </footer> 16301 * <header> 16302 * Header B 16303 * </header> 16304 * <div class="body"> 16305 * Body B 16306 * </div> 16307 * <footer> 16308 * Footer B 16309 * </footer> 16310 * </pre> 16311 * 16312 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 16313 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 16314 * 16315 * @animations 16316 * enter - when a new item is added to the list or when an item is revealed after a filter 16317 * leave - when an item is removed from the list or when an item is filtered out 16318 * move - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 16319 * 16320 * @element ANY 16321 * @scope 16322 * @priority 1000 16323 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 16324 * formats are currently supported: 16325 * 16326 * * `variable in expression` ââ¬â where variable is the user defined loop variable and `expression` 16327 * is a scope expression giving the collection to enumerate. 16328 * 16329 * For example: `album in artist.albums`. 16330 * 16331 * * `(key, value) in expression` ââ¬â where `key` and `value` can be any user defined identifiers, 16332 * and `expression` is the scope expression giving the collection to enumerate. 16333 * 16334 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 16335 * 16336 * * `variable in expression track by tracking_expression` ââ¬â You can also provide an optional tracking function 16337 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 16338 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 16339 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 16340 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 16341 * before specifying a tracking expression. 16342 * 16343 * For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements 16344 * will be associated by item identity in the array. 16345 * 16346 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 16347 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 16348 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 16349 * element in the same way ian the DOM. 16350 * 16351 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 16352 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 16353 * property is same. 16354 * 16355 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 16356 * to items in conjunction with a tracking expression. 16357 * 16358 * @example 16359 * This example initializes the scope to a list of names and 16360 * then uses `ngRepeat` to display every person: 16361 <example animations="true"> 16362 <file name="index.html"> 16363 <div ng-init="friends = [ 16364 {name:'John', age:25, gender:'boy'}, 16365 {name:'Jessie', age:30, gender:'girl'}, 16366 {name:'Johanna', age:28, gender:'girl'}, 16367 {name:'Joy', age:15, gender:'girl'}, 16368 {name:'Mary', age:28, gender:'girl'}, 16369 {name:'Peter', age:95, gender:'boy'}, 16370 {name:'Sebastian', age:50, gender:'boy'}, 16371 {name:'Erika', age:27, gender:'girl'}, 16372 {name:'Patrick', age:40, gender:'boy'}, 16373 {name:'Samantha', age:60, gender:'girl'} 16374 ]"> 16375 I have {{friends.length}} friends. They are: 16376 <input type="search" ng-model="q" placeholder="filter friends..." /> 16377 <ul class="example-animate-container"> 16378 <li class="animate-repeat" ng-repeat="friend in friends | filter:q"> 16379 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 16380 </li> 16381 </ul> 16382 </div> 16383 </file> 16384 <file name="animations.css"> 16385 .example-animate-container { 16386 background:white; 16387 border:1px solid black; 16388 list-style:none; 16389 margin:0; 16390 padding:0; 16391 } 16392 16393 .example-animate-container > li { 16394 padding:10px; 16395 list-style:none; 16396 } 16397 16398 .animate-repeat.ng-enter, 16399 .animate-repeat.ng-leave, 16400 .animate-repeat.ng-move {
16401 -webkit-transition:all linear 0.5s; 16402 -moz-transition:all linear 0.5s; 16403 -o-transition:all linear 0.5s; 16404 transition:all linear 0.5s; 16405 } 16406 16407 .animate-repeat.ng-enter { 16408 line-height:0; 16409 opacity:0; 16410 padding-top:0; 16411 padding-bottom:0; 16412 } 16413 .animate-repeat.ng-enter.ng-enter-active { 16414 line-height:20px; 16415 opacity:1; 16416 padding:10px; 16417 } 16418 16419 .animate-repeat.ng-leave { 16420 opacity:1; 16421 line-height:20px; 16422 padding:10px; 16423 } 16424 .animate-repeat.ng-leave.ng-leave-active { 16425 opacity:0; 16426 line-height:0; 16427 padding-top:0; 16428 padding-bottom:0; 16429 } 16430 16431 .animate-repeat.ng-move { } 16432 .animate-repeat.ng-move.ng-move-active { } 16433 </file> 16434 <file name="scenario.js"> 16435 it('should render initial data set', function() { 16436 var r = using('.doc-example-live').repeater('ul li'); 16437 expect(r.count()).toBe(10); 16438 expect(r.row(0)).toEqual(["1","John","25"]); 16439 expect(r.row(1)).toEqual(["2","Jessie","30"]); 16440 expect(r.row(9)).toEqual(["10","Samantha","60"]); 16441 expect(binding('friends.length')).toBe("10"); 16442 }); 16443 16444 it('should update repeater when filter predicate changes', function() { 16445 var r = using('.doc-example-live').repeater('ul li'); 16446 expect(r.count()).toBe(10); 16447 16448 input('q').enter('ma'); 16449 16450 expect(r.count()).toBe(2); 16451 expect(r.row(0)).toEqual(["1","Mary","28"]); 16452 expect(r.row(1)).toEqual(["2","Samantha","60"]); 16453 }); 16454 </file> 16455 </example> 16456 */ 16457var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 16458 var NG_REMOVED = '$$NG_REMOVED'; 16459 var ngRepeatMinErr = minErr('ngRepeat'); 16460 return { 16461 transclude: 'element', 16462 priority: 1000, 16463 terminal: true, 16464 compile: function(element, attr, linker) { 16465 return function($scope, $element, $attr){ 16466 var expression = $attr.ngRepeat; 16467 var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/), 16468 trackByExp, trackByExpGetter, trackByIdFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier, 16469 hashFnLocals = {$id: hashKey}; 16470 16471 if (!match) { 16472 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 16473 expression); 16474 } 16475 16476 lhs = match[1]; 16477 rhs = match[2]; 16478 trackByExp = match[4]; 16479 16480 if (trackByExp) { 16481 trackByExpGetter = $parse(trackByExp); 16482 trackByIdFn = function(key, value, index) { 16483 // assign key, value, and $index to the locals so that they can be used in hash functions 16484 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 16485 hashFnLocals[valueIdentifier] = value; 16486 hashFnLocals.$index = index; 16487 return trackByExpGetter($scope, hashFnLocals); 16488 }; 16489 } else { 16490 trackByIdArrayFn = function(key, value) { 16491 return hashKey(value); 16492 } 16493 trackByIdObjFn = function(key) { 16494 return key; 16495 } 16496 } 16497 16498 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); 16499 if (!match) { 16500 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 16501 lhs); 16502 } 16503 valueIdentifier = match[3] || match[1]; 16504 keyIdentifier = match[2]; 16505 16506 // Store a list of elements from previous run. This is a hash where key is the item from the 16507 // iterator, and the value is objects with following properties. 16508 // - scope: bound scope 16509 // - element: previous element. 16510 // - index: position 16511 var lastBlockMap = {}; 16512 16513 //watch props 16514 $scope.$watchCollection(rhs, function ngRepeatAction(collection){ 16515 var index, length, 16516 previousNode = $element[0], // current position of the node 16517 nextNode, 16518 // Same as lastBlockMap but it has the current state. It will become the 16519 // lastBlockMap on the next iteration. 16520 nextBlockMap = {}, 16521 arrayLength, 16522 childScope, 16523 key, value, // key/value of iteration 16524 trackById, 16525 collectionKeys, 16526 block, // last object information {scope, element, id} 16527 nextBlockOrder = []; 16528 16529 16530 if (isArrayLike(collection)) { 16531 collectionKeys = collection; 16532 trackByIdFn = trackByIdFn || trackByIdArrayFn; 16533 } else { 16534 trackByIdFn = trackByIdFn || trackByIdObjFn; 16535 // if object, extract keys, sort them and use to determine order of iteration over obj props 16536 collectionKeys = []; 16537 for (key in collection) { 16538 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { 16539 collectionKeys.push(key); 16540 } 16541 } 16542 collectionKeys.sort(); 16543 } 16544 16545 arrayLength = collectionKeys.length; 16546 16547 // locate existing items 16548 length = nextBlockOrder.length = collectionKeys.length; 16549 for(index = 0; index < length; index++) { 16550 key = (collection === collectionKeys) ? index : collectionKeys[index]; 16551 value = collection[key]; 16552 trackById = trackByIdFn(key, value, index); 16553 if(lastBlockMap.hasOwnProperty(trackById)) { 16554 block = lastBlockMap[trackById] 16555 delete lastBlockMap[trackById]; 16556 nextBlockMap[trackById] = block; 16557 nextBlockOrder[index] = block; 16558 } else if (nextBlockMap.hasOwnProperty(trackById)) { 16559 // restore lastBlockMap
16560 forEach(nextBlockOrder, function(block) { 16561 if (block && block.startNode) lastBlockMap[block.id] = block; 16562 }); 16563 // This is a duplicate and we need to throw an error 16564 throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", 16565 expression, trackById); 16566 } else { 16567 // new never before seen block 16568 nextBlockOrder[index] = { id: trackById }; 16569 nextBlockMap[trackById] = false; 16570 } 16571 } 16572 16573 // remove existing items 16574 for (key in lastBlockMap) { 16575 if (lastBlockMap.hasOwnProperty(key)) { 16576 block = lastBlockMap[key]; 16577 $animate.leave(block.elements); 16578 forEach(block.elements, function(element) { element[NG_REMOVED] = true}); 16579 block.scope.$destroy(); 16580 } 16581 } 16582 16583 // we are not using forEach for perf reasons (trying to avoid #call) 16584 for (index = 0, length = collectionKeys.length; index < length; index++) { 16585 key = (collection === collectionKeys) ? index : collectionKeys[index]; 16586 value = collection[key]; 16587 block = nextBlockOrder[index]; 16588 16589 if (block.startNode) { 16590 // if we have already seen this object, then we need to reuse the 16591 // associated scope/element 16592 childScope = block.scope; 16593 16594 nextNode = previousNode; 16595 do { 16596 nextNode = nextNode.nextSibling; 16597 } while(nextNode && nextNode[NG_REMOVED]); 16598 16599 if (block.startNode == nextNode) { 16600 // do nothing 16601 } else { 16602 // existing item which got moved 16603 $animate.move(block.elements, null, jqLite(previousNode)); 16604 } 16605 previousNode = block.endNode; 16606 } else { 16607 // new item which we don't know about 16608 childScope = $scope.$new(); 16609 } 16610 16611 childScope[valueIdentifier] = value; 16612 if (keyIdentifier) childScope[keyIdentifier] = key; 16613 childScope.$index = index; 16614 childScope.$first = (index === 0); 16615 childScope.$last = (index === (arrayLength - 1)); 16616 childScope.$middle = !(childScope.$first || childScope.$last); 16617 childScope.$odd = !(childScope.$even = index%2==0); 16618 16619 if (!block.startNode) { 16620 linker(childScope, function(clone) { 16621 $animate.enter(clone, null, jqLite(previousNode)); 16622 previousNode = clone; 16623 block.scope = childScope; 16624 block.startNode = clone[0]; 16625 block.elements = clone; 16626 block.endNode = clone[clone.length - 1]; 16627 nextBlockMap[block.id] = block; 16628 }); 16629 } 16630 } 16631 lastBlockMap = nextBlockMap; 16632 }); 16633 }; 16634 } 16635 }; 16636}]; 16637 16638/** 16639 * @ngdoc directive 16640 * @name ng.directive:ngShow 16641 * 16642 * @description 16643 * The `ngShow` directive shows and hides the given HTML element conditionally based on the expression 16644 * provided to the ngShow attribute. The show and hide mechanism is a achieved by removing and adding 16645 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is a predefined CSS class present 16646 * in AngularJS which sets the display style to none (using an !important flag). 16647 * 16648 * <pre> 16649 * <!-- when $scope.myValue is truthy (element is visible) --> 16650 * <div ng-show="myValue"></div> 16651 * 16652 * <!-- when $scope.myValue is falsy (element is hidden) --> 16653 * <div ng-show="myValue" class="ng-hide"></div> 16654 * </pre> 16655 * 16656 * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute 16657 * on the element causing it to become hidden. When true, the ng-hide CSS class is removed 16658 * from the element causing the element not to appear hidden. 16659 * 16660 * ## Why is !important used? 16661 * 16662 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 16663 * can be easily overridden by heavier selectors. For example, something as simple 16664 * as changing the display style on a HTML list item would make hidden elements appear visible.
16665 * This also becomes a bigger issue when dealing with CSS frameworks. 16666 * 16667 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 16668 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 16669 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 16670 * 16671 * ### Overriding .ng-hide 16672 * 16673 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 16674 * restating the styles for the .ng-hide class in CSS: 16675 * <pre> 16676 * .ng-hide { 16677 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 16678 * display:block!important; 16679 * 16680 * //this is just another form of hiding an element 16681 * position:absolute; 16682 * top:-9999px; 16683 * left:-9999px; 16684 * } 16685 * </pre> 16686 * 16687 * Just remember to include the important flag so the CSS override will function. 16688 * 16689 * ## A note about animations with ngShow 16690 * 16691 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 16692 * is true and false. This system works similar to the animation system present with ngClass, however, the 16693 * only difference is that you must also include the !important flag to override the display property so 16694 * that you can perform an animation when the element is hidden during the time of the animation. 16695 * 16696 * <pre> 16697 * // 16698 * //a working example can be found at the bottom of this page 16699 * // 16700 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 16701 * transition:0.5s linear all; 16702 * display:block!important; 16703 * } 16704 * 16705 * .my-element.ng-hide-add { ... } 16706 * .my-element.ng-hide-add.ng-hide-add-active { ... } 16707 * .my-element.ng-hide-remove { ... } 16708 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 16709 * </pre> 16710 * 16711 * @animations 16712 * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible 16713 * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden 16714 * 16715 * @element ANY 16716 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 16717 * then the element is shown or hidden respectively. 16718 * 16719 * @example 16720 <example animations="true"> 16721 <file name="index.html"> 16722 Click me: <input type="checkbox" ng-model="checked"><br/> 16723 <div> 16724 Show: 16725 <div class="check-element animate-show" ng-show="checked"> 16726 <span class="icon-thumbs-up"></span> I show up when your checkbox is checked. 16727 </div> 16728 </div> 16729 <div> 16730 Hide: 16731 <div class="check-element animate-show" ng-hide="checked"> 16732 <span class="icon-thumbs-down"></span> I hide when your checkbox is checked. 16733 </div> 16734 </div> 16735 </file> 16736 <file name="animations.css"> 16737 .animate-show.ng-hide-add, 16738 .animate-show.ng-hide-remove { 16739 -webkit-transition:all linear 0.5s; 16740 -moz-transition:all linear 0.5s; 16741 -o-transition:all linear 0.5s; 16742 transition:all linear 0.5s; 16743 display:block!important; 16744 } 16745 16746 .animate-show.ng-hide-add.ng-hide-add-active, 16747 .animate-show.ng-hide-remove { 16748 line-height:0; 16749 opacity:0; 16750 padding:0 10px; 16751 } 16752 16753 .animate-show.ng-hide-add, 16754 .animate-show.ng-hide-remove.ng-hide-remove-active { 16755 line-height:20px; 16756 opacity:1; 16757 padding:10px; 16758 border:1px solid black; 16759 background:white; 16760 } 16761 16762 .check-element { 16763 padding:10px; 16764 border:1px solid black; 16765 background:white; 16766 } 16767 </file> 16768 <file name="scenario.js"> 16769 it('should check ng-show / ng-hide', function() { 16770 expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); 16771 expect(element('.doc-example-live span:last:visible').count()).toEqual(1); 16772 16773 input('checked').check(); 16774 16775 expect(element('.doc-example-live span:first:visible').count()).toEqual(1); 16776 expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); 16777 }); 16778 </file> 16779 </example> 16780 */ 16781var ngShowDirective = ['$animate', function($animate) { 16782 return function(scope, element, attr) { 16783 scope.$watch(attr.ngShow, function ngShowWatchAction(value){ 16784 $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); 16785 }); 16786 }; 16787}]; 16788 16789 16790/** 16791 * @ngdoc directive 16792 * @name ng.directive:ngHide 16793 * 16794 * @description 16795 * The `ngHide` directive shows and hides the given HTML element conditionally based on the expression 16796 * provided to the ngHide attribute. The show and hide mechanism is a achieved by removing and adding 16797 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is a predefined CSS class present 16798 * in AngularJS which sets the display style to none (using an !important flag). 16799 * 16800 * <pre> 16801 * <!-- when $scope.myValue is truthy (element is hidden) --> 16802 * <div ng-hide="myValue"></div> 16803 * 16804 * <!-- when $scope.myValue is falsy (element is visible) --> 16805 * <div ng-hide="myValue" class="ng-hide"></div> 16806 * </pre> 16807 * 16808 * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute 16809 * on the element causing it to become hidden. When false, the ng-hide CSS class is removed 16810 * from the element causing the element not to appear hidden. 16811 * 16812 * ## Why is !important used? 16813 * 16814 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 16815 * can be easily overridden by heavier selectors. For example, something as simple 16816 * as changing the display style on a HTML list item would make hidden elements appear visible.
16817 * This also becomes a bigger issue when dealing with CSS frameworks. 16818 * 16819 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 16820 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 16821 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 16822 * 16823 * ### Overriding .ng-hide 16824 * 16825 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 16826 * restating the styles for the .ng-hide class in CSS: 16827 * <pre> 16828 * .ng-hide { 16829 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 16830 * display:block!important; 16831 * 16832 * //this is just another form of hiding an element 16833 * position:absolute; 16834 * top:-9999px; 16835 * left:-9999px; 16836 * } 16837 * </pre> 16838 * 16839 * Just remember to include the important flag so the CSS override will function. 16840 * 16841 * ## A note about animations with ngHide 16842 * 16843 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 16844 * is true and false. This system works similar to the animation system present with ngClass, however, the 16845 * only difference is that you must also include the !important flag to override the display property so 16846 * that you can perform an animation when the element is hidden during the time of the animation. 16847 * 16848 * <pre> 16849 * // 16850 * //a working example can be found at the bottom of this page 16851 * // 16852 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 16853 * transition:0.5s linear all; 16854 * display:block!important; 16855 * } 16856 * 16857 * .my-element.ng-hide-add { ... } 16858 * .my-element.ng-hide-add.ng-hide-add-active { ... } 16859 * .my-element.ng-hide-remove { ... } 16860 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 16861 * </pre> 16862 * 16863 * @animations 16864 * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden 16865 * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible 16866 * 16867 * @element ANY 16868 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 16869 * the element is shown or hidden respectively. 16870 * 16871 * @example 16872 <example animations="true"> 16873 <file name="index.html"> 16874 Click me: <input type="checkbox" ng-model="checked"><br/> 16875 <div> 16876 Show: 16877 <div class="check-element animate-hide" ng-show="checked"> 16878 <span class="icon-thumbs-up"></span> I show up when your checkbox is checked. 16879 </div> 16880 </div> 16881 <div> 16882 Hide: 16883 <div class="check-element animate-hide" ng-hide="checked"> 16884 <span class="icon-thumbs-down"></span> I hide when your checkbox is checked. 16885 </div> 16886 </div> 16887 </file> 16888 <file name="animations.css"> 16889 .animate-hide.ng-hide-add, 16890 .animate-hide.ng-hide-remove { 16891 -webkit-transition:all linear 0.5s; 16892 -moz-transition:all linear 0.5s; 16893 -o-transition:all linear 0.5s; 16894 transition:all linear 0.5s; 16895 display:block!important; 16896 } 16897 16898 .animate-hide.ng-hide-add.ng-hide-add-active, 16899 .animate-hide.ng-hide-remove { 16900 line-height:0; 16901 opacity:0; 16902 padding:0 10px; 16903 } 16904 16905 .animate-hide.ng-hide-add, 16906 .animate-hide.ng-hide-remove.ng-hide-remove-active { 16907 line-height:20px; 16908 opacity:1; 16909 padding:10px; 16910 border:1px solid black; 16911 background:white; 16912 } 16913 16914 .check-element { 16915 padding:10px; 16916 border:1px solid black; 16917 background:white; 16918 } 16919 </file> 16920 <file name="scenario.js"> 16921 it('should check ng-show / ng-hide', function() { 16922 expect(element('.doc-example-live .check-element:first:hidden').count()).toEqual(1); 16923 expect(element('.doc-example-live .check-element:last:visible').count()).toEqual(1); 16924 16925 input('checked').check(); 16926 16927 expect(element('.doc-example-live .check-element:first:visible').count()).toEqual(1); 16928 expect(element('.doc-example-live .check-element:last:hidden').count()).toEqual(1); 16929 }); 16930 </file> 16931 </example> 16932 */ 16933var ngHideDirective = ['$animate', function($animate) { 16934 return function(scope, element, attr) { 16935 scope.$watch(attr.ngHide, function ngHideWatchAction(value){ 16936 $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); 16937 }); 16938 }; 16939}]; 16940 16941/** 16942 * @ngdoc directive 16943 * @name ng.directive:ngStyle 16944 * 16945 * @description 16946 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 16947 * 16948 * @element ANY 16949 * @param {expression} ngStyle {@link guide/expression Expression} which evals to an 16950 * object whose keys are CSS style names and values are corresponding values for those CSS 16951 * keys. 16952 * 16953 * @example 16954 <example> 16955 <file name="index.html"> 16956 <input type="button" value="set" ng-click="myStyle={color:'red'}"> 16957 <input type="button" value="clear" ng-click="myStyle={}"> 16958 <br/>
16959 <span ng-style="myStyle">Sample Text</span> 16960 <pre>myStyle={{myStyle}}</pre> 16961 </file> 16962 <file name="style.css"> 16963 span { 16964 color: black; 16965 } 16966 </file> 16967 <file name="scenario.js"> 16968 it('should check ng-style', function() { 16969 expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); 16970 element('.doc-example-live :button[value=set]').click(); 16971 expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)'); 16972 element('.doc-example-live :button[value=clear]').click(); 16973 expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); 16974 }); 16975 </file> 16976 </example> 16977 */ 16978var ngStyleDirective = ngDirective(function(scope, element, attr) { 16979 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 16980 if (oldStyles && (newStyles !== oldStyles)) { 16981 forEach(oldStyles, function(val, style) { element.css(style, '');}); 16982 } 16983 if (newStyles) element.css(newStyles); 16984 }, true); 16985}); 16986 16987/** 16988 * @ngdoc directive 16989 * @name ng.directive:ngSwitch 16990 * @restrict EA 16991 * 16992 * @description 16993 * The ngSwitch directive is used to conditionally swap DOM structure on your template based on a scope expression. 16994 * Elements within ngSwitch but without ngSwitchWhen or ngSwitchDefault directives will be preserved at the location 16995 * as specified in the template. 16996 * 16997 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 16998 * from the template cache), ngSwitch simply choses one of the nested elements and makes it visible based on which element 16999 * matches the value obtained from the evaluated expression. In other words, you define a container element 17000 * (where you place the directive), place an expression on the **on="..." attribute** 17001 * (or the **ng-switch="..." attribute**), define any inner elements inside of the directive and place 17002 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 17003 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 17004 * attribute is displayed. 17005 * 17006 * @animations 17007 * enter - happens after the ngSwtich contents change and the matched child element is placed inside the container 17008 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 17009 * 17010 * @usage 17011 * <ANY ng-switch="expression"> 17012 * <ANY ng-switch-when="matchValue1">...</ANY> 17013 * <ANY ng-switch-when="matchValue2">...</ANY> 17014 * <ANY ng-switch-default>...</ANY> 17015 * </ANY> 17016 * 17017 * @scope 17018 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 17019 * @paramDescription 17020 * On child elements add: 17021 * 17022 * * `ngSwitchWhen`: the case statement to match against. If match then this 17023 * case will be displayed. If the same match appears multiple times, all the 17024 * elements will be displayed. 17025 * * `ngSwitchDefault`: the default case when no other case match. If there 17026 * are multiple default cases, all of them will be displayed when no other 17027 * case match. 17028 * 17029 * 17030 * @example 17031 <example animations="true"> 17032 <file name="index.html"> 17033 <div ng-controller="Ctrl"> 17034 <select ng-model="selection" ng-options="item for item in items"> 17035 </select> 17036 <tt>selection={{selection}}</tt> 17037 <hr/> 17038 <div class="animate-switch-container" 17039 ng-switch on="selection"> 17040 <div ng-switch-when="settings">Settings Div</div> 17041 <div ng-switch-when="home">Home Span</div> 17042 <div ng-switch-default>default</div> 17043 </div> 17044 </div> 17045 </file> 17046 <file name="script.js"> 17047 function Ctrl($scope) { 17048 $scope.items = ['settings', 'home', 'other']; 17049 $scope.selection = $scope.items[0]; 17050 } 17051 </file> 17052 <file name="animations.css"> 17053 .animate-switch-container { 17054 position:relative; 17055 background:white; 17056 border:1px solid black; 17057 height:40px; 17058 overflow:hidden; 17059 } 17060 17061 .animate-switch-container > div { 17062 padding:10px; 17063 } 17064 17065 .animate-switch-container > .ng-enter, 17066 .animate-switch-container > .ng-leave { 17067 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17068 -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17069 -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17070 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17071 17072 position:absolute; 17073 top:0; 17074 left:0; 17075 right:0; 17076 bottom:0; 17077 } 17078 17079 .animate-switch-container > .ng-enter { 17080 top:-50px; 17081 } 17082 .animate-switch-container > .ng-enter.ng-enter-active { 17083 top:0; 17084 } 17085 17086 .animate-switch-container > .ng-leave { 17087 top:0; 17088 } 17089 .animate-switch-container > .ng-leave.ng-leave-active { 17090 top:50px; 17091 } 17092 </file> 17093 <file name="scenario.js"> 17094 it('should start in settings', function() { 17095 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/); 17096 }); 17097 it('should change to home', function() { 17098 select('selection').option('home'); 17099 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/); 17100 }); 17101 it('should select default', function() { 17102 select('selection').option('other'); 17103 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/); 17104 }); 17105 </file> 17106 </example> 17107 */ 17108var ngSwitchDirective = ['$animate', function($animate) { 17109 return { 17110 restrict: 'EA', 17111 require: 'ngSwitch', 17112 17113 // asks for $scope to fool the BC controller module 17114 controller: ['$scope', function ngSwitchController() { 17115 this.cases = {}; 17116 }], 17117 link: function(scope, element, attr, ngSwitchController) { 17118 var watchExpr = attr.ngSwitch || attr.on, 17119 selectedTranscludes, 17120 selectedElements, 17121 selectedScopes = []; 17122 17123 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 17124 for (var i= 0, ii=selectedScopes.length; i<ii; i++) { 17125 selectedScopes[i].$destroy(); 17126 $animate.leave(selectedElements[i]); 17127 } 17128 17129 selectedElements = []; 17130 selectedScopes = []; 17131 17132 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 17133 scope.$eval(attr.change);
17134 forEach(selectedTranscludes, function(selectedTransclude) { 17135 var selectedScope = scope.$new(); 17136 selectedScopes.push(selectedScope); 17137 selectedTransclude.transclude(selectedScope, function(caseElement) { 17138 var anchor = selectedTransclude.element; 17139 17140 selectedElements.push(caseElement); 17141 $animate.enter(caseElement, anchor.parent(), anchor); 17142 }); 17143 }); 17144 } 17145 }); 17146 } 17147 } 17148}]; 17149 17150var ngSwitchWhenDirective = ngDirective({ 17151 transclude: 'element', 17152 priority: 500, 17153 require: '^ngSwitch', 17154 compile: function(element, attrs, transclude) { 17155 return function(scope, element, attr, ctrl) { 17156 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 17157 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: transclude, element: element }); 17158 }; 17159 } 17160}); 17161 17162var ngSwitchDefaultDirective = ngDirective({ 17163 transclude: 'element', 17164 priority: 500, 17165 require: '^ngSwitch', 17166 compile: function(element, attrs, transclude) { 17167 return function(scope, element, attr, ctrl) { 17168 ctrl.cases['?'] = (ctrl.cases['?'] || []); 17169 ctrl.cases['?'].push({ transclude: transclude, element: element }); 17170 }; 17171 } 17172}); 17173 17174/** 17175 * @ngdoc directive 17176 * @name ng.directive:ngTransclude 17177 * 17178 * @description 17179 * Insert the transcluded DOM here. 17180 * 17181 * @element ANY 17182 * 17183 * @example 17184 <doc:example module="transclude"> 17185 <doc:source> 17186 <script> 17187 function Ctrl($scope) { 17188 $scope.title = 'Lorem Ipsum'; 17189 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 17190 } 17191 17192 angular.module('transclude', []) 17193 .directive('pane', function(){ 17194 return { 17195 restrict: 'E', 17196 transclude: true, 17197 scope: { title:'@' }, 17198 template: '<div style="border: 1px solid black;">' + 17199 '<div style="background-color: gray">{{title}}</div>' + 17200 '<div ng-transclude></div>' + 17201 '</div>' 17202 }; 17203 }); 17204 </script> 17205 <div ng-controller="Ctrl"> 17206 <input ng-model="title"><br> 17207 <textarea ng-model="text"></textarea> <br/> 17208 <pane title="{{title}}">{{text}}</pane> 17209 </div> 17210 </doc:source> 17211 <doc:scenario> 17212 it('should have transcluded', function() { 17213 input('title').enter('TITLE'); 17214 input('text').enter('TEXT'); 17215 expect(binding('title')).toEqual('TITLE'); 17216 expect(binding('text')).toEqual('TEXT'); 17217 }); 17218 </doc:scenario> 17219 </doc:example> 17220 * 17221 */ 17222var ngTranscludeDirective = ngDirective({ 17223 controller: ['$transclude', '$element', '$scope', function($transclude, $element, $scope) { 17224 // use evalAsync so that we don't process transclusion before directives on the parent element even when the 17225 // transclusion replaces the current element. (we can't use priority here because that applies only to compile fns 17226 // and not controllers 17227 $scope.$evalAsync(function() { 17228 $transclude(function(clone) { 17229 $element.append(clone); 17230 }); 17231 }); 17232 }] 17233}); 17234 17235/** 17236 * @ngdoc directive 17237 * @name ng.directive:script 17238 * 17239 * @description 17240 * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the 17241 * template can be used by `ngInclude`, `ngView` or directive templates. 17242 * 17243 * @restrict E 17244 * @param {'text/ng-template'} type must be set to `'text/ng-template'` 17245 * 17246 * @example 17247 <doc:example> 17248 <doc:source> 17249 <script type="text/ng-template" id="/tpl.html"> 17250 Content of the template. 17251 </script> 17252 17253 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 17254 <div id="tpl-content" ng-include src="currentTpl"></div> 17255 </doc:source> 17256 <doc:scenario> 17257 it('should load template defined inside script tag', function() { 17258 element('#tpl-link').click(); 17259 expect(element('#tpl-content').text()).toMatch(/Content of the template/); 17260 }); 17261 </doc:scenario> 17262 </doc:example> 17263 */ 17264var scriptDirective = ['$templateCache', function($templateCache) { 17265 return { 17266 restrict: 'E', 17267 terminal: true, 17268 compile: function(element, attr) { 17269 if (attr.type == 'text/ng-template') { 17270 var templateUrl = attr.id, 17271 // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent 17272 text = element[0].text; 17273 17274 $templateCache.put(templateUrl, text); 17275 } 17276 } 17277 }; 17278}]; 17279 17280/** 17281 * @ngdoc directive 17282 * @name ng.directive:select 17283 * @restrict E 17284 * 17285 * @description 17286 * HTML `SELECT` element with angular data-binding. 17287 * 17288 * # `ngOptions` 17289 * 17290 * Optionally `ngOptions` attribute can be used to dynamically generate a list of `<option>` 17291 * elements for a `<select>` element using an array or an object obtained by evaluating the 17292 * `ngOptions` expression. 17293 * 17294 * When an item in the `<select>` menu is selected, the value of array element or object property 17295 * represented by the selected option will be bound to the model identified by the `ngModel` 17296 * directive of the parent select element. 17297 * 17298 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 17299 * be nested into the `<select>` element. This element will then represent `null` or "not selected" 17300 * option. See example below for demonstration. 17301 *
17302 * Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead 17303 * of {@link ng.directive:ngRepeat ngRepeat} when you want the 17304 * `select` model to be bound to a non-string value. This is because an option element can currently 17305 * be bound to string values only. 17306 * 17307 * @param {string} ngModel Assignable angular expression to data-bind to. 17308 * @param {string=} name Property name of the form under which the control is published. 17309 * @param {string=} required The control is considered valid only if value is entered. 17310 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 17311 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 17312 * `required` when you want to data-bind to the `required` attribute. 17313 * @param {comprehension_expression=} ngOptions in one of the following forms: 17314 * 17315 * * for array data sources: 17316 * * `label` **`for`** `value` **`in`** `array` 17317 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 17318 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 17319 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 17320 * * for object data sources: 17321 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 17322 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 17323 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 17324 * * `select` **`as`** `label` **`group by`** `group` 17325 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 17326 * 17327 * Where: 17328 * 17329 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 17330 * * `value`: local variable which will refer to each item in the `array` or each property value 17331 * of `object` during iteration. 17332 * * `key`: local variable which will refer to a property name in `object` during iteration. 17333 * * `label`: The result of this expression will be the label for `<option>` element. The 17334 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 17335 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 17336 * element. If not specified, `select` expression will default to `value`. 17337 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 17338 * DOM element. 17339 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 17340 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 17341 * `value` variable (e.g. `value.propertyName`). 17342 * 17343 * @example 17344 <doc:example> 17345 <doc:source> 17346 <script> 17347 function MyCntrl($scope) { 17348 $scope.colors = [ 17349 {name:'black', shade:'dark'}, 17350 {name:'white', shade:'light'}, 17351 {name:'red', shade:'dark'}, 17352 {name:'blue', shade:'dark'}, 17353 {name:'yellow', shade:'light'} 17354 ]; 17355 $scope.color = $scope.colors[2]; // red 17356 } 17357 </script> 17358 <div ng-controller="MyCntrl"> 17359 <ul> 17360 <li ng-repeat="color in colors"> 17361 Name: <input ng-model="color.name"> 17362 [<a href ng-click="colors.splice($index, 1)">X</a>] 17363 </li> 17364 <li> 17365 [<a href ng-click="colors.push({})">add</a>] 17366 </li> 17367 </ul> 17368 <hr/> 17369 Color (null not allowed): 17370 <select ng-model="color" ng-options="c.name for c in colors"></select><br> 17371 17372 Color (null allowed): 17373 <span class="nullable"> 17374 <select ng-model="color" ng-options="c.name for c in colors"> 17375 <option value="">-- chose color --</option> 17376 </select> 17377 </span><br/> 17378 17379 Color grouped by shade: 17380 <select ng-model="color" ng-options="c.name group by c.shade for c in colors"> 17381 </select><br/> 17382 17383 17384 Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br> 17385 <hr/> 17386 Currently selected: {{ {selected_color:color} }} 17387 <div style="border:solid 1px black; height:20px" 17388 ng-style="{'background-color':color.name}"> 17389 </div> 17390 </div> 17391 </doc:source> 17392 <doc:scenario> 17393 it('should check ng-options', function() { 17394 expect(binding('{selected_color:color}')).toMatch('red'); 17395 select('color').option('0'); 17396 expect(binding('{selected_color:color}')).toMatch('black'); 17397 using('.nullable').select('color').option(''); 17398 expect(binding('{selected_color:color}')).toMatch('null'); 17399 }); 17400 </doc:scenario> 17401 </doc:example> 17402 */ 17403 17404var ngOptionsDirective = valueFn({ terminal: true }); 17405var selectDirective = ['$compile', '$parse', function($compile, $parse) { 17406 //0000111110000000000022220000000000000000000000333300000000000000444444444444444440000000005555555555555555500000006666666666666666600000000000000007777000000000000000000088888 17407 var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*
17407)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/, 17408 nullModelCtrl = {$setViewValue: noop}; 17409 17410 return { 17411 restrict: 'E', 17412 require: ['select', '?ngModel'], 17413 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 17414 var self = this, 17415 optionsMap = {}, 17416 ngModelCtrl = nullModelCtrl, 17417 nullOption, 17418 unknownOption; 17419 17420 17421 self.databound = $attrs.ngModel; 17422 17423 17424 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { 17425 ngModelCtrl = ngModelCtrl_; 17426 nullOption = nullOption_; 17427 unknownOption = unknownOption_; 17428 } 17429 17430 17431 self.addOption = function(value) { 17432 optionsMap[value] = true; 17433 17434 if (ngModelCtrl.$viewValue == value) { 17435 $element.val(value); 17436 if (unknownOption.parent()) unknownOption.remove(); 17437 } 17438 }; 17439 17440 17441 self.removeOption = function(value) { 17442 if (this.hasOption(value)) { 17443 delete optionsMap[value]; 17444 if (ngModelCtrl.$viewValue == value) { 17445 this.renderUnknownOption(value); 17446 } 17447 } 17448 }; 17449 17450 17451 self.renderUnknownOption = function(val) { 17452 var unknownVal = '? ' + hashKey(val) + ' ?'; 17453 unknownOption.val(unknownVal); 17454 $element.prepend(unknownOption); 17455 $element.val(unknownVal); 17456 unknownOption.prop('selected', true); // needed for IE 17457 } 17458 17459 17460 self.hasOption = function(value) { 17461 return optionsMap.hasOwnProperty(value); 17462 } 17463 17464 $scope.$on('$destroy', function() { 17465 // disable unknown option so that we don't do work when the whole select is being destroyed 17466 self.renderUnknownOption = noop; 17467 }); 17468 }], 17469 17470 link: function(scope, element, attr, ctrls) { 17471 // if ngModel is not defined, we don't need to do anything 17472 if (!ctrls[1]) return; 17473 17474 var selectCtrl = ctrls[0], 17475 ngModelCtrl = ctrls[1], 17476 multiple = attr.multiple, 17477 optionsExp = attr.ngOptions, 17478 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 17479 emptyOption, 17480 // we can't just jqLite('<option>') since jqLite is not smart enough 17481 // to create it in <select> and IE barfs otherwise. 17482 optionTemplate = jqLite(document.createElement('option')), 17483 optGroupTemplate =jqLite(document.createElement('optgroup')), 17484 unknownOption = optionTemplate.clone(); 17485 17486 // find "null" option 17487 for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 17488 if (children[i].value == '') { 17489 emptyOption = nullOption = children.eq(i); 17490 break; 17491 } 17492 } 17493 17494 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 17495 17496 // required validator 17497 if (multiple && (attr.required || attr.ngRequired)) { 17498 var requiredValidator = function(value) { 17499 ngModelCtrl.$setValidity('required', !attr.required || (value && value.length)); 17500 return value; 17501 }; 17502 17503 ngModelCtrl.$parsers.push(requiredValidator); 17504 ngModelCtrl.$formatters.unshift(requiredValidator); 17505 17506 attr.$observe('required', function() { 17507 requiredValidator(ngModelCtrl.$viewValue); 17508 }); 17509 } 17510 17511 if (optionsExp) Options(scope, element, ngModelCtrl); 17512 else if (multiple) Multiple(scope, element, ngModelCtrl); 17513 else Single(scope, element, ngModelCtrl, selectCtrl); 17514 17515 17516 //////////////////////////// 17517 17518 17519 17520 function Single(scope, selectElement, ngModelCtrl, selectCtrl) { 17521 ngModelCtrl.$render = function() { 17522 var viewValue = ngModelCtrl.$viewValue; 17523 17524 if (selectCtrl.hasOption(viewValue)) { 17525 if (unknownOption.parent()) unknownOption.remove(); 17526 selectElement.val(viewValue); 17527 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 17528 } else { 17529 if (isUndefined(viewValue) && emptyOption) { 17530 selectElement.val(''); 17531 } else { 17532 selectCtrl.renderUnknownOption(viewValue); 17533 } 17534 } 17535 }; 17536 17537 selectElement.on('change', function() { 17538 scope.$apply(function() { 17539 if (unknownOption.parent()) unknownOption.remove(); 17540 ngModelCtrl.$setViewValue(selectElement.val()); 17541 }); 17542 }); 17543 } 17544 17545 function Multiple(scope, selectElement, ctrl) { 17546 var lastView; 17547 ctrl.$render = function() { 17548 var items = new HashMap(ctrl.$viewValue);
17549 forEach(selectElement.find('option'), function(option) { 17550 option.selected = isDefined(items.get(option.value)); 17551 }); 17552 }; 17553 17554 // we have to do it on each watch since ngModel watches reference, but 17555 // we need to work of an array, so we need to see if anything was inserted/removed 17556 scope.$watch(function selectMultipleWatch() { 17557 if (!equals(lastView, ctrl.$viewValue)) { 17558 lastView = copy(ctrl.$viewValue); 17559 ctrl.$render(); 17560 } 17561 }); 17562 17563 selectElement.on('change', function() { 17564 scope.$apply(function() { 17565 var array = []; 17566 forEach(selectElement.find('option'), function(option) { 17567 if (option.selected) { 17568 array.push(option.value); 17569 } 17570 }); 17571 ctrl.$setViewValue(array); 17572 }); 17573 }); 17574 } 17575 17576 function Options(scope, selectElement, ctrl) { 17577 var match; 17578 17579 if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) { 17580 throw minErr('ngOptions')('iexp', 17581 "Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '{0}'. Element: {1}", 17582 optionsExp, startingTag(selectElement)); 17583 } 17584 17585 var displayFn = $parse(match[2] || match[1]), 17586 valueName = match[4] || match[6], 17587 keyName = match[5], 17588 groupByFn = $parse(match[3] || ''), 17589 valueFn = $parse(match[2] ? match[1] : valueName), 17590 valuesFn = $parse(match[7]), 17591 track = match[8], 17592 trackFn = track ? $parse(match[8]) : null, 17593 // This is an array of array of existing option groups in DOM. We try to reuse these if possible 17594 // optionGroupsCache[0] is the options with no option group 17595 // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 17596 optionGroupsCache = [[{element: selectElement, label:''}]]; 17597 17598 if (nullOption) { 17599 // compile the element since there might be bindings in it 17600 $compile(nullOption)(scope); 17601 17602 // remove the class, which is added automatically because we recompile the element and it 17603 // becomes the compilation root 17604 nullOption.removeClass('ng-scope'); 17605 17606 // we need to remove it before calling selectElement.html('') because otherwise IE will 17607 // remove the label from the element. wtf? 17608 nullOption.remove(); 17609 } 17610 17611 // clear contents, we'll add what's needed based on the model 17612 selectElement.html(''); 17613 17614 selectElement.on('change', function() { 17615 scope.$apply(function() { 17616 var optionGroup, 17617 collection = valuesFn(scope) || [], 17618 locals = {}, 17619 key, value, optionElement, index, groupIndex, length, groupLength; 17620 17621 if (multiple) { 17622 value = []; 17623 for (groupIndex = 0, groupLength = optionGroupsCache.length; 17624 groupIndex < groupLength; 17625 groupIndex++) { 17626 // list of options for that group. (first item has the parent) 17627 optionGroup = optionGroupsCache[groupIndex]; 17628 17629 for(index = 1, length = optionGroup.length; index < length; index++) { 17630 if ((optionElement = optionGroup[index].element)[0].selected) { 17631 key = optionElement.val(); 17632 if (keyName) locals[keyName] = key; 17633 if (trackFn) { 17634 for (var trackIndex = 0; trackIndex < collection.length; trackIndex++) { 17635 locals[valueName] = collection[trackIndex]; 17636 if (trackFn(scope, locals) == key) break; 17637 } 17638 } else { 17639 locals[valueName] = collection[key]; 17640 } 17641 value.push(valueFn(scope, locals)); 17642 } 17643 } 17644 } 17645 } else { 17646 key = selectElement.val(); 17647 if (key == '?') { 17648 value = undefined; 17649 } else if (key == ''){ 17650 value = null; 17651 } else { 17652 if (trackFn) { 17653 for (var trackIndex = 0; trackIndex < collection.length; trackIndex++) { 17654 locals[valueName] = collection[trackIndex]; 17655 if (trackFn(scope, locals) == key) { 17656 value = valueFn(scope, locals); 17657 break; 17658 } 17659 } 17660 } else { 17661 locals[valueName] = collection[key]; 17662 if (keyName) locals[keyName] = key; 17663 value = valueFn(scope, locals); 17664 } 17665 } 17666 } 17667 ctrl.$setViewValue(value); 17668 }); 17669 }); 17670 17671 ctrl.$render = render; 17672 17673 // TODO(vojta): can't we optimize this ? 17674 scope.$watch(render); 17675 17676 function render() { 17677 var optionGroups = {'':[]}, // Temporary location for the option groups before we render them 17678 optionGroupNames = [''], 17679 optionGroupName, 17680 optionGroup, 17681 option, 17682 existingParent, existingOptions, existingOption, 17683 modelValue = ctrl.$modelValue, 17684 values = valuesFn(scope) || [], 17685 keys = keyName ? sortedKeys(values) : values, 17686 groupLength, length, 17687 groupIndex, index, 17688 locals = {}, 17689 selected, 17690 selectedSet = false, // nothing is selected yet 17691 lastElement, 17692 element, 17693 label; 17694 17695 if (multiple) { 17696 if (trackFn && isArray(modelValue)) { 17697 selectedSet = new HashMap([]); 17698 for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { 17699 locals[valueName] = modelValue[trackIndex]; 17700 selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); 17701 } 17702 } else { 17703 selectedSet = new HashMap(modelValue); 17704 } 17705 } 17706 17707 // We now build up the list of options we need (we merge later) 17708 for (index = 0; length = keys.length, index < length; index++) { 17709 locals[valueName] = values[keyName ? locals[keyName]=keys[index]:index]; 17710 optionGroupName = groupByFn(scope, locals) || ''; 17711 if (!(optionGroup = optionGroups[optionGroupName])) { 17712 optionGroup = optionGroups[optionGroupName] = []; 17713 optionGroupNames.push(optionGroupName); 17714 } 17715 if (multiple) { 17716 selected = selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) != undefined; 17717 } else { 17718 if (trackFn) { 17719 var modelCast = {}; 17720 modelCast[valueName] = modelValue; 17721 selected = trackFn(scope, modelCast) === trackFn(scope, locals); 17722 } else { 17723 selected = modelValue === valueFn(scope, locals); 17724 } 17725 selectedSet = selectedSet || selected; // see if at least one item is selected 17726 } 17727 label = displayFn(scope, locals); // what will be seen by the user 17728 label = label === undefined ? '' : label; // doing displayFn(scope, locals) || '' overwrites zero values 17729 optionGroup.push({ 17730 id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), // either the index into array or key from object 17731 label: label, 17732 selected: selected // determine if we should be selected 17733 }); 17734 } 17735 if (!multiple) { 17736 if (nullOption || modelValue === null) { 17737 // insert null option if we have a placeholder, or the model is null 17738 optionGroups[''].unshift({id:'', label:'', selected:!selectedSet}); 17739 } else if (!selectedSet) { 17740 // option could not be found, we have to insert the undefined item 17741 optionGroups[''].unshift({id:'?', label:'', selected:true}); 17742 } 17743 } 17744 17745 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 17746 for (groupIndex = 0, groupLength = optionGroupNames.length; 17747 groupIndex < groupLength; 17748 groupIndex++) { 17749 // current option group name or '' if no group 17750 optionGroupName = optionGroupNames[groupIndex]; 17751 17752 // list of options for that group. (first item has the parent) 17753 optionGroup = optionGroups[optionGroupName]; 17754 17755 if (optionGroupsCache.length <= groupIndex) { 17756 // we need to grow the optionGroups 17757 existingParent = { 17758 element: optGroupTemplate.clone().attr('label', optionGroupName), 17759 label: optionGroup.label 17760 }; 17761 existingOptions = [existingParent]; 17762 optionGroupsCache.push(existingOptions); 17763 selectElement.append(existingParent.element); 17764 } else { 17765 existingOptions = optionGroupsCache[groupIndex]; 17766 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 17767 17768 // update the OPTGROUP label if not the same. 17769 if (existingParent.label != optionGroupName) { 17770 existingParent.element.attr('label', existingParent.label = optionGroupName); 17771 } 17772 } 17773 17774 lastElement = null; // start at the beginning 17775 for(index = 0, length = optionGroup.length; index < length; index++) { 17776 option = optionGroup[index]; 17777 if ((existingOption = existingOptions[index+1])) { 17778 // reuse elements 17779 lastElement = existingOption.element; 17780 if (existingOption.label !== option.label) { 17781 lastElement.text(existingOption.label = option.label); 17782 } 17783 if (existingOption.id !== option.id) { 17784 lastElement.val(existingOption.id = option.id); 17785 } 17786 // lastElement.prop('selected') provided by jQuery has side-effects 17787 if (lastElement[0].selected !== option.selected) {
17788 lastElement.prop('selected', (existingOption.selected = option.selected)); 17789 } 17790 } else { 17791 // grow elements 17792 17793 // if it's a null option 17794 if (option.id === '' && nullOption) { 17795 // put back the pre-compiled element 17796 element = nullOption; 17797 } else { 17798 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 17799 // in this version of jQuery on some browser the .text() returns a string 17800 // rather then the element. 17801 (element = optionTemplate.clone()) 17802 .val(option.id) 17803 .attr('selected', option.selected) 17804 .text(option.label); 17805 } 17806 17807 existingOptions.push(existingOption = { 17808 element: element, 17809 label: option.label, 17810 id: option.id, 17811 selected: option.selected 17812 }); 17813 if (lastElement) { 17814 lastElement.after(element); 17815 } else { 17816 existingParent.element.append(element); 17817 } 17818 lastElement = element; 17819 } 17820 } 17821 // remove any excessive OPTIONs in a group 17822 index++; // increment since the existingOptions[0] is parent element not OPTION 17823 while(existingOptions.length > index) { 17824 existingOptions.pop().element.remove(); 17825 } 17826 } 17827 // remove any excessive OPTGROUPs from select 17828 while(optionGroupsCache.length > groupIndex) { 17829 optionGroupsCache.pop()[0].element.remove(); 17830 } 17831 } 17832 } 17833 } 17834 } 17835}]; 17836 17837var optionDirective = ['$interpolate', function($interpolate) { 17838 var nullSelectCtrl = { 17839 addOption: noop, 17840 removeOption: noop 17841 }; 17842 17843 return { 17844 restrict: 'E', 17845 priority: 100, 17846 compile: function(element, attr) { 17847 if (isUndefined(attr.value)) { 17848 var interpolateFn = $interpolate(element.text(), true); 17849 if (!interpolateFn) { 17850 attr.$set('value', element.text()); 17851 } 17852 } 17853 17854 return function (scope, element, attr) { 17855 var selectCtrlName = '$selectController', 17856 parent = element.parent(), 17857 selectCtrl = parent.data(selectCtrlName) || 17858 parent.parent().data(selectCtrlName); // in case we are in optgroup 17859 17860 if (selectCtrl && selectCtrl.databound) { 17861 // For some reason Opera defaults to true and if not overridden this messes up the repeater. 17862 // We don't want the view to drive the initialization of the model anyway. 17863 element.prop('selected', false); 17864 } else { 17865 selectCtrl = nullSelectCtrl; 17866 } 17867 17868 if (interpolateFn) { 17869 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 17870 attr.$set('value', newVal); 17871 if (newVal !== oldVal) selectCtrl.removeOption(oldVal); 17872 selectCtrl.addOption(newVal); 17873 }); 17874 } else { 17875 selectCtrl.addOption(attr.value); 17876 } 17877 17878 element.on('$destroy', function() { 17879 selectCtrl.removeOption(attr.value); 17880 }); 17881 }; 17882 } 17883 } 17884}]; 17885 17886var styleDirective = valueFn({ 17887 restrict: 'E', 17888 terminal: true 17889}); 17890 17891 //try to bind to jquery now so that one can write angular.element().read() 17892 //but we will rebind on bootstrap again. 17893 bindJQuery(); 17894 17895 publishExternalAPI(angular); 17896 17897 jqLite(document).ready(function() { 17898 angularInit(document, bootstrap); 17899 }); 17900 17901})(window, document); 17902angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}</style>');
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.