1// AngularJS v1.4.7 ie8 build 2 3(function () { 4 5 // detect IE8 one way or another 6 /*one way 7 var ie=function(){ 8 for(var e,i=3,n=document.createElement("div"),t=n.getElementsByTagName("i"); 9 n.innerHTML="<!--[if gt IE "+ ++i+"]><i></i><![endif]-->",t[0];);return i>4?i:e 10 }(); 11 if (ie !== 8) { 12 // proper browsers exit here 13 return; 14 } 15 */ 16 17 // another way 18 if (window.addEventListener) { 19 // proper browsers exit here 20 return; 21 } 22 23 // ie8 ftw... 24 if (!window.XMLHttpRequest) { 25 //console.log('XMLHttpRequest not enabled'); 26 27 window.XMLHttpRequest = function () { 28 var xmlhttp = new ActiveXObject('Microsoft.XMLHTTP'), 29 out = { 30 isFake: true, 31 send: function (a) { 32 return xmlhttp.send(a); 33 }, 34 open: function (a, b, c, d, e) { 35 return xmlhttp.open(a, b, c, d, e); 36 }, 37 abort: function () { 38 return xmlhttp.abort(); 39 }, 40 setRequestHeader: function (a, b) { 41 return xmlhttp.setRequestHeader(a, b); 42 }, 43 getResponseHeader: function (a) { 44 return xmlhttp.getResponseHeader(a); 45 }, 46 getAllResponseHeaders: function () { 47 return xmlhttp.getAllResponseHeaders(); 48 }, 49 overrideMimeType: function (a) { 50 return xmlhttp.overrideMimeType(a); 51 } 52 }; 53 54 xmlhttp.onreadystatechange = function () { 55 out.readyState = xmlhttp.readyState; 56 57 if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { 58 out.status = xmlhttp.status; 59 out.responseText = xmlhttp.responseText; 60 out.responseXML = xmlhttp.responseXML; 61 out.statusText = xmlhttp.statusText; 62 63 if (out.onload) { 64 out.onload.apply(this, arguments); 65 } 66 } 67 68 if (out.onreadystatechange) { 69 out.onreadystatechange.apply(this, arguments); 70 } 71 72 }; 73 74 return out; 75 76 }; 77 } 78 79 // ------ XMLHttpRequest.onload ------ 80 // monkey patch XMLHttpRequest to make IE8 call onload when readyState === 4 81 var sendFn = XMLHttpRequest.prototype.send; 82 XMLHttpRequest.prototype.send = function () { 83 // only if onreadystatechange has not already been set 84 // to avoid breaking anything outside of angular 85 if (!this.onreadystatechange) { 86 this.onreadystatechange = function () { 87 if (this.readyState === 4 && this.onload) { 88 this.onload(); 89 } 90 }; 91 } 92 // apply this & args to original send 93 sendFn.apply(this, arguments); 94 }; 95 96 97 // ------ Object.create ------ 98 // force Object.create to this implementation for IE8 99 // (es5-sham version doesn't work in this instance) 100 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create 101 Object.create = (function () { 102 var Object = function () {}; 103 return function (prototype) { 104 if (arguments.length > 1) { 105 throw Error('Second argument not supported'); 106 } 107 if (typeof prototype != 'object') { 108 throw TypeError('Argument must be an object'); 109 } 110 Object.prototype = prototype; 111 var result = new Object(); 112 Object.prototype = null; 113 return result; 114 }; 115 })(); 116 117 118 // ------ Object.getPrototypeOf ------ 119 // http://stackoverflow.com/a/15851520/674863 120 if (typeof Object.getPrototypeOf !== 'function') { 121 Object.getPrototypeOf = ''.__proto__ === String.prototype ? function (object) { 122 return object.__proto__; 123 } : function (object) { 124 // May break if the constructor has been tampered with 125 return object.constructor.prototype; 126 }; 127 } 128 129 // textContent shim using Sizzle / jQuery get & set text methods 130 (function () { 131 132 var getText = function (elem) { 133 var node, 134 ret = '', 135 i = 0, 136 nodeType = elem.nodeType; 137 138 if (!nodeType) { 139 // If no nodeType, this is expected to be an array 140 while ((node = elem[i++])) { 141 // Do not traverse comment nodes 142 ret += getText(node); 143 } 144 } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) { 145 // Traverse its children 146 for (elem = elem.firstChild; elem; elem = elem.nextSibling) { 147 ret += getText(elem); 148 } 149 } else if (nodeType === 3 || nodeType === 4) { 150 return elem.nodeValue; 151 }
152 // Do not include comment or processing instruction nodes 153 154 return ret; 155 }; 156 157 if (Object.defineProperty && Object.getOwnPropertyDescriptor && 158 Object.getOwnPropertyDescriptor(Element.prototype, 'textContent') && 159 !Object.getOwnPropertyDescriptor(Element.prototype, 'textContent').get) { 160 var innerText = Object.getOwnPropertyDescriptor(Element.prototype, 'innerText'); 161 Object.defineProperty(Element.prototype, 'textContent', { 162 get: function () { 163 return getText(this); 164 }, 165 set: function (x) { 166 //var $this = $(this); 167 //return $this.empty().append(($this[0] && $this[0].ownerDocument || document).createTextNode(x)); 168 // empty 169 while (this.hasChildNodes()) { 170 this.removeChild(this.lastChild); 171 } 172 // add text node 173 return this.appendChild((this && this.ownerDocument || document).createTextNode(x)); 174 } 175 }); 176 } 177 178 })(); 179 180 // ------ addEventListener ------ 181 // https://gist.github.com/jonathantneal/3748027 182 !window.addEventListener && (function (WindowPrototype, DocumentPrototype, ElementPrototype, addEventListener, removeEventListener, dispatchEvent, registry) { 183 WindowPrototype[addEventListener] = DocumentPrototype[addEventListener] = ElementPrototype[addEventListener] = function (type, listener) { 184 var target = this; 185 registry.unshift([target, type, listener, 186 function (event) { 187 event.currentTarget = target; 188 event.preventDefault = function () { 189 event.returnValue = false; 190 }; 191 event.stopPropagation = function () { 192 event.cancelBubble = true; 193 }; 194 event.target = event.srcElement || target; 195 listener.call(target, event); 196 }]); 197 198 // http://msdn.microsoft.com/en-us/library/ie/hh180173%28v=vs.85%29.aspx 199 if (type === 'load' && this.tagName && this.tagName === 'SCRIPT') { 200 var reg = registry[0][3]; 201 this.onreadystatechange = function (event) { 202 if (this.readyState === "loaded" || this.readyState === "complete") { 203 reg.call(this, { 204 type: "load" 205 }); 206 } 207 } 208 } else { 209 this.attachEvent('on' + type, registry[0][3]); 210 } 211 }; 212 WindowPrototype[removeEventListener] = DocumentPrototype[removeEventListener] = ElementPrototype[removeEventListener] = function (type, listener) { 213 for (var index = 0, register; register = registry[index]; ++index) { 214 if (register[0] == this && register[1] == type && register[2] == listener) { 215 if (type === 'load' && this.tagName && this.tagName === 'SCRIPT') { 216 this.onreadystatechange = null; 217 } 218 219 return this.detachEvent('on' + type, registry.splice(index, 1)[0][3]); 220 } 221 } 222 }; 223 WindowPrototype[dispatchEvent] = DocumentPrototype[dispatchEvent] = ElementPrototype[dispatchEvent] = function (eventObject) { 224 return this.fireEvent('on' + eventObject.type, eventObject); 225 }; 226 })(Window.prototype, HTMLDocument.prototype, Element.prototype, 'addEventListener', 'removeEventListener', 'dispatchEvent', []); 227 228}()); 229 230/** 231 * @license AngularJS v1.4.7 232 * (c) 2010-2015 Google, Inc. http://angularjs.org 233 * License: MIT 234 */ 235(function(window, document, undefined) {'use strict'; 236 237/** 238 * @description 239 * 240 * This object provides a utility for producing rich Error messages within 241 * Angular. It can be called as follows: 242 * 243 * var exampleMinErr = minErr('example'); 244 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 245 * 246 * The above creates an instance of minErr in the example namespace. The 247 * resulting error will have a namespaced error code of example.one. The 248 * resulting error will replace {0} with the value of foo, and {1} with the 249 * value of bar. The object is not restricted in the number of arguments it can 250 * take. 251 * 252 * If fewer arguments are specified than necessary for interpolation, the extra 253 * interpolation markers will be preserved in the final string. 254 * 255 * Since data will be parsed statically during a build step, some restrictions 256 * are applied with respect to how minErr instances are created and called. 257 * Instances should have names of the form namespaceMinErr for a minErr created 258 * using minErr('namespace') . Error codes, namespaces and template strings 259 * should all be static strings, not variables or general expressions. 260 * 261 * @param {string} module The namespace to use for the new minErr instance. 262 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 263 * error from returned function, for cases when a particular type of error is useful. 264 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 265 */ 266 267function minErr(module, ErrorConstructor) { 268 ErrorConstructor = ErrorConstructor || Error; 269 return function() { 270 var SKIP_INDEXES = 2; 271 272 var templateArgs = arguments, 273 code = templateArgs[0], 274 message = '[' + (module ? module + ':' : '') + code + '] ', 275 template = templateArgs[1], 276 paramPrefix, i; 277 278 message += template.replace(/\{\d+\}/g, function(match) { 279 var index = +match.slice(1, -1), 280 shiftedIndex = index + SKIP_INDEXES; 281 282 if (shiftedIndex < templateArgs.length) { 283 return toDebugString(templateArgs[shiftedIndex]); 284 } 285 286 return match; 287 }); 288 289 message += '\nhttp://errors.angularjs.org/1.4.7/' + 290 (module ? module + '/' : '') + code; 291 292 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
293 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + 294 encodeURIComponent(toDebugString(templateArgs[i])); 295 } 296 297 return new ErrorConstructor(message); 298 }; 299} 300 301/* We need to tell jshint what variables are being exported */ 302/* global angular: true, 303 msie: true, 304 jqLite: true, 305 jQuery: true, 306 slice: true, 307 splice: true, 308 push: true, 309 toString: true, 310 ngMinErr: true, 311 angularModule: true, 312 uid: true, 313 REGEX_STRING_REGEXP: true, 314 VALIDITY_STATE_PROPERTY: true, 315 316 lowercase: true, 317 uppercase: true, 318 manualLowercase: true, 319 manualUppercase: true, 320 nodeName_: true, 321 isArrayLike: true, 322 forEach: true, 323 forEachSorted: true, 324 reverseParams: true, 325 nextUid: true, 326 setHashKey: true, 327 extend: true, 328 toInt: true, 329 inherit: true, 330 merge: true, 331 noop: true, 332 identity: true, 333 valueFn: true, 334 isUndefined: true, 335 isDefined: true, 336 isObject: true, 337 isBlankObject: true, 338 isString: true, 339 isNumber: true, 340 isDate: true, 341 isArray: true, 342 isFunction: true, 343 isRegExp: true, 344 isWindow: true, 345 isScope: true, 346 isFile: true, 347 isFormData: true, 348 isBlob: true, 349 isBoolean: true, 350 isPromiseLike: true, 351 trim: true, 352 escapeForRegexp: true, 353 isElement: true, 354 makeMap: true, 355 includes: true, 356 arrayRemove: true, 357 copy: true, 358 shallowCopy: true, 359 equals: true, 360 csp: true, 361 jq: true, 362 concat: true, 363 sliceArgs: true, 364 bind: true, 365 toJsonReplacer: true, 366 toJson: true, 367 fromJson: true, 368 convertTimezoneToLocal: true, 369 timezoneToOffset: true, 370 startingTag: true, 371 tryDecodeURIComponent: true, 372 parseKeyValue: true, 373 toKeyValue: true, 374 encodeUriSegment: true, 375 encodeUriQuery: true, 376 angularInit: true, 377 bootstrap: true, 378 getTestability: true, 379 snake_case: true, 380 bindJQuery: true, 381 assertArg: true, 382 assertArgFn: true, 383 assertNotHasOwnProperty: true, 384 getter: true, 385 getBlockNodes: true, 386 hasOwnProperty: true, 387 createMap: true, 388 389 NODE_TYPE_ELEMENT: true, 390 NODE_TYPE_ATTRIBUTE: true, 391 NODE_TYPE_TEXT: true, 392 NODE_TYPE_COMMENT: true, 393 NODE_TYPE_DOCUMENT: true, 394 NODE_TYPE_DOCUMENT_FRAGMENT: true, 395*/ 396 397//////////////////////////////////// 398 399/** 400 * @ngdoc module 401 * @name ng 402 * @module ng 403 * @description 404 * 405 * # ng (core module) 406 * The ng module is loaded by default when an AngularJS application is started. The module itself 407 * contains the essential components for an AngularJS application to function. The table below 408 * lists a high level breakdown of each of the services/factories, filters, directives and testing 409 * components available within this core module. 410 * 411 * <div doc-module-components="ng"></div> 412 */ 413 414var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 415 416// The name of a form control's ValidityState property. 417// This is used so that it's possible for internal tests to create mock ValidityStates. 418var VALIDITY_STATE_PROPERTY = 'validity'; 419 420/** 421 * @ngdoc function 422 * @name angular.lowercase 423 * @module ng 424 * @kind function 425 * 426 * @description Converts the specified string to lowercase. 427 * @param {string} string String to be converted to lowercase. 428 * @returns {string} Lowercased string. 429 */ 430var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 431var hasOwnProperty = Object.prototype.hasOwnProperty; 432 433/** 434 * @ngdoc function 435 * @name angular.uppercase 436 * @module ng 437 * @kind function 438 * 439 * @description Converts the specified string to uppercase. 440 * @param {string} string String to be converted to uppercase. 441 * @returns {string} Uppercased string. 442 */ 443var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 444 445 446var manualLowercase = function(s) { 447 /* jshint bitwise: false */ 448 return isString(s) 449 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 450 : s; 451}; 452var manualUppercase = function(s) { 453 /* jshint bitwise: false */ 454 return isString(s) 455 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 456 : s; 457}; 458 459 460// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 461// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 462// with correct but slower alternatives. 463if ('i' !== 'I'.toLowerCase()) { 464 lowercase = manualLowercase; 465 uppercase = manualUppercase; 466} 467 468 469var 470 msie, // holds major version number for IE, or NaN if UA is not IE. 471 jqLite, // delay binding since jQuery could be loaded after us. 472 jQuery, // delay binding 473 slice = [].slice, 474 splice = [].splice, 475 push = [].push, 476 toString = Object.prototype.toString, 477 getPrototypeOf = Object.getPrototypeOf, 478 ngMinErr = minErr('ng'), 479 480 /** @name angular */ 481 angular = window.angular || (window.angular = {}), 482 angularModule, 483 uid = 0; 484 485/** 486 * documentMode is an IE-only property
487 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 488 */ 489msie = document.documentMode; 490 491 492/** 493 * @private 494 * @param {*} obj 495 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 496 * String ...) 497 */ 498function isArrayLike(obj) { 499 if (obj == null || isWindow(obj)) { 500 return false; 501 } 502 503 // Support: iOS 8.2 (not reproducible in simulator) 504 // "length" in obj used to prevent JIT error (gh-11508) 505 var length = "length" in Object(obj) && obj.length; 506 507 if (obj.nodeType === NODE_TYPE_ELEMENT && length) { 508 return true; 509 } 510 511 return isString(obj) || isArray(obj) || length === 0 || 512 typeof length === 'number' && length > 0 && (length - 1) in obj; 513} 514 515/** 516 * @ngdoc function 517 * @name angular.forEach 518 * @module ng 519 * @kind function 520 * 521 * @description 522 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 523 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 524 * is the value of an object property or an array element, `key` is the object property key or 525 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 526 * 527 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 528 * using the `hasOwnProperty` method. 529 * 530 * Unlike ES262's 531 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 532 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 533 * return the value provided. 534 * 535 ```js 536 var values = {name: 'misko', gender: 'male'}; 537 var log = []; 538 angular.forEach(values, function(value, key) { 539 this.push(key + ': ' + value); 540 }, log); 541 expect(log).toEqual(['name: misko', 'gender: male']); 542 ``` 543 * 544 * @param {Object|Array} obj Object to iterate over. 545 * @param {Function} iterator Iterator function. 546 * @param {Object=} context Object to become context (`this`) for the iterator function. 547 * @returns {Object|Array} Reference to `obj`. 548 */ 549 550function forEach(obj, iterator, context) { 551 var key, length; 552 if (obj) { 553 if (isFunction(obj)) { 554 for (key in obj) { 555 // Need to check if hasOwnProperty exists, 556 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 557 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 558 iterator.call(context, obj[key], key, obj); 559 } 560 } 561 } else if (isArray(obj) || isArrayLike(obj)) { 562 var isPrimitive = typeof obj !== 'object'; 563 for (key = 0, length = obj.length; key < length; key++) { 564 if (isPrimitive || key in obj) { 565 iterator.call(context, obj[key], key, obj); 566 } 567 } 568 } else if (obj.forEach && obj.forEach !== forEach) { 569 obj.forEach(iterator, context, obj); 570 } else if (isBlankObject(obj)) { 571 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 572 for (key in obj) { 573 iterator.call(context, obj[key], key, obj); 574 } 575 } else if (typeof obj.hasOwnProperty === 'function') { 576 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 577 for (key in obj) { 578 if (obj.hasOwnProperty(key)) { 579 iterator.call(context, obj[key], key, obj); 580 } 581 } 582 } else { 583 // Slow path for objects which do not have a method `hasOwnProperty` 584 for (key in obj) { 585 if (hasOwnProperty.call(obj, key)) { 586 iterator.call(context, obj[key], key, obj); 587 } 588 } 589 } 590 } 591 return obj; 592} 593 594function forEachSorted(obj, iterator, context) { 595 var keys = Object.keys(obj).sort(); 596 for (var i = 0; i < keys.length; i++) { 597 iterator.call(context, obj[keys[i]], keys[i]); 598 } 599 return keys; 600} 601 602 603/** 604 * when using forEach the params are value, key, but it is often useful to have key, value. 605 * @param {function(string, *)} iteratorFn 606 * @returns {function(*, string)} 607 */ 608function reverseParams(iteratorFn) { 609 return function(value, key) { iteratorFn(key, value); }; 610} 611 612/** 613 * A consistent way of creating unique IDs in angular. 614 * 615 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 616 * we hit number precision issues in JavaScript. 617 * 618 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 619 * 620 * @returns {number}
vendor: 9,733 bytes, lines 620-1044
620 an unique alpha-numeric string 621 */ 622function nextUid() { 623 return ++uid; 624} 625 626 627/** 628 * Set or clear the hashkey for an object. 629 * @param obj object 630 * @param h the hashkey (!truthy to delete the hashkey) 631 */ 632function setHashKey(obj, h) { 633 if (h) { 634 obj.$$hashKey = h; 635 } else { 636 delete obj.$$hashKey; 637 } 638} 639 640 641function baseExtend(dst, objs, deep) { 642 var h = dst.$$hashKey; 643 644 for (var i = 0, ii = objs.length; i < ii; ++i) { 645 var obj = objs[i]; 646 if (!isObject(obj) && !isFunction(obj)) continue; 647 var keys = Object.keys(obj); 648 for (var j = 0, jj = keys.length; j < jj; j++) { 649 var key = keys[j]; 650 var src = obj[key]; 651 652 if (deep && isObject(src)) { 653 if (isDate(src)) { 654 dst[key] = new Date(src.valueOf()); 655 } else if (isRegExp(src)) { 656 dst[key] = new RegExp(src); 657 } else { 658 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 659 baseExtend(dst[key], [src], true); 660 } 661 } else { 662 dst[key] = src; 663 } 664 } 665 } 666 667 setHashKey(dst, h); 668 return dst; 669} 670 671/** 672 * @ngdoc function 673 * @name angular.extend 674 * @module ng 675 * @kind function 676 * 677 * @description 678 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 679 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 680 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 681 * 682 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 683 * {@link angular.merge} for this. 684 * 685 * @param {Object} dst Destination object. 686 * @param {...Object} src Source object(s). 687 * @returns {Object} Reference to `dst`. 688 */ 689function extend(dst) { 690 return baseExtend(dst, slice.call(arguments, 1), false); 691} 692 693 694/** 695* @ngdoc function 696* @name angular.merge 697* @module ng 698* @kind function 699* 700* @description 701* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 702* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 703* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 704* 705* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 706* objects, performing a deep copy. 707* 708* @param {Object} dst Destination object. 709* @param {...Object} src Source object(s). 710* @returns {Object} Reference to `dst`. 711*/ 712function merge(dst) { 713 return baseExtend(dst, slice.call(arguments, 1), true); 714} 715 716 717 718function toInt(str) { 719 return parseInt(str, 10); 720} 721 722 723function inherit(parent, extra) { 724 return extend(Object.create(parent), extra); 725} 726 727/** 728 * @ngdoc function 729 * @name angular.noop 730 * @module ng 731 * @kind function 732 * 733 * @description 734 * A function that performs no operations. This function can be useful when writing code in the 735 * functional style. 736 ```js 737 function foo(callback) { 738 var result = calculateResult(); 739 (callback || angular.noop)(result); 740 } 741 ``` 742 */ 743function noop() {} 744noop.$inject = []; 745 746 747/** 748 * @ngdoc function 749 * @name angular.identity 750 * @module ng 751 * @kind function 752 * 753 * @description 754 * A function that returns its first argument. This function is useful when writing code in the 755 * functional style. 756 * 757 ```js 758 function transformer(transformationFn, value) { 759 return (transformationFn || angular.identity)(value); 760 }; 761 ``` 762 * @param {*} value to be returned. 763 * @returns {*} the value passed in. 764 */ 765function identity($) {return $;} 766identity.$inject = []; 767 768 769function valueFn(value) {return function() {return value;};} 770 771function hasCustomToString(obj) { 772 return isFunction(obj.toString) && obj.toString !== Object.prototype.toString; 773} 774 775 776/** 777 * @ngdoc function 778 * @name angular.isUndefined 779 * @module ng 780 * @kind function 781 * 782 * @description 783 * Determines if a reference is undefined. 784 * 785 * @param {*} value Reference to check. 786 * @returns {boolean} True if `value` is undefined. 787 */ 788function isUndefined(value) {return typeof value === 'undefined';} 789 790 791/** 792 * @ngdoc function 793 * @name angular.isDefined 794 * @module ng 795 * @kind function 796 * 797 * @description 798 * Determines if a reference is defined. 799 * 800 * @param {*} value Reference to check. 801 * @returns {boolean} True if `value` is defined. 802 */ 803function isDefined(value) {return typeof value !== 'undefined';} 804 805 806/** 807 * @ngdoc function 808 * @name angular.isObject 809 * @module ng 810 * @kind function 811 * 812 * @description 813 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 814 * considered to be objects. Note that JavaScript arrays are objects. 815 * 816 * @param {*} value Reference to check. 817 * @returns {boolean} True if `value` is an `Object` but not `null`. 818 */ 819function isObject(value) { 820 // http://jsperf.com/isobject4 821 return value !== null && typeof value === 'object'; 822} 823 824 825/** 826 * Determine if a value is an object with a null prototype 827 * 828 * @returns {boolean} True if `value` is an `Object` with a null prototype 829 */ 830function isBlankObject(value) { 831 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 832} 833 834 835/** 836 * @ngdoc function 837 * @name angular.isString 838 * @module ng 839 * @kind function 840 * 841 * @description 842 * Determines if a reference is a `String`. 843 * 844 * @param {*} value Reference to check. 845 * @returns {boolean} True if `value` is a `String`. 846 */ 847function isString(value) {return typeof value === 'string';} 848 849 850/** 851 * @ngdoc function 852 * @name angular.isNumber 853 * @module ng 854 * @kind function 855 * 856 * @description 857 * Determines if a reference is a `Number`. 858 * 859 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 860 * 861 * If you wish to exclude these then you can use the native 862 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 863 * method. 864 * 865 * @param {*} value Reference to check. 866 * @returns {boolean} True if `value` is a `Number`. 867 */ 868function isNumber(value) {return typeof value === 'number';} 869 870 871/** 872 * @ngdoc function 873 * @name angular.isDate 874 * @module ng 875 * @kind function 876 * 877 * @description 878 * Determines if a value is a date. 879 * 880 * @param {*} value Reference to check. 881 * @returns {boolean} True if `value` is a `Date`. 882 */ 883function isDate(value) { 884 return toString.call(value) === '[object Date]'; 885} 886 887 888/** 889 * @ngdoc function 890 * @name angular.isArray 891 * @module ng 892 * @kind function 893 * 894 * @description 895 * Determines if a reference is an `Array`. 896 * 897 * @param {*} value Reference to check. 898 * @returns {boolean} True if `value` is an `Array`. 899 */ 900var isArray = Array.isArray; 901 902/** 903 * @ngdoc function 904 * @name angular.isFunction 905 * @module ng 906 * @kind function 907 * 908 * @description 909 * Determines if a reference is a `Function`. 910 * 911 * @param {*} value Reference to check. 912 * @returns {boolean} True if `value` is a `Function`. 913 */ 914function isFunction(value) {return typeof value === 'function';} 915 916 917/** 918 * Determines if a value is a regular expression object. 919 * 920 * @private 921 * @param {*} value Reference to check. 922 * @returns {boolean} True if `value` is a `RegExp`. 923 */ 924function isRegExp(value) { 925 return toString.call(value) === '[object RegExp]'; 926} 927 928 929/** 930 * Checks if `obj` is a window object. 931 * 932 * @private 933 * @param {*} obj Object to check 934 * @returns {boolean} True if `obj` is a window obj. 935 */ 936function isWindow(obj) { 937 return obj && obj.window === obj; 938} 939 940 941function isScope(obj) { 942 return obj && obj.$evalAsync && obj.$watch; 943} 944 945 946function isFile(obj) { 947 return toString.call(obj) === '[object File]'; 948} 949 950 951function isFormData(obj) { 952 return toString.call(obj) === '[object FormData]'; 953} 954 955 956function isBlob(obj) { 957 return toString.call(obj) === '[object Blob]'; 958} 959 960 961function isBoolean(value) { 962 return typeof value === 'boolean'; 963} 964 965 966function isPromiseLike(obj) { 967 return obj && isFunction(obj.then); 968} 969 970 971var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/; 972function isTypedArray(value) { 973 return TYPED_ARRAY_REGEXP.test(toString.call(value)); 974} 975 976 977var trim = function(value) { 978 return isString(value) ? value.trim() : value; 979}; 980 981// Copied from: 982// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 983// Prereq: s is a string. 984var escapeForRegexp = function(s) { 985 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 986 replace(/\x08/g, '\\x08'); 987}; 988 989 990/** 991 * @ngdoc function 992 * @name angular.isElement 993 * @module ng 994 * @kind function 995 * 996 * @description 997 * Determines if a reference is a DOM element (or wrapped jQuery element). 998 * 999 * @param {*} value Reference to check. 1000 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 1001 */ 1002function isElement(node) { 1003 return !!(node && 1004 (node.nodeName // we are a direct element 1005 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 1006} 1007 1008/** 1009 * @param str 'key1,key2,...' 1010 * @returns {object} in the form of {key1:true, key2:true, ...} 1011 */ 1012function makeMap(str) { 1013 var obj = {}, items = str.split(","), i; 1014 for (i = 0; i < items.length; i++) { 1015 obj[items[i]] = true; 1016 } 1017 return obj; 1018} 1019 1020 1021function nodeName_(element) { 1022 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 1023} 1024 1025function includes(array, obj) { 1026 return Array.prototype.indexOf.call(array, obj) != -1; 1027} 1028 1029function arrayRemove(array, value) { 1030 var index = array.indexOf(value); 1031 if (index >= 0) { 1032 array.splice(index, 1); 1033 } 1034 return index; 1035} 1036 1037/** 1038 * @ngdoc function 1039 * @name angular.copy 1040 * @module ng 1041 * @kind function 1042 * 1043 * @description 1044 * Creates a deep copy of `source`, which should be an object or an array.
1045 * 1046 * * If no destination is supplied, a copy of the object or array is created. 1047 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 1048 * are deleted and then all elements/properties from the source are copied to it. 1049 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 1050 * * If `source` is identical to 'destination' an exception will be thrown. 1051 * 1052 * @param {*} source The source that will be used to make a copy. 1053 * Can be any type, including primitives, `null`, and `undefined`. 1054 * @param {(Object|Array)=} destination Destination into which the source is copied. If 1055 * provided, must be of the same type as `source`. 1056 * @returns {*} The copy or updated `destination`, if `destination` was specified. 1057 * 1058 * @example 1059 <example module="copyExample"> 1060 <file name="index.html"> 1061 <div ng-controller="ExampleController"> 1062 <form novalidate class="simple-form"> 1063 Name: <input type="text" ng-model="user.name" /><br /> 1064 E-mail: <input type="email" ng-model="user.email" /><br /> 1065 Gender: <input type="radio" ng-model="user.gender" value="male" />male 1066 <input type="radio" ng-model="user.gender" value="female" />female<br /> 1067 <button ng-click="reset()">RESET</button> 1068 <button ng-click="update(user)">SAVE</button> 1069 </form> 1070 <pre>form = {{user | json}}</pre> 1071 <pre>master = {{master | json}}</pre> 1072 </div> 1073 1074 <script> 1075 angular.module('copyExample', []) 1076 .controller('ExampleController', ['$scope', function($scope) { 1077 $scope.master= {}; 1078 1079 $scope.update = function(user) { 1080 // Example with 1 argument 1081 $scope.master= angular.copy(user); 1082 }; 1083 1084 $scope.reset = function() { 1085 // Example with 2 arguments 1086 angular.copy($scope.master, $scope.user); 1087 }; 1088 1089 $scope.reset(); 1090 }]); 1091 </script> 1092 </file> 1093 </example> 1094 */ 1095function copy(source, destination, stackSource, stackDest) { 1096 if (isWindow(source) || isScope(source)) { 1097 throw ngMinErr('cpws', 1098 "Can't copy! Making copies of Window or Scope instances is not supported."); 1099 } 1100 if (isTypedArray(destination)) { 1101 throw ngMinErr('cpta', 1102 "Can't copy! TypedArray destination cannot be mutated."); 1103 } 1104 1105 if (!destination) { 1106 destination = source; 1107 if (isObject(source)) { 1108 var index; 1109 if (stackSource && (index = stackSource.indexOf(source)) !== -1) { 1110 return stackDest[index]; 1111 } 1112 1113 // TypedArray, Date and RegExp have specific copy functionality and must be 1114 // pushed onto the stack before returning. 1115 // Array and other objects create the base object and recurse to copy child 1116 // objects. The array/object will be pushed onto the stack when recursed. 1117 if (isArray(source)) { 1118 return copy(source, [], stackSource, stackDest); 1119 } else if (isTypedArray(source)) { 1120 destination = new source.constructor(source); 1121 } else if (isDate(source)) { 1122 destination = new Date(source.getTime()); 1123 } else if (isRegExp(source)) { 1124 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 1125 destination.lastIndex = source.lastIndex; 1126 } else if (isFunction(source.cloneNode)) { 1127 destination = source.cloneNode(true); 1128 } else { 1129 var emptyObject = Object.create(getPrototypeOf(source)); 1130 return copy(source, emptyObject, stackSource, stackDest); 1131 } 1132 1133 if (stackDest) { 1134 stackSource.push(source); 1135 stackDest.push(destination); 1136 } 1137 } 1138 } else { 1139 if (source === destination) throw ngMinErr('cpi', 1140 "Can't copy! Source and destination are identical."); 1141 1142 stackSource = stackSource || []; 1143 stackDest = stackDest || []; 1144 1145 if (isObject(source)) { 1146 stackSource.push(source); 1147 stackDest.push(destination); 1148 } 1149 1150 var result, key; 1151 if (isArray(source)) { 1152 destination.length = 0; 1153 for (var i = 0; i < source.length; i++) { 1154 destination.push(copy(source[i], null, stackSource, stackDest)); 1155 } 1156 } else { 1157 var h = destination.$$hashKey; 1158 if (isArray(destination)) { 1159 destination.length = 0; 1160 } else {
vendor: 15,070 bytes, lines 1161-1659
1161 forEach(destination, function(value, key) { 1162 delete destination[key]; 1163 }); 1164 } 1165 if (isBlankObject(source)) { 1166 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 1167 for (key in source) { 1168 destination[key] = copy(source[key], null, stackSource, stackDest); 1169 } 1170 } else if (source && typeof source.hasOwnProperty === 'function') { 1171 // Slow path, which must rely on hasOwnProperty 1172 for (key in source) { 1173 if (source.hasOwnProperty(key)) { 1174 destination[key] = copy(source[key], null, stackSource, stackDest); 1175 } 1176 } 1177 } else { 1178 // Slowest path --- hasOwnProperty can't be called as a method 1179 for (key in source) { 1180 if (hasOwnProperty.call(source, key)) { 1181 destination[key] = copy(source[key], null, stackSource, stackDest); 1182 } 1183 } 1184 } 1185 setHashKey(destination,h); 1186 } 1187 } 1188 return destination; 1189} 1190 1191/** 1192 * Creates a shallow copy of an object, an array or a primitive. 1193 * 1194 * Assumes that there are no proto properties for objects. 1195 */ 1196function shallowCopy(src, dst) { 1197 if (isArray(src)) { 1198 dst = dst || []; 1199 1200 for (var i = 0, ii = src.length; i < ii; i++) { 1201 dst[i] = src[i]; 1202 } 1203 } else if (isObject(src)) { 1204 dst = dst || {}; 1205 1206 for (var key in src) { 1207 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 1208 dst[key] = src[key]; 1209 } 1210 } 1211 } 1212 1213 return dst || src; 1214} 1215 1216 1217/** 1218 * @ngdoc function 1219 * @name angular.equals 1220 * @module ng 1221 * @kind function 1222 * 1223 * @description 1224 * Determines if two objects or two values are equivalent. Supports value types, regular 1225 * expressions, arrays and objects. 1226 * 1227 * Two objects or values are considered equivalent if at least one of the following is true: 1228 * 1229 * * Both objects or values pass `===` comparison. 1230 * * Both objects or values are of the same type and all of their properties are equal by 1231 * comparing them with `angular.equals`. 1232 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 1233 * * Both values represent the same regular expression (In JavaScript, 1234 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 1235 * representation matches). 1236 * 1237 * During a property comparison, properties of `function` type and properties with names 1238 * that begin with `$` are ignored. 1239 * 1240 * Scope and DOMWindow objects are being compared only by identify (`===`). 1241 * 1242 * @param {*} o1 Object or value to compare. 1243 * @param {*} o2 Object or value to compare. 1244 * @returns {boolean} True if arguments are equal. 1245 */ 1246function equals(o1, o2) { 1247 if (o1 === o2) return true; 1248 if (o1 === null || o2 === null) return false; 1249 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1250 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1251 if (t1 == t2) { 1252 if (t1 == 'object') { 1253 if (isArray(o1)) { 1254 if (!isArray(o2)) return false; 1255 if ((length = o1.length) == o2.length) { 1256 for (key = 0; key < length; key++) { 1257 if (!equals(o1[key], o2[key])) return false; 1258 } 1259 return true; 1260 } 1261 } else if (isDate(o1)) { 1262 if (!isDate(o2)) return false; 1263 return equals(o1.getTime(), o2.getTime()); 1264 } else if (isRegExp(o1)) { 1265 return isRegExp(o2) ? o1.toString() == o2.toString() : false; 1266 } else { 1267 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1268 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1269 keySet = createMap(); 1270 for (key in o1) { 1271 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1272 if (!equals(o1[key], o2[key])) return false; 1273 keySet[key] = true; 1274 } 1275 for (key in o2) { 1276 if (!(key in keySet) && 1277 key.charAt(0) !== '$' && 1278 isDefined(o2[key]) && 1279 !isFunction(o2[key])) return false; 1280 } 1281 return true; 1282 } 1283 } 1284 } 1285 return false; 1286} 1287 1288var csp = function() { 1289 if (!isDefined(csp.rules)) { 1290 1291 1292 var ngCspElement = (document.querySelector('[ng-csp]') || 1293 document.querySelector('[data-ng-csp]')); 1294 1295 if (ngCspElement) { 1296 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 1297 ngCspElement.getAttribute('data-ng-csp'); 1298 csp.rules = { 1299 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 1300 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 1301 }; 1302 } else { 1303 csp.rules = { 1304 noUnsafeEval: noUnsafeEval(), 1305 noInlineStyle: false 1306 }; 1307 } 1308 } 1309 1310 return csp.rules; 1311 1312 function noUnsafeEval() { 1313 try { 1314 /* jshint -W031, -W054 */ 1315 new Function(''); 1316 /* jshint +W031, +W054 */ 1317 return false; 1318 } catch (e) { 1319 return true; 1320 } 1321 } 1322}; 1323 1324/** 1325 * @ngdoc directive 1326 * @module ng 1327 * @name ngJq 1328 * 1329 * @element ANY 1330 * @param {string=} ngJq the name of the library available under `window` 1331 * to be used for angular.element 1332 * @description 1333 * Use this directive to force the angular.element library. This should be 1334 * used to force either jqLite by leaving ng-jq blank or setting the name of 1335 * the jquery variable under window (eg. jQuery). 1336 * 1337 * Since angular looks for this directive when it is loaded (doesn't wait for the 1338 * DOMContentLoaded event), it must be placed on an element that comes before the script 1339 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1340 * others ignored. 1341 * 1342 * @example 1343 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1344 ```html 1345 <!doctype html> 1346 <html ng-app ng-jq> 1347 ... 1348 ... 1349 </html> 1350 ``` 1351 * @example 1352 * This example shows how to use a jQuery based library of a different name. 1353 * The library name must be available at the top most 'window'. 1354 ```html 1355 <!doctype html> 1356 <html ng-app ng-jq="jQueryLib"> 1357 ... 1358 ... 1359 </html> 1360 ``` 1361 */ 1362var jq = function() { 1363 if (isDefined(jq.name_)) return jq.name_; 1364 var el; 1365 var i, ii = ngAttrPrefixes.length, prefix, name; 1366 for (i = 0; i < ii; ++i) { 1367 prefix = ngAttrPrefixes[i]; 1368 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 1369 name = el.getAttribute(prefix + 'jq'); 1370 break; 1371 } 1372 } 1373 1374 return (jq.name_ = name); 1375}; 1376 1377function concat(array1, array2, index) { 1378 return array1.concat(slice.call(array2, index)); 1379} 1380 1381function sliceArgs(args, startIndex) { 1382 return slice.call(args, startIndex || 0); 1383} 1384 1385 1386/* jshint -W101 */ 1387/** 1388 * @ngdoc function 1389 * @name angular.bind 1390 * @module ng 1391 * @kind function 1392 * 1393 * @description 1394 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1395 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1396 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1397 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1398 * 1399 * @param {Object} self Context which `fn` should be evaluated in. 1400 * @param {function()} fn Function to be bound. 1401 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1402 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1403 */ 1404/* jshint +W101 */ 1405function bind(self, fn) { 1406 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1407 if (isFunction(fn) && !(fn instanceof RegExp)) { 1408 return curryArgs.length 1409 ? function() { 1410 return arguments.length 1411 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1412 : fn.apply(self, curryArgs); 1413 } 1414 : function() { 1415 return arguments.length 1416 ? fn.apply(self, arguments) 1417 : fn.call(self); 1418 }; 1419 } else { 1420 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1421 return fn; 1422 } 1423} 1424 1425 1426function toJsonReplacer(key, value) { 1427 var val = value; 1428 1429 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1430 val = undefined; 1431 } else if (isWindow(value)) { 1432 val = '$WINDOW'; 1433 } else if (value && document === value) { 1434 val = '$DOCUMENT'; 1435 } else if (isScope(value)) { 1436 val = '$SCOPE'; 1437 } 1438 1439 return val; 1440} 1441 1442 1443/** 1444 * @ngdoc function 1445 * @name angular.toJson 1446 * @module ng 1447 * @kind function 1448 * 1449 * @description 1450 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1451 * stripped since angular uses this notation internally. 1452 * 1453 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1454 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1455 * If set to an integer, the JSON output will contain that many spaces per indentation. 1456 * @returns {string|undefined} JSON-ified string representing `obj`. 1457 */ 1458function toJson(obj, pretty) { 1459 if (typeof obj === 'undefined') return undefined; 1460 if (!isNumber(pretty)) { 1461 pretty = pretty ? 2 : null; 1462 } 1463 return JSON.stringify(obj, toJsonReplacer, pretty); 1464} 1465 1466 1467/** 1468 * @ngdoc function 1469 * @name angular.fromJson 1470 * @module ng 1471 * @kind function 1472 * 1473 * @description 1474 * Deserializes a JSON string. 1475 * 1476 * @param {string} json JSON string to deserialize. 1477 * @returns {Object|Array|string|number} Deserialized JSON string. 1478 */ 1479function fromJson(json) { 1480 return isString(json) 1481 ? JSON.parse(json) 1482 : json; 1483} 1484 1485 1486function timezoneToOffset(timezone, fallback) { 1487 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1488 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1489} 1490 1491 1492function addDateMinutes(date, minutes) { 1493 date = new Date(date.getTime()); 1494 date.setMinutes(date.getMinutes() + minutes); 1495 return date; 1496} 1497 1498 1499function convertTimezoneToLocal(date, timezone, reverse) { 1500 reverse = reverse ? -1 : 1; 1501 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 1502 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); 1503} 1504 1505 1506/** 1507 * @returns {string} Returns the string representation of the element. 1508 */ 1509function startingTag(element) { 1510 element = jqLite(element).clone(); 1511 try { 1512 // turns out IE does not let you set .html() on elements which 1513 // are not allowed to have children. So we just ignore it. 1514 element.empty(); 1515 } catch (e) {} 1516 var elemHtml = jqLite('<div>').append(element).html(); 1517 try { 1518 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1519 elemHtml. 1520 match(/^(<[^>]+>)/)[1]. 1521 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1522 } catch (e) { 1523 return lowercase(elemHtml); 1524 } 1525 1526} 1527 1528 1529///////////////////////////////////////////////// 1530 1531/** 1532 * Tries to decode the URI component without throwing an exception. 1533 * 1534 * @private 1535 * @param str value potential URI component to check. 1536 * @returns {boolean} True if `value` can be decoded 1537 * with the decodeURIComponent function. 1538 */ 1539function tryDecodeURIComponent(value) { 1540 try { 1541 return decodeURIComponent(value); 1542 } catch (e) { 1543 // Ignore any invalid uri component 1544 } 1545} 1546 1547 1548/** 1549 * Parses an escaped url query string into key-value pairs. 1550 * @returns {Object.<string,boolean|Array>} 1551 */ 1552function parseKeyValue(/**string*/keyValue) { 1553 var obj = {}; 1554 forEach((keyValue || "").split('&'), function(keyValue) { 1555 var splitPoint, key, val; 1556 if (keyValue) { 1557 key = keyValue = keyValue.replace(/\+/g,'%20'); 1558 splitPoint = keyValue.indexOf('='); 1559 if (splitPoint !== -1) { 1560 key = keyValue.substring(0, splitPoint); 1561 val = keyValue.substring(splitPoint + 1); 1562 } 1563 key = tryDecodeURIComponent(key); 1564 if (isDefined(key)) { 1565 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 1566 if (!hasOwnProperty.call(obj, key)) { 1567 obj[key] = val; 1568 } else if (isArray(obj[key])) { 1569 obj[key].push(val); 1570 } else { 1571 obj[key] = [obj[key],val]; 1572 } 1573 } 1574 } 1575 }); 1576 return obj; 1577} 1578 1579function toKeyValue(obj) { 1580 var parts = []; 1581 forEach(obj, function(value, key) { 1582 if (isArray(value)) { 1583 forEach(value, function(arrayValue) { 1584 parts.push(encodeUriQuery(key, true) + 1585 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1586 }); 1587 } else { 1588 parts.push(encodeUriQuery(key, true) + 1589 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1590 } 1591 }); 1592 return parts.length ? parts.join('&') : ''; 1593} 1594 1595 1596/** 1597 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1598 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1599 * segments: 1600 * segment = *pchar 1601 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1602 * pct-encoded = "%" HEXDIG HEXDIG 1603 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1604 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1605 * / "*" / "+" / "," / ";" / "=" 1606 */ 1607function encodeUriSegment(val) { 1608 return encodeUriQuery(val, true). 1609 replace(/%26/gi, '&'). 1610 replace(/%3D/gi, '='). 1611 replace(/%2B/gi, '+'); 1612} 1613 1614 1615/** 1616 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1617 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1618 * encoded per http://tools.ietf.org/html/rfc3986: 1619 * query = *( pchar / "/" / "?" ) 1620 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1621 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1622 * pct-encoded = "%" HEXDIG HEXDIG 1623 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1624 * / "*" / "+" / "," / ";" / "=" 1625 */ 1626function encodeUriQuery(val, pctEncodeSpaces) { 1627 return encodeURIComponent(val). 1628 replace(/%40/gi, '@'). 1629 replace(/%3A/gi, ':'). 1630 replace(/%24/g, '$'). 1631 replace(/%2C/gi, ','). 1632 replace(/%3B/gi, ';'). 1633 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1634} 1635 1636var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1637 1638function getNgAttribute(element, ngAttr) { 1639 var attr, i, ii = ngAttrPrefixes.length; 1640 for (i = 0; i < ii; ++i) { 1641 attr = ngAttrPrefixes[i] + ngAttr; 1642 if (isString(attr = element.getAttribute(attr))) { 1643 return attr; 1644 } 1645 } 1646 return null; 1647} 1648 1649/** 1650 * @ngdoc directive 1651 * @name ngApp 1652 * @module ng 1653 * 1654 * @element ANY 1655 * @param {angular.Module} ngApp an optional application 1656 * {@link angular.module module} name to load. 1657 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1658 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1659 * do not use explicit function annotation (and are thus unsuitable for minification), as de
1659scribed 1660 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1661 * tracking down the root of these bugs. 1662 * 1663 * @description 1664 * 1665 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1666 * designates the **root element** of the application and is typically placed near the root element 1667 * of the page - e.g. on the `<body>` or `<html>` tags. 1668 * 1669 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1670 * found in the document will be used to define the root element to auto-bootstrap as an 1671 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1672 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1673 * 1674 * You can specify an **AngularJS module** to be used as the root module for the application. This 1675 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1676 * should contain the application code needed or have dependencies on other modules that will 1677 * contain the code. See {@link angular.module} for more information. 1678 * 1679 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1680 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1681 * would not be resolved to `3`. 1682 * 1683 * `ngApp` is the easiest, and most common way to bootstrap an application. 1684 * 1685 <example module="ngAppDemo"> 1686 <file name="index.html"> 1687 <div ng-controller="ngAppDemoController"> 1688 I can add: {{a}} + {{b}} = {{ a+b }} 1689 </div> 1690 </file> 1691 <file name="script.js"> 1692 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1693 $scope.a = 1; 1694 $scope.b = 2; 1695 }); 1696 </file> 1697 </example> 1698 * 1699 * Using `ngStrictDi`, you would see something like this: 1700 * 1701 <example ng-app-included="true"> 1702 <file name="index.html"> 1703 <div ng-app="ngAppStrictDemo" ng-strict-di> 1704 <div ng-controller="GoodController1"> 1705 I can add: {{a}} + {{b}} = {{ a+b }} 1706 1707 <p>This renders because the controller does not fail to 1708 instantiate, by using explicit annotation style (see 1709 script.js for details) 1710 </p> 1711 </div> 1712 1713 <div ng-controller="GoodController2"> 1714 Name: <input ng-model="name"><br /> 1715 Hello, {{name}}! 1716 1717 <p>This renders because the controller does not fail to 1718 instantiate, by using explicit annotation style 1719 (see script.js for details) 1720 </p> 1721 </div> 1722 1723 <div ng-controller="BadController"> 1724 I can add: {{a}} + {{b}} = {{ a+b }} 1725 1726 <p>The controller could not be instantiated, due to relying 1727 on automatic function annotations (which are disabled in 1728 strict mode). As such, the content of this section is not 1729 interpolated, and there should be an error in your web console. 1730 </p> 1731 </div> 1732 </div> 1733 </file> 1734 <file name="script.js"> 1735 angular.module('ngAppStrictDemo', []) 1736 // BadController will fail to instantiate, due to relying on automatic function annotation, 1737 // rather than an explicit annotation 1738 .controller('BadController', function($scope) { 1739 $scope.a = 1; 1740 $scope.b = 2; 1741 }) 1742 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1743 // due to using explicit annotations using the array style and $inject property, respectively. 1744 .controller('GoodController1', ['$scope', function($scope) { 1745 $scope.a = 1; 1746 $scope.b = 2; 1747 }]) 1748 .controller('GoodController2', GoodController2); 1749 function GoodController2($scope) { 1750 $scope.name = "World"; 1751 } 1752 GoodController2.$inject = ['$scope']; 1753 </file> 1754 <file name="style.css"> 1755 div[ng-controller] { 1756 margin-bottom: 1em; 1757 -webkit-border-radius: 4px; 1758 border-radius: 4px; 1759 border: 1px solid; 1760 padding: .5em; 1761 } 1762 div[ng-controller^=Good] { 1763 border-color: #d6e9c6; 1764 background-color: #dff0d8; 1765 color: #3c763d; 1766 } 1767 div[ng-controller^=Bad] { 1768 border-color: #ebccd1; 1769 background-color: #f2dede; 1770 color: #a94442; 1771 margin-bottom: 0; 1772 } 1773 </file> 1774 </example> 1775 */ 1776function angularInit(element, bootstrap) { 1777 var appElement, 1778 module, 1779 config = {}; 1780 1781 // The element `element` has priority over any other element 1782 forEach(ngAttrPrefixes, function(prefix) { 1783 var name = prefix + 'app'; 1784 1785 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1786 appElement = element; 1787 module = element.getAttribute(name); 1788 } 1789 }); 1790 forEach(ngAttrPrefixes, function(prefix) { 1791 var name = prefix + 'app'; 1792 var candidate; 1793 1794 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1795 appElement = candidate; 1796 module = candidate.getAttribute(name); 1797 } 1798 }); 1799 if (appElement) { 1800 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 1801 bootstrap(appElement, module ? [module] : [], config); 1802 } 1803} 1804 1805/** 1806 * @ngdoc function 1807 * @name angular.bootstrap 1808 * @module ng 1809 * @description 1810 * Use this function to manually start up angular application. 1811 * 1812 * See: {@link guide/bootstrap Bootstrap} 1813 *
1814 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. 1815 * They must use {@link ng.directive:ngApp ngApp}. 1816 * 1817 * Angular will detect if it has been loaded into the browser more than once and only allow the 1818 * first loaded script to be bootstrapped and will report a warning to the browser console for 1819 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1820 * multiple instances of Angular try to work on the DOM. 1821 * 1822 * ```html 1823 * <!doctype html> 1824 * <html> 1825 * <body> 1826 * <div ng-controller="WelcomeController"> 1827 * {{greeting}} 1828 * </div> 1829 * 1830 * <script src="angular.js"></script> 1831 * <script> 1832 * var app = angular.module('demo', []) 1833 * .controller('WelcomeController', function($scope) { 1834 * $scope.greeting = 'Welcome!'; 1835 * }); 1836 * angular.bootstrap(document, ['demo']); 1837 * </script> 1838 * </body> 1839 * </html> 1840 * ``` 1841 * 1842 * @param {DOMElement} element DOM element which is the root of angular application. 1843 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1844 * Each item in the array should be the name of a predefined module or a (DI annotated) 1845 * function that will be invoked by the injector as a `config` block. 1846 * See: {@link angular.module modules} 1847 * @param {Object=} config an object for defining configuration options for the application. The 1848 * following keys are supported: 1849 * 1850 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1851 * assist in finding bugs which break minified code. Defaults to `false`. 1852 * 1853 * @returns {auto.$injector} Returns the newly created injector for this app. 1854 */ 1855function bootstrap(element, modules, config) { 1856 if (!isObject(config)) config = {}; 1857 var defaultConfig = { 1858 strictDi: false 1859 }; 1860 config = extend(defaultConfig, config); 1861 var doBootstrap = function() { 1862 element = jqLite(element); 1863 1864 if (element.injector()) { 1865 var tag = (element[0] === document) ? 'document' : startingTag(element); 1866 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1867 throw ngMinErr( 1868 'btstrpd', 1869 "App Already Bootstrapped with this Element '{0}'", 1870 tag.replace(/</,'<').replace(/>/,'>')); 1871 } 1872 1873 modules = modules || []; 1874 modules.unshift(['$provide', function($provide) { 1875 $provide.value('$rootElement', element); 1876 }]); 1877 1878 if (config.debugInfoEnabled) { 1879 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1880 modules.push(['$compileProvider', function($compileProvider) { 1881 $compileProvider.debugInfoEnabled(true); 1882 }]); 1883 } 1884 1885 modules.unshift('ng'); 1886 var injector = createInjector(modules, config.strictDi); 1887 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1888 function bootstrapApply(scope, element, compile, injector) { 1889 scope.$apply(function() { 1890 element.data('$injector', injector); 1891 compile(element)(scope); 1892 }); 1893 }] 1894 ); 1895 return injector; 1896 }; 1897 1898 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1899 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1900 1901 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1902 config.debugInfoEnabled = true; 1903 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1904 } 1905 1906 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1907 return doBootstrap(); 1908 } 1909 1910 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1911 angular.resumeBootstrap = function(extraModules) { 1912 forEach(extraModules, function(module) { 1913 modules.push(module); 1914 }); 1915 return doBootstrap(); 1916 }; 1917 1918 if (isFunction(angular.resumeDeferredBootstrap)) { 1919 angular.resumeDeferredBootstrap(); 1920 } 1921} 1922 1923/** 1924 * @ngdoc function 1925 * @name angular.reloadWithDebugInfo 1926 * @module ng 1927 * @description 1928 * Use this function to reload the current application with debug information turned on. 1929 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 1930 * 1931 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 1932 */ 1933function reloadWithDebugInfo() { 1934 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 1935 window.location.reload(); 1936} 1937 1938/** 1939 * @name angular.getTestability 1940 * @module ng 1941 * @description 1942 * Get the testability service for the instance of Angular on the given 1943 * element. 1944 * @param {DOMElement} element DOM element which is the root of angular application. 1945 */ 1946function getTestability(rootElement) { 1947 var injector = angular.element(rootElement).injector(); 1948 if (!injector) { 1949 throw ngMinErr('test', 1950 'no injector found for element argument to getTestability'); 1951 } 1952 return injector.get('$$testability'); 1953} 1954 1955var SNAKE_CASE_REGEXP = /[A-Z]/g; 1956function snake_case(name, separator) { 1957 separator = separator || '_'; 1958 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1959 return (pos ? separator : '') + letter.toLowerCase(); 1960 }); 1961} 1962 1963var bindJQueryFired = false;
vendor: 9,099 bytes, lines 1964-2253
1964var skipDestroyOnNextJQueryCleanData; 1965function bindJQuery() { 1966 var originalCleanData; 1967 1968 if (bindJQueryFired) { 1969 return; 1970 } 1971 1972 // bind to jQuery if present; 1973 var jqName = jq(); 1974 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 1975 !jqName ? undefined : // use jqLite 1976 window[jqName]; // use jQuery specified by `ngJq` 1977 1978 // Use jQuery if it exists with proper functionality, otherwise default to us. 1979 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 1980 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 1981 // versions. It will not work for sure with jQuery <1.7, though. 1982 if (jQuery && jQuery.fn.on) { 1983 jqLite = jQuery; 1984 extend(jQuery.fn, { 1985 scope: JQLitePrototype.scope, 1986 isolateScope: JQLitePrototype.isolateScope, 1987 controller: JQLitePrototype.controller, 1988 injector: JQLitePrototype.injector, 1989 inheritedData: JQLitePrototype.inheritedData 1990 }); 1991 1992 // All nodes removed from the DOM via various jQuery APIs like .remove() 1993 // are passed through jQuery.cleanData. Monkey-patch this method to fire 1994 // the $destroy event on all removed nodes. 1995 originalCleanData = jQuery.cleanData; 1996 jQuery.cleanData = function(elems) { 1997 var events; 1998 if (!skipDestroyOnNextJQueryCleanData) { 1999 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 2000 events = jQuery._data(elem, "events"); 2001 if (events && events.$destroy) { 2002 jQuery(elem).triggerHandler('$destroy'); 2003 } 2004 } 2005 } else { 2006 skipDestroyOnNextJQueryCleanData = false; 2007 } 2008 originalCleanData(elems); 2009 }; 2010 } else { 2011 jqLite = JQLite; 2012 } 2013 2014 angular.element = jqLite; 2015 2016 // Prevent double-proxying. 2017 bindJQueryFired = true; 2018} 2019 2020/** 2021 * throw error if the argument is falsy. 2022 */ 2023function assertArg(arg, name, reason) { 2024 if (!arg) { 2025 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 2026 } 2027 return arg; 2028} 2029 2030function assertArgFn(arg, name, acceptArrayAnnotation) { 2031 if (acceptArrayAnnotation && isArray(arg)) { 2032 arg = arg[arg.length - 1]; 2033 } 2034 2035 assertArg(isFunction(arg), name, 'not a function, got ' + 2036 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 2037 return arg; 2038} 2039 2040/** 2041 * throw error if the name given is hasOwnProperty 2042 * @param {String} name the name to test 2043 * @param {String} context the context in which the name is used, such as module or directive 2044 */ 2045function assertNotHasOwnProperty(name, context) { 2046 if (name === 'hasOwnProperty') { 2047 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 2048 } 2049} 2050 2051/** 2052 * Return the value accessible from the object by path. Any undefined traversals are ignored 2053 * @param {Object} obj starting object 2054 * @param {String} path path to traverse 2055 * @param {boolean} [bindFnToScope=true] 2056 * @returns {Object} value as accessible by path 2057 */ 2058//TODO(misko): this function needs to be removed 2059function getter(obj, path, bindFnToScope) { 2060 if (!path) return obj; 2061 var keys = path.split('.'); 2062 var key; 2063 var lastInstance = obj; 2064 var len = keys.length; 2065 2066 for (var i = 0; i < len; i++) { 2067 key = keys[i]; 2068 if (obj) { 2069 obj = (lastInstance = obj)[key]; 2070 } 2071 } 2072 if (!bindFnToScope && isFunction(obj)) { 2073 return bind(lastInstance, obj); 2074 } 2075 return obj; 2076} 2077 2078/** 2079 * Return the DOM siblings between the first and last node in the given array. 2080 * @param {Array} array like object 2081 * @returns {Array} the inputted object or a jqLite collection containing the nodes 2082 */ 2083function getBlockNodes(nodes) { 2084 // TODO(perf): update `nodes` instead of creating a new object? 2085 var node = nodes[0]; 2086 var endNode = nodes[nodes.length - 1]; 2087 var blockNodes; 2088 2089 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 2090 if (blockNodes || nodes[i] !== node) { 2091 if (!blockNodes) { 2092 blockNodes = jqLite(slice.call(nodes, 0, i)); 2093 } 2094 blockNodes.push(node); 2095 } 2096 } 2097 2098 return blockNodes || nodes; 2099} 2100 2101 2102/** 2103 * Creates a new object without a prototype. This object is useful for lookup without having to 2104 * guard against prototypically inherited properties via hasOwnProperty. 2105 * 2106 * Related micro-benchmarks: 2107 * - http://jsperf.com/object-create2 2108 * - http://jsperf.com/proto-map-lookup/2 2109 * - http://jsperf.com/for-in-vs-object-keys2 2110 * 2111 * @returns {Object} 2112 */ 2113function createMap() { 2114 return Object.create(null); 2115} 2116 2117var NODE_TYPE_ELEMENT = 1; 2118var NODE_TYPE_ATTRIBUTE = 2; 2119var NODE_TYPE_TEXT = 3; 2120var NODE_TYPE_COMMENT = 8; 2121var NODE_TYPE_DOCUMENT = 9; 2122var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 2123 2124/** 2125 * @ngdoc type 2126 * @name angular.Module 2127 * @module ng 2128 * @description 2129 * 2130 * Interface for configuring angular {@link angular.module modules}. 2131 */ 2132 2133function setupModuleLoader(window) { 2134 2135 var $injectorMinErr = minErr('$injector'); 2136 var ngMinErr = minErr('ng'); 2137 2138 function ensure(obj, name, factory) { 2139 return obj[name] || (obj[name] = factory()); 2140 } 2141 2142 var angular = ensure(window, 'angular', Object); 2143 2144 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 2145 angular.$$minErr = angular.$$minErr || minErr; 2146 2147 return ensure(angular, 'module', function() { 2148 /** @type {Object.<string, angular.Module>} */ 2149 var modules = {}; 2150 2151 /** 2152 * @ngdoc function 2153 * @name angular.module 2154 * @module ng 2155 * @description 2156 * 2157 * The `angular.module` is a global place for creating, registering and retrieving Angular 2158 * modules. 2159 * All modules (angular core or 3rd party) that should be available to an application must be 2160 * registered using this mechanism. 2161 * 2162 * Passing one argument retrieves an existing {@link angular.Module}, 2163 * whereas passing more than one argument creates a new {@link angular.Module} 2164 * 2165 * 2166 * # Module 2167 * 2168 * A module is a collection of services, directives, controllers, filters, and configuration information. 2169 * `angular.module` is used to configure the {@link auto.$injector $injector}. 2170 * 2171 * ```js 2172 * // Create a new module 2173 * var myModule = angular.module('myModule', []); 2174 * 2175 * // register a new service 2176 * myModule.value('appName', 'MyCoolApp'); 2177 * 2178 * // configure existing services inside initialization blocks. 2179 * myModule.config(['$locationProvider', function($locationProvider) { 2180 * // Configure existing providers 2181 * $locationProvider.hashPrefix('!'); 2182 * }]); 2183 * ``` 2184 * 2185 * Then you can create an injector and load your modules like this: 2186 * 2187 * ```js 2188 * var injector = angular.injector(['ng', 'myModule']) 2189 * ``` 2190 * 2191 * However it's more likely that you'll just use 2192 * {@link ng.directive:ngApp ngApp} or 2193 * {@link angular.bootstrap} to simplify this process for you. 2194 * 2195 * @param {!string} name The name of the module to create or retrieve. 2196 * @param {!Array.<string>=} requires If specified then new module is being created. If 2197 * unspecified then the module is being retrieved for further configuration. 2198 * @param {Function=} configFn Optional configuration function for the module. Same as 2199 * {@link angular.Module#config Module#config()}. 2200 * @returns {module} new module with the {@link angular.Module} api. 2201 */ 2202 return function module(name, requires, configFn) { 2203 var assertNotHasOwnProperty = function(name, context) { 2204 if (name === 'hasOwnProperty') { 2205 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 2206 } 2207 }; 2208 2209 assertNotHasOwnProperty(name, 'module'); 2210 if (requires && modules.hasOwnProperty(name)) { 2211 modules[name] = null; 2212 } 2213 return ensure(modules, name, function() { 2214 if (!requires) { 2215 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 2216 "the module name or forgot to load it. If registering a module ensure that you " + 2217 "specify the dependencies as the second argument.", name); 2218 } 2219 2220 /** @type {!Array.<Array.<*>>} */ 2221 var invokeQueue = []; 2222 2223 /** @type {!Array.<Function>} */ 2224 var configBlocks = []; 2225 2226 /** @type {!Array.<Function>} */ 2227 var runBlocks = []; 2228 2229 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 2230 2231 /** @type {angular.Module} */ 2232 var moduleInstance = { 2233 // Private state 2234 _invokeQueue: invokeQueue, 2235 _configBlocks: configBlocks, 2236 _runBlocks: runBlocks, 2237 2238 /** 2239 * @ngdoc property 2240 * @name angular.Module#requires 2241 * @module ng 2242 * 2243 * @description 2244 * Holds the list of modules which the injector will load before the current module is 2245 * loaded. 2246 */ 2247 requires: requires, 2248 2249 /** 2250 * @ngdoc property 2251 * @name angular.Module#name 2252 * @module ng 2253 *
2254 * @description 2255 * Name of the module. 2256 */ 2257 name: name, 2258 2259 2260 /** 2261 * @ngdoc method 2262 * @name angular.Module#provider 2263 * @module ng 2264 * @param {string} name service name 2265 * @param {Function} providerType Construction function for creating new instance of the 2266 * service. 2267 * @description 2268 * See {@link auto.$provide#provider $provide.provider()}. 2269 */ 2270 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 2271 2272 /** 2273 * @ngdoc method 2274 * @name angular.Module#factory 2275 * @module ng 2276 * @param {string} name service name 2277 * @param {Function} providerFunction Function for creating new instance of the service. 2278 * @description 2279 * See {@link auto.$provide#factory $provide.factory()}. 2280 */ 2281 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 2282 2283 /** 2284 * @ngdoc method 2285 * @name angular.Module#service 2286 * @module ng 2287 * @param {string} name service name 2288 * @param {Function} constructor A constructor function that will be instantiated. 2289 * @description 2290 * See {@link auto.$provide#service $provide.service()}. 2291 */ 2292 service: invokeLaterAndSetModuleName('$provide', 'service'), 2293 2294 /** 2295 * @ngdoc method 2296 * @name angular.Module#value 2297 * @module ng 2298 * @param {string} name service name 2299 * @param {*} object Service instance object. 2300 * @description 2301 * See {@link auto.$provide#value $provide.value()}. 2302 */ 2303 value: invokeLater('$provide', 'value'), 2304 2305 /** 2306 * @ngdoc method 2307 * @name angular.Module#constant 2308 * @module ng 2309 * @param {string} name constant name 2310 * @param {*} object Constant value. 2311 * @description 2312 * Because the constant are fixed, they get applied before other provide methods. 2313 * See {@link auto.$provide#constant $provide.constant()}. 2314 */ 2315 constant: invokeLater('$provide', 'constant', 'unshift'), 2316 2317 /** 2318 * @ngdoc method 2319 * @name angular.Module#decorator 2320 * @module ng 2321 * @param {string} The name of the service to decorate. 2322 * @param {Function} This function will be invoked when the service needs to be 2323 * instantiated and should return the decorated service instance. 2324 * @description 2325 * See {@link auto.$provide#decorator $provide.decorator()}. 2326 */ 2327 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 2328 2329 /** 2330 * @ngdoc method 2331 * @name angular.Module#animation 2332 * @module ng 2333 * @param {string} name animation name 2334 * @param {Function} animationFactory Factory function for creating new instance of an 2335 * animation. 2336 * @description 2337 * 2338 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2339 * 2340 * 2341 * Defines an animation hook that can be later used with 2342 * {@link $animate $animate} service and directives that use this service. 2343 * 2344 * ```js 2345 * module.animation('.animation-name', function($inject1, $inject2) { 2346 * return { 2347 * eventName : function(element, done) { 2348 * //code to run the animation 2349 * //once complete, then run done() 2350 * return function cancellationFunction(element) { 2351 * //code to cancel the animation 2352 * } 2353 * } 2354 * } 2355 * }) 2356 * ``` 2357 * 2358 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2359 * {@link ngAnimate ngAnimate module} for more information. 2360 */ 2361 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 2362 2363 /** 2364 * @ngdoc method 2365 * @name angular.Module#filter 2366 * @module ng 2367 * @param {string} name Filter name - this must be a valid angular expression identifier 2368 * @param {Function} filterFactory Factory function for creating new instance of filter.
2369 * @description 2370 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2371 * 2372 * <div class="alert alert-warning"> 2373 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 2374 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2375 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2376 * (`myapp_subsection_filterx`). 2377 * </div> 2378 */ 2379 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 2380 2381 /** 2382 * @ngdoc method 2383 * @name angular.Module#controller 2384 * @module ng 2385 * @param {string|Object} name Controller name, or an object map of controllers where the 2386 * keys are the names and the values are the constructors. 2387 * @param {Function} constructor Controller constructor function. 2388 * @description 2389 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2390 */ 2391 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 2392 2393 /** 2394 * @ngdoc method 2395 * @name angular.Module#directive 2396 * @module ng 2397 * @param {string|Object} name Directive name, or an object map of directives where the 2398 * keys are the names and the values are the factories. 2399 * @param {Function} directiveFactory Factory function for creating new instance of 2400 * directives. 2401 * @description 2402 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2403 */ 2404 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 2405 2406 /** 2407 * @ngdoc method 2408 * @name angular.Module#config 2409 * @module ng 2410 * @param {Function} configFn Execute this function on module load. Useful for service 2411 * configuration. 2412 * @description 2413 * Use this method to register work which needs to be performed on module loading. 2414 * For more about how to configure services, see 2415 * {@link providers#provider-recipe Provider Recipe}. 2416 */ 2417 config: config, 2418 2419 /** 2420 * @ngdoc method 2421 * @name angular.Module#run 2422 * @module ng 2423 * @param {Function} initializationFn Execute this function after injector creation. 2424 * Useful for application initialization. 2425 * @description 2426 * Use this method to register work which should be performed when the injector is done 2427 * loading all modules. 2428 */ 2429 run: function(block) { 2430 runBlocks.push(block); 2431 return this; 2432 } 2433 }; 2434 2435 if (configFn) { 2436 config(configFn); 2437 } 2438 2439 return moduleInstance; 2440 2441 /** 2442 * @param {string} provider 2443 * @param {string} method 2444 * @param {String=} insertMethod 2445 * @returns {angular.Module} 2446 */ 2447 function invokeLater(provider, method, insertMethod, queue) { 2448 if (!queue) queue = invokeQueue; 2449 return function() { 2450 queue[insertMethod || 'push']([provider, method, arguments]); 2451 return moduleInstance; 2452 }; 2453 } 2454 2455 /** 2456 * @param {string} provider 2457 * @param {string} method 2458 * @returns {angular.Module} 2459 */ 2460 function invokeLaterAndSetModuleName(provider, method) { 2461 return function(recipeName, factoryFunction) { 2462 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 2463 invokeQueue.push([provider, method, arguments]); 2464 return moduleInstance; 2465 }; 2466 } 2467 }); 2468 }; 2469 }); 2470 2471} 2472 2473/* global: toDebugString: true */ 2474 2475function serializeObject(obj) { 2476 var seen = []; 2477 2478 return JSON.stringify(obj, function(key, val) { 2479 val = toJsonReplacer(key, val); 2480 if (isObject(val)) { 2481 2482 if (seen.indexOf(val) >= 0) return '...'; 2483 2484 seen.push(val); 2485 } 2486 return val; 2487 }); 2488} 2489 2490function toDebugString(obj) { 2491 if (typeof obj === 'function') { 2492 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2493 } else if (isUndefined(obj)) { 2494 return 'undefined'; 2495 } else if (typeof obj !== 'string') { 2496 return serializeObject(obj); 2497 } 2498 return obj; 2499} 2500 2501/* global angularModule: true, 2502 version: true, 2503 2504 $CompileProvider, 2505 2506 htmlAnchorDirective, 2507 inputDirective, 2508 inputDirective, 2509 formDirective, 2510 scriptDirective, 2511 selectDirective, 2512 styleDirective, 2513 optionDirective, 2514 ngBindDirective, 2515 ngBindHtmlDirective, 2516 ngBindTemplateDirective, 2517 ngClassDirective, 2518 ngClassEvenDirective, 2519 ngClassOddDirective, 2520 ngCloakDirective, 2521 ngControllerDirective, 2522 ngFormDirective, 2523 ngHideDirective, 2524 ngIfDirective, 2525 ngIncludeDirective, 2526 ngIncludeFillContentDirective, 2527 ngInitDirective, 2528 ngNonBindableDirective, 2529 ngPluralizeDirective, 2530 ngRepeatDirective, 2531 ngShowDirective, 2532 ngStyleDirective, 2533 ngSwitchDirective, 2534 ngSwitchWhenDirective, 2535 ngSwitchDefaultDirective, 2536 ngOptionsDirective, 2537 ngTranscludeDirective, 2538 ngModelDirective, 2539 ngListDirective, 2540 ngChangeDirective, 2541 patternDirective, 2542 patternDirective, 2543 requiredDirective, 2544 requiredDirective, 2545 minlengthDirective, 2546 minlengthDirective, 2547 maxlengthDirective, 2548 maxlengthDirective, 2549 ngValueDirective, 2550 ngModelOptionsDirective, 2551 ngAttributeAliasDirectives, 2552 ngEventDirectives, 2553 2554 $AnchorScrollProvider, 2555 $AnimateProvider, 2556 $CoreAnimateCssProvider, 2557 $$CoreAnimateQueueProvider, 2558 $$CoreAnimateRunnerProvider, 2559 $BrowserProvider,
vendor: 4,985 bytes, lines 2560-2719
2560 $CacheFactoryProvider, 2561 $ControllerProvider, 2562 $DocumentProvider, 2563 $ExceptionHandlerProvider, 2564 $FilterProvider, 2565 $$ForceReflowProvider, 2566 $InterpolateProvider, 2567 $IntervalProvider, 2568 $$HashMapProvider, 2569 $HttpProvider, 2570 $HttpParamSerializerProvider, 2571 $HttpParamSerializerJQLikeProvider, 2572 $HttpBackendProvider, 2573 $xhrFactoryProvider, 2574 $LocationProvider, 2575 $LogProvider, 2576 $ParseProvider, 2577 $RootScopeProvider, 2578 $QProvider, 2579 $$QProvider, 2580 $$SanitizeUriProvider, 2581 $SceProvider, 2582 $SceDelegateProvider, 2583 $SnifferProvider, 2584 $TemplateCacheProvider, 2585 $TemplateRequestProvider, 2586 $$TestabilityProvider, 2587 $TimeoutProvider, 2588 $$RAFProvider, 2589 $WindowProvider, 2590 $$jqLiteProvider, 2591 $$CookieReaderProvider 2592*/ 2593 2594 2595/** 2596 * @ngdoc object 2597 * @name angular.version 2598 * @module ng 2599 * @description 2600 * An object that contains information about the current AngularJS version. 2601 * 2602 * This object has the following properties: 2603 * 2604 * - `full` â `{string}` â Full version string, such as "0.9.18". 2605 * - `major` â `{number}` â Major version number, such as "0". 2606 * - `minor` â `{number}` â Minor version number, such as "9". 2607 * - `dot` â `{number}` â Dot version number, such as "18". 2608 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 2609 */ 2610var version = { 2611 full: '1.4.7', // all of these placeholder strings will be replaced by grunt's 2612 major: 1, // package task 2613 minor: 4, 2614 dot: 7, 2615 codeName: 'snapshot' 2616}; 2617 2618 2619function publishExternalAPI(angular) { 2620 extend(angular, { 2621 'bootstrap': bootstrap, 2622 'copy': copy, 2623 'extend': extend, 2624 'merge': merge, 2625 'equals': equals, 2626 'element': jqLite, 2627 'forEach': forEach, 2628 'injector': createInjector, 2629 'noop': noop, 2630 'bind': bind, 2631 'toJson': toJson, 2632 'fromJson': fromJson, 2633 'identity': identity, 2634 'isUndefined': isUndefined, 2635 'isDefined': isDefined, 2636 'isString': isString, 2637 'isFunction': isFunction, 2638 'isObject': isObject, 2639 'isNumber': isNumber, 2640 'isElement': isElement, 2641 'isArray': isArray, 2642 'version': version, 2643 'isDate': isDate, 2644 'lowercase': lowercase, 2645 'uppercase': uppercase, 2646 'callbacks': {counter: 0}, 2647 'getTestability': getTestability, 2648 '$$minErr': minErr, 2649 '$$csp': csp, 2650 'reloadWithDebugInfo': reloadWithDebugInfo 2651 }); 2652 2653 angularModule = setupModuleLoader(window); 2654 2655 angularModule('ng', ['ngLocale'], ['$provide', 2656 function ngModule($provide) { 2657 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2658 $provide.provider({ 2659 $$sanitizeUri: $$SanitizeUriProvider 2660 }); 2661 $provide.provider('$compile', $CompileProvider). 2662 directive({ 2663 a: htmlAnchorDirective, 2664 input: inputDirective, 2665 textarea: inputDirective, 2666 form: formDirective, 2667 script: scriptDirective, 2668 select: selectDirective, 2669 style: styleDirective, 2670 option: optionDirective, 2671 ngBind: ngBindDirective, 2672 ngBindHtml: ngBindHtmlDirective, 2673 ngBindTemplate: ngBindTemplateDirective, 2674 ngClass: ngClassDirective, 2675 ngClassEven: ngClassEvenDirective, 2676 ngClassOdd: ngClassOddDirective, 2677 ngCloak: ngCloakDirective, 2678 ngController: ngControllerDirective, 2679 ngForm: ngFormDirective, 2680 ngHide: ngHideDirective, 2681 ngIf: ngIfDirective, 2682 ngInclude: ngIncludeDirective, 2683 ngInit: ngInitDirective, 2684 ngNonBindable: ngNonBindableDirective, 2685 ngPluralize: ngPluralizeDirective, 2686 ngRepeat: ngRepeatDirective, 2687 ngShow: ngShowDirective, 2688 ngStyle: ngStyleDirective, 2689 ngSwitch: ngSwitchDirective, 2690 ngSwitchWhen: ngSwitchWhenDirective, 2691 ngSwitchDefault: ngSwitchDefaultDirective, 2692 ngOptions: ngOptionsDirective, 2693 ngTransclude: ngTranscludeDirective, 2694 ngModel: ngModelDirective, 2695 ngList: ngListDirective, 2696 ngChange: ngChangeDirective, 2697 pattern: patternDirective, 2698 ngPattern: patternDirective, 2699 required: requiredDirective, 2700 ngRequired: requiredDirective, 2701 minlength: minlengthDirective, 2702 ngMinlength: minlengthDirective, 2703 maxlength: maxlengthDirective, 2704 ngMaxlength: maxlengthDirective, 2705 ngValue: ngValueDirective, 2706 ngModelOptions: ngModelOptionsDirective 2707 }). 2708 directive({ 2709 ngInclude: ngIncludeFillContentDirective 2710 }). 2711 directive(ngAttributeAliasDirectives). 2712 directive(ngEventDirectives); 2713 $provide.provider({ 2714 $anchorScroll: $AnchorScrollProvider, 2715 $animate: $AnimateProvider, 2716 $animateCss: $CoreAnimateCssProvider, 2717 $$animateQueue: $$CoreAnimateQueueProvider, 2718 $$AnimateRunner: $$CoreAnimateRunnerProvider, 2719 $browser: $BrowserProvider,
2720 $cacheFactory: $CacheFactoryProvider, 2721 $controller: $ControllerProvider, 2722 $document: $DocumentProvider, 2723 $exceptionHandler: $ExceptionHandlerProvider, 2724 $filter: $FilterProvider, 2725 $$forceReflow: $$ForceReflowProvider, 2726 $interpolate: $InterpolateProvider, 2727 $interval: $IntervalProvider, 2728 $http: $HttpProvider, 2729 $httpParamSerializer: $HttpParamSerializerProvider, 2730 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2731 $httpBackend: $HttpBackendProvider, 2732 $xhrFactory: $xhrFactoryProvider, 2733 $location: $LocationProvider, 2734 $log: $LogProvider, 2735 $parse: $ParseProvider, 2736 $rootScope: $RootScopeProvider, 2737 $q: $QProvider, 2738 $$q: $$QProvider, 2739 $sce: $SceProvider, 2740 $sceDelegate: $SceDelegateProvider, 2741 $sniffer: $SnifferProvider, 2742 $templateCache: $TemplateCacheProvider, 2743 $templateRequest: $TemplateRequestProvider, 2744 $$testability: $$TestabilityProvider, 2745 $timeout: $TimeoutProvider, 2746 $window: $WindowProvider, 2747 $$rAF: $$RAFProvider, 2748 $$jqLite: $$jqLiteProvider, 2749 $$HashMap: $$HashMapProvider, 2750 $$cookieReader: $$CookieReaderProvider 2751 }); 2752 } 2753 ]); 2754} 2755 2756/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2757 * Any commits to this file should be reviewed with security in mind. * 2758 * Changes to this file can potentially create security vulnerabilities. * 2759 * An approval from 2 Core members with history of modifying * 2760 * this file is required. * 2761 * * 2762 * Does the change somehow allow for arbitrary javascript to be executed? * 2763 * Or allows for someone to change the prototype of built-in objects? * 2764 * Or gives undesired access to variables likes document or window? * 2765 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2766 2767/* global JQLitePrototype: true, 2768 addEventListenerFn: true, 2769 removeEventListenerFn: true, 2770 BOOLEAN_ATTR: true, 2771 ALIASED_ATTR: true, 2772*/ 2773 2774////////////////////////////////// 2775//JQLite 2776////////////////////////////////// 2777 2778/** 2779 * @ngdoc function 2780 * @name angular.element 2781 * @module ng 2782 * @kind function 2783 * 2784 * @description 2785 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2786 * 2787 * If jQuery is available, `angular.element` is an alias for the 2788 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2789 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2790 * 2791 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2792 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2793 * commonly needed functionality with the goal of having a very small footprint.</div> 2794 * 2795 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. 2796 * 2797 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2798 * jqLite; they are never raw DOM references.</div> 2799 * 2800 * ## Angular's jqLite 2801 * jqLite provides only the following jQuery methods: 2802 * 2803 * - [`addClass()`](http://api.jquery.com/addClass/) 2804 * - [`after()`](http://api.jquery.com/after/) 2805 * - [`append()`](http://api.jquery.com/append/) 2806 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 2807 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2808 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2809 * - [`clone()`](http://api.jquery.com/clone/) 2810 * - [`contents()`](http://api.jquery.com/contents/) 2811 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'. 2812 * - [`data()`](http://api.jquery.com/data/) 2813 * - [`detach()`](http://api.jquery.com/detach/) 2814 * - [`empty()`](http://api.jquery.com/empty/) 2815 * - [`eq()`](http://api.jquery.com/eq/) 2816 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2817 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2818 * - [`html()`](http://api.jquery.com/html/) 2819 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2820 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2821 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 2822 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2823 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2824 * - [`prepend()`](http://api.jquery.com/prepend/) 2825 * - [`prop()`](http://api.jquery.com/prop/) 2826 * - [`ready()`](http://api.jquery.com/ready/) 2827 * - [`remove()`](http://api.jquery.com/remove/) 2828 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2829 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2830 * - [`removeData()`](http://api.jquery.com/removeData/) 2831 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2832 * - [`text()`](http://api.jquery.com/text/) 2833 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2834 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2835 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter 2836 * - [`val()`](http://api.jquery.com/val/) 2837 * - [`wrap()`](http://api.jquery.com/wrap/) 2838 * 2839 * ## jQuery/jqLite Extras 2840 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2841 * 2842 * ### Events 2843 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2843pis and fires this event 2844 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2845 * element before it is removed. 2846 * 2847 * ### Methods 2848 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2849 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2850 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2851 * `'ngModel'`). 2852 * - `injector()` - retrieves the injector of the current element or its parent. 2853 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2854 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 2855 * be enabled. 2856 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2857 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2858 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2859 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 2860 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2861 * parent element is reached. 2862 * 2863 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2864 * @returns {Object} jQuery object. 2865 */ 2866 2867JQLite.expando = 'ng339'; 2868 2869var jqCache = JQLite.cache = {}, 2870 jqId = 1, 2871 addEventListenerFn = function(element, type, fn) { 2872 element.addEventListener(type, fn, false); 2873 }, 2874 removeEventListenerFn = function(element, type, fn) { 2875 element.removeEventListener(type, fn, false); 2876 }; 2877 2878/* 2879 * !!! This is an undocumented "private" function !!! 2880 */ 2881JQLite._data = function(node) { 2882 //jQuery always returns an object on cache miss 2883 return this.cache[node[this.expando]] || {}; 2884}; 2885 2886function jqNextId() { return ++jqId; } 2887 2888 2889var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2890var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2891var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 2892var jqLiteMinErr = minErr('jqLite'); 2893 2894/** 2895 * Converts snake_case to camelCase. 2896 * Also there is special case for Moz prefix starting with upper case letter. 2897 * @param name Name to normalize 2898 */ 2899function camelCase(name) { 2900 return name. 2901 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2902 return offset ? letter.toUpperCase() : letter; 2903 }). 2904 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2905} 2906 2907var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 2908var HTML_REGEXP = /<|&#?\w+;/; 2909var TAG_NAME_REGEXP = /<([\w:-]+)/; 2910var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 2911 2912var wrapMap = { 2913 'option': [1, '<select multiple="multiple">', '</select>'], 2914 2915 'thead': [1, '<table>', '</table>'], 2916 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2917 'tr': [2, '<table><tbody>', '</tbody></table>'], 2918 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2919 '_default': [0, "", ""] 2920}; 2921 2922wrapMap.optgroup = wrapMap.option; 2923wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2924wrapMap.th = wrapMap.td; 2925 2926 2927function jqLiteIsTextNode(html) { 2928 return !HTML_REGEXP.test(html); 2929} 2930 2931function jqLiteAcceptsData(node) { 2932 // The window object can accept data but has no nodeType 2933 // Otherwise we are only interested in elements (1) and documents (9) 2934 var nodeType = node.nodeType; 2935 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 2936} 2937 2938function jqLiteHasData(node) { 2939 for (var key in jqCache[node.ng339]) { 2940 return true; 2941 } 2942 return false; 2943} 2944 2945function jqLiteBuildFragment(html, context) { 2946 var tmp, tag, wrap, 2947 fragment = context.createDocumentFragment(), 2948 nodes = [], i; 2949 2950 if (jqLiteIsTextNode(html)) { 2951 // Convert non-html into a text node 2952 nodes.push(context.createTextNode(html)); 2953 } else { 2954 // Convert html into DOM nodes 2955 tmp = tmp || fragment.appendChild(context.createElement("div")); 2956 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2957 wrap = wrapMap[tag] || wrapMap._default; 2958 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2959 2960 // Descend through wrappers to the right content 2961 i = wrap[0]; 2962 while (i--) { 2963 tmp = tmp.lastChild; 2964 } 2965 2966 nodes = concat(nodes, tmp.childNodes); 2967 2968 tmp = fragment.firstChild; 2969 tmp.textContent = ""; 2970 } 2971 2972 // Remove wrapper from fragment 2973 fragment.textContent = ""; 2974 fragment.innerHTML = ""; // Clear inner HTML 2975 forEach(nodes, function(node) { 2976 fragment.appendChild(node); 2977 }); 2978 2979 return fragment; 2980} 2981 2982function jqLiteParseHTML(html, context) { 2983 context = context || document; 2984 var parsed; 2985 2986 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2987 return [context.createElement(parsed[1])]; 2988 } 2989 2990 if ((parsed = jqLiteBuildFragment(html, context))) { 2991 return parsed.childNodes; 2992 } 2993 2994 return []; 2995} 2996
vendor: 1,065 bytes, lines 2997-3038
2997///////////////////////////////////////////// 2998function JQLite(element) { 2999 if (element instanceof JQLite) { 3000 return element; 3001 } 3002 3003 var argIsString; 3004 3005 if (isString(element)) { 3006 element = trim(element); 3007 argIsString = true; 3008 } 3009 if (!(this instanceof JQLite)) { 3010 if (argIsString && element.charAt(0) != '<') { 3011 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 3012 } 3013 return new JQLite(element); 3014 } 3015 3016 if (argIsString) { 3017 jqLiteAddNodes(this, jqLiteParseHTML(element)); 3018 } else { 3019 jqLiteAddNodes(this, element); 3020 } 3021} 3022 3023function jqLiteClone(element) { 3024 return element.cloneNode(true); 3025} 3026 3027function jqLiteDealoc(element, onlyDescendants) { 3028 if (!onlyDescendants) jqLiteRemoveData(element); 3029 3030 if (element.querySelectorAll) { 3031 var descendants = element.querySelectorAll('*'); 3032 for (var i = 0, l = descendants.length; i < l; i++) { 3033 jqLiteRemoveData(descendants[i]); 3034 } 3035 } 3036} 3037 3038function jqLiteOff(element, type, fn, unsupported) {
3039 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 3040 3041 var expandoStore = jqLiteExpandoStore(element); 3042 var events = expandoStore && expandoStore.events; 3043 var handle = expandoStore && expandoStore.handle; 3044 3045 if (!handle) return; //no listeners registered 3046 3047 if (!type) { 3048 for (type in events) { 3049 if (type !== '$destroy') { 3050 removeEventListenerFn(element, type, handle); 3051 } 3052 delete events[type]; 3053 } 3054 } else { 3055 forEach(type.split(' '), function(type) { 3056 if (isDefined(fn)) { 3057 var listenerFns = events[type]; 3058 arrayRemove(listenerFns || [], fn); 3059 if (listenerFns && listenerFns.length > 0) { 3060 return; 3061 } 3062 } 3063 3064 removeEventListenerFn(element, type, handle); 3065 delete events[type]; 3066 }); 3067 } 3068} 3069 3070function jqLiteRemoveData(element, name) { 3071 var expandoId = element.ng339; 3072 var expandoStore = expandoId && jqCache[expandoId]; 3073 3074 if (expandoStore) { 3075 if (name) { 3076 delete expandoStore.data[name]; 3077 return; 3078 } 3079 3080 if (expandoStore.handle) { 3081 if (expandoStore.events.$destroy) { 3082 expandoStore.handle({}, '$destroy'); 3083 } 3084 jqLiteOff(element); 3085 } 3086 delete jqCache[expandoId]; 3087 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 3088 } 3089} 3090 3091 3092function jqLiteExpandoStore(element, createIfNecessary) { 3093 var expandoId = element.ng339, 3094 expandoStore = expandoId && jqCache[expandoId]; 3095 3096 if (createIfNecessary && !expandoStore) { 3097 element.ng339 = expandoId = jqNextId(); 3098 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 3099 } 3100 3101 return expandoStore; 3102} 3103 3104 3105function jqLiteData(element, key, value) { 3106 if (jqLiteAcceptsData(element)) { 3107 3108 var isSimpleSetter = isDefined(value); 3109 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 3110 var massGetter = !key; 3111 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 3112 var data = expandoStore && expandoStore.data; 3113 3114 if (isSimpleSetter) { // data('key', value) 3115 data[key] = value; 3116 } else { 3117 if (massGetter) { // data() 3118 return data; 3119 } else { 3120 if (isSimpleGetter) { // data('key') 3121 // don't force creation of expandoStore if it doesn't exist yet 3122 return data && data[key]; 3123 } else { // mass-setter: data({key1: val1, key2: val2}) 3124 extend(data, key); 3125 } 3126 } 3127 } 3128 } 3129} 3130 3131function jqLiteHasClass(element, selector) { 3132 if (!element.getAttribute) return false; 3133 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 3134 indexOf(" " + selector + " ") > -1); 3135} 3136 3137function jqLiteRemoveClass(element, cssClasses) { 3138 if (cssClasses && element.setAttribute) { 3139 forEach(cssClasses.split(' '), function(cssClass) { 3140 element.setAttribute('class', trim( 3141 (" " + (element.getAttribute('class') || '') + " ") 3142 .replace(/[\n\t]/g, " ") 3143 .replace(" " + trim(cssClass) + " ", " ")) 3144 ); 3145 }); 3146 } 3147} 3148 3149function jqLiteAddClass(element, cssClasses) { 3150 if (cssClasses && element.setAttribute) { 3151 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 3152 .replace(/[\n\t]/g, " "); 3153 3154 forEach(cssClasses.split(' '), function(cssClass) { 3155 cssClass = trim(cssClass); 3156 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 3157 existingClasses += cssClass + ' '; 3158 } 3159 }); 3160 3161 element.setAttribute('class', trim(existingClasses)); 3162 } 3163} 3164 3165 3166function jqLiteAddNodes(root, elements) { 3167 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 3168 3169 if (elements) { 3170 3171 // if a Node (the most common case) 3172 if (elements.nodeType) { 3173 root[root.length++] = elements; 3174 } else { 3175 var length = elements.length; 3176 3177 // if an Array or NodeList and not a Window 3178 if (typeof length === 'number' && elements.window !== elements) { 3179 if (length) { 3180 for (var i = 0; i < length; i++) { 3181 root[root.length++] = elements[i]; 3182 } 3183 } 3184 } else { 3185 root[root.length++] = elements; 3186 } 3187 } 3188 } 3189} 3190 3191 3192function jqLiteController(element, name) { 3193 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 3194} 3195 3196function jqLiteInheritedData(element, name, value) { 3197 // if element is the document object work with the html element instead 3198 // this makes $(document).scope() possible 3199 if (element.nodeType == NODE_TYPE_DOCUMENT) { 3200 element = element.documentElement; 3201 } 3202 var names = isArray(name) ? name : [name]; 3203 3204 while (element) { 3205 for (var i = 0, ii = names.length; i < ii; i++) { 3206 if (isDefined(value = jqLite.data(element, names[i]))) return value; 3207 } 3208 3209 // If dealing with a document fragment node with a host element, and no parent, use the host 3210 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 3211 // to lookup parent controllers. 3212 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 3213 } 3214} 3215 3216function jqLiteEmpty(element) { 3217 jqLiteDealoc(element, true); 3218 while (element.firstChild) { 3219 element.removeChild(element.firstChild); 3220 } 3221} 3222 3223function jqLiteRemove(element, keepData) { 3224 if (!keepData) jqLiteDealoc(element); 3225 var parent = element.parentNode; 3226 if (parent) parent.removeChild(element); 3227} 3228 3229 3230function jqLiteDocumentLoaded(action, win) { 3231 win = win || window; 3232 if (win.document.readyState === 'complete') { 3233 // Force the action to be run async for consistent behaviour 3234 // from the action's point of view 3235 // i.e. it will definitely not be in a $apply 3236 win.setTimeout(action); 3237 } else { 3238 // No need to unbind this handler as load is only ever called once 3239 jqLite(win).on('load', action); 3240 } 3241} 3242
vendor: 8,923 bytes, lines 3243-3546
3243////////////////////////////////////////// 3244// Functions which are declared directly. 3245////////////////////////////////////////// 3246var JQLitePrototype = JQLite.prototype = { 3247 ready: function(fn) { 3248 var fired = false; 3249 3250 function trigger() { 3251 if (fired) return; 3252 fired = true; 3253 fn(); 3254 } 3255 3256 // check if document is already loaded 3257 if (document.readyState === 'complete') { 3258 setTimeout(trigger); 3259 } else { 3260 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 3261 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 3262 // jshint -W064 3263 JQLite(window).on('load', trigger); // fallback to window.onload for others 3264 // jshint +W064 3265 } 3266 }, 3267 toString: function() { 3268 var value = []; 3269 forEach(this, function(e) { value.push('' + e);}); 3270 return '[' + value.join(', ') + ']'; 3271 }, 3272 3273 eq: function(index) { 3274 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 3275 }, 3276 3277 length: 0, 3278 push: push, 3279 sort: [].sort, 3280 splice: [].splice 3281}; 3282 3283////////////////////////////////////////// 3284// Functions iterating getter/setters. 3285// these functions return self on setter and 3286// value on get. 3287////////////////////////////////////////// 3288var BOOLEAN_ATTR = {}; 3289forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3290 BOOLEAN_ATTR[lowercase(value)] = value; 3291}); 3292var BOOLEAN_ELEMENTS = {}; 3293forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3294 BOOLEAN_ELEMENTS[value] = true; 3295}); 3296var ALIASED_ATTR = { 3297 'ngMinlength': 'minlength', 3298 'ngMaxlength': 'maxlength', 3299 'ngMin': 'min', 3300 'ngMax': 'max', 3301 'ngPattern': 'pattern' 3302}; 3303 3304function getBooleanAttrName(element, name) { 3305 // check dom last since we will most likely fail on name 3306 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3307 3308 // booleanAttr is here twice to minimize DOM access 3309 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3310} 3311 3312function getAliasedAttrName(name) { 3313 return ALIASED_ATTR[name]; 3314} 3315 3316forEach({ 3317 data: jqLiteData, 3318 removeData: jqLiteRemoveData, 3319 hasData: jqLiteHasData 3320}, function(fn, name) { 3321 JQLite[name] = fn; 3322}); 3323 3324forEach({ 3325 data: jqLiteData, 3326 inheritedData: jqLiteInheritedData, 3327 3328 scope: function(element) { 3329 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3330 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3331 }, 3332 3333 isolateScope: function(element) { 3334 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3335 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3336 }, 3337 3338 controller: jqLiteController, 3339 3340 injector: function(element) { 3341 return jqLiteInheritedData(element, '$injector'); 3342 }, 3343 3344 removeAttr: function(element, name) { 3345 element.removeAttribute(name); 3346 }, 3347 3348 hasClass: jqLiteHasClass, 3349 3350 css: function(element, name, value) { 3351 name = camelCase(name); 3352 3353 if (isDefined(value)) { 3354 element.style[name] = value; 3355 } else { 3356 return element.style[name]; 3357 } 3358 }, 3359 3360 attr: function(element, name, value) { 3361 var nodeType = element.nodeType; 3362 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 3363 return; 3364 } 3365 var lowercasedName = lowercase(name); 3366 if (BOOLEAN_ATTR[lowercasedName]) { 3367 if (isDefined(value)) { 3368 if (!!value) { 3369 element[name] = true; 3370 element.setAttribute(name, lowercasedName); 3371 } else { 3372 element[name] = false; 3373 element.removeAttribute(lowercasedName); 3374 } 3375 } else { 3376 return (element[name] || 3377 (element.attributes.getNamedItem(name) || noop).specified) 3378 ? lowercasedName 3379 : undefined; 3380 } 3381 } else if (isDefined(value)) { 3382 element.setAttribute(name, value); 3383 } else if (element.getAttribute) { 3384 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 3385 // some elements (e.g. Document) don't have get attribute, so return undefined 3386 var ret = element.getAttribute(name, 2); 3387 // normalize non-existing attributes to undefined (as jQuery) 3388 return ret === null ? undefined : ret; 3389 } 3390 }, 3391 3392 prop: function(element, name, value) { 3393 if (isDefined(value)) { 3394 element[name] = value; 3395 } else { 3396 return element[name]; 3397 } 3398 }, 3399 3400 text: (function() { 3401 getText.$dv = ''; 3402 return getText; 3403 3404 function getText(element, value) { 3405 if (isUndefined(value)) { 3406 var nodeType = element.nodeType; 3407 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3408 } 3409 element.textContent = value; 3410 } 3411 })(), 3412 3413 val: function(element, value) { 3414 if (isUndefined(value)) { 3415 if (element.multiple && nodeName_(element) === 'select') { 3416 var result = []; 3417 forEach(element.options, function(option) { 3418 if (option.selected) { 3419 result.push(option.value || option.text); 3420 } 3421 }); 3422 return result.length === 0 ? null : result; 3423 } 3424 return element.value; 3425 } 3426 element.value = value; 3427 }, 3428 3429 html: function(element, value) { 3430 if (isUndefined(value)) { 3431 return element.innerHTML; 3432 } 3433 jqLiteDealoc(element, true); 3434 element.innerHTML = value; 3435 }, 3436 3437 empty: jqLiteEmpty 3438}, function(fn, name) { 3439 /** 3440 * Properties: writes return selection, reads return first value 3441 */ 3442 JQLite.prototype[name] = function(arg1, arg2) { 3443 var i, key; 3444 var nodeCount = this.length; 3445 3446 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3447 // in a way that survives minification. 3448 // jqLiteEmpty takes no arguments but is a setter. 3449 if (fn !== jqLiteEmpty && 3450 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 3451 if (isObject(arg1)) { 3452 3453 // we are a write, but the object properties are the key/values 3454 for (i = 0; i < nodeCount; i++) { 3455 if (fn === jqLiteData) { 3456 // data() takes the whole object in jQuery 3457 fn(this[i], arg1); 3458 } else { 3459 for (key in arg1) { 3460 fn(this[i], key, arg1[key]); 3461 } 3462 } 3463 } 3464 // return self for chaining 3465 return this; 3466 } else { 3467 // we are a read, so read the first child. 3468 // TODO: do we still need this? 3469 var value = fn.$dv; 3470 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3471 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 3472 for (var j = 0; j < jj; j++) { 3473 var nodeValue = fn(this[j], arg1, arg2); 3474 value = value ? value + nodeValue : nodeValue; 3475 } 3476 return value; 3477 } 3478 } else { 3479 // we are a write, so apply to all children 3480 for (i = 0; i < nodeCount; i++) { 3481 fn(this[i], arg1, arg2); 3482 } 3483 // return self for chaining 3484 return this; 3485 } 3486 }; 3487}); 3488 3489function createEventHandler(element, events) { 3490 var eventHandler = function(event, type) { 3491 // jQuery specific api 3492 event.isDefaultPrevented = function() { 3493 return event.defaultPrevented; 3494 }; 3495 3496 var eventFns = events[type || event.type]; 3497 var eventFnsLength = eventFns ? eventFns.length : 0; 3498 3499 if (!eventFnsLength) return; 3500 3501 if (isUndefined(event.immediatePropagationStopped)) { 3502 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3503 event.stopImmediatePropagation = function() { 3504 event.immediatePropagationStopped = true; 3505 3506 if (event.stopPropagation) { 3507 event.stopPropagation(); 3508 } 3509 3510 if (originalStopImmediatePropagation) { 3511 originalStopImmediatePropagation.call(event); 3512 } 3513 }; 3514 } 3515 3516 event.isImmediatePropagationStopped = function() { 3517 return event.immediatePropagationStopped === true; 3518 }; 3519 3520 // Copy event handlers in case event handlers array is modified during execution. 3521 if ((eventFnsLength > 1)) { 3522 eventFns = shallowCopy(eventFns); 3523 } 3524 3525 for (var i = 0; i < eventFnsLength; i++) { 3526 if (!event.isImmediatePropagationStopped()) { 3527 eventFns[i].call(element, event); 3528 } 3529 } 3530 }; 3531 3532 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3533 // events on `element` 3534 eventHandler.elem = element; 3535 return eventHandler; 3536} 3537 3538////////////////////////////////////////// 3539// Functions iterating traversal. 3540// These functions chain results into a single 3541// selector. 3542////////////////////////////////////////// 3543forEach({ 3544 removeData: jqLiteRemoveData, 3545 3546 on: function jqLiteOn(element, type, fn, unsupported) {
3547 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3548 3549 // Do not add event handlers to non-elements because they will not be cleaned up. 3550 if (!jqLiteAcceptsData(element)) { 3551 return; 3552 } 3553 3554 var expandoStore = jqLiteExpandoStore(element, true); 3555 var events = expandoStore.events; 3556 var handle = expandoStore.handle; 3557 3558 if (!handle) { 3559 handle = expandoStore.handle = createEventHandler(element, events); 3560 } 3561 3562 // http://jsperf.com/string-indexof-vs-split 3563 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3564 var i = types.length; 3565 3566 while (i--) { 3567 type = types[i]; 3568 var eventFns = events[type]; 3569 3570 if (!eventFns) { 3571 events[type] = []; 3572 3573 if (type === 'mouseenter' || type === 'mouseleave') { 3574 // Refer to jQuery's implementation of mouseenter & mouseleave 3575 // Read about mouseenter and mouseleave: 3576 // http://www.quirksmode.org/js/events_mouse.html#link8 3577 3578 jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) { 3579 var target = this, related = event.relatedTarget; 3580 // For mousenter/leave call the handler if related is outside the target. 3581 // NB: No relatedTarget if the mouse left/entered the browser window 3582 if (!related || (related !== target && !target.contains(related))) { 3583 handle(event, type); 3584 } 3585 }); 3586 3587 } else { 3588 if (type !== '$destroy') { 3589 addEventListenerFn(element, type, handle); 3590 } 3591 } 3592 eventFns = events[type]; 3593 } 3594 eventFns.push(fn); 3595 } 3596 }, 3597 3598 off: jqLiteOff, 3599 3600 one: function(element, type, fn) { 3601 element = jqLite(element); 3602 3603 //add the listener twice so that when it is called 3604 //you can remove the original function and still be 3605 //able to call element.off(ev, fn) normally 3606 element.on(type, function onFn() { 3607 element.off(type, fn); 3608 element.off(type, onFn); 3609 }); 3610 element.on(type, fn); 3611 }, 3612 3613 replaceWith: function(element, replaceNode) { 3614 var index, parent = element.parentNode; 3615 jqLiteDealoc(element); 3616 forEach(new JQLite(replaceNode), function(node) { 3617 if (index) { 3618 parent.insertBefore(node, index.nextSibling); 3619 } else { 3620 parent.replaceChild(node, element); 3621 } 3622 index = node; 3623 }); 3624 }, 3625 3626 children: function(element) { 3627 var children = []; 3628 forEach(element.childNodes, function(element) { 3629 if (element.nodeType === NODE_TYPE_ELEMENT) { 3630 children.push(element); 3631 } 3632 }); 3633 return children; 3634 }, 3635 3636 contents: function(element) { 3637 return element.contentDocument || element.childNodes || []; 3638 }, 3639 3640 append: function(element, node) { 3641 var nodeType = element.nodeType; 3642 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 3643 3644 node = new JQLite(node); 3645 3646 for (var i = 0, ii = node.length; i < ii; i++) { 3647 var child = node[i]; 3648 element.appendChild(child); 3649 } 3650 }, 3651 3652 prepend: function(element, node) { 3653 if (element.nodeType === NODE_TYPE_ELEMENT) { 3654 var index = element.firstChild; 3655 forEach(new JQLite(node), function(child) { 3656 element.insertBefore(child, index); 3657 }); 3658 } 3659 }, 3660 3661 wrap: function(element, wrapNode) { 3662 wrapNode = jqLite(wrapNode).eq(0).clone()[0]; 3663 var parent = element.parentNode; 3664 if (parent) { 3665 parent.replaceChild(wrapNode, element); 3666 } 3667 wrapNode.appendChild(element); 3668 }, 3669 3670 remove: jqLiteRemove, 3671 3672 detach: function(element) { 3673 jqLiteRemove(element, true); 3674 }, 3675 3676 after: function(element, newElement) { 3677 var index = element, parent = element.parentNode; 3678 newElement = new JQLite(newElement); 3679 3680 for (var i = 0, ii = newElement.length; i < ii; i++) { 3681 var node = newElement[i]; 3682 parent.insertBefore(node, index.nextSibling); 3683 index = node; 3684 } 3685 }, 3686 3687 addClass: jqLiteAddClass, 3688 removeClass: jqLiteRemoveClass, 3689 3690 toggleClass: function(element, selector, condition) { 3691 if (selector) {
vendor: 5,441 bytes, lines 3692-3893
3692 forEach(selector.split(' '), function(className) { 3693 var classCondition = condition; 3694 if (isUndefined(classCondition)) { 3695 classCondition = !jqLiteHasClass(element, className); 3696 } 3697 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3698 }); 3699 } 3700 }, 3701 3702 parent: function(element) { 3703 var parent = element.parentNode; 3704 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 3705 }, 3706 3707 next: function(element) { 3708 return element.nextElementSibling; 3709 }, 3710 3711 find: function(element, selector) { 3712 if (element.getElementsByTagName) { 3713 return element.getElementsByTagName(selector); 3714 } else { 3715 return []; 3716 } 3717 }, 3718 3719 clone: jqLiteClone, 3720 3721 triggerHandler: function(element, event, extraParameters) { 3722 3723 var dummyEvent, eventFnsCopy, handlerArgs; 3724 var eventName = event.type || event; 3725 var expandoStore = jqLiteExpandoStore(element); 3726 var events = expandoStore && expandoStore.events; 3727 var eventFns = events && events[eventName]; 3728 3729 if (eventFns) { 3730 // Create a dummy event to pass to the handlers 3731 dummyEvent = { 3732 preventDefault: function() { this.defaultPrevented = true; }, 3733 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3734 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 3735 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 3736 stopPropagation: noop, 3737 type: eventName, 3738 target: element 3739 }; 3740 3741 // If a custom event was provided then extend our dummy event with it 3742 if (event.type) { 3743 dummyEvent = extend(dummyEvent, event); 3744 } 3745 3746 // Copy event handlers in case event handlers array is modified during execution. 3747 eventFnsCopy = shallowCopy(eventFns); 3748 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3749 3750 forEach(eventFnsCopy, function(fn) { 3751 if (!dummyEvent.isImmediatePropagationStopped()) { 3752 fn.apply(element, handlerArgs); 3753 } 3754 }); 3755 } 3756 } 3757}, function(fn, name) { 3758 /** 3759 * chaining functions 3760 */ 3761 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3762 var value; 3763 3764 for (var i = 0, ii = this.length; i < ii; i++) { 3765 if (isUndefined(value)) { 3766 value = fn(this[i], arg1, arg2, arg3); 3767 if (isDefined(value)) { 3768 // any function which returns a value needs to be wrapped 3769 value = jqLite(value); 3770 } 3771 } else { 3772 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3773 } 3774 } 3775 return isDefined(value) ? value : this; 3776 }; 3777 3778 // bind legacy bind/unbind to on/off 3779 JQLite.prototype.bind = JQLite.prototype.on; 3780 JQLite.prototype.unbind = JQLite.prototype.off; 3781}); 3782 3783 3784// Provider for private $$jqLite service 3785function $$jqLiteProvider() { 3786 this.$get = function $$jqLite() { 3787 return extend(JQLite, { 3788 hasClass: function(node, classes) { 3789 if (node.attr) node = node[0]; 3790 return jqLiteHasClass(node, classes); 3791 }, 3792 addClass: function(node, classes) { 3793 if (node.attr) node = node[0]; 3794 return jqLiteAddClass(node, classes); 3795 }, 3796 removeClass: function(node, classes) { 3797 if (node.attr) node = node[0]; 3798 return jqLiteRemoveClass(node, classes); 3799 } 3800 }); 3801 }; 3802} 3803 3804/** 3805 * Computes a hash of an 'obj'. 3806 * Hash of a: 3807 * string is string 3808 * number is number as string 3809 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3810 * that is also assigned to the $$hashKey property of the object. 3811 * 3812 * @param obj 3813 * @returns {string} hash string such that the same input will have the same hash string. 3814 * The resulting string key is in 'type:hashKey' format. 3815 */ 3816function hashKey(obj, nextUidFn) { 3817 var key = obj && obj.$$hashKey; 3818 3819 if (key) { 3820 if (typeof key === 'function') { 3821 key = obj.$$hashKey(); 3822 } 3823 return key; 3824 } 3825 3826 var objType = typeof obj; 3827 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3828 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 3829 } else { 3830 key = objType + ':' + obj; 3831 } 3832 3833 return key; 3834} 3835 3836/** 3837 * HashMap which can use objects as keys 3838 */ 3839function HashMap(array, isolatedUid) { 3840 if (isolatedUid) { 3841 var uid = 0; 3842 this.nextUid = function() { 3843 return ++uid; 3844 }; 3845 } 3846 forEach(array, this.put, this); 3847} 3848HashMap.prototype = { 3849 /** 3850 * Store key value pair 3851 * @param key key to store can be any type 3852 * @param value value to store can be any type 3853 */ 3854 put: function(key, value) { 3855 this[hashKey(key, this.nextUid)] = value; 3856 }, 3857 3858 /** 3859 * @param key 3860 * @returns {Object} the value for the key 3861 */ 3862 get: function(key) { 3863 return this[hashKey(key, this.nextUid)]; 3864 }, 3865 3866 /** 3867 * Remove the key/value pair 3868 * @param key 3869 */ 3870 remove: function(key) { 3871 var value = this[key = hashKey(key, this.nextUid)]; 3872 delete this[key]; 3873 return value; 3874 } 3875}; 3876 3877var $$HashMapProvider = [function() { 3878 this.$get = [function() { 3879 return HashMap; 3880 }]; 3881}]; 3882 3883/** 3884 * @ngdoc function 3885 * @module ng 3886 * @name angular.injector 3887 * @kind function 3888 * 3889 * @description 3890 * Creates an injector object that can be used for retrieving services as well as for 3891 * dependency injection (see {@link guide/di dependency injection}). 3892 * 3893 * @param {Array.<string|Function>}
3893 modules A list of module functions or their aliases. See 3894 * {@link angular.module}. The `ng` module must be explicitly added. 3895 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 3896 * disallows argument name annotation inference. 3897 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3898 * 3899 * @example 3900 * Typical usage 3901 * ```js 3902 * // create an injector 3903 * var $injector = angular.injector(['ng']); 3904 * 3905 * // use the injector to kick off your application 3906 * // use the type inference to auto inject arguments, or use implicit injection 3907 * $injector.invoke(function($rootScope, $compile, $document) { 3908 * $compile($document)($rootScope); 3909 * $rootScope.$digest(); 3910 * }); 3911 * ``` 3912 * 3913 * Sometimes you want to get access to the injector of a currently running Angular app 3914 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3915 * application has been bootstrapped. You can do this using the extra `injector()` added 3916 * to JQuery/jqLite elements. See {@link angular.element}. 3917 * 3918 * *This is fairly rare but could be the case if a third party library is injecting the 3919 * markup.* 3920 * 3921 * In the following example a new block of HTML containing a `ng-controller` 3922 * directive is added to the end of the document body by JQuery. We then compile and link 3923 * it into the current AngularJS scope. 3924 * 3925 * ```js 3926 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3927 * $(document.body).append($div); 3928 * 3929 * angular.element(document).injector().invoke(function($compile) { 3930 * var scope = angular.element($div).scope(); 3931 * $compile($div)(scope); 3932 * }); 3933 * ``` 3934 */ 3935 3936 3937/** 3938 * @ngdoc module 3939 * @name auto 3940 * @description 3941 * 3942 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3943 */ 3944 3945var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; 3946var FN_ARG_SPLIT = /,/; 3947var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3948var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3949var $injectorMinErr = minErr('$injector'); 3950 3951function anonFn(fn) { 3952 // For anonymous functions, showing at the very least the function signature can help in 3953 // debugging. 3954 var fnText = fn.toString().replace(STRIP_COMMENTS, ''), 3955 args = fnText.match(FN_ARGS); 3956 if (args) { 3957 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 3958 } 3959 return 'fn'; 3960} 3961 3962function annotate(fn, strictDi, name) { 3963 var $inject, 3964 fnText, 3965 argDecl, 3966 last; 3967 3968 if (typeof fn === 'function') { 3969 if (!($inject = fn.$inject)) { 3970 $inject = []; 3971 if (fn.length) { 3972 if (strictDi) { 3973 if (!isString(name) || !name) { 3974 name = fn.name || anonFn(fn); 3975 } 3976 throw $injectorMinErr('strictdi', 3977 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 3978 } 3979 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3980 argDecl = fnText.match(FN_ARGS); 3981 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 3982 arg.replace(FN_ARG, function(all, underscore, name) { 3983 $inject.push(name); 3984 }); 3985 }); 3986 } 3987 fn.$inject = $inject; 3988 } 3989 } else if (isArray(fn)) { 3990 last = fn.length - 1; 3991 assertArgFn(fn[last], 'fn'); 3992 $inject = fn.slice(0, last); 3993 } else { 3994 assertArgFn(fn, 'fn', true); 3995 } 3996 return $inject; 3997} 3998 3999/////////////////////////////////////// 4000 4001/** 4002 * @ngdoc service 4003 * @name $injector 4004 * 4005 * @description 4006 * 4007 * `$injector` is used to retrieve object instances as defined by 4008 * {@link auto.$provide provider}, instantiate types, invoke methods, 4009 * and load modules. 4010 * 4011 * The following always holds true: 4012 * 4013 * ```js 4014 * var $injector = angular.injector(); 4015 * expect($injector.get('$injector')).toBe($injector); 4016 * expect($injector.invoke(function($injector) { 4017 * return $injector; 4018 * })).toBe($injector); 4019 * ``` 4020 * 4021 * # Injection Function Annotation 4022 * 4023 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 4024 * following are all valid ways of annotating function with injection arguments and are equivalent. 4025 * 4026 * ```js 4027 * // inferred (only works if code not minified/obfuscated) 4028 * $injector.invoke(function(serviceA){}); 4029 * 4030 * // annotated 4031 * function explicit(serviceA) {}; 4032 * explicit.$inject = ['serviceA']; 4033 * $injector.invoke(explicit); 4034 * 4035 * // inline 4036 * $injector.invoke(['serviceA', function(serviceA){}]); 4037 * ``` 4038 * 4039 * ## Inference 4040 * 4041 * In JavaScript calling `toString()` on a function returns the function definition. The definition 4042 * can then be parsed and the function arguments can be extracted. This method of discovering 4043 * annotations is disallowed when the injector is in strict mode. 4044 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 4045 * argument names. 4046 * 4047 * ## `$inject` Annotation 4048 * By adding an `$inject` property onto a function the injection parameters can be specified. 4049 * 4050 * ## Inline 4051 * As an array of injection names, where the last item in the array is the function to call. 4052 */ 4053 4054/** 4055 * @ngdoc method 4056 * @name $injector#get 4057 * 4058 * @description 4059 * Return an instance of the service. 4060 * 4061 * @param {string} name The name of the instance to retrieve. 4062 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 4063 * @return {*} The instance. 4064 */ 4065 4066/** 4067 * @ngdoc method 4068 * @name $injector#invoke 4069 * 4070 * @description 4071 * Invoke the method and supply the method arguments from the `$injector`. 4072 * 4073 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 4074 * injected according to the {@link guide/di $inject Annotation} rules. 4075 * @param {Object=} self The `this` for the invoked method. 4076 * @param {Object=} locals Optional object. If preset then any argument names are read from this 4077 * object first, before the `$injector` is consulted. 4078 * @returns {*} the value returned by the invoked `fn` function. 4079 */ 4080 4081/** 4082 * @ngdoc method 4083 * @name $injector#has 4084 * 4085 * @description 4086 * Allows the user to query if the particular service exists. 4087 * 4088 * @param {string} name Name of the service to query. 4089 * @returns {boolean} `true` if injector has given service. 4090 */ 4091 4092/** 4093 * @ngdoc method 4094 * @name $injector#instantiate 4095 * @description 4096 * Create a new instance of JS type. The method takes a constructor function, invokes the new 4097 * operator, and supplies all of the arguments to the constructor function as specified by the 4098 * constructor annotation. 4099 * 4100 * @param {Function} Type Annotated constructor function. 4101 * @param {Object=} locals Optional object. If preset then any argument names are read from this 4102 * object first, before the `$injector` is consulted. 4103 * @returns {Object} new instance of `Type`. 4104 */ 4105 4106/** 4107 * @ngdoc method 4108 * @name $injector#annotate 4109 * 4110 * @description 4111 * Returns an array of service names which the function is requesting for injection. This API is 4112 * used by the injector to determine which services need to be injected into the function when the 4113 * function is invoked. There are three ways in which the function can be annotated with the needed 4114 * dependencies. 4115 * 4116 * # Argument names 4117 * 4118 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4119 * by converting the function into a string using `toString()` method and extracting the argument 4120 * names. 4121 * ```js 4122 * // Given 4123 * function MyController($scope, $route) { 4124 * // ... 4125 * } 4126 * 4127 * // Then 4128 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4129 * ``` 4130 * 4131 * You can disallow this method by using strict injection mode. 4132 * 4133 * This method does not work with code minification / obfuscation. For this reason the following 4134 * annotation strategies are supported. 4135 * 4136 * # The `$inject` property 4137 * 4138 * If a function has an `$inject` property and its value is an array of strings, then the strings 4139 * represent names of services to be injected into the function. 4140 * ```js 4141 * // Given 4142 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 4143 * // ... 4144 * } 4145 * // Define function dependencies 4146 * MyController['$inject'] = ['$scope', '$route']; 4147 * 4148 * // Then 4149 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 4150 * ``` 4151 * 4152 * # The array notation 4153 * 4154 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 4155 * is very inconvenient. In these situations using the array notation to specify the dependencies in 4156 * a way that survives minification is a better choice: 4157 * 4158 * ```js 4159 * // We wish to write this (not minification / obfuscation safe) 4160 * injector.invoke(function($compile, $rootScope) { 4161 * // ... 4162 * }); 4163 * 4164 * // We are forced to write break inlining 4165 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 4166 * // ... 4167 * }; 4168 * tmpFn.$inject = ['$compile', '$rootScope']; 4169 * injector.invoke(tmpFn); 4170 * 4171 * // To better support inline function the inline annotation is supported 4172 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 4173 * // ... 4174 * }]); 4175 * 4176 * // Therefore 4177 * expect(injector.annotate( 4178 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 4179 * ).toEqual(['$compile', '$rootScope']); 4180 * ``` 4181 * 4182 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 4183 * be retrieved as described above. 4184 * 4185 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 4186 * 4187 * @returns {Array.<string>} The names of the services which the function requires. 4188 */ 4189 4190 4191 4192 4193/** 4194 * @ngdoc service 4195 * @name $provide 4196 * 4197 * @description 4198 * 4199 * The {@link auto.$provide $provide} service has a number of methods for registering components 4200 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 4201 * {@link angular.Module}. 4202 * 4203 * An Angular **service** is a singleton object created by a **service factory**. These **service 4204 * factories** are functions which, in turn, are created by a **service provider**. 4205 * The **service providers** are constructor functions. When instantiated they must contain a 4206 * property called `$get`, which holds the **service factory** function. 4207 * 4208 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 4209 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 4210 * function to get the instance of the **service**. 4211 * 4212 * Often services have no configuration options and there is no need to add methods to the service 4213 * provider. The provider will be no more than a constructor function with a `$get` property. For 4214 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 4215 * services without specifying a provider. 4216 * 4217 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 4218 * {@link auto.$injector $injector} 4219 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 4220 * providers and services. 4221 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 4222 * services, not providers. 4223 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 4224 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 4225 * given factory function. 4226 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 4227 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 4228 * a new object using the given constructor function. 4229 * 4230 * See the individual methods for more information and examples. 4231 */ 4232 4233/** 4234 * @ngdoc method 4235 * @name $provide#provider 4236 * @description 4237 * 4238 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 4239 * are constructor functions, whose instances are responsible for "providing" a factory for a 4240 * service. 4241 * 4242 * Service provider names start with the name of the service they provide followed by `Provider`. 4243 * For example, the {@link ng.$log $log} service has a provider called 4244 * {@link ng.$logProvider $logProvider}. 4245 * 4246 * Service provider objects can have additional methods which allow configuration of the provider 4247 * and its service. Importantly, you can configure what kind of service is created by the `$get` 4248 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 4249 * method {@link ng.$logProvider#debugEnabled debugEnabled} 4250 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 4251 * console or not. 4252 * 4253 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 4254 'Provider'` key. 4255 * @param {(Object|function())} provider If the provider is: 4256 * 4257 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 4258 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 4259 * - `Constructor`: a new instance of the provider will be created using 4260 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 4261 * 4262 * @returns {Object} registered provider instance 4263 4264 * @example 4265 * 4266 * The following example shows how to create a simple event tracking service and register it using 4267 * {@link auto.$provide#provider $provide.provider()}. 4268 * 4269 * ```js 4270 * // Define the eventTracker provider 4271 * function EventTrackerProvider() { 4272 * var trackingUrl = '/track'; 4273 * 4274 * // A provider method for configuring where the tracked events should been saved 4275 * this.setTrackingUrl = function(url) { 4276 * trackingUrl = url; 4277 * }; 4278 * 4279 * // The service factory function 4280 * this.$get = ['$http', function($http) { 4281 * var trackedEvents = {}; 4282 * return { 4283 * // Call this to track an event 4284 * event: function(event) { 4285 * var count = trackedEvents[event] || 0; 4286 * count += 1; 4287 * trackedEvents[event] = count; 4288 * return count; 4289 * }, 4290 * // Call this to save the tracked events to the trackingUrl 4291 * save: function() { 4292 * $http.post(trackingUrl, trackedEvents); 4293 * } 4294 * }; 4295 * }]; 4296 * } 4297 * 4298 * describe('eventTracker', function() { 4299 * var postSpy; 4300 * 4301 * beforeEach(module(function($provide) { 4302 * // Register the eventTracker provider 4303 * $provide.provider('eventTracker', EventTrackerProvider); 4304 * })); 4305 * 4306 * beforeEach(module(function(eventTrackerProvider) { 4307 * // Configure eventTracker provider 4308 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4309 * })); 4310 * 4311 * it('tracks events', inject(function(eventTracker) { 4312 * expect(eventTracker.event('login')).toEqual(1); 4313 * expect(eventTracker.event('login')).toEqual(2); 4314 * })); 4315 * 4316 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4317 * postSpy = spyOn($http, 'post'); 4318 * eventTracker.event('login'); 4319 * eventTracker.save(); 4320 * expect(postSpy).toHaveBeenCalled(); 4321 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4322 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4323 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4324 * })); 4325 * }); 4326 * ``` 4327 */ 4328 4329/** 4330 * @ngdoc method 4331 * @name $provide#factory 4332 * @description 4333 * 4334 * Register a **service factory**, which will be called to return the service instance. 4335 * This is short for registering a service where its provider consists of only a `$get` property, 4336 * which is the given service factory function. 4337 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4338 * configure your service in a provider. 4339 * 4340 * @param {string} name The name of the instance. 4341 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4342 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4343 * @returns {Object} registered provider instance 4344 * 4345 * @example 4346 * Here is an example of registering a service 4347 * ```js 4348 * $provide.factory('ping', ['$http', function($http) { 4349 * return function ping() { 4350 * return $http.send('/ping'); 4351 * }; 4352 * }]); 4353 * ``` 4354 * You would then inject and use this service like this: 4355 * ```js 4356 * someModule.controller('Ctrl', ['ping', function(ping) { 4357 * ping(); 4358 * }]); 4359 * ``` 4360 */ 4361 4362 4363/** 4364 * @ngdoc method 4365 * @name $provide#service 4366 * @description 4367 * 4368 * Register a **service constructor**, which will be invoked with `new` to create the service 4369 * instance. 4370 * This is short for registering a service where its provider's `$get` property is the service 4371 * constructor function that will be used to instantiate the service instance. 4372 * 4373 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4374 * as a type/class. 4375 * 4376 * @param {string} name The name of the instance. 4377 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4378 * that will be instantiated. 4379 * @returns {Object} registered provider instance 4380 * 4381 * @example 4382 * Here is an example of registering a service using 4383 * {@link auto.$provide#service $provide.service(class)}. 4384 * ```js 4385 * var Ping = function($http) { 4386 * this.$http = $http; 4387 * }; 4388 * 4389 * Ping.$inject = ['$http']; 4390 * 4391 * Ping.prototype.send = function() { 4392 * return this.$http.get('/ping'); 4393 * }; 4394 * $provide.service('ping', Ping); 4395 * ``` 4396 * You would then inject and use this service like this: 4397 * ```js 4398 * someModule.controller('Ctrl', ['ping', function(ping) { 4399 * ping.send(); 4400 * }]); 4401 * ``` 4402 */ 4403 4404 4405/** 4406 * @ngdoc method 4407 * @name $provide#value 4408 * @description 4409 * 4410 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4411 * number, an array, an object or a function. This is short for registering a service where its 4412 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4413 * service**. 4414 * 4415 * Value services are similar to constant services, except that they cannot be injected into a 4416 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4417 * an Angular 4418 * {@link auto.$provide#decorator decorator}. 4419 * 4420 * @param {string} name The name of the instance. 4421 * @param {*} value The value. 4422 * @returns {Object} registered provider instance 4423 * 4424 * @example 4425 * Here are some examples of creating value services. 4426 * ```js 4427 * $provide.value('ADMIN_USER', 'admin'); 4428 *
4429 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4430 * 4431 * $provide.value('halfOf', function(value) { 4432 * return value / 2; 4433 * }); 4434 * ``` 4435 */ 4436 4437 4438/** 4439 * @ngdoc method 4440 * @name $provide#constant 4441 * @description 4442 * 4443 * Register a **constant service**, such as a string, a number, an array, an object or a function, 4444 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 4445 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4446 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 4447 * 4448 * @param {string} name The name of the constant. 4449 * @param {*} value The constant value. 4450 * @returns {Object} registered instance 4451 * 4452 * @example 4453 * Here a some examples of creating constants: 4454 * ```js 4455 * $provide.constant('SHARD_HEIGHT', 306); 4456 * 4457 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4458 * 4459 * $provide.constant('double', function(value) { 4460 * return value * 2; 4461 * }); 4462 * ``` 4463 */ 4464 4465 4466/** 4467 * @ngdoc method 4468 * @name $provide#decorator 4469 * @description 4470 * 4471 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 4472 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 4473 * service. The object returned by the decorator may be the original service, or a new service 4474 * object which replaces or wraps and delegates to the original service. 4475 * 4476 * @param {string} name The name of the service to decorate. 4477 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4478 * instantiated and should return the decorated service instance. The function is called using 4479 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4480 * Local injection arguments: 4481 * 4482 * * `$delegate` - The original service instance, which can be monkey patched, configured, 4483 * decorated or delegated to. 4484 * 4485 * @example 4486 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4487 * calls to {@link ng.$log#error $log.warn()}. 4488 * ```js 4489 * $provide.decorator('$log', ['$delegate', function($delegate) { 4490 * $delegate.warn = $delegate.error; 4491 * return $delegate; 4492 * }]); 4493 * ``` 4494 */ 4495 4496 4497function createInjector(modulesToLoad, strictDi) { 4498 strictDi = (strictDi === true); 4499 var INSTANTIATING = {}, 4500 providerSuffix = 'Provider', 4501 path = [], 4502 loadedModules = new HashMap([], true), 4503 providerCache = { 4504 $provide: { 4505 provider: supportObject(provider), 4506 factory: supportObject(factory), 4507 service: supportObject(service), 4508 value: supportObject(value), 4509 constant: supportObject(constant), 4510 decorator: decorator 4511 } 4512 }, 4513 providerInjector = (providerCache.$injector = 4514 createInternalInjector(providerCache, function(serviceName, caller) { 4515 if (angular.isString(caller)) { 4516 path.push(caller); 4517 } 4518 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 4519 })), 4520 instanceCache = {}, 4521 instanceInjector = (instanceCache.$injector = 4522 createInternalInjector(instanceCache, function(serviceName, caller) { 4523 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4524 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); 4525 })); 4526 4527 4528 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); }); 4529 4530 return instanceInjector; 4531 4532 //////////////////////////////////// 4533 // $provider 4534 //////////////////////////////////// 4535 4536 function supportObject(delegate) { 4537 return function(key, value) { 4538 if (isObject(key)) { 4539 forEach(key, reverseParams(delegate)); 4540 } else { 4541 return delegate(key, value); 4542 } 4543 }; 4544 } 4545 4546 function provider(name, provider_) { 4547 assertNotHasOwnProperty(name, 'service'); 4548 if (isFunction(provider_) || isArray(provider_)) { 4549 provider_ = providerInjector.instantiate(provider_); 4550 } 4551 if (!provider_.$get) { 4552 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 4553 } 4554 return providerCache[name + providerSuffix] = provider_; 4555 } 4556 4557 function enforceReturnValue(name, factory) { 4558 return function enforcedReturnValue() { 4559 var result = instanceInjector.invoke(factory, this); 4560 if (isUndefined(result)) { 4561 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 4562 } 4563 return result; 4564 }; 4565 } 4566 4567 function factory(name, factoryFn, enforce) { 4568 return provider(name, { 4569 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 4570 }); 4571 } 4572 4573 function service(name, constructor) { 4574 return factory(name, ['$injector', function($injector) { 4575 return $injector.instantiate(constructor); 4576 }]); 4577 } 4578 4579 function value(name, val) { return factory(name, valueFn(val), false); } 4580 4581 function constant(name, value) { 4582 assertNotHasOwnProperty(name, 'constant'); 4583 providerCache[name] = value; 4584 instanceCache[name] = value; 4585 } 4586 4587 function decorator(serviceName, decorFn) { 4588 var origProvider = providerInjector.get(serviceName + providerSuffix), 4589 orig$get = origProvider.$get; 4590 4591 origProvider.$get = function() { 4592 var origInstance = instanceInjector.invoke(orig$get, origProvider); 4593 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 4594 }; 4595 } 4596 4597 //////////////////////////////////// 4598 // Module Loading 4599 //////////////////////////////////// 4600 function loadModules(modulesToLoad) { 4601 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 4602 var runBlocks = [], moduleFn;
vendor: 5,290 bytes, lines 4603-4760
4603 forEach(modulesToLoad, function(module) { 4604 if (loadedModules.get(module)) return; 4605 loadedModules.put(module, true); 4606 4607 function runInvokeQueue(queue) { 4608 var i, ii; 4609 for (i = 0, ii = queue.length; i < ii; i++) { 4610 var invokeArgs = queue[i], 4611 provider = providerInjector.get(invokeArgs[0]); 4612 4613 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 4614 } 4615 } 4616 4617 try { 4618 if (isString(module)) { 4619 moduleFn = angularModule(module); 4620 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 4621 runInvokeQueue(moduleFn._invokeQueue); 4622 runInvokeQueue(moduleFn._configBlocks); 4623 } else if (isFunction(module)) { 4624 runBlocks.push(providerInjector.invoke(module)); 4625 } else if (isArray(module)) { 4626 runBlocks.push(providerInjector.invoke(module)); 4627 } else { 4628 assertArgFn(module, 'module'); 4629 } 4630 } catch (e) { 4631 if (isArray(module)) { 4632 module = module[module.length - 1]; 4633 } 4634 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 4635 // Safari & FF's stack traces don't contain error.message content 4636 // unlike those of Chrome and IE 4637 // So if stack doesn't contain message, we create a new string that contains both. 4638 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 4639 /* jshint -W022 */ 4640 e = e.message + '\n' + e.stack; 4641 } 4642 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 4643 module, e.stack || e.message || e); 4644 } 4645 }); 4646 return runBlocks; 4647 } 4648 4649 //////////////////////////////////// 4650 // internal Injector 4651 //////////////////////////////////// 4652 4653 function createInternalInjector(cache, factory) { 4654 4655 function getService(serviceName, caller) { 4656 if (cache.hasOwnProperty(serviceName)) { 4657 if (cache[serviceName] === INSTANTIATING) { 4658 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 4659 serviceName + ' <- ' + path.join(' <- ')); 4660 } 4661 return cache[serviceName]; 4662 } else { 4663 try { 4664 path.unshift(serviceName); 4665 cache[serviceName] = INSTANTIATING; 4666 return cache[serviceName] = factory(serviceName, caller); 4667 } catch (err) { 4668 if (cache[serviceName] === INSTANTIATING) { 4669 delete cache[serviceName]; 4670 } 4671 throw err; 4672 } finally { 4673 path.shift(); 4674 } 4675 } 4676 } 4677 4678 function invoke(fn, self, locals, serviceName) { 4679 if (typeof locals === 'string') { 4680 serviceName = locals; 4681 locals = null; 4682 } 4683 4684 var args = [], 4685 $inject = createInjector.$$annotate(fn, strictDi, serviceName), 4686 length, i, 4687 key; 4688 4689 for (i = 0, length = $inject.length; i < length; i++) { 4690 key = $inject[i]; 4691 if (typeof key !== 'string') { 4692 throw $injectorMinErr('itkn', 4693 'Incorrect injection token! Expected service name as string, got {0}', key); 4694 } 4695 args.push( 4696 locals && locals.hasOwnProperty(key) 4697 ? locals[key] 4698 : getService(key, serviceName) 4699 ); 4700 } 4701 if (isArray(fn)) { 4702 fn = fn[length]; 4703 } 4704 4705 // http://jsperf.com/angularjs-invoke-apply-vs-switch 4706 // #5388 4707 return fn.apply(self, args); 4708 } 4709 4710 function instantiate(Type, locals, serviceName) { 4711 // Check if Type is annotated and use just the given function at n-1 as parameter 4712 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 4713 // Object creation: http://jsperf.com/create-constructor/2 4714 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); 4715 var returnedValue = invoke(Type, instance, locals, serviceName); 4716 4717 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 4718 } 4719 4720 return { 4721 invoke: invoke, 4722 instantiate: instantiate, 4723 get: getService, 4724 annotate: createInjector.$$annotate, 4725 has: function(name) { 4726 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 4727 } 4728 }; 4729 } 4730} 4731 4732createInjector.$$annotate = annotate; 4733 4734/** 4735 * @ngdoc provider 4736 * @name $anchorScrollProvider 4737 * 4738 * @description 4739 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 4740 * {@link ng.$location#hash $location.hash()} changes. 4741 */ 4742function $AnchorScrollProvider() { 4743 4744 var autoScrollingEnabled = true; 4745 4746 /** 4747 * @ngdoc method 4748 * @name $anchorScrollProvider#disableAutoScrolling 4749 * 4750 * @description 4751 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4752 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4753 * Use this method to disable automatic scrolling. 4754 * 4755 * If automatic scrolling is disabled, one must explicitly call 4756 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4757 * current hash. 4758 */ 4759 this.disableAutoScrolling = function() { 4760 autoScrollingEnabled = false;
4761 }; 4762 4763 /** 4764 * @ngdoc service 4765 * @name $anchorScroll 4766 * @kind function 4767 * @requires $window 4768 * @requires $location 4769 * @requires $rootScope 4770 * 4771 * @description 4772 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 4773 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 4774 * in the 4775 * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 4776 * 4777 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 4778 * match any anchor whenever it changes. This can be disabled by calling 4779 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 4780 * 4781 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 4782 * vertical scroll-offset (either fixed or dynamic). 4783 * 4784 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 4785 * {@link ng.$location#hash $location.hash()} will be used. 4786 * 4787 * @property {(number|function|jqLite)} yOffset 4788 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 4789 * positioned elements at the top of the page, such as navbars, headers etc. 4790 * 4791 * `yOffset` can be specified in various ways: 4792 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 4793 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 4794 * a number representing the offset (in pixels).<br /><br /> 4795 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 4796 * the top of the page to the element's bottom will be used as offset.<br /> 4797 * **Note**: The element will be taken into account only as long as its `position` is set to 4798 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 4799 * their height and/or positioning according to the viewport's size. 4800 * 4801 * <br /> 4802 * <div class="alert alert-warning"> 4803 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 4804 * not some child element. 4805 * </div> 4806 * 4807 * @example 4808 <example module="anchorScrollExample"> 4809 <file name="index.html"> 4810 <div id="scrollArea" ng-controller="ScrollController"> 4811 <a ng-click="gotoBottom()">Go to bottom</a> 4812 <a id="bottom"></a> You're at the bottom! 4813 </div> 4814 </file> 4815 <file name="script.js"> 4816 angular.module('anchorScrollExample', []) 4817 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 4818 function ($scope, $location, $anchorScroll) { 4819 $scope.gotoBottom = function() { 4820 // set the location.hash to the id of 4821 // the element you wish to scroll to. 4822 $location.hash('bottom'); 4823 4824 // call $anchorScroll() 4825 $anchorScroll(); 4826 }; 4827 }]); 4828 </file> 4829 <file name="style.css"> 4830 #scrollArea { 4831 height: 280px; 4832 overflow: auto; 4833 } 4834 4835 #bottom { 4836 display: block; 4837 margin-top: 2000px; 4838 } 4839 </file> 4840 </example> 4841 * 4842 * <hr /> 4843 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 4844 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 4845 * 4846 * @example 4847 <example module="anchorScrollOffsetExample"> 4848 <file name="index.html"> 4849 <div class="fixed-header" ng-controller="headerCtrl"> 4850 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 4851 Go to anchor {{x}} 4852 </a> 4853 </div> 4854 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 4855 Anchor {{x}} of 5 4856 </div> 4857 </file> 4858 <file name="script.js"> 4859 angular.module('anchorScrollOffsetExample', []) 4860 .run(['$anchorScroll', function($anchorScroll) { 4861 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 4862 }]) 4863 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 4864 function ($anchorScroll, $location, $scope) {
4865 $scope.gotoAnchor = function(x) { 4866 var newHash = 'anchor' + x; 4867 if ($location.hash() !== newHash) { 4868 // set the $location.hash to `newHash` and 4869 // $anchorScroll will automatically scroll to it 4870 $location.hash('anchor' + x); 4871 } else { 4872 // call $anchorScroll() explicitly, 4873 // since $location.hash hasn't changed 4874 $anchorScroll(); 4875 } 4876 }; 4877 } 4878 ]); 4879 </file> 4880 <file name="style.css"> 4881 body { 4882 padding-top: 50px; 4883 } 4884 4885 .anchor { 4886 border: 2px dashed DarkOrchid; 4887 padding: 10px 10px 200px 10px; 4888 } 4889 4890 .fixed-header { 4891 background-color: rgba(0, 0, 0, 0.2); 4892 height: 50px; 4893 position: fixed; 4894 top: 0; left: 0; right: 0; 4895 } 4896 4897 .fixed-header > a { 4898 display: inline-block; 4899 margin: 5px 15px; 4900 } 4901 </file> 4902 </example> 4903 */ 4904 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4905 var document = $window.document; 4906 4907 // Helper function to get first anchor from a NodeList 4908 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 4909 // and working in all supported browsers.) 4910 function getFirstAnchor(list) { 4911 var result = null; 4912 Array.prototype.some.call(list, function(element) { 4913 if (nodeName_(element) === 'a') { 4914 result = element; 4915 return true; 4916 } 4917 }); 4918 return result; 4919 } 4920 4921 function getYOffset() { 4922 4923 var offset = scroll.yOffset; 4924 4925 if (isFunction(offset)) { 4926 offset = offset(); 4927 } else if (isElement(offset)) { 4928 var elem = offset[0]; 4929 var style = $window.getComputedStyle(elem); 4930 if (style.position !== 'fixed') { 4931 offset = 0; 4932 } else { 4933 offset = elem.getBoundingClientRect().bottom; 4934 } 4935 } else if (!isNumber(offset)) { 4936 offset = 0; 4937 } 4938 4939 return offset; 4940 } 4941 4942 function scrollTo(elem) { 4943 if (elem) { 4944 elem.scrollIntoView(); 4945 4946 var offset = getYOffset(); 4947 4948 if (offset) { 4949 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 4950 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 4951 // top of the viewport. 4952 // 4953 // IF the number of pixels from the top of `elem` to the end of the page's content is less 4954 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 4955 // way down the page. 4956 // 4957 // This is often the case for elements near the bottom of the page. 4958 // 4959 // In such cases we do not need to scroll the whole `offset` up, just the difference between 4960 // the top of the element and the offset, which is enough to align the top of `elem` at the 4961 // desired position. 4962 var elemTop = elem.getBoundingClientRect().top; 4963 $window.scrollBy(0, elemTop - offset); 4964 } 4965 } else { 4966 $window.scrollTo(0, 0); 4967 } 4968 } 4969 4970 function scroll(hash) { 4971 hash = isString(hash) ? hash : $location.hash(); 4972 var elm; 4973 4974 // empty hash, scroll to the top of the page 4975 if (!hash) scrollTo(null); 4976 4977 // element with given id 4978 else if ((elm = document.getElementById(hash))) scrollTo(elm); 4979 4980 // first anchor with given name :-D 4981 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 4982 4983 // no element and hash == 'top', scroll to the top of the page 4984 else if (hash === 'top') scrollTo(null); 4985 } 4986 4987 // does not scroll when user clicks on anchor link that is currently on 4988 // (no url change, no $location.hash() change), browser native does scroll 4989 if (autoScrollingEnabled) { 4990 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4991 function autoScrollWatchAction(newVal, oldVal) { 4992 // skip the initial scroll if $location.hash is empty 4993 if (newVal === oldVal && newVal === '') return; 4994 4995 jqLiteDocumentLoaded(function() { 4996 $rootScope.$evalAsync(scroll); 4997 }); 4998 }); 4999 } 5000 5001 return scroll; 5002 }]; 5003} 5004 5005var $animateMinErr = minErr('$animate'); 5006var ELEMENT_NODE = 1; 5007var NG_ANIMATE_CLASSNAME = 'ng-animate'; 5008 5009function mergeClasses(a,b) { 5010 if (!a && !b) return ''; 5011 if (!a) return b; 5012 if (!b) return a; 5013 if (isArray(a)) a = a.join(' '); 5014 if (isArray(b)) b = b.join(' '); 5015 return a + ' ' + b; 5016} 5017 5018function extractElementNode(element) { 5019 for (var i = 0; i < element.length; i++) { 5020 var elm = element[i]; 5021 if (elm.nodeType === ELEMENT_NODE) { 5022 return elm; 5023 } 5024 } 5025} 5026 5027function splitClasses(classes) { 5028 if (isString(classes)) { 5029 classes = classes.split(' '); 5030 } 5031 5032 // Use createMap() to prevent class assumptions involving property names in 5033 // Object.prototype 5034 var obj = createMap(); 5035 forEach(classes, function(klass) { 5036 // sometimes the split leaves empty string values 5037 // incase extra spaces were applied to the options 5038 if (klass.length) { 5039 obj[klass] = true; 5040 } 5041 }); 5042 return obj; 5043} 5044 5045// if any other type of options value besides an Object value is 5046// passed into the $animate.method() animation then this helper code 5047// will be run which will ignore it. While this patch is not the 5048// greatest solution to this, a lot of existing plugins depend on 5049// $animate to either call the callback (< 1.2) or return a promise 5050// that can be changed. This helper function ensures that the options 5051// are wiped clean incase a callback function is provided. 5052function prepareAnimateOptions(options) { 5053 return isObject(options) 5054 ? options 5055 : {}; 5056} 5057 5058var $$CoreAnimateRunnerProvider = function() { 5059 this.$get = ['$q', '$$rAF', function($q, $$rAF) { 5060 function AnimateRunner() {} 5061 AnimateRunner.all = noop; 5062 AnimateRunner.chain = noop; 5063 AnimateRunner.prototype = { 5064 end: noop, 5065 cancel: noop, 5066 resume: noop, 5067 pause: noop, 5068 complete: noop, 5069 then: function(pass, fail) { 5070 return $q(function(resolve) { 5071 $$rAF(function() { 5072 resolve(); 5073 }); 5074 }).then(pass, fail); 5075 } 5076 }; 5077 return AnimateRunner; 5078 }]; 5079}; 5080 5081// this is prefixed with Core since it conflicts with 5082// the animateQueueProvider defined in ngAnimate/animateQueue.js 5083var $$CoreAnimateQueueProvider = function() { 5084 var postDigestQueue = new HashMap(); 5085 var postDigestElements = []; 5086 5087 this.$get = ['$$AnimateRunner', '$rootScope', 5088 function($$AnimateRunner, $rootScope) { 5089 return { 5090 enabled: noop, 5091 on: noop, 5092 off: noop, 5093 pin: noop, 5094 5095 push: function(element, event, options, domOperation) { 5096 domOperation && domOperation(); 5097 5098 options = options || {}; 5099 options.from && element.css(options.from); 5100 options.to && element.css(options.to); 5101 5102 if (options.addClass || options.removeClass) { 5103 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 5104 } 5105 5106 return new $$AnimateRunner(); // jshint ignore:line 5107 } 5108 }; 5109 5110 5111 function updateData(data, classes, value) { 5112 var changed = false;
vendor: 6,104 bytes, lines 5113-5271
5113 if (classes) { 5114 classes = isString(classes) ? classes.split(' ') : 5115 isArray(classes) ? classes : []; 5116 forEach(classes, function(className) { 5117 if (className) { 5118 changed = true; 5119 data[className] = value; 5120 } 5121 }); 5122 } 5123 return changed; 5124 } 5125 5126 function handleCSSClassChanges() { 5127 forEach(postDigestElements, function(element) { 5128 var data = postDigestQueue.get(element); 5129 if (data) { 5130 var existing = splitClasses(element.attr('class')); 5131 var toAdd = ''; 5132 var toRemove = ''; 5133 forEach(data, function(status, className) { 5134 var hasClass = !!existing[className]; 5135 if (status !== hasClass) { 5136 if (status) { 5137 toAdd += (toAdd.length ? ' ' : '') + className; 5138 } else { 5139 toRemove += (toRemove.length ? ' ' : '') + className; 5140 } 5141 } 5142 }); 5143 5144 forEach(element, function(elm) { 5145 toAdd && jqLiteAddClass(elm, toAdd); 5146 toRemove && jqLiteRemoveClass(elm, toRemove); 5147 }); 5148 postDigestQueue.remove(element); 5149 } 5150 }); 5151 postDigestElements.length = 0; 5152 } 5153 5154 5155 function addRemoveClassesPostDigest(element, add, remove) { 5156 var data = postDigestQueue.get(element) || {}; 5157 5158 var classesAdded = updateData(data, add, true); 5159 var classesRemoved = updateData(data, remove, false); 5160 5161 if (classesAdded || classesRemoved) { 5162 5163 postDigestQueue.put(element, data); 5164 postDigestElements.push(element); 5165 5166 if (postDigestElements.length === 1) { 5167 $rootScope.$$postDigest(handleCSSClassChanges); 5168 } 5169 } 5170 } 5171 }]; 5172}; 5173 5174/** 5175 * @ngdoc provider 5176 * @name $animateProvider 5177 * 5178 * @description 5179 * Default implementation of $animate that doesn't perform any animations, instead just 5180 * synchronously performs DOM updates and resolves the returned runner promise. 5181 * 5182 * In order to enable animations the `ngAnimate` module has to be loaded. 5183 * 5184 * To see the functional implementation check out `src/ngAnimate/animate.js`. 5185 */ 5186var $AnimateProvider = ['$provide', function($provide) { 5187 var provider = this; 5188 5189 this.$$registeredAnimations = Object.create(null); 5190 5191 /** 5192 * @ngdoc method 5193 * @name $animateProvider#register 5194 * 5195 * @description 5196 * Registers a new injectable animation factory function. The factory function produces the 5197 * animation object which contains callback functions for each event that is expected to be 5198 * animated. 5199 * 5200 * * `eventFn`: `function(element, ... , doneFunction, options)` 5201 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 5202 * on the type of animation additional arguments will be injected into the animation function. The 5203 * list below explains the function signatures for the different animation methods: 5204 * 5205 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 5206 * - addClass: function(element, addedClasses, doneFunction, options) 5207 * - removeClass: function(element, removedClasses, doneFunction, options) 5208 * - enter, leave, move: function(element, doneFunction, options) 5209 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 5210 * 5211 * Make sure to trigger the `doneFunction` once the animation is fully complete. 5212 * 5213 * ```js 5214 * return { 5215 * //enter, leave, move signature 5216 * eventFn : function(element, done, options) { 5217 * //code to run the animation 5218 * //once complete, then run done() 5219 * return function endFunction(wasCancelled) { 5220 * //code to cancel the animation 5221 * } 5222 * } 5223 * } 5224 * ``` 5225 * 5226 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 5227 * @param {Function} factory The factory function that will be executed to return the animation 5228 * object. 5229 */ 5230 this.register = function(name, factory) { 5231 if (name && name.charAt(0) !== '.') { 5232 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 5233 } 5234 5235 var key = name + '-animation'; 5236 provider.$$registeredAnimations[name.substr(1)] = key; 5237 $provide.factory(key, factory); 5238 }; 5239 5240 /** 5241 * @ngdoc method 5242 * @name $animateProvider#classNameFilter 5243 * 5244 * @description 5245 * Sets and/or returns the CSS class regular expression that is checked when performing 5246 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 5247 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 5248 * When setting the `classNameFilter` value, animations will only be performed on elements 5249 * that successfully match the filter expression. This in turn can boost performance 5250 * for low-powered devices as well as applications containing a lot of structural operations. 5251 * @param {RegExp=} expression The className expression which will be checked against all animations 5252 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 5253 */ 5254 this.classNameFilter = function(expression) { 5255 if (arguments.length === 1) { 5256 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 5257 if (this.$$classNameFilter) { 5258 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 5259 if (reservedRegex.test(this.$$classNameFilter.toString())) { 5260 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 5261 5262 } 5263 } 5264 } 5265 return this.$$classNameFilter; 5266 }; 5267 5268 this.$get = ['$$animateQueue', function($$animateQueue) { 5269 function domInsert(element, parentElement, afterElement) { 5270 // if for some reason the previous element was removed 5271 // from the dom sometime before this code runs then let's
5272 // just stick to using the parent element as the anchor 5273 if (afterElement) { 5274 var afterNode = extractElementNode(afterElement); 5275 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 5276 afterElement = null; 5277 } 5278 } 5279 afterElement ? afterElement.after(element) : parentElement.prepend(element); 5280 } 5281 5282 /** 5283 * @ngdoc service 5284 * @name $animate 5285 * @description The $animate service exposes a series of DOM utility methods that provide support 5286 * for animation hooks. The default behavior is the application of DOM operations, however, 5287 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5288 * to ensure that animation runs with the triggered DOM operation. 5289 * 5290 * By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't 5291 * included and only when it is active then the animation hooks that `$animate` triggers will be 5292 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5293 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5294 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5295 * 5296 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5297 * 5298 * To learn more about enabling animation support, click here to visit the 5299 * {@link ngAnimate ngAnimate module page}. 5300 */ 5301 return { 5302 // we don't call it directly since non-existant arguments may 5303 // be interpreted as null within the sub enabled function 5304 5305 /** 5306 * 5307 * @ngdoc method 5308 * @name $animate#on 5309 * @kind function 5310 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5311 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5312 * is fired with the following params: 5313 * 5314 * ```js 5315 * $animate.on('enter', container, 5316 * function callback(element, phase) { 5317 * // cool we detected an enter animation within the container 5318 * } 5319 * ); 5320 * ``` 5321 * 5322 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5323 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5324 * as well as among its children 5325 * @param {Function} callback the callback function that will be fired when the listener is triggered 5326 * 5327 * The arguments present in the callback function are: 5328 * * `element` - The captured DOM element that the animation was fired on. 5329 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5330 */ 5331 on: $$animateQueue.on, 5332 5333 /** 5334 * 5335 * @ngdoc method 5336 * @name $animate#off 5337 * @kind function 5338 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5339 * can be used in three different ways depending on the arguments: 5340 * 5341 * ```js 5342 * // remove all the animation event listeners listening for `enter` 5343 * $animate.off('enter'); 5344 * 5345 * // remove all the animation event listeners listening for `enter` on the given element and its children 5346 * $animate.off('enter', container); 5347 * 5348 * // remove the event listener function provided by `listenerFn` that is set 5349 * // to listen for `enter` on the given `element` as well as its children 5350 * $animate.off('enter', container, callback); 5351 * ``` 5352 * 5353 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) 5354 * @param {DOMElement=} container the container element the event listener was placed on 5355 * @param {Function=} callback the callback function that was registered as the listener 5356 */ 5357 off: $$animateQueue.off, 5358 5359 /** 5360 * @ngdoc method 5361 * @name $animate#pin 5362 * @kind function
5363 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5364 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 5365 * element despite being outside the realm of the application or within another application. Say for example if the application 5366 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5367 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5368 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5369 * 5370 * Note that this feature is only active when the `ngAnimate` module is used. 5371 * 5372 * @param {DOMElement} element the external element that will be pinned 5373 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5374 */ 5375 pin: $$animateQueue.pin, 5376 5377 /** 5378 * 5379 * @ngdoc method 5380 * @name $animate#enabled 5381 * @kind function 5382 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5383 * function can be called in four ways: 5384 * 5385 * ```js 5386 * // returns true or false 5387 * $animate.enabled(); 5388 * 5389 * // changes the enabled state for all animations 5390 * $animate.enabled(false); 5391 * $animate.enabled(true); 5392 * 5393 * // returns true or false if animations are enabled for an element 5394 * $animate.enabled(element); 5395 * 5396 * // changes the enabled state for an element and its children 5397 * $animate.enabled(element, true); 5398 * $animate.enabled(element, false); 5399 * ``` 5400 * 5401 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5402 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5403 * 5404 * @return {boolean} whether or not animations are enabled 5405 */ 5406 enabled: $$animateQueue.enabled, 5407 5408 /** 5409 * @ngdoc method 5410 * @name $animate#cancel 5411 * @kind function 5412 * @description Cancels the provided animation. 5413 * 5414 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 5415 */ 5416 cancel: function(runner) { 5417 runner.end && runner.end(); 5418 }, 5419 5420 /** 5421 * 5422 * @ngdoc method 5423 * @name $animate#enter 5424 * @kind function 5425 * @description Inserts the element into the DOM either after the `after` element (if provided) or 5426 * as the first child within the `parent` element and then triggers an animation. 5427 * A promise is returned that will be resolved during the next digest once the animation 5428 * has completed. 5429 * 5430 * @param {DOMElement} element the element which will be inserted into the DOM 5431 * @param {DOMElement} parent the parent element which will append the element as 5432 * a child (so long as the after element is not present) 5433 * @param {DOMElement=} after the sibling element after which the element will be appended 5434 * @param {object=} options an optional collection of options/styles that will be applied to the element 5435 * 5436 * @return {Promise} the animation callback promise 5437 */ 5438 enter: function(element, parent, after, options) { 5439 parent = parent && jqLite(parent); 5440 after = after && jqLite(after); 5441 parent = parent || after.parent(); 5442 domInsert(element, parent, after); 5443 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 5444 }, 5445 5446 /** 5447 * 5448 * @ngdoc method 5449 * @name $animate#move 5450 * @kind function
5451 * @description Inserts (moves) the element into its new position in the DOM either after 5452 * the `after` element (if provided) or as the first child within the `parent` element 5453 * and then triggers an animation. A promise is returned that will be resolved 5454 * during the next digest once the animation has completed. 5455 * 5456 * @param {DOMElement} element the element which will be moved into the new DOM position 5457 * @param {DOMElement} parent the parent element which will append the element as 5458 * a child (so long as the after element is not present) 5459 * @param {DOMElement=} after the sibling element after which the element will be appended 5460 * @param {object=} options an optional collection of options/styles that will be applied to the element 5461 * 5462 * @return {Promise} the animation callback promise 5463 */ 5464 move: function(element, parent, after, options) { 5465 parent = parent && jqLite(parent); 5466 after = after && jqLite(after); 5467 parent = parent || after.parent(); 5468 domInsert(element, parent, after); 5469 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 5470 }, 5471 5472 /** 5473 * @ngdoc method 5474 * @name $animate#leave 5475 * @kind function 5476 * @description Triggers an animation and then removes the element from the DOM. 5477 * When the function is called a promise is returned that will be resolved during the next 5478 * digest once the animation has completed. 5479 * 5480 * @param {DOMElement} element the element which will be removed from the DOM 5481 * @param {object=} options an optional collection of options/styles that will be applied to the element 5482 * 5483 * @return {Promise} the animation callback promise 5484 */ 5485 leave: function(element, options) { 5486 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 5487 element.remove(); 5488 }); 5489 }, 5490 5491 /** 5492 * @ngdoc method 5493 * @name $animate#addClass 5494 * @kind function 5495 * 5496 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 5497 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 5498 * animation if element already contains the CSS class or if the class is removed at a later step. 5499 * Note that class-based animations are treated differently compared to structural animations 5500 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5501 * depending if CSS or JavaScript animations are used. 5502 * 5503 * @param {DOMElement} element the element which the CSS classes will be applied to 5504 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 5505 * @param {object=} options an optional collection of options/styles that will be applied to the element 5506 * 5507 * @return {Promise} the animation callback promise 5508 */ 5509 addClass: function(element, className, options) { 5510 options = prepareAnimateOptions(options); 5511 options.addClass = mergeClasses(options.addclass, className); 5512 return $$animateQueue.push(element, 'addClass', options); 5513 }, 5514 5515 /** 5516 * @ngdoc method 5517 * @name $animate#removeClass 5518 * @kind function 5519 * 5520 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 5521 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 5522 * animation if element does not contain the CSS class or if the class is added at a later step. 5523 * Note that class-based animations are treated differently compared to structural animations 5524 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5525 * depending if CSS or JavaScript animations are used. 5526 * 5527 * @param {DOMElement} element the element which the CSS classes will be applied to 5528 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 5529 * @param {object=} options an optional collection of options/styles that will be applied to the element 5530 * 5531 * @return {Promise} the animation callback promise 5532 */ 5533 removeClass: function(element, className, options) { 5534 options = prepareAnimateOptions(options); 5535 options.removeClass = mergeClasses(options.removeClass, className); 5536 return $$animateQueue.push(element, 'removeClass', options); 5537 }, 5538 5539 /** 5540 * @ngdoc method 5541 * @name $animate#setClass 5542 * @kind function 5543 *
5544 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 5545 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 5546 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 5547 * passed. Note that class-based animations are treated differently compared to structural animations 5548 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5549 * depending if CSS or JavaScript animations are used. 5550 * 5551 * @param {DOMElement} element the element which the CSS classes will be applied to 5552 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 5553 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 5554 * @param {object=} options an optional collection of options/styles that will be applied to the element 5555 * 5556 * @return {Promise} the animation callback promise 5557 */ 5558 setClass: function(element, add, remove, options) { 5559 options = prepareAnimateOptions(options); 5560 options.addClass = mergeClasses(options.addClass, add); 5561 options.removeClass = mergeClasses(options.removeClass, remove); 5562 return $$animateQueue.push(element, 'setClass', options); 5563 }, 5564 5565 /** 5566 * @ngdoc method 5567 * @name $animate#animate 5568 * @kind function 5569 * 5570 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 5571 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take 5572 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and 5573 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles 5574 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter). 5575 * 5576 * @param {DOMElement} element the element which the CSS styles will be applied to 5577 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 5578 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 5579 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 5580 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 5581 * (Note that if no animation is detected then this value will not be appplied to the element.) 5582 * @param {object=} options an optional collection of options/styles that will be applied to the element 5583 * 5584 * @return {Promise} the animation callback promise 5585 */ 5586 animate: function(element, from, to, className, options) { 5587 options = prepareAnimateOptions(options); 5588 options.from = options.from ? extend(options.from, from) : from; 5589 options.to = options.to ? extend(options.to, to) : to; 5590 5591 className = className || 'ng-inline-animate'; 5592 options.tempClasses = mergeClasses(options.tempClasses, className); 5593 return $$animateQueue.push(element, 'animate', options); 5594 } 5595 }; 5596 }]; 5597}]; 5598 5599/** 5600 * @ngdoc service 5601 * @name $animateCss 5602 * @kind object 5603 * 5604 * @description 5605 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 5606 * then the `$animateCss` service will actually perform animations. 5607 * 5608 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 5609 */ 5610var $CoreAnimateCssProvider = function() { 5611 this.$get = ['$$rAF', '$q', function($$rAF, $q) { 5612 5613 var RAFPromise = function() {}; 5614 RAFPromise.prototype = { 5615 done: function(cancel) { 5616 this.defer && this.defer[cancel === true ? 'reject' : 'resolve'](); 5617 }, 5618 end: function() { 5619 this.done(); 5620 }, 5621 cancel: function() { 5622 this.done(true); 5623 }, 5624 getPromise: function() { 5625 if (!this.defer) { 5626 this.defer = $q.defer(); 5627 } 5628 return this.defer.promise; 5629 }, 5630 then: function(f1,f2) { 5631 return this.getPromise().then(f1,f2); 5632 }, 5633 'catch': function(f1) { 5634 return this.getPromise()['catch'](f1); 5635 }, 5636 'finally': function(f1) {
5637 return this.getPromise()['finally'](f1); 5638 } 5639 }; 5640 5641 return function(element, options) { 5642 // there is no point in applying the styles since 5643 // there is no animation that goes on at all in 5644 // this version of $animateCss. 5645 if (options.cleanupStyles) { 5646 options.from = options.to = null; 5647 } 5648 5649 if (options.from) { 5650 element.css(options.from); 5651 options.from = null; 5652 } 5653 5654 var closed, runner = new RAFPromise(); 5655 return { 5656 start: run, 5657 end: run 5658 }; 5659 5660 function run() { 5661 $$rAF(function() { 5662 close(); 5663 if (!closed) { 5664 runner.done(); 5665 } 5666 closed = true; 5667 }); 5668 return runner; 5669 } 5670 5671 function close() { 5672 if (options.addClass) { 5673 element.addClass(options.addClass); 5674 options.addClass = null; 5675 } 5676 if (options.removeClass) { 5677 element.removeClass(options.removeClass); 5678 options.removeClass = null; 5679 } 5680 if (options.to) { 5681 element.css(options.to); 5682 options.to = null; 5683 } 5684 } 5685 }; 5686 }]; 5687}; 5688 5689/* global stripHash: true */ 5690 5691/** 5692 * ! This is a private undocumented service ! 5693 * 5694 * @name $browser 5695 * @requires $log 5696 * @description 5697 * This object has two goals: 5698 * 5699 * - hide all the global state in the browser caused by the window object 5700 * - abstract away all the browser specific features and inconsistencies 5701 * 5702 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 5703 * service, which can be used for convenient testing of the application without the interaction with 5704 * the real browser apis. 5705 */ 5706/** 5707 * @param {object} window The global window object. 5708 * @param {object} document jQuery wrapped document. 5709 * @param {object} $log window.console or an object with the same interface. 5710 * @param {object} $sniffer $sniffer service 5711 */ 5712function Browser(window, document, $log, $sniffer) { 5713 var self = this, 5714 rawDocument = document[0], 5715 location = window.location, 5716 history = window.history, 5717 setTimeout = window.setTimeout, 5718 clearTimeout = window.clearTimeout, 5719 pendingDeferIds = {}; 5720 5721 self.isMock = false; 5722 5723 var outstandingRequestCount = 0; 5724 var outstandingRequestCallbacks = []; 5725 5726 // TODO(vojta): remove this temporary api 5727 self.$$completeOutstandingRequest = completeOutstandingRequest; 5728 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 5729 5730 /** 5731 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 5732 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 5733 */ 5734 function completeOutstandingRequest(fn) { 5735 try { 5736 fn.apply(null, sliceArgs(arguments, 1)); 5737 } finally { 5738 outstandingRequestCount--; 5739 if (outstandingRequestCount === 0) { 5740 while (outstandingRequestCallbacks.length) { 5741 try { 5742 outstandingRequestCallbacks.pop()(); 5743 } catch (e) { 5744 $log.error(e); 5745 } 5746 } 5747 } 5748 } 5749 } 5750 5751 function getHash(url) { 5752 var index = url.indexOf('#'); 5753 return index === -1 ? '' : url.substr(index); 5754 } 5755 5756 /** 5757 * @private 5758 * Note: this method is used only by scenario runner 5759 * TODO(vojta): prefix this method with $$ ? 5760 * @param {function()} callback Function that will be called when no outstanding request 5761 */ 5762 self.notifyWhenNoOutstandingRequests = function(callback) { 5763 if (outstandingRequestCount === 0) { 5764 callback(); 5765 } else { 5766 outstandingRequestCallbacks.push(callback); 5767 } 5768 }; 5769 5770 ////////////////////////////////////////////////////////////// 5771 // URL API 5772 ////////////////////////////////////////////////////////////// 5773 5774 var cachedState, lastHistoryState, 5775 lastBrowserUrl = location.href, 5776 baseElement = document.find('base'), 5777 pendingLocation = null; 5778 5779 cacheState(); 5780 lastHistoryState = cachedState; 5781 5782 /** 5783 * @name $browser#url 5784 * 5785 * @description 5786 * GETTER: 5787 * Without any argument, this method just returns current value of location.href. 5788 * 5789 * SETTER: 5790 * With at least one argument, this method sets url to new value. 5791 * If html5 history api supported, pushState/replaceState is used, otherwise 5792 * location.href/location.replace is used. 5793 * Returns its own instance to allow chaining 5794 * 5795 * NOTE: this api is intended for use only by the $location service. Please use the 5796 * {@link ng.$location $location service} to change url. 5797 * 5798 * @param {string} url New url (when used as setter) 5799 * @param {boolean=} replace Should new url replace current history record? 5800 * @param {object=} state object to use with pushState/replaceState 5801 */ 5802 self.url = function(url, replace, state) { 5803 // In modern browsers `history.state` is `null` by default; treating it separately 5804 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 5805 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 5806 if (isUndefined(state)) { 5807 state = null; 5808 } 5809 5810 // Android Browser BFCache causes location, history reference to become stale. 5811 if (location !== window.location) location = window.location; 5812 if (history !== window.history) history = window.history; 5813 5814 // setter 5815 if (url) { 5816 var sameState = lastHistoryState === state; 5817 5818 // Don't change anything if previous and current URLs and states match. This also prevents 5819 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 5820 // See https://github.com/angular/angular.js/commit/ffb2701 5821 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 5822 return self; 5823 } 5824 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 5825 lastBrowserUrl = url; 5826 lastHistoryState = state; 5827 // Don't use history API if only the hash changed 5828 // due to a bug in IE10/IE11 which leads 5829 // to not firing a `hashchange` nor `popstate` event 5830 // in some cases (see #9143). 5831 if ($sniffer.history && (!sameBase || !sameState)) { 5832 history[replace ? 'replaceState' : 'pushState'](state, '', url); 5833 cacheState(); 5834 // Do the assignment again so that those two variables are referentially identical. 5835 lastHistoryState = cachedState; 5836 } else { 5837 if (!sameBase || pendingLocation) { 5838 pendingLocation = url; 5839 } 5840 if (replace) { 5841 location.replace(url); 5842 } else if (!sameBase) { 5843 location.href = url; 5844 } else { 5845 location.hash = getHash(url); 5846 } 5847 if (location.href !== url) { 5848 pendingLocation = url; 5849 } 5850 } 5851 return self; 5852 // getter 5853 } else { 5854 // - pendingLocation is needed as browsers don't allow to read out 5855 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 5856 // https://openradar.appspot.com/22186109). 5857 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 5858 return pendingLocation || location.href.replace(/%27/g,"'"); 5859 } 5860 }; 5861 5862 /** 5863 * @name $browser#state 5864 * 5865 * @description 5866 * This method is a getter. 5867 * 5868 * Return history.state or null if history.state is undefined. 5869 * 5870 * @returns {object} state 5871 */ 5872 self.state = function() { 5873 return cachedState; 5874 }; 5875 5876 var urlChangeListeners = [], 5877 urlChangeInit = false;
5878 5879 function cacheStateAndFireUrlChange() { 5880 pendingLocation = null; 5881 cacheState(); 5882 fireUrlChange(); 5883 } 5884 5885 function getCurrentState() { 5886 try { 5887 return history.state; 5888 } catch (e) { 5889 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 5890 } 5891 } 5892 5893 // This variable should be used *only* inside the cacheState function. 5894 var lastCachedState = null; 5895 function cacheState() { 5896 // This should be the only place in $browser where `history.state` is read. 5897 cachedState = getCurrentState(); 5898 cachedState = isUndefined(cachedState) ? null : cachedState; 5899 5900 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 5901 if (equals(cachedState, lastCachedState)) { 5902 cachedState = lastCachedState; 5903 } 5904 lastCachedState = cachedState; 5905 } 5906 5907 function fireUrlChange() { 5908 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 5909 return; 5910 } 5911 5912 lastBrowserUrl = self.url(); 5913 lastHistoryState = cachedState; 5914 forEach(urlChangeListeners, function(listener) { 5915 listener(self.url(), cachedState); 5916 }); 5917 } 5918 5919 /** 5920 * @name $browser#onUrlChange 5921 * 5922 * @description 5923 * Register callback function that will be called, when url changes. 5924 * 5925 * It's only called when the url is changed from outside of angular: 5926 * - user types different url into address bar 5927 * - user clicks on history (forward/back) button 5928 * - user clicks on a link 5929 * 5930 * It's not called when url is changed by $browser.url() method 5931 * 5932 * The listener gets called with new url as parameter. 5933 * 5934 * NOTE: this api is intended for use only by the $location service. Please use the 5935 * {@link ng.$location $location service} to monitor url changes in angular apps. 5936 * 5937 * @param {function(string)} listener Listener function to be called when url changes. 5938 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 5939 */ 5940 self.onUrlChange = function(callback) { 5941 // TODO(vojta): refactor to use node's syntax for events 5942 if (!urlChangeInit) { 5943 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 5944 // don't fire popstate when user change the address bar and don't fire hashchange when url 5945 // changed by push/replaceState 5946 5947 // html5 history api - popstate event 5948 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 5949 // hashchange event 5950 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 5951 5952 urlChangeInit = true; 5953 } 5954 5955 urlChangeListeners.push(callback); 5956 return callback; 5957 }; 5958 5959 /** 5960 * @private 5961 * Remove popstate and hashchange handler from window. 5962 * 5963 * NOTE: this api is intended for use only by $rootScope. 5964 */ 5965 self.$$applicationDestroyed = function() { 5966 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 5967 }; 5968 5969 /** 5970 * Checks whether the url has changed outside of Angular. 5971 * Needs to be exported to be able to check for changes that have been done in sync, 5972 * as hashchange/popstate events fire in async. 5973 */ 5974 self.$$checkUrlChange = fireUrlChange; 5975 5976 ////////////////////////////////////////////////////////////// 5977 // Misc API 5978 ////////////////////////////////////////////////////////////// 5979 5980 /** 5981 * @name $browser#baseHref 5982 * 5983 * @description 5984 * Returns current <base href> 5985 * (always relative - without domain) 5986 * 5987 * @returns {string} The current base href 5988 */ 5989 self.baseHref = function() { 5990 var href = baseElement.attr('href'); 5991 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 5992 }; 5993 5994 /** 5995 * @name $browser#defer 5996 * @param {function()} fn A function, who's execution should be deferred. 5997 * @param {number=} [delay=0] of milliseconds to defer the function execution. 5998 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 5999 * 6000 * @description 6001 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 6002 * 6003 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 6004 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 6005 * via `$browser.defer.flush()`. 6006 * 6007 */ 6008 self.defer = function(fn, delay) { 6009 var timeoutId; 6010 outstandingRequestCount++; 6011 timeoutId = setTimeout(function() {
6012 delete pendingDeferIds[timeoutId]; 6013 completeOutstandingRequest(fn); 6014 }, delay || 0); 6015 pendingDeferIds[timeoutId] = true; 6016 return timeoutId; 6017 }; 6018 6019 6020 /** 6021 * @name $browser#defer.cancel 6022 * 6023 * @description 6024 * Cancels a deferred task identified with `deferId`. 6025 * 6026 * @param {*} deferId Token returned by the `$browser.defer` function. 6027 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 6028 * canceled. 6029 */ 6030 self.defer.cancel = function(deferId) { 6031 if (pendingDeferIds[deferId]) { 6032 delete pendingDeferIds[deferId]; 6033 clearTimeout(deferId); 6034 completeOutstandingRequest(noop); 6035 return true; 6036 } 6037 return false; 6038 }; 6039 6040} 6041 6042function $BrowserProvider() { 6043 this.$get = ['$window', '$log', '$sniffer', '$document', 6044 function($window, $log, $sniffer, $document) { 6045 return new Browser($window, $document, $log, $sniffer); 6046 }]; 6047} 6048 6049/** 6050 * @ngdoc service 6051 * @name $cacheFactory 6052 * 6053 * @description 6054 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 6055 * them. 6056 * 6057 * ```js 6058 * 6059 * var cache = $cacheFactory('cacheId'); 6060 * expect($cacheFactory.get('cacheId')).toBe(cache); 6061 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 6062 * 6063 * cache.put("key", "value"); 6064 * cache.put("another key", "another value"); 6065 * 6066 * // We've specified no options on creation 6067 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 6068 * 6069 * ``` 6070 * 6071 * 6072 * @param {string} cacheId Name or id of the newly created cache. 6073 * @param {object=} options Options object that specifies the cache behavior. Properties: 6074 * 6075 * - `{number=}` `capacity` â turns the cache into LRU cache. 6076 * 6077 * @returns {object} Newly created cache object with the following set of methods: 6078 * 6079 * - `{object}` `info()` â Returns id, size, and options of cache. 6080 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 6081 * it. 6082 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 6083 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 6084 * - `{void}` `removeAll()` â Removes all cached values. 6085 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 6086 * 6087 * @example 6088 <example module="cacheExampleApp"> 6089 <file name="index.html"> 6090 <div ng-controller="CacheController"> 6091 <input ng-model="newCacheKey" placeholder="Key"> 6092 <input ng-model="newCacheValue" placeholder="Value"> 6093 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 6094 6095 <p ng-if="keys.length">Cached Values</p> 6096 <div ng-repeat="key in keys"> 6097 <span ng-bind="key"></span> 6098 <span>: </span> 6099 <b ng-bind="cache.get(key)"></b> 6100 </div> 6101 6102 <p>Cache Info</p> 6103 <div ng-repeat="(key, value) in cache.info()"> 6104 <span ng-bind="key"></span> 6105 <span>: </span> 6106 <b ng-bind="value"></b> 6107 </div> 6108 </div> 6109 </file> 6110 <file name="script.js"> 6111 angular.module('cacheExampleApp', []). 6112 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 6113 $scope.keys = []; 6114 $scope.cache = $cacheFactory('cacheId'); 6115 $scope.put = function(key, value) { 6116 if (angular.isUndefined($scope.cache.get(key))) { 6117 $scope.keys.push(key); 6118 } 6119 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 6120 }; 6121 }]); 6122 </file> 6123 <file name="style.css"> 6124 p { 6125 margin: 10px 0 3px; 6126 } 6127 </file> 6128 </example> 6129 */ 6130function $CacheFactoryProvider() { 6131 6132 this.$get = function() { 6133 var caches = {}; 6134 6135 function cacheFactory(cacheId, options) { 6136 if (cacheId in caches) { 6137 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 6138 } 6139 6140 var size = 0, 6141 stats = extend({}, options, {id: cacheId}), 6142 data = {}, 6143 capacity = (options && options.capacity) || Number.MAX_VALUE, 6144 lruHash = {}, 6145 freshEnd = null, 6146 staleEnd = null; 6147 6148 /** 6149 * @ngdoc type 6150 * @name $cacheFactory.Cache 6151 *
6152 * @description 6153 * A cache object used to store and retrieve data, primarily used by 6154 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 6155 * templates and other data. 6156 * 6157 * ```js 6158 * angular.module('superCache') 6159 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 6160 * return $cacheFactory('super-cache'); 6161 * }]); 6162 * ``` 6163 * 6164 * Example test: 6165 * 6166 * ```js 6167 * it('should behave like a cache', inject(function(superCache) { 6168 * superCache.put('key', 'value'); 6169 * superCache.put('another key', 'another value'); 6170 * 6171 * expect(superCache.info()).toEqual({ 6172 * id: 'super-cache', 6173 * size: 2 6174 * }); 6175 * 6176 * superCache.remove('another key'); 6177 * expect(superCache.get('another key')).toBeUndefined(); 6178 * 6179 * superCache.removeAll(); 6180 * expect(superCache.info()).toEqual({ 6181 * id: 'super-cache', 6182 * size: 0 6183 * }); 6184 * })); 6185 * ``` 6186 */ 6187 return caches[cacheId] = { 6188 6189 /** 6190 * @ngdoc method 6191 * @name $cacheFactory.Cache#put 6192 * @kind function 6193 * 6194 * @description 6195 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 6196 * retrieved later, and incrementing the size of the cache if the key was not already 6197 * present in the cache. If behaving like an LRU cache, it will also remove stale 6198 * entries from the set. 6199 * 6200 * It will not insert undefined values into the cache. 6201 * 6202 * @param {string} key the key under which the cached data is stored. 6203 * @param {*} value the value to store alongside the key. If it is undefined, the key 6204 * will not be stored. 6205 * @returns {*} the value stored. 6206 */ 6207 put: function(key, value) { 6208 if (isUndefined(value)) return; 6209 if (capacity < Number.MAX_VALUE) { 6210 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 6211 6212 refresh(lruEntry); 6213 } 6214 6215 if (!(key in data)) size++; 6216 data[key] = value; 6217 6218 if (size > capacity) { 6219 this.remove(staleEnd.key); 6220 } 6221 6222 return value; 6223 }, 6224 6225 /** 6226 * @ngdoc method 6227 * @name $cacheFactory.Cache#get 6228 * @kind function 6229 * 6230 * @description 6231 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 6232 * 6233 * @param {string} key the key of the data to be retrieved 6234 * @returns {*} the value stored. 6235 */ 6236 get: function(key) { 6237 if (capacity < Number.MAX_VALUE) { 6238 var lruEntry = lruHash[key]; 6239 6240 if (!lruEntry) return; 6241 6242 refresh(lruEntry); 6243 } 6244 6245 return data[key]; 6246 }, 6247 6248 6249 /** 6250 * @ngdoc method 6251 * @name $cacheFactory.Cache#remove 6252 * @kind function 6253 * 6254 * @description 6255 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 6256 * 6257 * @param {string} key the key of the entry to be removed 6258 */ 6259 remove: function(key) { 6260 if (capacity < Number.MAX_VALUE) { 6261 var lruEntry = lruHash[key]; 6262 6263 if (!lruEntry) return; 6264 6265 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 6266 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 6267 link(lruEntry.n,lruEntry.p); 6268 6269 delete lruHash[key]; 6270 } 6271 6272 delete data[key]; 6273 size--; 6274 }, 6275 6276 6277 /** 6278 * @ngdoc method 6279 * @name $cacheFactory.Cache#removeAll 6280 * @kind function 6281 * 6282 * @description 6283 * Clears the cache object of any entries. 6284 */ 6285 removeAll: function() { 6286 data = {}; 6287 size = 0; 6288 lruHash = {}; 6289 freshEnd = staleEnd = null; 6290 }, 6291 6292 6293 /** 6294 * @ngdoc method 6295 * @name $cacheFactory.Cache#destroy 6296 * @kind function 6297 *
6298 * @description 6299 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 6300 * removing it from the {@link $cacheFactory $cacheFactory} set. 6301 */ 6302 destroy: function() { 6303 data = null; 6304 stats = null; 6305 lruHash = null; 6306 delete caches[cacheId]; 6307 }, 6308 6309 6310 /** 6311 * @ngdoc method 6312 * @name $cacheFactory.Cache#info 6313 * @kind function 6314 * 6315 * @description 6316 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 6317 * 6318 * @returns {object} an object with the following properties: 6319 * <ul> 6320 * <li>**id**: the id of the cache instance</li> 6321 * <li>**size**: the number of entries kept in the cache instance</li> 6322 * <li>**...**: any additional properties from the options object when creating the 6323 * cache.</li> 6324 * </ul> 6325 */ 6326 info: function() { 6327 return extend({}, stats, {size: size}); 6328 } 6329 }; 6330 6331 6332 /** 6333 * makes the `entry` the freshEnd of the LRU linked list 6334 */ 6335 function refresh(entry) { 6336 if (entry != freshEnd) { 6337 if (!staleEnd) { 6338 staleEnd = entry; 6339 } else if (staleEnd == entry) { 6340 staleEnd = entry.n; 6341 } 6342 6343 link(entry.n, entry.p); 6344 link(entry, freshEnd); 6345 freshEnd = entry; 6346 freshEnd.n = null; 6347 } 6348 } 6349 6350 6351 /** 6352 * bidirectionally links two entries of the LRU linked list 6353 */ 6354 function link(nextEntry, prevEntry) { 6355 if (nextEntry != prevEntry) { 6356 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 6357 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 6358 } 6359 } 6360 } 6361 6362 6363 /** 6364 * @ngdoc method 6365 * @name $cacheFactory#info 6366 * 6367 * @description 6368 * Get information about all the caches that have been created 6369 * 6370 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 6371 */ 6372 cacheFactory.info = function() { 6373 var info = {}; 6374 forEach(caches, function(cache, cacheId) { 6375 info[cacheId] = cache.info(); 6376 }); 6377 return info; 6378 }; 6379 6380 6381 /** 6382 * @ngdoc method 6383 * @name $cacheFactory#get 6384 * 6385 * @description 6386 * Get access to a cache object by the `cacheId` used when it was created. 6387 * 6388 * @param {string} cacheId Name or id of a cache to access. 6389 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 6390 */ 6391 cacheFactory.get = function(cacheId) { 6392 return caches[cacheId]; 6393 }; 6394 6395 6396 return cacheFactory; 6397 }; 6398} 6399 6400/** 6401 * @ngdoc service 6402 * @name $templateCache 6403 * 6404 * @description 6405 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 6406 * can load templates directly into the cache in a `script` tag, or by consuming the 6407 * `$templateCache` service directly. 6408 * 6409 * Adding via the `script` tag: 6410 * 6411 * ```html 6412 * <script type="text/ng-template" id="templateId.html"> 6413 * <p>This is the content of the template</p> 6414 * </script> 6415 * ``` 6416 * 6417 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 6418 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 6419 * element with ng-app attribute), otherwise the template will be ignored. 6420 * 6421 * Adding via the `$templateCache` service: 6422 * 6423 * ```js 6424 * var myApp = angular.module('myApp', []); 6425 * myApp.run(function($templateCache) { 6426 * $templateCache.put('templateId.html', 'This is the content of the template'); 6427 * }); 6428 * ``` 6429 * 6430 * To retrieve the template later, simply use it in your HTML: 6431 * ```html 6432 * <div ng-include=" 'templateId.html' "></div> 6433 * ``` 6434 * 6435 * or get it via Javascript: 6436 * ```js 6437 * $templateCache.get('templateId.html') 6438 * ``` 6439 * 6440 * See {@link ng.$cacheFactory $cacheFactory}. 6441 * 6442 */ 6443function $TemplateCacheProvider() { 6444 this.$get = ['$cacheFactory', function($cacheFactory) { 6445 return $cacheFactory('templates'); 6446 }]; 6447} 6448 6449/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 6450 * Any commits to this file should be reviewed with security in mind. * 6451 * Changes to this file can potentially create security vulnerabilities. * 6452 * An approval from 2 Core members with history of modifying * 6453 * this file is required. * 6454 * * 6455 * Does the change somehow allow for arbitrary javascript to be executed? * 6456 * Or allows for someone to change the prototype of built-in objects? * 6457 * Or gives undesired access to variables likes document or window? * 6458 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 6459
6460/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 6461 * 6462 * DOM-related variables: 6463 * 6464 * - "node" - DOM Node 6465 * - "element" - DOM Element or Node 6466 * - "$node" or "$element" - jqLite-wrapped node or element 6467 * 6468 * 6469 * Compiler related stuff: 6470 * 6471 * - "linkFn" - linking fn of a single directive 6472 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 6473 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 6474 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 6475 */ 6476 6477 6478/** 6479 * @ngdoc service 6480 * @name $compile 6481 * @kind function 6482 * 6483 * @description 6484 * Compiles an HTML string or DOM into a template and produces a template function, which 6485 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 6486 * 6487 * The compilation is a process of walking the DOM tree and matching DOM elements to 6488 * {@link ng.$compileProvider#directive directives}. 6489 * 6490 * <div class="alert alert-warning"> 6491 * **Note:** This document is an in-depth reference of all directive options. 6492 * For a gentle introduction to directives with examples of common use cases, 6493 * see the {@link guide/directive directive guide}. 6494 * </div> 6495 * 6496 * ## Comprehensive Directive API 6497 * 6498 * There are many different options for a directive. 6499 * 6500 * The difference resides in the return value of the factory function. 6501 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 6502 * or just the `postLink` function (all other properties will have the default values). 6503 * 6504 * <div class="alert alert-success"> 6505 * **Best Practice:** It's recommended to use the "directive definition object" form. 6506 * </div> 6507 * 6508 * Here's an example directive declared with a Directive Definition Object: 6509 * 6510 * ```js 6511 * var myModule = angular.module(...); 6512 * 6513 * myModule.directive('directiveName', function factory(injectables) { 6514 * var directiveDefinitionObject = { 6515 * priority: 0, 6516 * template: '<div></div>
6516', // or // function(tElement, tAttrs) { ... }, 6517 * // or 6518 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 6519 * transclude: false, 6520 * restrict: 'A', 6521 * templateNamespace: 'html', 6522 * scope: false, 6523 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 6524 * controllerAs: 'stringIdentifier', 6525 * bindToController: false, 6526 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 6527 * compile: function compile(tElement, tAttrs, transclude) { 6528 * return { 6529 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6530 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 6531 * } 6532 * // or 6533 * // return function postLink( ... ) { ... } 6534 * }, 6535 * // or 6536 * // link: { 6537 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6538 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 6539 * // } 6540 * // or 6541 * // link: function postLink( ... ) { ... } 6542 * }; 6543 * return directiveDefinitionObject; 6544 * }); 6545 * ``` 6546 * 6547 * <div class="alert alert-warning"> 6548 * **Note:** Any unspecified options will use the default value. You can see the default values below. 6549 * </div> 6550 * 6551 * Therefore the above can be simplified as: 6552 * 6553 * ```js 6554 * var myModule = angular.module(...); 6555 * 6556 * myModule.directive('directiveName', function factory(injectables) { 6557 * var directiveDefinitionObject = { 6558 * link: function postLink(scope, iElement, iAttrs) { ... } 6559 * }; 6560 * return directiveDefinitionObject; 6561 * // or 6562 * // return function postLink(scope, iElement, iAttrs) { ... } 6563 * }); 6564 * ``` 6565 * 6566 * 6567 * 6568 * ### Directive Definition Object 6569 * 6570 * The directive definition object provides instructions to the {@link ng.$compile 6571 * compiler}. The attributes are: 6572 * 6573 * #### `multiElement` 6574 * When this property is set to true, the HTML compiler will collect DOM nodes between 6575 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 6576 * together as the directive elements. It is recommended that this feature be used on directives 6577 * which are not strictly behavioural (such as {@link ngClick}), and which 6578 * do not manipulate or replace child nodes (such as {@link ngInclude}). 6579 * 6580 * #### `priority` 6581 * When there are multiple directives defined on a single DOM element, sometimes it 6582 * is necessary to specify the order in which the directives are applied. The `priority` is used 6583 * to sort the directives before their `compile` functions get called. Priority is defined as a 6584 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 6585 * are also run in priority order, but post-link functions are run in reverse order. The order 6586 * of directives with the same priority is undefined. The default priority is `0`. 6587 * 6588 * #### `terminal` 6589 * If set to true then the current `priority` will be the last set of directives 6590 * which will execute (any directives at the current priority will still execute 6591 * as the order of execution on same `priority` is undefined). Note that expressions 6592 * and other directives used in the directive's template will also be excluded from execution. 6593 * 6594 * #### `scope` 6595 * The scope property can be `true`, an object or a falsy value: 6596 * 6597 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. 6598 * 6599 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 6600 * the directive's element. If multiple directives on the same element request a new scope, 6601 * only one new scope is created. The new scope rule does not apply for the root of the template 6602 * since the root of the template always gets a new scope. 6603 * 6604 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The 6605 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent 6606 * scope. This is useful when creating reusable components, which should not accidentally read or modify 6607 * data in the parent scope. 6608 * 6609 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 6610 * directive's element. These local properties are useful for aliasing values for templates. The keys in 6611 * the object hash map to the name of the property on the isolate scope; the values define how the property 6612 * is bound to the parent scope, via matching attributes on the directive's element: 6613 * 6614 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6615 * always a string since DOM attributes are strings. If no `attr` name is specified then the 6616 * attribute name is assumed to be the same as the local name. 6617 * Given `<widget my-attr="hello {{name}}">` and widget definition 6618 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 6619 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 6620 * `localName` property on the widget scope. The `name` is read from the parent scope (not 6621 * component scope). 6622 * 6623 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 6624 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 6625 * name is specified then the attribute name is assumed to be the same as the local name. 6626 * Given `<widget my-attr="parentModel">` and widget definition of 6627 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 6628 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 6629 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 6630 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 6631 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If 6632 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use 6633 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). 6634 * 6635 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 6636 * If no `attr` name is specified then the attribute name is assumed to be the same as the 6637 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 6638 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 6639 * a function wrapper for the `count = count + value` expression. Often it's desirable to 6640 * pass data from the isolated scope via an expression to the parent scope, this can be 6641 * done by passing a map of local variable names and values into the expression wrapper fn. 6642 * For example, if the expression is `increment(amount)` then we can specify the amount value 6643 * by calling the `localFn` as `localFn({amount: 22})`. 6644 * 6645 * In general it's possible to apply more than one directive to one element, but there might be limitations 6646 * depending on the type of scope required by the directives. The following points will help explain these limitations. 6647 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 6648 * 6649 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 6650 * * **child scope** + **no scope** => Both directives will share one single child scope 6651 * * **child scope** + **child scope** => Both directives will share one single child scope 6652 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use 6653 * its parent's scope 6654 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 6655 * be applied to the same element. 6656 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 6657 * cannot be applied to the same element. 6658 * 6659 * 6660 * #### `bindToController` 6661 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will 6662 * allow a component to have its properties bound to the controller, rather than to scope. When the controller 6663 * is instantiated, the initial values of the isolate scope bindings are already available. 6664 * 6665 * #### `controller` 6666 * Controller constructor function. The controller is instantiated before the 6667 * pre-linking phase and can be accessed by other directives (see 6668 * `require` attribute). This allows the directives to communicate with each other and augment 6669 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 6670 * 6671 * * `$scope` - Current scope associated with the element 6672 * * `$element` - Current element 6673 * * `$attrs` - Current attributes object for the element 6674 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 6675 * `function([scope], cloneLinkingFn, futureParentElement)`. 6676 * * `scope`: optional argument to override the scope. 6677 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. 6678 * * `futureParentElement`: 6679 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 6680 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 6681 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 6682 * and when the `cloneLinkinFn` is passed, 6683 * as those elements need to created and cloned in a special way when they are defined outside their 6684 * usual containers (e.g. like `<svg>`). 6685 * * See also the `directive.templateNamespace` property. 6686 * 6687 * 6688 * #### `require` 6689 * Require another directive and inject its controller as the fourth argument to the linking function. The
6690 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 6691 * injected argument will be an array in corresponding order. If no such directive can be 6692 * found, or if the directive does not have a controller, then an error is raised (unless no link function 6693 * is specified, in which case error checking is skipped). The name can be prefixed with: 6694 * 6695 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 6696 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 6697 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 6698 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 6699 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 6700 * `null` to the `link` fn if not found. 6701 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 6702 * `null` to the `link` fn if not found. 6703 * 6704 * 6705 * #### `controllerAs` 6706 * Identifier name for a reference to the controller in the directive's scope. 6707 * This allows the controller to be referenced from the directive template. This is especially 6708 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 6709 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 6710 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 6711 * 6712 * 6713 * #### `restrict` 6714 * String of subset of `EACM` which restricts the directive to a specific directive 6715 * declaration style. If omitted, the defaults (elements and attributes) are used. 6716 * 6717 * * `E` - Element name (default): `<my-directive></my-directive>` 6718 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 6719 * * `C` - Class: `<div class="my-directive: exp;"></div>` 6720 * * `M` - Comment: `<!-- directive: my-directive exp -->` 6721 * 6722 * 6723 * #### `templateNamespace` 6724 * String representing the document type used by the markup in the template. 6725 * AngularJS needs this information as those elements need to be created and cloned 6726 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 6727 * 6728 * * `html` - All root nodes in the template are HTML. Root nodes may also be 6729 * top-level elements such as `<svg>` or `<math>`. 6730 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 6731 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 6732 * 6733 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 6734 * 6735 * #### `template` 6736 * HTML markup that may: 6737 * * Replace the contents of the directive's element (default). 6738 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 6739 * * Wrap the contents of the directive's element (if `transclude` is true). 6740 * 6741 * Value may be: 6742 * 6743 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 6744 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 6745 * function api below) and returns a string value. 6746 * 6747 * 6748 * #### `templateUrl` 6749 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 6750 * 6751 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 6752 * for later when the template has been resolved. In the meantime it will continue to compile and link 6753 * sibling and parent elements as though this element had not contained any directives. 6754 * 6755 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 6756 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 6757 * case when only one deeply nested directive has `templateUrl`. 6758 * 6759 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 6760 * 6761 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6762 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 6763 * a string value representing the url. In either case, the template URL is passed through {@link 6764 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 6765 * 6766 * 6767 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 6768 * specify what the template should replace. Defaults to `false`. 6769 * 6770 * * `true` - the template will replace the directive's element. 6771 * * `false` - the template will replace the contents of the directive's element. 6772 * 6773 * The replacement process migrates all of the attributes / classes from the old element to the new 6774 * one. See the {@link guide/directive#template-expanding-directive 6775 * Directives Guide} for an example. 6776 * 6777 * There are very few scenarios where element replacement is required for the application function, 6778 * the main one being reusable custom components that are used within SVG contexts 6779 * (because SVG doesn't work with custom elements in the DOM tree). 6780 * 6781 * #### `transclude` 6782 * Extract the contents of the element where the directive appears and make it available to the directive. 6783 * The contents are compiled and provided to the directive as a **transclusion function**. See the 6784 * {@link $compile#transclusion Transclusion} section below. 6785 * 6786 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the 6787 * directive's element or the entire element: 6788 * 6789 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 6790 * * `'element'` - transclude the whole of the directive's element including any directives on this 6791 * element that defined at a lower priority than this directive. When used, the `template` 6792 * property is ignored. 6793 * 6794 * 6795 * #### `compile` 6796 * 6797 * ```js 6798 * function compile(tElement, tAttrs, transclude) { ... } 6799 * ``` 6800 * 6801 * The compile function deals with transforming the template DOM. Since most directives do not do 6802 * template transformation, it is not used often. The compile function takes the following arguments: 6803 * 6804 * * `tElement` - template element - The element where the directive has been declared. It is 6805 * safe to do template transformation on the element and child elements only. 6806 * 6807 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 6808 * between all directive compile functions. 6809 * 6810 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 6811 * 6812 * <div class="alert alert-warning"> 6813 * **Note:** The template instance and the link instance may be different objects if the template has 6814 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 6815 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 6816 * should be done in a linking function rather than in a compile function. 6817 * </div> 6818 6819 * <div class="alert alert-warning"> 6820 * **Note:** The compile function cannot handle directives that recursively use themselves in their 6821 * own templates or compile functions. Compiling these directives results in an infinite loop and a 6822 * stack overflow errors. 6823 * 6824 * This can be avoided by manually using $compile in the postLink function to imperatively compile 6825 * a directive's template instead of relying on automatic template compilation via `template` or 6826 * `templateUrl` declaration or manual compilation inside the compile function. 6827 * </div> 6828 * 6829 * <div class="alert alert-danger"> 6830 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 6831 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 6832 * to the link function instead. 6833 * </div> 6834 6835 * A compile function can have a return value which can be either a function or an object. 6836 * 6837 * * returning a (post-link) function - is equivalent to registering the linking function via the 6838 * `link` property of the config object when the compile function is empty. 6839 * 6840 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 6841 * control when a linking function should be called during the linking phase. See info about 6842 * pre-linking and post-linking functions below. 6843 * 6844 * 6845 * #### `link` 6846 * This property is used only if the `compile` property is not defined. 6847 * 6848 * ```js 6849 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 6850 * ``` 6851 * 6852 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 6853 * executed after the template has been cloned. This is where most of the directive logic will be 6854 * put. 6855 * 6856 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 6857 * directive for registering {@link ng.$rootScope.Scope#$watch w
6857atches}. 6858 * 6859 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 6860 * manipulate the children of the element only in `postLink` function since the children have 6861 * already been linked. 6862 * 6863 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 6864 * between all directive linking functions. 6865 * 6866 * * `controller` - the directive's required controller instance(s) - Instances are shared 6867 * among all directives, which allows the directives to use the controllers as a communication 6868 * channel. The exact value depends on the directive's `require` property: 6869 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 6870 * * `string`: the controller instance 6871 * * `array`: array of controller instances 6872 * 6873 * If a required controller cannot be found, and it is optional, the instance is `null`, 6874 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 6875 * 6876 * Note that you can also require the directive's own controller - it will be made available like 6877 * any other controller. 6878 * 6879 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 6880 * This is the same as the `$transclude` 6881 * parameter of directive controllers, see there for details. 6882 * `function([scope], cloneLinkingFn, futureParentElement)`. 6883 * 6884 * #### Pre-linking function 6885 * 6886 * Executed before the child elements are linked. Not safe to do DOM transformation since the 6887 * compiler linking function will fail to locate the correct elements for linking. 6888 * 6889 * #### Post-linking function 6890 * 6891 * Executed after the child elements are linked. 6892 *
6893 * Note that child elements that contain `templateUrl` directives will not have been compiled 6894 * and linked since they are waiting for their template to load asynchronously and their own 6895 * compilation and linking has been suspended until that occurs. 6896 * 6897 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 6898 * for their async templates to be resolved. 6899 * 6900 * 6901 * ### Transclusion 6902 * 6903 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 6904 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 6905 * scope from where they were taken. 6906 * 6907 * Transclusion is used (often with {@link ngTransclude}) to insert the 6908 * original contents of a directive's element into a specified place in the template of the directive. 6909 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 6910 * content has access to the properties on the scope from which it was taken, even if the directive 6911 * has isolated scope. 6912 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 6913 * 6914 * This makes it possible for the widget to have private state for its template, while the transcluded 6915 * content has access to its originating scope. 6916 * 6917 * <div class="alert alert-warning"> 6918 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 6919 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 6920 * Testing Transclusion Directives}. 6921 * </div> 6922 * 6923 * #### Transclusion Functions 6924 * 6925 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 6926 * function** to the directive's `link` function and `controller`. This transclusion function is a special 6927 * **linking function** that will return the compiled contents linked to a new transclusion scope. 6928 * 6929 * <div class="alert alert-info"> 6930 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 6931 * ngTransclude will deal with it for us. 6932 * </div> 6933 * 6934 * If you want to manually control the insertion and removal of the transcluded content in your directive 6935 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery 6936 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 6937 * 6938 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 6939 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 6940 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 6941 * 6942 * <div class="alert alert-info"> 6943 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function 6944 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 6945 * </div> 6946 * 6947 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 6948 * attach function**: 6949 * 6950 * ```js 6951 * var transcludedContent, transclusionScope; 6952 * 6953 * $transclude(function(clone, scope) { 6954 * element.append(clone); 6955 * transcludedContent = clone; 6956 * transclusionScope = scope; 6957 * }); 6958 * ``` 6959 * 6960 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 6961 * associated transclusion scope: 6962 * 6963 * ```js 6964 * transcludedContent.remove(); 6965 * transclusionScope.$destroy(); 6966 * ``` 6967 * 6968 * <div class="alert alert-info"> 6969 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 6970 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 6971 * then you are also responsible for calling `$destroy` on the transclusion scope. 6972 * </div> 6973 * 6974 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 6975 * automatically destroy their transluded clones as necessary so you do not need to worry about this if 6976 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 6977 * 6978 * 6979 * #### Transclusion Scopes 6980 * 6981 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 6982 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 6983 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 6984 * was taken. 6985 * 6986 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 6987 * like this: 6988 * 6989 * ```html 6990 * <div ng-app> 6991 * <div isolate> 6992 * <div transclusion>
6993 * </div> 6994 * </div> 6995 * </div> 6996 * ``` 6997 * 6998 * The `$parent` scope hierarchy will look like this: 6999 * 7000 * ``` 7001 * - $rootScope 7002 * - isolate 7003 * - transclusion 7004 * ``` 7005 * 7006 * but the scopes will inherit prototypically from different scopes to their `$parent`. 7007 * 7008 * ``` 7009 * - $rootScope 7010 * - transclusion 7011 * - isolate 7012 * ``` 7013 * 7014 * 7015 * ### Attributes 7016 * 7017 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 7018 * `link()` or `compile()` functions. It has a variety of uses. 7019 * 7020 * accessing *Normalized attribute names:* 7021 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 7022 * the attributes object allows for normalized access to 7023 * the attributes. 7024 * 7025 * * *Directive inter-communication:* All directives share the same instance of the attributes 7026 * object which allows the directives to use the attributes object as inter directive 7027 * communication. 7028 * 7029 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 7030 * allowing other directives to read the interpolated value. 7031 * 7032 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 7033 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 7034 * the only way to easily get the actual value because during the linking phase the interpolation 7035 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 7036 * 7037 * ```js 7038 * function linkingFn(scope, elm, attrs, ctrl) { 7039 * // get the attribute value 7040 * console.log(attrs.ngModel); 7041 * 7042 * // change the attribute 7043 * attrs.$set('ngModel', 'new value'); 7044 * 7045 * // observe changes to interpolated attribute 7046 * attrs.$observe('ngModel', function(value) { 7047 * console.log('ngModel has changed value to ' + value); 7048 * }); 7049 * } 7050 * ``` 7051 * 7052 * ## Example 7053 * 7054 * <div class="alert alert-warning"> 7055 * **Note**: Typically directives are registered with `module.directive`. The example below is 7056 * to illustrate how `$compile` works. 7057 * </div> 7058 * 7059 <example module="compileExample"> 7060 <file name="index.html"> 7061 <script> 7062 angular.module('compileExample', [], function($compileProvider) { 7063 // configure new 'compile' directive by passing a directive 7064 // factory function. The factory function injects the '$compile' 7065 $compileProvider.directive('compile', function($compile) { 7066 // directive factory creates a link function 7067 return function(scope, element, attrs) { 7068 scope.$watch( 7069 function(scope) { 7070 // watch the 'compile' expression for changes 7071 return scope.$eval(attrs.compile); 7072 }, 7073 function(value) { 7074 // when the 'compile' expression changes 7075 // assign it into the current DOM 7076 element.html(value); 7077 7078 // compile the new DOM and link it to the current 7079 // scope. 7080 // NOTE: we only compile .childNodes so that 7081 // we don't get into infinite loop compiling ourselves 7082 $compile(element.contents())(scope); 7083 } 7084 ); 7085 }; 7086 }); 7087 }) 7088 .controller('GreeterController', ['$scope', function($scope) { 7089 $scope.name = 'Angular'; 7090 $scope.html = 'Hello {{name}}'; 7091 }]); 7092 </script> 7093 <div ng-controller="GreeterController"> 7094 <input ng-model="name"> <br/> 7095 <textarea ng-model="html"></textarea> <br/> 7096 <div compile="html"></div> 7097 </div> 7098 </file> 7099 <file name="protractor.js" type="protractor"> 7100 it('should auto compile', function() { 7101 var textarea = $('textarea'); 7102 var output = $('div[compile]'); 7103 // The initial state reads 'Hello Angular'. 7104 expect(output.getText()).toBe('Hello Angular'); 7105 textarea.clear(); 7106 textarea.sendKeys('{{name}}!'); 7107 expect(output.getText()).toBe('Angular!'); 7108 }); 7109 </file> 7110 </example> 7111 7112 * 7113 * 7114 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 7115 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 7116 * 7117 * <div class="alert alert-danger"> 7118 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 7119 * e.g. will not use the right outer scope. Please pass the transclude function as a 7120 * `parentBoundTranscludeFn` to the link function instead. 7121 * </div> 7122 * 7123 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7124 * root element(s), not their children) 7125 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 7126 * (a DOM element/tree) to a scope. Where: 7127 * 7128 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 7129 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 7130 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 7131 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 7132 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 7133 * 7134 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 7135 * * `scope` - is the current scope with which the linking function is working with. 7136 * 7137 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 7138 * keys may be used to control linking behavior: 7139 * 7140 * * `parentBoundTranscludeFn` - the transclude function made available to 7141 * directives; if given, it will be passed through to the link functions of 7142 * directives found in `element` during compilation. 7143 * * `transcludeControllers` - an object hash with keys that map controller names 7144 * to controller instances; if given, it will make the controllers 7145 * available to directives. 7146 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 7147 * the cloned elements; only needed for transcludes that are allowed to contain non html 7148 * elements (e.g. SVG elements). See also the directive.controller property. 7149 * 7150 * Calling the linking function returns the element of the template. It is either the original 7151 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 7152 * 7153 * After linking the view is not updated until after a call to $digest which typically is done by 7154 * Angular automatically. 7155 * 7156 * If you need access to the bound view, there are two ways to do it: 7157 * 7158 * - If you are not asking the linking function to clone the template, create the DOM element(s) 7159 * before you send them to the compiler and keep this reference around. 7160 * ```js 7161 * var element = $compile('<p>{{total}}</p>')(scope); 7162 * ``` 7163 * 7164 * - if on the other hand, you need the element to be cloned, the view reference from the original 7165 * example would not point to the clone, but rather to the original template that was cloned. In 7166 * this case, you can access the clone via the cloneAttachFn: 7167 * ```js 7168 * var templateElement = angular.element('<p>{{total}}</p>'), 7169 * scope = ....; 7170 * 7171 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 7172 * //attach the clone to DOM document at the right place 7173 * }); 7174 * 7175 * //now we have reference to the cloned DOM via `clonedElement` 7176 * ``` 7177 * 7178 * 7179 * For information on how the compiler works, see the 7180 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 7181 */ 7182 7183var $compileMinErr = minErr('$compile'); 7184 7185/** 7186 * @ngdoc provider 7187 * @name $compileProvider 7188 * 7189 * @description 7190 */ 7191$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 7192function $CompileProvider($provide, $$sanitizeUriProvider) { 7193 var hasDirectives = {}, 7194 Suffix = 'Directive', 7195 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 7196 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 7197 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 7198 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 7199 7200 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 7201 // The assumption is that future DOM event attribute names will begin with 7202 // 'on' and be composed of only English letters. 7203 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 7204 7205 function parseIsolateBindings(scope, directiveName, isController) { 7206 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; 7207 7208 var bindings = {}; 7209 7210 forEach(scope, function(definition, scopeName) { 7211 var match = definition.match(LOCAL_REGEXP); 7212 7213 if (!match) { 7214 throw $compileMinErr('iscp', 7215 "Invalid {3} for directive '{0}'." + 7216 " Definition: {... {1}: '{2}' ...}", 7217 directiveName, scopeName, definition, 7218 (isController ? "controller bindings definition" : 7219 "isolate scope definition")); 7220 } 7221 7222 bindings[scopeName] = { 7223 mode: match[1][0], 7224 collection: match[2] === '*', 7225 optional: match[3] === '?', 7226 attrName: match[4] || scopeName 7227 }; 7228 }); 7229 7230 return bindings; 7231 } 7232 7233 function parseDirectiveBindings(directive, directiveName) { 7234 var bindings = { 7235 isolateScope: null, 7236 bindToController: null 7237 }; 7238 if (isObject(directive.scope)) { 7239 if (directive.bindToController === true) { 7240 bindings.bindToController = parseIsolateBindings(directive.scope, 7241 directiveName, true); 7242 bindings.isolateScope = {}; 7243 } else { 7244 bindings.isolateScope = parseIsolateBindings(directive.scope, 7245 directiveName, false); 7246 } 7247 } 7248 if (isObject(directive.bindToController)) { 7249 bindings.bindToController = 7250 parseIsolateBindings(directive.bindToController, directiveName, true); 7251 } 7252 if (isObject(bindings.bindToController)) { 7253 var controller = directive.controller; 7254 var controllerAs = directive.controllerAs; 7255 if (!controller) { 7256 // There is no controller, there may or may not be a controllerAs property 7257 throw $compileMinErr('noctrl', 7258 "Cannot bind to controller without directive '{0}'s controller.", 7259 directiveName); 7260 } else if (!identifierForController(controller, controllerAs)) { 7261 // There is a controller, but no identifier or controllerAs property 7262 throw $compileMinErr('noident', 7263 "Cannot bind to controller without identifier for directive '{0}'.", 7264 directiveName); 7265 } 7266 } 7267 return bindings; 7268 } 7269 7270 function assertValidDirectiveName(name) { 7271 var letter = name.charAt(0); 7272 if (!letter || letter !== lowercase(letter)) { 7273 throw $compileMinErr('baddir', "Directive name '{0}
vendor: 4,630 bytes, lines 7273-7385
7273' is invalid. The first character must be a lowercase letter", name); 7274 } 7275 if (name !== name.trim()) { 7276 throw $compileMinErr('baddir', 7277 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 7278 name); 7279 } 7280 } 7281 7282 /** 7283 * @ngdoc method 7284 * @name $compileProvider#directive 7285 * @kind function 7286 * 7287 * @description 7288 * Register a new directive with the compiler. 7289 * 7290 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 7291 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 7292 * names and the values are the factories. 7293 * @param {Function|Array} directiveFactory An injectable directive factory function. See 7294 * {@link guide/directive} for more info. 7295 * @returns {ng.$compileProvider} Self for chaining. 7296 */ 7297 this.directive = function registerDirective(name, directiveFactory) { 7298 assertNotHasOwnProperty(name, 'directive'); 7299 if (isString(name)) { 7300 assertValidDirectiveName(name); 7301 assertArg(directiveFactory, 'directiveFactory'); 7302 if (!hasDirectives.hasOwnProperty(name)) { 7303 hasDirectives[name] = []; 7304 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 7305 function($injector, $exceptionHandler) { 7306 var directives = []; 7307 forEach(hasDirectives[name], function(directiveFactory, index) { 7308 try { 7309 var directive = $injector.invoke(directiveFactory); 7310 if (isFunction(directive)) { 7311 directive = { compile: valueFn(directive) }; 7312 } else if (!directive.compile && directive.link) { 7313 directive.compile = valueFn(directive.link); 7314 } 7315 directive.priority = directive.priority || 0; 7316 directive.index = index; 7317 directive.name = directive.name || name; 7318 directive.require = directive.require || (directive.controller && directive.name); 7319 directive.restrict = directive.restrict || 'EA'; 7320 var bindings = directive.$$bindings = 7321 parseDirectiveBindings(directive, directive.name); 7322 if (isObject(bindings.isolateScope)) { 7323 directive.$$isolateBindings = bindings.isolateScope; 7324 } 7325 directive.$$moduleName = directiveFactory.$$moduleName; 7326 directives.push(directive); 7327 } catch (e) { 7328 $exceptionHandler(e); 7329 } 7330 }); 7331 return directives; 7332 }]); 7333 } 7334 hasDirectives[name].push(directiveFactory); 7335 } else { 7336 forEach(name, reverseParams(registerDirective)); 7337 } 7338 return this; 7339 }; 7340 7341 7342 /** 7343 * @ngdoc method 7344 * @name $compileProvider#aHrefSanitizationWhitelist 7345 * @kind function 7346 * 7347 * @description 7348 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7349 * urls during a[href] sanitization. 7350 * 7351 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 7352 * 7353 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 7354 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 7355 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7356 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7357 * 7358 * @param {RegExp=} regexp New regexp to whitelist urls with. 7359 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7360 * chaining otherwise. 7361 */ 7362 this.aHrefSanitizationWhitelist = function(regexp) { 7363 if (isDefined(regexp)) { 7364 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 7365 return this; 7366 } else { 7367 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 7368 } 7369 }; 7370 7371 7372 /** 7373 * @ngdoc method 7374 * @name $compileProvider#imgSrcSanitizationWhitelist 7375 * @kind function 7376 * 7377 * @description 7378 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7379 * urls during img[src] sanitization. 7380 * 7381 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 7382 * 7383 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 7384 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 7385 * regular expression. If a match is found, the original url is
7385written into the dom. Otherwise, 7386 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7387 * 7388 * @param {RegExp=} regexp New regexp to whitelist urls with. 7389 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7390 * chaining otherwise. 7391 */ 7392 this.imgSrcSanitizationWhitelist = function(regexp) { 7393 if (isDefined(regexp)) { 7394 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 7395 return this; 7396 } else { 7397 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 7398 } 7399 }; 7400 7401 /** 7402 * @ngdoc method 7403 * @name $compileProvider#debugInfoEnabled 7404 * 7405 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 7406 * current debugInfoEnabled state 7407 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7408 * 7409 * @kind function 7410 * 7411 * @description 7412 * Call this method to enable/disable various debug runtime information in the compiler such as adding 7413 * binding information and a reference to the current scope on to DOM elements. 7414 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 7415 * * `ng-binding` CSS class 7416 * * `$binding` data property containing an array of the binding expressions 7417 * 7418 * You may want to disable this in production for a significant performance boost. See 7419 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 7420 * 7421 * The default value is true. 7422 */ 7423 var debugInfoEnabled = true; 7424 this.debugInfoEnabled = function(enabled) { 7425 if (isDefined(enabled)) { 7426 debugInfoEnabled = enabled; 7427 return this; 7428 } 7429 return debugInfoEnabled; 7430 }; 7431 7432 this.$get = [ 7433 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 7434 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 7435 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 7436 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 7437 7438 var Attributes = function(element, attributesToCopy) { 7439 if (attributesToCopy) { 7440 var keys = Object.keys(attributesToCopy); 7441 var i, l, key; 7442 7443 for (i = 0, l = keys.length; i < l; i++) { 7444 key = keys[i]; 7445 this[key] = attributesToCopy[key]; 7446 } 7447 } else { 7448 this.$attr = {}; 7449 } 7450 7451 this.$$element = element; 7452 }; 7453 7454 Attributes.prototype = { 7455 /** 7456 * @ngdoc method 7457 * @name $compile.directive.Attributes#$normalize 7458 * @kind function 7459 * 7460 * @description 7461 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 7462 * `data-`) to its normalized, camelCase form. 7463 * 7464 * Also there is special case for Moz prefix starting with upper case letter. 7465 * 7466 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7467 * 7468 * @param {string} name Name to normalize 7469 */ 7470 $normalize: directiveNormalize, 7471 7472 7473 /** 7474 * @ngdoc method 7475 * @name $compile.directive.Attributes#$addClass 7476 * @kind function 7477 * 7478 * @description 7479 * Adds the CSS class value specified by the classVal parameter to the element. If animations 7480 * are enabled then an animation will be triggered for the class addition. 7481 * 7482 * @param {string} classVal The className value that will be added to the element 7483 */ 7484 $addClass: function(classVal) { 7485 if (classVal && classVal.length > 0) { 7486 $animate.addClass(this.$$element, classVal); 7487 } 7488 }, 7489 7490 /** 7491 * @ngdoc method 7492 * @name $compile.directive.Attributes#$removeClass 7493 * @kind function 7494 * 7495 * @description 7496 * Removes the CSS class value specified by the classVal parameter from the element. If 7497 * animations are enabled then an animation will be triggered for the class removal. 7498 * 7499 * @param {string} classVal The className value that will be removed from the element 7500 */ 7501 $removeClass: function(classVal) { 7502 if (classVal && classVal.length > 0) { 7503 $animate.removeClass(this.$$element, classVal); 7504 } 7505 }, 7506 7507 /** 7508 * @ngdoc method 7509 * @name $compile.directive.Attributes#$updateClass 7510 * @kind function 7511 *
7512 * @description 7513 * Adds and removes the appropriate CSS class values to the element based on the difference 7514 * between the new and old CSS class values (specified as newClasses and oldClasses). 7515 * 7516 * @param {string} newClasses The current CSS className value 7517 * @param {string} oldClasses The former CSS className value 7518 */ 7519 $updateClass: function(newClasses, oldClasses) { 7520 var toAdd = tokenDifference(newClasses, oldClasses); 7521 if (toAdd && toAdd.length) { 7522 $animate.addClass(this.$$element, toAdd); 7523 } 7524 7525 var toRemove = tokenDifference(oldClasses, newClasses); 7526 if (toRemove && toRemove.length) { 7527 $animate.removeClass(this.$$element, toRemove); 7528 } 7529 }, 7530 7531 /** 7532 * Set a normalized attribute on the element in a way such that all directives 7533 * can share the attribute. This function properly handles boolean attributes. 7534 * @param {string} key Normalized key. (ie ngAttribute) 7535 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 7536 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 7537 * Defaults to true. 7538 * @param {string=} attrName Optional none normalized name. Defaults to key. 7539 */ 7540 $set: function(key, value, writeAttr, attrName) { 7541 // TODO: decide whether or not to throw an error if "class" 7542 //is set through this function since it may cause $updateClass to 7543 //become unstable. 7544 7545 var node = this.$$element[0], 7546 booleanKey = getBooleanAttrName(node, key), 7547 aliasedKey = getAliasedAttrName(key), 7548 observer = key, 7549 nodeName; 7550 7551 if (booleanKey) { 7552 this.$$element.prop(key, value); 7553 attrName = booleanKey; 7554 } else if (aliasedKey) { 7555 this[aliasedKey] = value; 7556 observer = aliasedKey; 7557 } 7558 7559 this[key] = value; 7560 7561 // translate normalized key to actual key 7562 if (attrName) { 7563 this.$attr[key] = attrName; 7564 } else { 7565 attrName = this.$attr[key]; 7566 if (!attrName) { 7567 this.$attr[key] = attrName = snake_case(key, '-'); 7568 } 7569 } 7570 7571 nodeName = nodeName_(this.$$element); 7572 7573 if ((nodeName === 'a' && key === 'href') || 7574 (nodeName === 'img' && key === 'src')) { 7575 // sanitize a[href] and img[src] values 7576 this[key] = value = $$sanitizeUri(value, key === 'src'); 7577 } else if (nodeName === 'img' && key === 'srcset') { 7578 // sanitize img[srcset] values 7579 var result = ""; 7580 7581 // first check if there are spaces because it's not the same pattern 7582 var trimmedSrcset = trim(value); 7583 // ( 999x ,| 999w ,| ,|, ) 7584 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 7585 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 7586 7587 // split srcset into tuple of uri and descriptor except for the last item 7588 var rawUris = trimmedSrcset.split(pattern); 7589 7590 // for each tuples 7591 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 7592 for (var i = 0; i < nbrUrisWith2parts; i++) { 7593 var innerIdx = i * 2; 7594 // sanitize the uri 7595 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 7596 // add the descriptor 7597 result += (" " + trim(rawUris[innerIdx + 1])); 7598 } 7599 7600 // split the last item into uri and descriptor 7601 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 7602 7603 // sanitize the last uri 7604 result += $$sanitizeUri(trim(lastTuple[0]), true); 7605 7606 // and add the last descriptor if any 7607 if (lastTuple.length === 2) { 7608 result += (" " + trim(lastTuple[1])); 7609 } 7610 this[key] = value = result; 7611 } 7612 7613 if (writeAttr !== false) { 7614 if (value === null || isUndefined(value)) { 7615 this.$$element.removeAttr(attrName); 7616 } else { 7617 this.$$element.attr(attrName, value); 7618 } 7619 } 7620 7621 // fire observers 7622 var $$observers = this.$$observers;
vendor: 13,821 bytes, lines 7623-7963
7623 $$observers && forEach($$observers[observer], function(fn) { 7624 try { 7625 fn(value); 7626 } catch (e) { 7627 $exceptionHandler(e); 7628 } 7629 }); 7630 }, 7631 7632 7633 /** 7634 * @ngdoc method 7635 * @name $compile.directive.Attributes#$observe 7636 * @kind function 7637 * 7638 * @description 7639 * Observes an interpolated attribute. 7640 * 7641 * The observer function will be invoked once during the next `$digest` following 7642 * compilation. The observer is then invoked whenever the interpolated value 7643 * changes. 7644 * 7645 * @param {string} key Normalized key. (ie ngAttribute) . 7646 * @param {function(interpolatedValue)} fn Function that will be called whenever 7647 the interpolated value of the attribute changes. 7648 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info. 7649 * @returns {function()} Returns a deregistration function for this observer. 7650 */ 7651 $observe: function(key, fn) { 7652 var attrs = this, 7653 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 7654 listeners = ($$observers[key] || ($$observers[key] = [])); 7655 7656 listeners.push(fn); 7657 $rootScope.$evalAsync(function() { 7658 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 7659 // no one registered attribute interpolation function, so lets call it manually 7660 fn(attrs[key]); 7661 } 7662 }); 7663 7664 return function() { 7665 arrayRemove(listeners, fn); 7666 }; 7667 } 7668 }; 7669 7670 7671 function safeAddClass($element, className) { 7672 try { 7673 $element.addClass(className); 7674 } catch (e) { 7675 // ignore, since it means that we are trying to set class on 7676 // SVG element, where class name is read-only. 7677 } 7678 } 7679 7680 7681 var startSymbol = $interpolate.startSymbol(), 7682 endSymbol = $interpolate.endSymbol(), 7683 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 7684 ? identity 7685 : function denormalizeTemplate(template) { 7686 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 7687 }, 7688 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 7689 7690 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 7691 var bindings = $element.data('$binding') || []; 7692 7693 if (isArray(binding)) { 7694 bindings = bindings.concat(binding); 7695 } else { 7696 bindings.push(binding); 7697 } 7698 7699 $element.data('$binding', bindings); 7700 } : noop; 7701 7702 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 7703 safeAddClass($element, 'ng-binding'); 7704 } : noop; 7705 7706 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 7707 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 7708 $element.data(dataName, scope); 7709 } : noop; 7710 7711 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 7712 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 7713 } : noop; 7714 7715 return compile; 7716 7717 //================================ 7718 7719 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 7720 previousCompileContext) { 7721 if (!($compileNodes instanceof jqLite)) { 7722 // jquery always rewraps, whereas we need to preserve the original selector so that we can 7723 // modify it. 7724 $compileNodes = jqLite($compileNodes); 7725 } 7726 // We can not compile top level text elements since text nodes can be merged and we will 7727 // not be able to attach scope data to them, so we will wrap them in <span> 7728 forEach($compileNodes, function(node, index) { 7729 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { 7730 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; 7731 } 7732 }); 7733 var compositeLinkFn = 7734 compileNodes($compileNodes, transcludeFn, $compileNodes, 7735 maxPriority, ignoreDirective, previousCompileContext); 7736 compile.$$addScopeClass($compileNodes); 7737 var namespace = null; 7738 return function publicLinkFn(scope, cloneConnectFn, options) { 7739 assertArg(scope, 'scope'); 7740 7741 options = options || {}; 7742 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 7743 transcludeControllers = options.transcludeControllers, 7744 futureParentElement = options.futureParentElement; 7745 7746 // When `parentBoundTranscludeFn` is passed, it is a 7747 // `controllersBoundTransclude` function (it was previously passed 7748 // as `transclude` to directive.link) so we must unwrap it to get 7749 // its `boundTranscludeFn` 7750 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 7751 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 7752 } 7753 7754 if (!namespace) { 7755 namespace = detectNamespaceForChildElements(futureParentElement); 7756 } 7757 var $linkNode; 7758 if (namespace !== 'html') { 7759 // When using a directive with replace:true and templateUrl the $compileNodes 7760 // (or a child element inside of them) 7761 // might change, so we need to recreate the namespace adapted compileNodes 7762 // for call to the link function. 7763 // Note: This will already clone the nodes... 7764 $linkNode = jqLite( 7765 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 7766 ); 7767 } else if (cloneConnectFn) { 7768 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 7769 // and sometimes changes the structure of the DOM. 7770 $linkNode = JQLitePrototype.clone.call($compileNodes); 7771 } else { 7772 $linkNode = $compileNodes; 7773 } 7774 7775 if (transcludeControllers) { 7776 for (var controllerName in transcludeControllers) { 7777 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 7778 } 7779 } 7780 7781 compile.$$addScopeInfo($linkNode, scope); 7782 7783 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 7784 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 7785 return $linkNode; 7786 }; 7787 } 7788 7789 function detectNamespaceForChildElements(parentElement) { 7790 // TODO: Make this detect MathML as well... 7791 var node = parentElement && parentElement[0]; 7792 if (!node) { 7793 return 'html'; 7794 } else { 7795 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; 7796 } 7797 } 7798 7799 /** 7800 * Compile function matches each node in nodeList against the directives. Once all directives 7801 * for a particular node are collected their compile functions are executed. The compile 7802 * functions return values - the linking functions - are combined into a composite linking 7803 * function, which is the a linking function for the node. 7804 * 7805 * @param {NodeList} nodeList an array of nodes or NodeList to compile 7806 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7807 * scope argument is auto-generated to the new child of the transcluded parent scope. 7808 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 7809 * the rootElement must be set the jqLite collection of the compile root. This is 7810 * needed so that the jqLite collection items can be replaced with widgets. 7811 * @param {number=} maxPriority Max directive priority. 7812 * @returns {Function} A composite linking function of all of the matched directives or null. 7813 */ 7814 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 7815 previousCompileContext) { 7816 var linkFns = [], 7817 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 7818 7819 for (var i = 0; i < nodeList.length; i++) { 7820 attrs = new Attributes(); 7821 7822 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 7823 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 7824 ignoreDirective); 7825 7826 nodeLinkFn = (directives.length) 7827 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 7828 null, [], [], previousCompileContext) 7829 : null; 7830 7831 if (nodeLinkFn && nodeLinkFn.scope) { 7832 compile.$$addScopeClass(attrs.$$element); 7833 } 7834 7835 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 7836 !(childNodes = nodeList[i].childNodes) || 7837 !childNodes.length) 7838 ? null 7839 : compileNodes(childNodes, 7840 nodeLinkFn ? ( 7841 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 7842 && nodeLinkFn.transclude) : transcludeFn); 7843 7844 if (nodeLinkFn || childLinkFn) { 7845 linkFns.push(i, nodeLinkFn, childLinkFn); 7846 linkFnFound = true; 7847 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 7848 } 7849 7850 //use the previous context only for the first element in the virtual group 7851 previousCompileContext = null; 7852 } 7853 7854 // return a linking function if we have found anything, null otherwise 7855 return linkFnFound ? compositeLinkFn : null; 7856 7857 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 7858 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 7859 var stableNodeList; 7860 7861 7862 if (nodeLinkFnFound) { 7863 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 7864 // offsets don't get screwed up 7865 var nodeListLength = nodeList.length; 7866 stableNodeList = new Array(nodeListLength); 7867 7868 // create a sparse array by only copying the elements which have a linkFn 7869 for (i = 0; i < linkFns.length; i+=3) { 7870 idx = linkFns[i]; 7871 stableNodeList[idx] = nodeList[idx]; 7872 } 7873 } else { 7874 stableNodeList = nodeList; 7875 } 7876 7877 for (i = 0, ii = linkFns.length; i < ii;) { 7878 node = stableNodeList[linkFns[i++]]; 7879 nodeLinkFn = linkFns[i++]; 7880 childLinkFn = linkFns[i++]; 7881 7882 if (nodeLinkFn) { 7883 if (nodeLinkFn.scope) { 7884 childScope = scope.$new(); 7885 compile.$$addScopeInfo(jqLite(node), childScope); 7886 var destroyBindings = nodeLinkFn.$$destroyBindings; 7887 if (destroyBindings) { 7888 nodeLinkFn.$$destroyBindings = null; 7889 childScope.$on('$destroyed', destroyBindings); 7890 } 7891 } else { 7892 childScope = scope; 7893 } 7894 7895 if (nodeLinkFn.transcludeOnThisElement) { 7896 childBoundTranscludeFn = createBoundTranscludeFn( 7897 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 7898 7899 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 7900 childBoundTranscludeFn = parentBoundTranscludeFn; 7901 7902 } else if (!parentBoundTranscludeFn && transcludeFn) { 7903 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 7904 7905 } else { 7906 childBoundTranscludeFn = null; 7907 } 7908 7909 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn, 7910 nodeLinkFn); 7911 7912 } else if (childLinkFn) { 7913 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 7914 } 7915 } 7916 } 7917 } 7918 7919 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 7920 7921 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 7922 7923 if (!transcludedScope) { 7924 transcludedScope = scope.$new(false, containingScope); 7925 transcludedScope.$$transcluded = true; 7926 } 7927 7928 return transcludeFn(transcludedScope, cloneFn, { 7929 parentBoundTranscludeFn: previousBoundTranscludeFn, 7930 transcludeControllers: controllers, 7931 futureParentElement: futureParentElement 7932 }); 7933 }; 7934 7935 return boundTranscludeFn; 7936 } 7937 7938 /** 7939 * Looks for directives on the given node and adds them to the directive collection which is 7940 * sorted. 7941 * 7942 * @param node Node to search. 7943 * @param directives An array to which the directives are added to. This array is sorted before 7944 * the function returns. 7945 * @param attrs The shared attrs object which is used to populate the normalized attributes. 7946 * @param {number=} maxPriority Max directive priority. 7947 */ 7948 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 7949 var nodeType = node.nodeType, 7950 attrsMap = attrs.$attr, 7951 match, 7952 className; 7953 7954 switch (nodeType) { 7955 case NODE_TYPE_ELEMENT: /* Element */ 7956 // use the node name: <directive> 7957 addDirective(directives, 7958 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 7959 7960 // iterate over the attributes 7961 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 7962 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 7963 var attrStartName = false;
7964 var attrEndName = false; 7965 7966 attr = nAttrs[j]; 7967 name = attr.name; 7968 value = trim(attr.value); 7969 7970 // support ngAttr attribute binding 7971 ngAttrName = directiveNormalize(name); 7972 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 7973 name = name.replace(PREFIX_REGEXP, '') 7974 .substr(8).replace(/_(.)/g, function(match, letter) { 7975 return letter.toUpperCase(); 7976 }); 7977 } 7978 7979 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 7980 if (directiveIsMultiElement(directiveNName)) { 7981 if (ngAttrName === directiveNName + 'Start') { 7982 attrStartName = name; 7983 attrEndName = name.substr(0, name.length - 5) + 'end'; 7984 name = name.substr(0, name.length - 6); 7985 } 7986 } 7987 7988 nName = directiveNormalize(name.toLowerCase()); 7989 attrsMap[nName] = name; 7990 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 7991 attrs[nName] = value; 7992 if (getBooleanAttrName(node, nName)) { 7993 attrs[nName] = true; // presence means true 7994 } 7995 } 7996 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 7997 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 7998 attrEndName); 7999 } 8000 8001 // use class as directive 8002 className = node.className; 8003 if (isObject(className)) { 8004 // Maybe SVGAnimatedString 8005 className = className.animVal; 8006 } 8007 if (isString(className) && className !== '') { 8008 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 8009 nName = directiveNormalize(match[2]); 8010 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 8011 attrs[nName] = trim(match[3]); 8012 } 8013 className = className.substr(match.index + match[0].length); 8014 } 8015 } 8016 break; 8017 case NODE_TYPE_TEXT: /* Text Node */ 8018 if (msie === 11) { 8019 // Workaround for #11781 8020 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 8021 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 8022 node.parentNode.removeChild(node.nextSibling); 8023 } 8024 } 8025 addTextInterpolateDirective(directives, node.nodeValue); 8026 break; 8027 case NODE_TYPE_COMMENT: /* Comment */ 8028 try { 8029 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 8030 if (match) { 8031 nName = directiveNormalize(match[1]); 8032 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 8033 attrs[nName] = trim(match[2]); 8034 } 8035 } 8036 } catch (e) { 8037 // turns out that under some circumstances IE9 throws errors when one attempts to read 8038 // comment's node value. 8039 // Just ignore it and continue. (Can't seem to reproduce in test case.) 8040 } 8041 break; 8042 } 8043 8044 directives.sort(byPriority); 8045 return directives; 8046 } 8047 8048 /** 8049 * Given a node with an directive-start it collects all of the siblings until it finds 8050 * directive-end. 8051 * @param node 8052 * @param attrStart 8053 * @param attrEnd 8054 * @returns {*} 8055 */ 8056 function groupScan(node, attrStart, attrEnd) { 8057 var nodes = []; 8058 var depth = 0; 8059 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 8060 do { 8061 if (!node) { 8062 throw $compileMinErr('uterdir', 8063 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 8064 attrStart, attrEnd); 8065 } 8066 if (node.nodeType == NODE_TYPE_ELEMENT) { 8067 if (node.hasAttribute(attrStart)) depth++; 8068 if (node.hasAttribute(attrEnd)) depth--; 8069 } 8070 nodes.push(node); 8071 node = node.nextSibling; 8072 } while (depth > 0); 8073 } else { 8074 nodes.push(node); 8075 } 8076 8077 return jqLite(nodes); 8078 } 8079 8080 /** 8081 * Wrapper for linking function which converts normal linking function into a grouped 8082 * linking function. 8083 * @param linkFn 8084 * @param attrStart 8085 * @param attrEnd 8086 * @returns {Function} 8087 */ 8088 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 8089 return function(scope, element, attrs, controllers, transcludeFn) { 8090 element = groupScan(element[0], attrStart, attrEnd); 8091 return linkFn(scope, element, attrs, controllers, transcludeFn); 8092 }; 8093 } 8094 8095 /** 8096 * Once the directives have been collected, their compile functions are executed. This method 8097 * is responsible for inlining directive templates as well as terminating the application 8098 * of the directives if the terminal directive has been reached. 8099 * 8100 * @param {Array} directives Array of collected directives to execute their compile function. 8101 * this needs to be pre-sorted by priority order. 8102 * @param {Node} compileNode The raw DOM node to apply the compile functions to 8103 * @param {Object} templateAttrs The shared attribute function 8104 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 8105 * scope argument is auto-generated to the new 8106 * child of the transcluded parent scope. 8107 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 8108 * argument has the root jqLite array so that we can replace nodes 8109 * on it. 8110 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 8111 * compiling the transclusion. 8112 * @param {Array.<Function>} preLinkFns 8113 * @param {Array.<Function>} postLinkFns 8114 * @param {Object} previousCompileContext Context used for previous compilation of the current 8115 * node 8116 * @returns {Function} linkFn 8117 */ 8118 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 8119 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 8120 previousCompileContext) { 8121 previousCompileContext = previousCompileContext || {}; 8122 8123 var terminalPriority = -Number.MAX_VALUE, 8124 newScopeDirective = previousCompileContext.newScopeDirective, 8125 controllerDirectives = previousCompileContext.controllerDirectives, 8126 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 8127 templateDirective = previousCompileContext.templateDirective, 8128 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 8129 hasTranscludeDirective = false, 8130 hasTemplate = false, 8131 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 8132 $compileNode = templateAttrs.$$element = jqLite(compileNode), 8133 directive, 8134 directiveName, 8135 $template, 8136 replaceDirective = originalReplaceDirective, 8137 childTranscludeFn = transcludeFn, 8138 linkFn, 8139 directiveValue; 8140 8141 // executes all directives on the current element 8142 for (var i = 0, ii = directives.length; i < ii; i++) { 8143 directive = directives[i]; 8144 var attrStart = directive.$$start; 8145 var attrEnd = directive.$$end; 8146 8147 // collect multiblock sections 8148 if (attrStart) { 8149 $compileNode = groupScan(compileNode, attrStart, attrEnd); 8150 } 8151 $template = undefined; 8152 8153 if (terminalPriority > directive.priority) { 8154 break; // prevent further processing of directives 8155 } 8156 8157 if (directiveValue = directive.scope) { 8158 8159 // skip the check for directives with async templates, we'll check the derived sync 8160 // directive when the template arrives 8161 if (!directive.templateUrl) { 8162 if (isObject(directiveValue)) { 8163 // This directive is trying to add an isolated scope. 8164 // Check that there is no scope of any kind already 8165 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 8166 directive, $compileNode); 8167 newIsolateScopeDirective = directive; 8168 } else { 8169 // This directive is trying to add a child scope. 8170 // Check that there is no isolated scope already 8171 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 8172 $compileNode); 8173 } 8174 } 8175 8176 newScopeDirective = newScopeDirective || directive; 8177 } 8178 8179 directiveName = directive.name; 8180 8181 if (!directive.templateUrl && directive.controller) { 8182 directiveValue = directive.controller; 8183 controllerDirectives = controllerDirectives || createMap(); 8184 assertNoDuplicate("'" + directiveName + "' controller", 8185 controllerDirectives[directiveName], directive, $compileNode); 8186 controllerDirectives[directiveName] = directive; 8187 } 8188 8189 if (directiveValue = directive.transclude) { 8190 hasTranscludeDirective = true; 8191 8192 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 8193 // This option should only be used by directives that know how to safely handle element transclusion, 8194 // where the transcluded nodes are added or replaced after linking. 8195 if (!directive.$$tlb) { 8196 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 8197 nonTlbTranscludeDirective = directive; 8198 } 8199 8200 if (directiveValue == 'element') { 8201 hasElementTranscludeDirective = true; 8202 terminalPriority = directive.priority; 8203 $template = $compileNode; 8204 $compileNode = templateAttrs.$$element = 8205 jqLite(document.createComment(' ' + directiveName + ': ' + 8206 templateAttrs[directiveName] + ' ')); 8207 compileNode = $compileNode[0]; 8208 replaceWith(jqCollection, sliceArgs($template), compileNode); 8209 8210 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 8211 replaceDirective && replaceDirective.name, { 8212 // Don't pass in: 8213 // - controllerDirectives - otherwise we'll create duplicates controllers 8214 // - newIsolateScopeDirective or templateDirective - comb
vendor: 4,761 bytes, lines 8214-8322
8214ining templates with 8215 // element transclusion doesn't make sense. 8216 // 8217 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 8218 // on the same element more than once. 8219 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8220 }); 8221 } else { 8222 $template = jqLite(jqLiteClone(compileNode)).contents(); 8223 $compileNode.empty(); // clear contents 8224 childTranscludeFn = compile($template, transcludeFn); 8225 } 8226 } 8227 8228 if (directive.template) { 8229 hasTemplate = true; 8230 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8231 templateDirective = directive; 8232 8233 directiveValue = (isFunction(directive.template)) 8234 ? directive.template($compileNode, templateAttrs) 8235 : directive.template; 8236 8237 directiveValue = denormalizeTemplate(directiveValue); 8238 8239 if (directive.replace) { 8240 replaceDirective = directive; 8241 if (jqLiteIsTextNode(directiveValue)) { 8242 $template = []; 8243 } else { 8244 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 8245 } 8246 compileNode = $template[0]; 8247 8248 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8249 throw $compileMinErr('tplrt', 8250 "Template for directive '{0}' must have exactly one root element. {1}", 8251 directiveName, ''); 8252 } 8253 8254 replaceWith(jqCollection, $compileNode, compileNode); 8255 8256 var newTemplateAttrs = {$attr: {}}; 8257 8258 // combine directives from the original node and from the template: 8259 // - take the array of directives for this element 8260 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 8261 // - collect directives from the template and sort them by priority 8262 // - combine directives as: processed + template + unprocessed 8263 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 8264 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 8265 8266 if (newIsolateScopeDirective) { 8267 markDirectivesAsIsolate(templateDirectives); 8268 } 8269 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 8270 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 8271 8272 ii = directives.length; 8273 } else { 8274 $compileNode.html(directiveValue); 8275 } 8276 } 8277 8278 if (directive.templateUrl) { 8279 hasTemplate = true; 8280 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8281 templateDirective = directive; 8282 8283 if (directive.replace) { 8284 replaceDirective = directive; 8285 } 8286 8287 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 8288 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 8289 controllerDirectives: controllerDirectives, 8290 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 8291 newIsolateScopeDirective: newIsolateScopeDirective, 8292 templateDirective: templateDirective, 8293 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8294 }); 8295 ii = directives.length; 8296 } else if (directive.compile) { 8297 try { 8298 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 8299 if (isFunction(linkFn)) { 8300 addLinkFns(null, linkFn, attrStart, attrEnd); 8301 } else if (linkFn) { 8302 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 8303 } 8304 } catch (e) { 8305 $exceptionHandler(e, startingTag($compileNode)); 8306 } 8307 } 8308 8309 if (directive.terminal) { 8310 nodeLinkFn.terminal = true; 8311 terminalPriority = Math.max(terminalPriority, directive.priority); 8312 } 8313 8314 } 8315 8316 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 8317 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 8318 nodeLinkFn.templateOnThisElement = hasTemplate; 8319 nodeLinkFn.transclude = childTranscludeFn; 8320 8321 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 8322
8323 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 8324 return nodeLinkFn; 8325 8326 //////////////////// 8327 8328 function addLinkFns(pre, post, attrStart, attrEnd) { 8329 if (pre) { 8330 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 8331 pre.require = directive.require; 8332 pre.directiveName = directiveName; 8333 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8334 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 8335 } 8336 preLinkFns.push(pre); 8337 } 8338 if (post) { 8339 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 8340 post.require = directive.require; 8341 post.directiveName = directiveName; 8342 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8343 post = cloneAndAnnotateFn(post, {isolateScope: true}); 8344 } 8345 postLinkFns.push(post); 8346 } 8347 } 8348 8349 8350 function getControllers(directiveName, require, $element, elementControllers) { 8351 var value; 8352 8353 if (isString(require)) { 8354 var match = require.match(REQUIRE_PREFIX_REGEXP); 8355 var name = require.substring(match[0].length); 8356 var inheritType = match[1] || match[3]; 8357 var optional = match[2] === '?'; 8358 8359 //If only parents then start at the parent element 8360 if (inheritType === '^^') { 8361 $element = $element.parent(); 8362 //Otherwise attempt getting the controller from elementControllers in case 8363 //the element is transcluded (and has no data) and to avoid .data if possible 8364 } else { 8365 value = elementControllers && elementControllers[name]; 8366 value = value && value.instance; 8367 } 8368 8369 if (!value) { 8370 var dataName = '$' + name + 'Controller'; 8371 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 8372 } 8373 8374 if (!value && !optional) { 8375 throw $compileMinErr('ctreq', 8376 "Controller '{0}', required by directive '{1}', can't be found!", 8377 name, directiveName); 8378 } 8379 } else if (isArray(require)) { 8380 value = []; 8381 for (var i = 0, ii = require.length; i < ii; i++) { 8382 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 8383 } 8384 } 8385 8386 return value || null; 8387 } 8388 8389 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) { 8390 var elementControllers = createMap(); 8391 for (var controllerKey in controllerDirectives) { 8392 var directive = controllerDirectives[controllerKey]; 8393 var locals = { 8394 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 8395 $element: $element, 8396 $attrs: attrs, 8397 $transclude: transcludeFn 8398 }; 8399 8400 var controller = directive.controller; 8401 if (controller == '@') { 8402 controller = attrs[directive.name]; 8403 } 8404 8405 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 8406 8407 // For directives with element transclusion the element is a comment, 8408 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 8409 // clean up (http://bugs.jquery.com/ticket/8335). 8410 // Instead, we save the controllers for the element in a local hash and attach to .data 8411 // later, once we have the actual element. 8412 elementControllers[directive.name] = controllerInstance; 8413 if (!hasElementTranscludeDirective) { 8414 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 8415 } 8416 } 8417 return elementControllers; 8418 } 8419 8420 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn, 8421 thisLinkFn) { 8422 var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element, 8423 attrs; 8424 8425 if (compileNode === linkNode) { 8426 attrs = templateAttrs; 8427 $element = templateAttrs.$$element; 8428 } else { 8429 $element = jqLite(linkNode); 8430 attrs = new Attributes($element, templateAttrs); 8431 } 8432 8433 if (newIsolateScopeDirective) { 8434 isolateScope = scope.$new(true); 8435 } 8436 8437 if (boundTranscludeFn) { 8438 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 8439 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 8440 transcludeFn = controllersBoundTransclude; 8441 transcludeFn.$$boundTransclude = boundTranscludeFn; 8442 } 8443 8444 if (controllerDirectives) { 8445 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope); 8446 } 8447 8448 if (newIsolateScopeDirective) { 8449 // Initialize isolate scope bindings for new isolate scope directive. 8450 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 8451 templateDirective === newIsolateScopeDirective.$$originalDirective))); 8452 compile.$$addScopeClass($element, true); 8453 isolateScope.$$isolateBindings = 8454 newIsolateScopeDirective.$$isolateBindings; 8455 initializeDirectiveBindings(scope, attrs, isolateScope, 8456 isolateScope.$$isolateBindings, 8457 newIsolateScopeDirective, isolateScope); 8458 } 8459 if (elementControllers) { 8460 // Initialize bindToController bindings for new/isolate scopes 8461 var scopeDirective = newIsolateScopeDirective || newScopeDirective; 8462 var bindings; 8463 var controllerForBindings; 8464 if (scopeDirective && elementControllers[scopeDirective.name]) { 8465 bindings = scopeDirective.$$bindings.bindToController; 8466 controller = elementControllers[scopeDirective.name]; 8467 8468 if (controller && controller.identifier && bindings) { 8469 controllerForBindings = controller; 8470 thisLinkFn.$$destroyBindings = 8471 initializeDirectiveBindings(scope, attrs, controller.instance, 8472 bindings, scopeDirective); 8473 } 8474 } 8475 for (i in elementControllers) { 8476 controller = elementControllers[i]; 8477 var controllerResult = controller(); 8478 8479 if (controllerResult !== controller.instance) { 8480 // If the controller constructor has a return value, overwrite the instance 8481 // from setupControllers and update the element data 8482 controller.instance = controllerResult; 8483 $element.data('$' + i + 'Controller', controllerResult); 8484 if (controller === controllerForBindings) { 8485 // Remove and re-install bindToController bindings 8486 thisLinkFn.$$destroyBindings(); 8487 thisLinkFn.$$destroyBindings = 8488 initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective); 8489 } 8490 } 8491 } 8492 } 8493 8494 // PRELINKING 8495 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 8496 linkFn = preLinkFns[i]; 8497 invokeLinkFn(linkFn, 8498 linkFn.isolateScope ? isolateScope : scope, 8499 $element, 8500 attrs, 8501 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8502 transcludeFn 8503 ); 8504 } 8505 8506 // RECURSION 8507 // We only pass the isolate scope, if the isolate directive has a template, 8508 // otherwise the child elements do not belong to the isolate directive. 8509 var scopeToChild = scope; 8510 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 8511 scopeToChild = isolateScope; 8512 } 8513 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 8514 8515 // POSTLINKING 8516 for (i = postLinkFns.length - 1; i >= 0; i--) { 8517 linkFn = postLinkFns[i]; 8518 invokeLinkFn(linkFn, 8519 linkFn.isolateScope ? isolateScope : scope, 8520 $element, 8521 attrs, 8522 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8523 transcludeFn 8524 ); 8525 } 8526 8527 // This is the function that is injected as `$transclude`. 8528 // Note: all arguments are optional! 8529 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { 8530 var transcludeControllers; 8531 8532 // No scope passed in: 8533 if (!isScope(scope)) { 8534 futureParentElement = cloneAttachFn; 8535 cloneAttachFn = scope; 8536 scope = undefined; 8537 } 8538 8539 if (hasElementTranscludeDirective) { 8540 transcludeControllers = elementControllers; 8541 } 8542 if (!futureParentElement) { 8543 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 8544 } 8545 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 8546 } 8547 } 8548 } 8549 8550 function markDirectivesAsIsolate(directives) { 8551 // mark all directives as needing isolate scope. 8552 for (var j = 0, jj = directives.length; j < jj; j++) { 8553 directives[j] = inherit(directives[j], {$$isolateScope: true}); 8554 } 8555 } 8556 8557 /** 8558 * looks up the directive and decorates it with exception handling and proper parameters. We 8559 * call this the boundDirective. 8560 * 8561 * @param {string} name name of the directive to look up. 8562 * @param {string} location The directive must be found in specific format. 8563 * String containing any of theses characters: 8564 * 8565 * * `E`: element name 8566 * * `A': attribute 8567 * * `C`: class 8568 * * `M`: comment 8569 * @returns {boolean} true if directive was added. 8570 */ 8571 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 8572 endAttrName) { 8573 if (name === ignoreDirective) return null; 8574 var match = null; 8575 if (hasDirectives.hasOwnProperty(name)) { 8576 for (var directive, directives = $injector.get(name + Suffix), 8577 i = 0, ii = directives.length; i < ii; i++) { 8578 try { 8579 directive = directives[i]; 8580 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 8581 directive.restrict.indexOf(location) != -1) { 8582 if (startAttrName) { 8583 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 8584 } 8585 tDirectives.push(directive); 8586 match = directive; 8587 } 8588 } catch (e) { $exceptionHandler(e); } 8589 } 8590 } 8591 return match; 8592 } 8593 8594 8595 /** 8596 * looks up the directive and returns true if it is a multi-element directive, 8597 * and therefore requires DOM nodes between -start and -end markers to be grouped 8598 * together. 8599 * 8600 * @param {string} name name of the directive to look up. 8601 * @returns true if directive was registered as multi-element. 8602 */ 8603 function directiveIsMultiElement(name) { 8604 if (hasDirectives.hasOwnProperty(name)) { 8605 for (var directive, directives = $injector.get(name + Suffix), 8606 i = 0, ii = directives.length; i < ii; i++) { 8607 directive = directives[i]; 8608 if (directive.multiElement) { 8609 return true; 8610 } 8611 } 8612 } 8613 return false;
8614 } 8615 8616 /** 8617 * When the element is replaced with HTML template then the new attributes 8618 * on the template need to be merged with the existing attributes in the DOM. 8619 * The desired effect is to have both of the attributes present. 8620 * 8621 * @param {object} dst destination attributes (original DOM) 8622 * @param {object} src source attributes (from the directive template) 8623 */ 8624 function mergeTemplateAttributes(dst, src) { 8625 var srcAttr = src.$attr, 8626 dstAttr = dst.$attr, 8627 $element = dst.$$element; 8628 8629 // reapply the old attributes to the new element 8630 forEach(dst, function(value, key) { 8631 if (key.charAt(0) != '$') { 8632 if (src[key] && src[key] !== value) { 8633 value += (key === 'style' ? ';' : ' ') + src[key]; 8634 } 8635 dst.$set(key, value, true, srcAttr[key]); 8636 } 8637 }); 8638 8639 // copy the new attributes on the old attrs object 8640 forEach(src, function(value, key) { 8641 if (key == 'class') { 8642 safeAddClass($element, value); 8643 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 8644 } else if (key == 'style') { 8645 $element.attr('style', $element.attr('style') + ';' + value); 8646 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 8647 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 8648 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 8649 // have an attribute like "has-own-property" or "data-has-own-property", etc. 8650 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 8651 dst[key] = value; 8652 dstAttr[key] = srcAttr[key]; 8653 } 8654 }); 8655 } 8656 8657 8658 function compileTemplateUrl(directives, $compileNode, tAttrs, 8659 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 8660 var linkQueue = [], 8661 afterTemplateNodeLinkFn, 8662 afterTemplateChildLinkFn, 8663 beforeTemplateCompileNode = $compileNode[0], 8664 origAsyncDirective = directives.shift(), 8665 derivedSyncDirective = inherit(origAsyncDirective, { 8666 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 8667 }), 8668 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 8669 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 8670 : origAsyncDirective.templateUrl, 8671 templateNamespace = origAsyncDirective.templateNamespace; 8672 8673 $compileNode.empty(); 8674 8675 $templateRequest(templateUrl) 8676 .then(function(content) { 8677 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 8678 8679 content = denormalizeTemplate(content); 8680 8681 if (origAsyncDirective.replace) { 8682 if (jqLiteIsTextNode(content)) { 8683 $template = []; 8684 } else { 8685 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 8686 } 8687 compileNode = $template[0]; 8688 8689 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8690 throw $compileMinErr('tplrt', 8691 "Template for directive '{0}' must have exactly one root element. {1}", 8692 origAsyncDirective.name, templateUrl); 8693 } 8694 8695 tempTemplateAttrs = {$attr: {}}; 8696 replaceWith($rootElement, $compileNode, compileNode); 8697 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 8698 8699 if (isObject(origAsyncDirective.scope)) { 8700 markDirectivesAsIsolate(templateDirectives); 8701 } 8702 directives = templateDirectives.concat(directives); 8703 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 8704 } else { 8705 compileNode = beforeTemplateCompileNode; 8706 $compileNode.html(content); 8707 } 8708 8709 directives.unshift(derivedSyncDirective); 8710 8711 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 8712 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 8713 previousCompileContext);
vendor: 6,217 bytes, lines 8714-8876
8714 forEach($rootElement, function(node, i) { 8715 if (node == compileNode) { 8716 $rootElement[i] = $compileNode[0]; 8717 } 8718 }); 8719 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 8720 8721 while (linkQueue.length) { 8722 var scope = linkQueue.shift(), 8723 beforeTemplateLinkNode = linkQueue.shift(), 8724 linkRootElement = linkQueue.shift(), 8725 boundTranscludeFn = linkQueue.shift(), 8726 linkNode = $compileNode[0]; 8727 8728 if (scope.$$destroyed) continue; 8729 8730 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 8731 var oldClasses = beforeTemplateLinkNode.className; 8732 8733 if (!(previousCompileContext.hasElementTranscludeDirective && 8734 origAsyncDirective.replace)) { 8735 // it was cloned therefore we have to clone as well. 8736 linkNode = jqLiteClone(compileNode); 8737 } 8738 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 8739 8740 // Copy in CSS classes from original node 8741 safeAddClass(jqLite(linkNode), oldClasses); 8742 } 8743 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8744 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8745 } else { 8746 childBoundTranscludeFn = boundTranscludeFn; 8747 } 8748 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 8749 childBoundTranscludeFn, afterTemplateNodeLinkFn); 8750 } 8751 linkQueue = null; 8752 }); 8753 8754 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 8755 var childBoundTranscludeFn = boundTranscludeFn; 8756 if (scope.$$destroyed) return; 8757 if (linkQueue) { 8758 linkQueue.push(scope, 8759 node, 8760 rootElement, 8761 childBoundTranscludeFn); 8762 } else { 8763 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8764 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8765 } 8766 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn, 8767 afterTemplateNodeLinkFn); 8768 } 8769 }; 8770 } 8771 8772 8773 /** 8774 * Sorting function for bound directives. 8775 */ 8776 function byPriority(a, b) { 8777 var diff = b.priority - a.priority; 8778 if (diff !== 0) return diff; 8779 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 8780 return a.index - b.index; 8781 } 8782 8783 function assertNoDuplicate(what, previousDirective, directive, element) { 8784 8785 function wrapModuleNameIfDefined(moduleName) { 8786 return moduleName ? 8787 (' (module: ' + moduleName + ')') : 8788 ''; 8789 } 8790 8791 if (previousDirective) { 8792 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 8793 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 8794 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 8795 } 8796 } 8797 8798 8799 function addTextInterpolateDirective(directives, text) { 8800 var interpolateFn = $interpolate(text, true); 8801 if (interpolateFn) { 8802 directives.push({ 8803 priority: 0, 8804 compile: function textInterpolateCompileFn(templateNode) { 8805 var templateNodeParent = templateNode.parent(), 8806 hasCompileParent = !!templateNodeParent.length; 8807 8808 // When transcluding a template that has bindings in the root 8809 // we don't have a parent and thus need to add the class during linking fn. 8810 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 8811 8812 return function textInterpolateLinkFn(scope, node) { 8813 var parent = node.parent(); 8814 if (!hasCompileParent) compile.$$addBindingClass(parent); 8815 compile.$$addBindingInfo(parent, interpolateFn.expressions); 8816 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 8817 node[0].nodeValue = value; 8818 }); 8819 }; 8820 } 8821 }); 8822 } 8823 } 8824 8825 8826 function wrapTemplate(type, template) { 8827 type = lowercase(type || 'html'); 8828 switch (type) { 8829 case 'svg': 8830 case 'math': 8831 var wrapper = document.createElement('div'); 8832 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 8833 return wrapper.childNodes[0].childNodes; 8834 default: 8835 return template; 8836 } 8837 } 8838 8839 8840 function getTrustedContext(node, attrNormalizedName) { 8841 if (attrNormalizedName == "srcdoc") { 8842 return $sce.HTML; 8843 } 8844 var tag = nodeName_(node); 8845 // maction[xlink:href] can source SVG. It's not limited to <maction>. 8846 if (attrNormalizedName == "xlinkHref" || 8847 (tag == "form" && attrNormalizedName == "action") || 8848 (tag != "img" && (attrNormalizedName == "src" || 8849 attrNormalizedName == "ngSrc"))) { 8850 return $sce.RESOURCE_URL; 8851 } 8852 } 8853 8854 8855 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 8856 var trustedContext = getTrustedContext(node, name); 8857 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 8858 8859 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 8860 8861 // no interpolation found -> ignore 8862 if (!interpolateFn) return; 8863 8864 8865 if (name === "multiple" && nodeName_(node) === "select") { 8866 throw $compileMinErr("selmulti", 8867 "Binding to the 'multiple' attribute is not supported. Element: {0}", 8868 startingTag(node)); 8869 } 8870 8871 directives.push({ 8872 priority: 100, 8873 compile: function() { 8874 return { 8875 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 8876 var $$observers = (attr.$$observers || (attr.$$observers = createMap())
8876); 8877 8878 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 8879 throw $compileMinErr('nodomevents', 8880 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 8881 "ng- versions (such as ng-click instead of onclick) instead."); 8882 } 8883 8884 // If the attribute has changed since last $interpolate()ed 8885 var newValue = attr[name]; 8886 if (newValue !== value) { 8887 // we need to interpolate again since the attribute value has been updated 8888 // (e.g. by another directive's compile function) 8889 // ensure unset/empty values make interpolateFn falsy 8890 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 8891 value = newValue; 8892 } 8893 8894 // if attribute was updated so that there is no interpolation going on we don't want to 8895 // register any observers 8896 if (!interpolateFn) return; 8897 8898 // initialize attr object so that it's ready in case we need the value for isolate 8899 // scope initialization, otherwise the value would not be available from isolate 8900 // directive's linking fn during linking phase 8901 attr[name] = interpolateFn(scope); 8902 8903 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 8904 (attr.$$observers && attr.$$observers[name].$$scope || scope). 8905 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 8906 //special case for class attribute addition + removal 8907 //so that class changes can tap into the animation 8908 //hooks provided by the $animate service. Be sure to 8909 //skip animations when the first digest occurs (when 8910 //both the new and the old values are the same) since 8911 //the CSS classes are the non-interpolated values 8912 if (name === 'class' && newValue != oldValue) { 8913 attr.$updateClass(newValue, oldValue); 8914 } else { 8915 attr.$set(name, newValue); 8916 } 8917 }); 8918 } 8919 }; 8920 } 8921 }); 8922 } 8923 8924 8925 /** 8926 * This is a special jqLite.replaceWith, which can replace items which 8927 * have no parents, provided that the containing jqLite collection is provided. 8928 * 8929 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 8930 * in the root of the tree. 8931 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 8932 * the shell, but replace its DOM node reference. 8933 * @param {Node} newNode The new DOM node. 8934 */ 8935 function replaceWith($rootElement, elementsToRemove, newNode) { 8936 var firstElementToRemove = elementsToRemove[0], 8937 removeCount = elementsToRemove.length, 8938 parent = firstElementToRemove.parentNode, 8939 i, ii; 8940 8941 if ($rootElement) { 8942 for (i = 0, ii = $rootElement.length; i < ii; i++) { 8943 if ($rootElement[i] == firstElementToRemove) { 8944 $rootElement[i++] = newNode; 8945 for (var j = i, j2 = j + removeCount - 1, 8946 jj = $rootElement.length; 8947 j < jj; j++, j2++) { 8948 if (j2 < jj) { 8949 $rootElement[j] = $rootElement[j2]; 8950 } else { 8951 delete $rootElement[j]; 8952 } 8953 } 8954 $rootElement.length -= removeCount - 1; 8955 8956 // If the replaced element is also the jQuery .context then replace it 8957 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 8958 // http://api.jquery.com/context/ 8959 if ($rootElement.context === firstElementToRemove) { 8960 $rootElement.context = newNode; 8961 } 8962 break; 8963 } 8964 } 8965 } 8966 8967 if (parent) { 8968 parent.replaceChild(newNode, firstElementToRemove); 8969 } 8970 8971 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? 8972 var fragment = document.createDocumentFragment(); 8973 fragment.appendChild(firstElementToRemove); 8974 8975 if (jqLite.hasData(firstElementToRemove)) { 8976 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 8977 // data here because there's no public interface in jQuery to do that and copying over 8978 // event listeners (which is the main use of private data) wouldn't work anyway. 8979 jqLite(newNode).data(jqLite(firstElementToRemove).data()); 8980 8981 // Remove data of the replaced element. We cannot just call .remove() 8982 // on the element it since that would deallocate scope that is needed 8983 // for the new node. Instead, remove the data "manually". 8984 if (!jQuery) { 8985 delete jqLite.cache[firstElementToRemove[jqLite.expando]]; 8986 } else { 8987 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after 8988 // the replaced element. The cleanData version monkey-patched by Angular would cause 8989 // the scope to be trashed and we do need the very same scope to work with the new 8990 // element. However, we cannot just cache the non-patched version and use it here as 8991 // that would break if another library patches the method after Angular does (one
8992 // example is jQuery UI). Instead, set a flag indicating scope destroying should be 8993 // skipped this one time. 8994 skipDestroyOnNextJQueryCleanData = true; 8995 jQuery.cleanData([firstElementToRemove]); 8996 } 8997 } 8998 8999 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 9000 var element = elementsToRemove[k]; 9001 jqLite(element).remove(); // must do this way to clean up expando 9002 fragment.appendChild(element); 9003 delete elementsToRemove[k]; 9004 } 9005 9006 elementsToRemove[0] = newNode; 9007 elementsToRemove.length = 1; 9008 } 9009 9010 9011 function cloneAndAnnotateFn(fn, annotation) { 9012 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 9013 } 9014 9015 9016 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 9017 try { 9018 linkFn(scope, $element, attrs, controllers, transcludeFn); 9019 } catch (e) { 9020 $exceptionHandler(e, startingTag($element)); 9021 } 9022 } 9023 9024 9025 // Set up $watches for isolate scope and controller bindings. This process 9026 // only occurs for isolate scopes and new scopes with controllerAs. 9027 function initializeDirectiveBindings(scope, attrs, destination, bindings, 9028 directive, newScope) { 9029 var onNewScopeDestroyed; 9030 forEach(bindings, function(definition, scopeName) { 9031 var attrName = definition.attrName, 9032 optional = definition.optional, 9033 mode = definition.mode, // @, =, or & 9034 lastValue, 9035 parentGet, parentSet, compare; 9036 9037 switch (mode) { 9038 9039 case '@': 9040 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 9041 destination[scopeName] = attrs[attrName] = void 0; 9042 } 9043 attrs.$observe(attrName, function(value) { 9044 if (isString(value)) { 9045 destination[scopeName] = value; 9046 } 9047 }); 9048 attrs.$$observers[attrName].$$scope = scope; 9049 if (isString(attrs[attrName])) { 9050 // If the attribute has been provided then we trigger an interpolation to ensure 9051 // the value is there for use in the link fn 9052 destination[scopeName] = $interpolate(attrs[attrName])(scope); 9053 } 9054 break; 9055 9056 case '=': 9057 if (!hasOwnProperty.call(attrs, attrName)) { 9058 if (optional) break; 9059 attrs[attrName] = void 0; 9060 } 9061 if (optional && !attrs[attrName]) break; 9062 9063 parentGet = $parse(attrs[attrName]); 9064 if (parentGet.literal) { 9065 compare = equals; 9066 } else { 9067 compare = function(a, b) { return a === b || (a !== a && b !== b); }; 9068 } 9069 parentSet = parentGet.assign || function() { 9070 // reset the change, or we will throw this exception on every $digest 9071 lastValue = destination[scopeName] = parentGet(scope); 9072 throw $compileMinErr('nonassign', 9073 "Expression '{0}' used with directive '{1}' is non-assignable!", 9074 attrs[attrName], directive.name); 9075 }; 9076 lastValue = destination[scopeName] = parentGet(scope); 9077 var parentValueWatch = function parentValueWatch(parentValue) { 9078 if (!compare(parentValue, destination[scopeName])) { 9079 // we are out of sync and need to copy 9080 if (!compare(parentValue, lastValue)) { 9081 // parent changed and it has precedence 9082 destination[scopeName] = parentValue; 9083 } else { 9084 // if the parent can be assigned then do so 9085 parentSet(scope, parentValue = destination[scopeName]); 9086 } 9087 } 9088 return lastValue = parentValue; 9089 }; 9090 parentValueWatch.$stateful = true; 9091 var unwatch; 9092 if (definition.collection) { 9093 unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 9094 } else { 9095 unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 9096 } 9097 onNewScopeDestroyed = (onNewScopeDestroyed || []); 9098 onNewScopeDestroyed.push(unwatch); 9099 break; 9100 9101 case '&': 9102 // Don't assign Object.prototype method to scope 9103 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 9104 9105 // Don't assign noop to destination if expression is not val
9105id 9106 if (parentGet === noop && optional) break; 9107 9108 destination[scopeName] = function(locals) { 9109 return parentGet(scope, locals); 9110 }; 9111 break; 9112 } 9113 }); 9114 var destroyBindings = onNewScopeDestroyed ? function destroyBindings() { 9115 for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) { 9116 onNewScopeDestroyed[i](); 9117 } 9118 } : noop; 9119 if (newScope && destroyBindings !== noop) { 9120 newScope.$on('$destroy', destroyBindings); 9121 return noop; 9122 } 9123 return destroyBindings; 9124 } 9125 }]; 9126} 9127 9128var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 9129/** 9130 * Converts all accepted directives format into proper directive name. 9131 * @param name Name to normalize 9132 */ 9133function directiveNormalize(name) { 9134 return camelCase(name.replace(PREFIX_REGEXP, '')); 9135} 9136 9137/** 9138 * @ngdoc type 9139 * @name $compile.directive.Attributes 9140 * 9141 * @description 9142 * A shared object between directive compile / linking functions which contains normalized DOM 9143 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 9144 * needed since all of these are treated as equivalent in Angular: 9145 * 9146 * ``` 9147 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 9148 * ``` 9149 */ 9150 9151/** 9152 * @ngdoc property 9153 * @name $compile.directive.Attributes#$attr 9154 * 9155 * @description 9156 * A map of DOM element attribute names to the normalized name. This is 9157 * needed to do reverse lookup from normalized name back to actual name. 9158 */ 9159 9160 9161/** 9162 * @ngdoc method 9163 * @name $compile.directive.Attributes#$set 9164 * @kind function 9165 * 9166 * @description 9167 * Set DOM element attribute value. 9168 * 9169 * 9170 * @param {string} name Normalized element attribute name of the property to modify. The name is 9171 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 9172 * property to the original name. 9173 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 9174 */ 9175 9176 9177 9178/** 9179 * Closure compiler type information 9180 */ 9181 9182function nodesetLinkingFn( 9183 /* angular.Scope */ scope, 9184 /* NodeList */ nodeList, 9185 /* Element */ rootElement, 9186 /* function(Function) */ boundTranscludeFn 9187) {} 9188 9189function directiveLinkingFn( 9190 /* nodesetLinkingFn */ nodesetLinkingFn, 9191 /* angular.Scope */ scope, 9192 /* Node */ node, 9193 /* Element */ rootElement, 9194 /* function(Function) */ boundTranscludeFn 9195) {} 9196 9197function tokenDifference(str1, str2) { 9198 var values = '', 9199 tokens1 = str1.split(/\s+/), 9200 tokens2 = str2.split(/\s+/); 9201 9202 outer: 9203 for (var i = 0; i < tokens1.length; i++) { 9204 var token = tokens1[i]; 9205 for (var j = 0; j < tokens2.length; j++) { 9206 if (token == tokens2[j]) continue outer; 9207 } 9208 values += (values.length > 0 ? ' ' : '') + token; 9209 } 9210 return values; 9211} 9212 9213function removeComments(jqNodes) { 9214 jqNodes = jqLite(jqNodes); 9215 var i = jqNodes.length; 9216 9217 if (i <= 1) { 9218 return jqNodes; 9219 } 9220 9221 while (i--) { 9222 var node = jqNodes[i]; 9223 if (node.nodeType === NODE_TYPE_COMMENT) { 9224 splice.call(jqNodes, i, 1); 9225 } 9226 } 9227 return jqNodes; 9228} 9229 9230var $controllerMinErr = minErr('$controller'); 9231 9232 9233var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 9234function identifierForController(controller, ident) { 9235 if (ident && isString(ident)) return ident; 9236 if (isString(controller)) { 9237 var match = CNTRL_REG.exec(controller); 9238 if (match) return match[3]; 9239 } 9240} 9241 9242 9243/** 9244 * @ngdoc provider 9245 * @name $controllerProvider 9246 * @description 9247 * The {@link ng.$controller $controller service} is used by Angular to create new 9248 * controllers. 9249 * 9250 * This provider allows controller registration via the 9251 * {@link ng.$controllerProvider#register register} method. 9252 */ 9253function $ControllerProvider() { 9254 var controllers = {}, 9255 globals = false; 9256 9257 /** 9258 * @ngdoc method 9259 * @name $controllerProvider#register 9260 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 9261 * the names and the values are the constructors. 9262 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 9263 * annotations in the array notation). 9264 */ 9265 this.register = function(name, constructor) { 9266 assertNotHasOwnProperty(name, 'controller'); 9267 if (isObject(name)) { 9268 extend(controllers, name); 9269 } else { 9270 controllers[name] = constructor; 9271 } 9272 }; 9273 9274 /** 9275 * @ngdoc method 9276 * @name $controllerProvider#allowGlobals 9277 * @description If called, allows `$controller` to find controller constructors on `window` 9278 */ 9279 this.allowGlobals = function() { 9280 globals = true; 9281 }; 9282 9283 9284 this.$get = ['$injector', '$window', function($injector, $window) { 9285 9286 /** 9287 * @ngdoc service 9288 * @name $controller 9289 * @requires $injector 9290 * 9291 * @param {Function|string} constructor If called with a function then it's considered to be the 9292 * controller constructor function. Otherwise it's considered to be a string which is used 9293 * to retrieve the controller constructor using the following steps: 9294 * 9295 * * check if a controller with given name is registered via `$controllerProvider` 9296 * * check if evaluating the string on the current scope returns a constructor 9297 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 9298 * `window` object (not recommended) 9299 * 9300 * The string can use the `controller as property` syntax, where the controller instance is published 9301 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 9302 * to work correctly. 9303 * 9304 * @param {Object} locals Injection locals for Controller. 9305 * @return {Object} Instance of given controller. 9306 *
vendor: 4,024 bytes, lines 9307-9411
9307 * @description 9308 * `$controller` service is responsible for instantiating controllers. 9309 * 9310 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 9311 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 9312 */ 9313 return function(expression, locals, later, ident) { 9314 // PRIVATE API: 9315 // param `later` --- indicates that the controller's constructor is invoked at a later time. 9316 // If true, $controller will allocate the object with the correct 9317 // prototype chain, but will not invoke the controller until a returned 9318 // callback is invoked. 9319 // param `ident` --- An optional label which overrides the label parsed from the controller 9320 // expression, if any. 9321 var instance, match, constructor, identifier; 9322 later = later === true; 9323 if (ident && isString(ident)) { 9324 identifier = ident; 9325 } 9326 9327 if (isString(expression)) { 9328 match = expression.match(CNTRL_REG); 9329 if (!match) { 9330 throw $controllerMinErr('ctrlfmt', 9331 "Badly formed controller string '{0}'. " + 9332 "Must match `__name__ as __id__` or `__name__`.", expression); 9333 } 9334 constructor = match[1], 9335 identifier = identifier || match[3]; 9336 expression = controllers.hasOwnProperty(constructor) 9337 ? controllers[constructor] 9338 : getter(locals.$scope, constructor, true) || 9339 (globals ? getter($window, constructor, true) : undefined); 9340 9341 assertArgFn(expression, constructor, true); 9342 } 9343 9344 if (later) { 9345 // Instantiate controller later: 9346 // This machinery is used to create an instance of the object before calling the 9347 // controller's constructor itself. 9348 // 9349 // This allows properties to be added to the controller before the constructor is 9350 // invoked. Primarily, this is used for isolate scope bindings in $compile. 9351 // 9352 // This feature is not intended for use by applications, and is thus not documented 9353 // publicly. 9354 // Object creation: http://jsperf.com/create-constructor/2 9355 var controllerPrototype = (isArray(expression) ? 9356 expression[expression.length - 1] : expression).prototype; 9357 instance = Object.create(controllerPrototype || null); 9358 9359 if (identifier) { 9360 addIdentifier(locals, identifier, instance, constructor || expression.name); 9361 } 9362 9363 var instantiate; 9364 return instantiate = extend(function() { 9365 var result = $injector.invoke(expression, instance, locals, constructor); 9366 if (result !== instance && (isObject(result) || isFunction(result))) { 9367 instance = result; 9368 if (identifier) { 9369 // If result changed, re-assign controllerAs value to scope. 9370 addIdentifier(locals, identifier, instance, constructor || expression.name); 9371 } 9372 } 9373 return instance; 9374 }, { 9375 instance: instance, 9376 identifier: identifier 9377 }); 9378 } 9379 9380 instance = $injector.instantiate(expression, locals, constructor); 9381 9382 if (identifier) { 9383 addIdentifier(locals, identifier, instance, constructor || expression.name); 9384 } 9385 9386 return instance; 9387 }; 9388 9389 function addIdentifier(locals, identifier, instance, name) { 9390 if (!(locals && isObject(locals.$scope))) { 9391 throw minErr('$controller')('noscp', 9392 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 9393 name, identifier); 9394 } 9395 9396 locals.$scope[identifier] = instance; 9397 } 9398 }]; 9399} 9400 9401/** 9402 * @ngdoc service 9403 * @name $document 9404 * @requires $window 9405 * 9406 * @description 9407 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 9408 * 9409 * @example 9410 <example module="documentExample"> 9411 <file name="index.html">
9412 <div ng-controller="ExampleController"> 9413 <p>$document title: <b ng-bind="title"></b></p> 9414 <p>window.document title: <b ng-bind="windowTitle"></b></p> 9415 </div> 9416 </file> 9417 <file name="script.js"> 9418 angular.module('documentExample', []) 9419 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 9420 $scope.title = $document[0].title; 9421 $scope.windowTitle = angular.element(window.document)[0].title; 9422 }]); 9423 </file> 9424 </example> 9425 */ 9426function $DocumentProvider() { 9427 this.$get = ['$window', function(window) { 9428 return jqLite(window.document); 9429 }]; 9430} 9431 9432/** 9433 * @ngdoc service 9434 * @name $exceptionHandler 9435 * @requires ng.$log 9436 * 9437 * @description 9438 * Any uncaught exception in angular expressions is delegated to this service. 9439 * The default implementation simply delegates to `$log.error` which logs it into 9440 * the browser console. 9441 * 9442 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 9443 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 9444 * 9445 * ## Example: 9446 * 9447 * ```js 9448 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 9449 * return function(exception, cause) { 9450 * exception.message += ' (caused by "' + cause + '")'; 9451 * throw exception; 9452 * }; 9453 * }); 9454 * ``` 9455 * 9456 * This example will override the normal action of `$exceptionHandler`, to make angular 9457 * exceptions fail hard when they happen, instead of just logging to the console. 9458 * 9459 * <hr /> 9460 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 9461 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 9462 * (unless executed during a digest). 9463 * 9464 * If you wish, you can manually delegate exceptions, e.g. 9465 * `try { ... } catch(e) { $exceptionHandler(e); }` 9466 * 9467 * @param {Error} exception Exception associated with the error. 9468 * @param {string=} cause optional information about the context in which 9469 * the error was thrown. 9470 * 9471 */ 9472function $ExceptionHandlerProvider() { 9473 this.$get = ['$log', function($log) { 9474 return function(exception, cause) { 9475 $log.error.apply($log, arguments); 9476 }; 9477 }]; 9478} 9479 9480var $$ForceReflowProvider = function() { 9481 this.$get = ['$document', function($document) { 9482 return function(domNode) { 9483 //the line below will force the browser to perform a repaint so 9484 //that all the animated elements within the animation frame will 9485 //be properly updated and drawn on screen. This is required to 9486 //ensure that the preparation animation is properly flushed so that 9487 //the active state picks up from there. DO NOT REMOVE THIS LINE. 9488 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 9489 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 9490 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 9491 if (domNode) { 9492 if (!domNode.nodeType && domNode instanceof jqLite) { 9493 domNode = domNode[0]; 9494 } 9495 } else { 9496 domNode = $document[0].body; 9497 } 9498 return domNode.offsetWidth + 1; 9499 }; 9500 }]; 9501}; 9502 9503var APPLICATION_JSON = 'application/json'; 9504var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 9505var JSON_START = /^\[|^\{(?!\{)/; 9506var JSON_ENDS = { 9507 '[': /]$/, 9508 '{': /}$/ 9509}; 9510var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 9511var $httpMinErr = minErr('$http'); 9512var $httpMinErrLegacyFn = function(method) { 9513 return function() { 9514 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); 9515 }; 9516}; 9517 9518function serializeValue(v) { 9519 if (isObject(v)) { 9520 return isDate(v) ? v.toISOString() : toJson(v); 9521 } 9522 return v; 9523} 9524 9525 9526function $HttpParamSerializerProvider() { 9527 /** 9528 * @ngdoc service 9529 * @name $httpParamSerializer 9530 * @description 9531 * 9532 * Default {@link $http `$http`} params serializer that converts objects to strings 9533 * according to the following rules: 9534 * 9535 * * `{'foo': 'bar'}` results in `foo=bar` 9536 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 9537 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 9538 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 9539 * 9540 * Note that serializer will sort the request parameters alphabetically. 9541 * */ 9542 9543 this.$get = function() { 9544 return function ngParamSerializer(params) { 9545 if (!params) return ''; 9546 var parts = [];
vendor: 3,682 bytes, lines 9547-9673
9547 forEachSorted(params, function(value, key) { 9548 if (value === null || isUndefined(value)) return; 9549 if (isArray(value)) { 9550 forEach(value, function(v, k) { 9551 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 9552 }); 9553 } else { 9554 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 9555 } 9556 }); 9557 9558 return parts.join('&'); 9559 }; 9560 }; 9561} 9562 9563function $HttpParamSerializerJQLikeProvider() { 9564 /** 9565 * @ngdoc service 9566 * @name $httpParamSerializerJQLike 9567 * @description 9568 * 9569 * Alternative {@link $http `$http`} params serializer that follows 9570 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 9571 * The serializer will also sort the params alphabetically. 9572 * 9573 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 9574 * 9575 * ```js 9576 * $http({ 9577 * url: myUrl, 9578 * method: 'GET', 9579 * params: myParams, 9580 * paramSerializer: '$httpParamSerializerJQLike' 9581 * }); 9582 * ``` 9583 * 9584 * It is also possible to set it as the default `paramSerializer` in the 9585 * {@link $httpProvider#defaults `$httpProvider`}. 9586 * 9587 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 9588 * form data for submission: 9589 * 9590 * ```js 9591 * .controller(function($http, $httpParamSerializerJQLike) { 9592 * //... 9593 * 9594 * $http({ 9595 * url: myUrl, 9596 * method: 'POST', 9597 * data: $httpParamSerializerJQLike(myData), 9598 * headers: { 9599 * 'Content-Type': 'application/x-www-form-urlencoded' 9600 * } 9601 * }); 9602 * 9603 * }); 9604 * ``` 9605 * 9606 * */ 9607 this.$get = function() { 9608 return function jQueryLikeParamSerializer(params) { 9609 if (!params) return ''; 9610 var parts = []; 9611 serialize(params, '', true); 9612 return parts.join('&'); 9613 9614 function serialize(toSerialize, prefix, topLevel) { 9615 if (toSerialize === null || isUndefined(toSerialize)) return; 9616 if (isArray(toSerialize)) { 9617 forEach(toSerialize, function(value, index) { 9618 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 9619 }); 9620 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 9621 forEachSorted(toSerialize, function(value, key) { 9622 serialize(value, prefix + 9623 (topLevel ? '' : '[') + 9624 key + 9625 (topLevel ? '' : ']')); 9626 }); 9627 } else { 9628 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 9629 } 9630 } 9631 }; 9632 }; 9633} 9634 9635function defaultHttpResponseTransform(data, headers) { 9636 if (isString(data)) { 9637 // Strip json vulnerability protection prefix and trim whitespace 9638 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 9639 9640 if (tempData) { 9641 var contentType = headers('Content-Type'); 9642 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 9643 data = fromJson(tempData); 9644 } 9645 } 9646 } 9647 9648 return data; 9649} 9650 9651function isJsonLike(str) { 9652 var jsonStart = str.match(JSON_START); 9653 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 9654} 9655 9656/** 9657 * Parse headers into key value object 9658 * 9659 * @param {string} headers Raw headers as a string 9660 * @returns {Object} Parsed headers as key value object 9661 */ 9662function parseHeaders(headers) { 9663 var parsed = createMap(), i; 9664 9665 function fillInParsed(key, val) { 9666 if (key) { 9667 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 9668 } 9669 } 9670 9671 if (isString(headers)) { 9672 forEach(headers.split('\n'), function(line) { 9673 i = line.indexOf(':');
vendor: 4,257 bytes, lines 9674-9811
9674 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 9675 }); 9676 } else if (isObject(headers)) { 9677 forEach(headers, function(headerVal, headerKey) { 9678 fillInParsed(lowercase(headerKey), trim(headerVal)); 9679 }); 9680 } 9681 9682 return parsed; 9683} 9684 9685 9686/** 9687 * Returns a function that provides access to parsed headers. 9688 * 9689 * Headers are lazy parsed when first requested. 9690 * @see parseHeaders 9691 * 9692 * @param {(string|Object)} headers Headers to provide access to. 9693 * @returns {function(string=)} Returns a getter function which if called with: 9694 * 9695 * - if called with single an argument returns a single header value or null 9696 * - if called with no arguments returns an object containing all headers. 9697 */ 9698function headersGetter(headers) { 9699 var headersObj; 9700 9701 return function(name) { 9702 if (!headersObj) headersObj = parseHeaders(headers); 9703 9704 if (name) { 9705 var value = headersObj[lowercase(name)]; 9706 if (value === void 0) { 9707 value = null; 9708 } 9709 return value; 9710 } 9711 9712 return headersObj; 9713 }; 9714} 9715 9716 9717/** 9718 * Chain all given functions 9719 * 9720 * This function is used for both request and response transforming 9721 * 9722 * @param {*} data Data to transform. 9723 * @param {function(string=)} headers HTTP headers getter fn. 9724 * @param {number} status HTTP status code of the response. 9725 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 9726 * @returns {*} Transformed data. 9727 */ 9728function transformData(data, headers, status, fns) { 9729 if (isFunction(fns)) { 9730 return fns(data, headers, status); 9731 } 9732 9733 forEach(fns, function(fn) { 9734 data = fn(data, headers, status); 9735 }); 9736 9737 return data; 9738} 9739 9740 9741function isSuccess(status) { 9742 return 200 <= status && status < 300; 9743} 9744 9745 9746/** 9747 * @ngdoc provider 9748 * @name $httpProvider 9749 * @description 9750 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 9751 * */ 9752function $HttpProvider() { 9753 /** 9754 * @ngdoc property 9755 * @name $httpProvider#defaults 9756 * @description 9757 * 9758 * Object containing default values for all {@link ng.$http $http} requests. 9759 * 9760 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} 9761 * that will provide the cache for all requests who set their `cache` property to `true`. 9762 * If you set the `defaults.cache = false` then only requests that specify their own custom 9763 * cache object will be cached. See {@link $http#caching $http Caching} for more information. 9764 * 9765 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 9766 * Defaults value is `'XSRF-TOKEN'`. 9767 * 9768 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 9769 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 9770 * 9771 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 9772 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 9773 * setting default headers. 9774 * - **`defaults.headers.common`** 9775 * - **`defaults.headers.post`** 9776 * - **`defaults.headers.put`** 9777 * - **`defaults.headers.patch`** 9778 * 9779 * 9780 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 9781 * used to the prepare string representation of request parameters (specified as an object). 9782 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 9783 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 9784 * 9785 **/ 9786 var defaults = this.defaults = { 9787 // transform incoming response data 9788 transformResponse: [defaultHttpResponseTransform], 9789 9790 // transform outgoing request data 9791 transformRequest: [function(d) { 9792 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 9793 }], 9794 9795 // default headers 9796 headers: { 9797 common: { 9798 'Accept': 'application/json, text/plain, */*' 9799 }, 9800 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9801 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9802 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 9803 }, 9804 9805 xsrfCookieName: 'XSRF-TOKEN', 9806 xsrfHeaderName: 'X-XSRF-TOKEN', 9807 9808 paramSerializer: '$httpParamSerializer' 9809 }; 9810 9811 var useApplyAsync = false;
vendor: 4,300 bytes, lines 9812-9922
9812 /** 9813 * @ngdoc method 9814 * @name $httpProvider#useApplyAsync 9815 * @description 9816 * 9817 * Configure $http service to combine processing of multiple http responses received at around 9818 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 9819 * significant performance improvement for bigger applications that make many HTTP requests 9820 * concurrently (common during application bootstrap). 9821 * 9822 * Defaults to false. If no value is specified, returns the current configured value. 9823 * 9824 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 9825 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 9826 * to load and share the same digest cycle. 9827 * 9828 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 9829 * otherwise, returns the current configured value. 9830 **/ 9831 this.useApplyAsync = function(value) { 9832 if (isDefined(value)) { 9833 useApplyAsync = !!value; 9834 return this; 9835 } 9836 return useApplyAsync; 9837 }; 9838 9839 var useLegacyPromise = true; 9840 /** 9841 * @ngdoc method 9842 * @name $httpProvider#useLegacyPromiseExtensions 9843 * @description 9844 * 9845 * Configure `$http` service to return promises without the shorthand methods `success` and `error`. 9846 * This should be used to make sure that applications work without these methods. 9847 * 9848 * Defaults to false. If no value is specified, returns the current configured value. 9849 * 9850 * @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods. 9851 * 9852 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 9853 * otherwise, returns the current configured value. 9854 **/ 9855 this.useLegacyPromiseExtensions = function(value) { 9856 if (isDefined(value)) { 9857 useLegacyPromise = !!value; 9858 return this; 9859 } 9860 return useLegacyPromise; 9861 }; 9862 9863 /** 9864 * @ngdoc property 9865 * @name $httpProvider#interceptors 9866 * @description 9867 * 9868 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 9869 * pre-processing of request or postprocessing of responses. 9870 * 9871 * These service factories are ordered by request, i.e. they are applied in the same order as the 9872 * array, on request, but reverse order, on response. 9873 * 9874 * {@link ng.$http#interceptors Interceptors detailed info} 9875 **/ 9876 var interceptorFactories = this.interceptors = []; 9877 9878 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 9879 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 9880 9881 var defaultCache = $cacheFactory('$http'); 9882 9883 /** 9884 * Make sure that default param serializer is exposed as a function 9885 */ 9886 defaults.paramSerializer = isString(defaults.paramSerializer) ? 9887 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 9888 9889 /** 9890 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 9891 * The reversal is needed so that we can build up the interception chain around the 9892 * server request. 9893 */ 9894 var reversedInterceptors = []; 9895 9896 forEach(interceptorFactories, function(interceptorFactory) { 9897 reversedInterceptors.unshift(isString(interceptorFactory) 9898 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 9899 }); 9900 9901 /** 9902 * @ngdoc service 9903 * @kind function 9904 * @name $http 9905 * @requires ng.$httpBackend 9906 * @requires $cacheFactory 9907 * @requires $rootScope 9908 * @requires $q 9909 * @requires $injector 9910 * 9911 * @description 9912 * The `$http` service is a core Angular service that facilitates communication with the remote 9913 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 9914 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 9915 * 9916 * For unit testing applications that use `$http` service, see 9917 * {@link ngMock.$httpBackend $httpBackend mock}. 9918 * 9919 * For a higher level of abstraction, please check out the {@link ngResource.$resource 9920 * $resource} service. 9921 * 9922 * The $http API is based on the {@link ng.$q deferred/promise APIs}
9922 exposed by 9923 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 9924 * it is important to familiarize yourself with these APIs and the guarantees they provide. 9925 * 9926 * 9927 * ## General usage 9928 * The `$http` service is a function which takes a single argument â a {@link $http#usage configuration object} â 9929 * that is used to generate an HTTP request and returns a {@link ng.$q promise}. 9930 * 9931 * ```js 9932 * // Simple GET request example: 9933 * $http({ 9934 * method: 'GET', 9935 * url: '/someUrl' 9936 * }).then(function successCallback(response) { 9937 * // this callback will be called asynchronously 9938 * // when the response is available 9939 * }, function errorCallback(response) { 9940 * // called asynchronously if an error occurs 9941 * // or server returns response with an error status. 9942 * }); 9943 * ``` 9944 * 9945 * The response object has these properties: 9946 * 9947 * - **data** â `{string|Object}` â The response body transformed with the transform 9948 * functions. 9949 * - **status** â `{number}` â HTTP status code of the response. 9950 * - **headers** â `{function([headerName])}` â Header getter function. 9951 * - **config** â `{Object}` â The configuration object that was used to generate the request. 9952 * - **statusText** â `{string}` â HTTP status text of the response. 9953 * 9954 * A response status code between 200 and 299 is considered a success status and 9955 * will result in the success callback being called. Note that if the response is a redirect, 9956 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 9957 * called for such responses. 9958 * 9959 * 9960 * ## Shortcut methods 9961 * 9962 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 9963 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 9964 * last argument. 9965 * 9966 * ```js 9967 * $http.get('/someUrl', config).then(successCallback, errorCallback); 9968 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 9969 * ``` 9970 * 9971 * Complete list of shortcut methods: 9972 * 9973 * - {@link ng.$http#get $http.get} 9974 * - {@link ng.$http#head $http.head} 9975 * - {@link ng.$http#post $http.post} 9976 * - {@link ng.$http#put $http.put} 9977 * - {@link ng.$http#delete $http.delete} 9978 * - {@link ng.$http#jsonp $http.jsonp} 9979 * - {@link ng.$http#patch $http.patch} 9980 * 9981 * 9982 * ## Writing Unit Tests that use $http 9983 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 9984 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 9985 * request using trained responses. 9986 * 9987 * ``` 9988 * $httpBackend.expectGET(...); 9989 * $http.get(...); 9990 * $httpBackend.flush(); 9991 * ``` 9992 * 9993 * ## Deprecation Notice 9994 * <div class="alert alert-danger"> 9995 * The `$http` legacy promise methods `success` and `error` have been deprecated. 9996 * Use the standard `then` method instead. 9997 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to 9998 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. 9999 * </div> 10000 * 10001 * ## Setting HTTP Headers 10002 * 10003 * The $http service will automatically add certain HTTP headers to all requests. These defaults 10004 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 10005 * object, which currently contains this default configuration: 10006 * 10007 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 10008 * - `Accept: application/json, text/plain, * / *` 10009 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 10010 * - `Content-Type: application/json` 10011 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 10012 * - `Content-Type: application/json` 10013 *
10014 * To add or overwrite these defaults, simply add or remove a property from these configuration 10015 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 10016 * with the lowercased HTTP method name as the key, e.g. 10017 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 10018 * 10019 * The defaults can also be set at runtime via the `$http.defaults` object in the same 10020 * fashion. For example: 10021 * 10022 * ``` 10023 * module.run(function($http) { 10024 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 10025 * }); 10026 * ``` 10027 * 10028 * In addition, you can supply a `headers` property in the config object passed when 10029 * calling `$http(config)`, which overrides the defaults without changing them globally. 10030 * 10031 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 10032 * Use the `headers` property, setting the desired header to `undefined`. For example: 10033 * 10034 * ```js 10035 * var req = { 10036 * method: 'POST', 10037 * url: 'http://example.com', 10038 * headers: { 10039 * 'Content-Type': undefined 10040 * }, 10041 * data: { test: 'test' } 10042 * } 10043 * 10044 * $http(req).then(function(){...}, function(){...}); 10045 * ``` 10046 * 10047 * ## Transforming Requests and Responses 10048 * 10049 * Both requests and responses can be transformed using transformation functions: `transformRequest` 10050 * and `transformResponse`. These properties can be a single function that returns 10051 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 10052 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 10053 * 10054 * ### Default Transformations 10055 * 10056 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 10057 * `defaults.transformResponse` properties. If a request does not provide its own transformations 10058 * then these will be applied. 10059 * 10060 * You can augment or replace the default transformations by modifying these properties by adding to or 10061 * replacing the array. 10062 * 10063 * Angular provides the following default transformations: 10064 * 10065 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 10066 * 10067 * - If the `data` property of the request configuration object contains an object, serialize it 10068 * into JSON format. 10069 * 10070 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 10071 * 10072 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 10073 * - If JSON response is detected, deserialize it using a JSON parser. 10074 * 10075 * 10076 * ### Overriding the Default Transformations Per Request 10077 * 10078 * If you wish override the request/response transformations only for a single request then provide 10079 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 10080 * into `$http`. 10081 * 10082 * Note that if you provide these properties on the config object the default transformations will be 10083 * overwritten. If you wish to augment the default transformations then you must include them in your 10084 * local transformation array. 10085 * 10086 * The following code demonstrates adding a new response transformation to be run after the default response 10087 * transformations have been run. 10088 * 10089 * ```js 10090 * function appendTransform(defaults, transform) { 10091 * 10092 * // We can't guarantee that the default transformation is an array 10093 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 10094 * 10095 * // Append the new transformation to the defaults 10096 * return defaults.concat(transform); 10097 * } 10098 * 10099 * $http({ 10100 * url: '...', 10101 * method: 'GET', 10102 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 10103 * return doTransform(value); 10104 * }) 10105 * }); 10106 * ``` 10107 * 10108 * 10109 * ## Caching 10110 *
10111 * To enable caching, set the request configuration `cache` property to `true` (to use default 10112 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 10113 * When the cache is enabled, `$http` stores the response from the server in the specified 10114 * cache. The next time the same request is made, the response is served from the cache without 10115 * sending a request to the server. 10116 * 10117 * Note that even if the response is served from cache, delivery of the data is asynchronous in 10118 * the same way that real requests are. 10119 * 10120 * If there are multiple GET requests for the same URL that should be cached using the same 10121 * cache, but the cache is not populated yet, only one request to the server will be made and 10122 * the remaining requests will be fulfilled using the response from the first request. 10123 * 10124 * You can change the default cache to a new object (built with 10125 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 10126 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set 10127 * their `cache` property to `true` will now use this cache object. 10128 * 10129 * If you set the default cache to `false` then only requests that specify their own custom 10130 * cache object will be cached. 10131 * 10132 * ## Interceptors 10133 * 10134 * Before you start creating interceptors, be sure to understand the 10135 * {@link ng.$q $q and deferred/promise APIs}. 10136 * 10137 * For purposes of global error handling, authentication, or any kind of synchronous or 10138 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 10139 * able to intercept requests before they are handed to the server and 10140 * responses before they are handed over to the application code that 10141 * initiated these requests. The interceptors leverage the {@link ng.$q 10142 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 10143 * 10144 * The interceptors are service factories that are registered with the `$httpProvider` by 10145 * adding them to the `$httpProvider.interceptors` array. The factory is called and 10146 * injected with dependencies (if specified) and returns the interceptor. 10147 * 10148 * There are two kinds of interceptors (and two kinds of rejection interceptors): 10149 * 10150 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 10151 * modify the `config` object or create a new one. The function needs to return the `config` 10152 * object directly, or a promise containing the `config` or a new `config` object. 10153 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 10154 * resolved with a rejection. 10155 * * `response`: interceptors get called with http `response` object. The function is free to 10156 * modify the `response` object or create a new one. The function needs to return the `response` 10157 * object directly, or as a promise containing the `response` or a new `response` object. 10158 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 10159 * resolved with a rejection. 10160 * 10161 * 10162 * ```js 10163 * // register the interceptor as a service 10164 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 10165 * return { 10166 * // optional method 10167 * 'request': function(config) { 10168 * // do something on success 10169 * return config; 10170 * }, 10171 * 10172 * // optional method 10173 * 'requestError': function(rejection) { 10174 * // do something on error 10175 * if (canRecover(rejection)) { 10176 * return responseOrNewPromise 10177 * } 10178 * return $q.reject(rejection); 10179 * }, 10180 * 10181 * 10182 * 10183 * // optional method 10184 * 'response': function(response) { 10185 * // do something on success 10186 * return response; 10187 * }, 10188 * 10189 * // optional method 10190 * 'responseError': function(rejection) {
10191 * // do something on error 10192 * if (canRecover(rejection)) { 10193 * return responseOrNewPromise 10194 * } 10195 * return $q.reject(rejection); 10196 * } 10197 * }; 10198 * }); 10199 * 10200 * $httpProvider.interceptors.push('myHttpInterceptor'); 10201 * 10202 * 10203 * // alternatively, register the interceptor via an anonymous factory 10204 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 10205 * return { 10206 * 'request': function(config) { 10207 * // same as above 10208 * }, 10209 * 10210 * 'response': function(response) { 10211 * // same as above 10212 * } 10213 * }; 10214 * }); 10215 * ``` 10216 * 10217 * ## Security Considerations 10218 * 10219 * When designing web applications, consider security threats from: 10220 * 10221 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10222 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 10223 * 10224 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 10225 * pre-configured with strategies that address these issues, but for this to work backend server 10226 * cooperation is required. 10227 * 10228 * ### JSON Vulnerability Protection 10229 * 10230 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10231 * allows third party website to turn your JSON resource URL into 10232 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 10233 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 10234 * Angular will automatically strip the prefix before processing it as JSON. 10235 * 10236 * For example if your server needs to return: 10237 * ```js 10238 * ['one','two'] 10239 * ``` 10240 * 10241 * which is vulnerable to attack, your server can return: 10242 * ```js 10243 * )]}', 10244 * ['one','two'] 10245 * ``` 10246 * 10247 * Angular will strip the prefix, before processing the JSON. 10248 * 10249 * 10250 * ### Cross Site Request Forgery (XSRF) Protection 10251 * 10252 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 10253 * an unauthorized site can gain your user's private data. Angular provides a mechanism 10254 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 10255 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 10256 * JavaScript that runs on your domain could read the cookie, your server can be assured that 10257 * the XHR came from JavaScript running on your domain. The header will not be set for 10258 * cross-domain requests. 10259 * 10260 * To take advantage of this, your server needs to set a token in a JavaScript readable session 10261 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 10262 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 10263 * that only JavaScript running on your domain could have sent the request. The token must be 10264 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 10265 * making up its own tokens). We recommend that the token is a digest of your site's 10266 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 10267 * for added security. 10268 * 10269 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 10270 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 10271 * or the per-request config object. 10272 * 10273 * In order to prevent collisions in environments where multiple Angular apps share the 10274 * same domain or subdomain, we recommend that each application uses unique cookie name. 10275 * 10276 * @param {object} config Object describing the request to be made and how it should be 10277 * processed. The object has following properties: 10278 * 10279 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 10280 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 10281 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 10282 * with the `paramSerializer` and appended as GET parameters. 10283 * - **data** â `{string|Object}` â Data to be sent as the request message data. 10284 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 10285 * HTTP headers to send to the server. If the return value of a function is null, the 10286 * header will not be sent. Functions accept a config object as an argument. 10287 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 10288 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 10289 * - **transformRequest** â 10290 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
10290}` â 10291 * transform function or an array of such functions. The transform function takes the http 10292 * request body and headers and returns its transformed (typically serialized) version. 10293 * See {@link ng.$http#overriding-the-default-transformations-per-request 10294 * Overriding the Default Transformations} 10295 * - **transformResponse** â 10296 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 10297 * transform function or an array of such functions. The transform function takes the http 10298 * response body, headers and status and returns its transformed (typically deserialized) version. 10299 * See {@link ng.$http#overriding-the-default-transformations-per-request 10300 * Overriding the Default TransformationjqLiks} 10301 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 10302 * prepare the string representation of request parameters (specified as an object). 10303 * If specified as string, it is interpreted as function registered with the 10304 * {@link $injector $injector}, which means you can create your own serializer 10305 * by registering it as a {@link auto.$provide#service service}. 10306 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 10307 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 10308 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 10309 * GET request, otherwise if a cache instance built with 10310 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 10311 * caching. 10312 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 10313 * that should abort the request when resolved. 10314 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 10315 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 10316 * for more information. 10317 * - **responseType** - `{string}` - see 10318 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 10319 * 10320 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object 10321 * when the request succeeds or fails. 10322 * 10323 * 10324 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 10325 * requests. This is primarily meant to be used for debugging purposes. 10326 * 10327 * 10328 * @example 10329<example module="httpExample"> 10330<file name="index.html"> 10331 <div ng-controller="FetchController"> 10332 <select ng-model="method" aria-label="Request method"> 10333 <option>GET</option> 10334 <option>JSONP</option> 10335 </select> 10336 <input type="text" ng-model="url" size="80" aria-label="URL" /> 10337 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 10338 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 10339 <button id="samplejsonpbtn" 10340 ng-click="updateModel('JSONP', 10341 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 10342 Sample JSONP 10343 </button> 10344 <button id="invalidjsonpbtn" 10345 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 10346 Invalid JSONP 10347 </button> 10348 <pre>http status code: {{status}}</pre> 10349 <pre>http response data: {{data}}</pre> 10350 </div> 10351</file> 10352<file name="script.js"> 10353 angular.module('httpExample', []) 10354 .controller('FetchController', ['$scope', '$http', '$templateCache', 10355 function($scope, $http, $templateCache) { 10356 $scope.method = 'GET'; 10357 $scope.url = 'http-hello.html'; 10358 10359 $scope.fetch = function() { 10360 $scope.code = null; 10361 $scope.response = null; 10362 10363 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 10364 then(function(response) { 10365 $scope.status = response.status; 10366 $scope.data = response.data; 10367 }, function(response) { 10368 $scope.data = response.data || "Request failed"; 10369 $scope.status = response.status; 10370 }); 10371 }; 10372 10373 $scope.updateModel = function(method, url) { 10374 $scope.method = method; 10375 $scope.url = url; 10376 }; 10377 }]); 10378</file> 10379<file name="http-hello.html"> 10380 Hello, $http! 10381</file> 10382<file name="protractor.js" type="protractor"> 10383 var status = element(by.binding('status')); 10384 var data = element(by.binding('data')); 10385 var fetchBtn = element(by.id('fetchbtn')); 10386 var sampleGetBtn = element(by.id('samplegetbtn')); 10387 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 10388 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 10389 10390 it('should make an xhr GET request', function() { 10391 sampleGetBtn.click(); 10392 fetchBtn.click(); 10393 expect(status.getText()).toMatch('200'); 10394 expect(data.getText()).toMatch(/Hello, \$http!/); 10395 }); 10396 10397// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 10398// it('should make a JSONP request to angularjs.org', function() { 10399// sampleJsonpBtn.click(); 10400// fetchBtn.click(); 10401// expect(status.getText()).toMatch('200'); 10402// expect(data.getText()).toMatch(/Super Hero!/); 10403// }); 10404
10405 it('should make JSONP request to invalid URL and invoke the error handler', 10406 function() { 10407 invalidJsonpBtn.click(); 10408 fetchBtn.click(); 10409 expect(status.getText()).toMatch('0'); 10410 expect(data.getText()).toMatch('Request failed'); 10411 }); 10412</file> 10413</example> 10414 */ 10415 function $http(requestConfig) { 10416 10417 if (!angular.isObject(requestConfig)) { 10418 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 10419 } 10420 10421 var config = extend({ 10422 method: 'get', 10423 transformRequest: defaults.transformRequest, 10424 transformResponse: defaults.transformResponse, 10425 paramSerializer: defaults.paramSerializer 10426 }, requestConfig); 10427 10428 config.headers = mergeHeaders(requestConfig); 10429 config.method = uppercase(config.method); 10430 config.paramSerializer = isString(config.paramSerializer) ? 10431 $injector.get(config.paramSerializer) : config.paramSerializer; 10432 10433 var serverRequest = function(config) { 10434 var headers = config.headers; 10435 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 10436 10437 // strip content-type if data is undefined 10438 if (isUndefined(reqData)) { 10439 forEach(headers, function(value, header) { 10440 if (lowercase(header) === 'content-type') { 10441 delete headers[header]; 10442 } 10443 }); 10444 } 10445 10446 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 10447 config.withCredentials = defaults.withCredentials; 10448 } 10449 10450 // send request 10451 return sendReq(config, reqData).then(transformResponse, transformResponse); 10452 }; 10453 10454 var chain = [serverRequest, undefined]; 10455 var promise = $q.when(config); 10456 10457 // apply interceptors 10458 forEach(reversedInterceptors, function(interceptor) { 10459 if (interceptor.request || interceptor.requestError) { 10460 chain.unshift(interceptor.request, interceptor.requestError); 10461 } 10462 if (interceptor.response || interceptor.responseError) { 10463 chain.push(interceptor.response, interceptor.responseError); 10464 } 10465 }); 10466 10467 while (chain.length) { 10468 var thenFn = chain.shift(); 10469 var rejectFn = chain.shift(); 10470 10471 promise = promise.then(thenFn, rejectFn); 10472 } 10473 10474 if (useLegacyPromise) { 10475 promise.success = function(fn) { 10476 assertArgFn(fn, 'fn'); 10477 10478 promise.then(function(response) { 10479 fn(response.data, response.status, response.headers, config); 10480 }); 10481 return promise; 10482 }; 10483 10484 promise.error = function(fn) { 10485 assertArgFn(fn, 'fn'); 10486 10487 promise.then(null, function(response) { 10488 fn(response.data, response.status, response.headers, config); 10489 }); 10490 return promise; 10491 }; 10492 } else { 10493 promise.success = $httpMinErrLegacyFn('success'); 10494 promise.error = $httpMinErrLegacyFn('error'); 10495 } 10496 10497 return promise; 10498 10499 function transformResponse(response) { 10500 // make a copy since the response must be cacheable 10501 var resp = extend({}, response); 10502 if (!response.data) { 10503 resp.data = response.data; 10504 } else { 10505 resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); 10506 } 10507 return (isSuccess(response.status)) 10508 ? resp 10509 : $q.reject(resp); 10510 } 10511 10512 function executeHeaderFns(headers, config) { 10513 var headerContent, processedHeaders = {}; 10514 10515 forEach(headers, function(headerFn, header) { 10516 if (isFunction(headerFn)) { 10517 headerContent = headerFn(config); 10518 if (headerContent != null) { 10519 processedHeaders[header] = headerContent; 10520 } 10521 } else { 10522 processedHeaders[header] = headerFn; 10523 } 10524 }); 10525 10526 return processedHeaders; 10527 } 10528 10529 function mergeHeaders(config) { 10530 var defHeaders = defaults.headers, 10531 reqHeaders = extend({}, config.headers), 10532 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 10533 10534 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 10535
vendor: 9,098 bytes, lines 10536-10836
10536 // using for-in instead of forEach to avoid unecessary iteration after header has been found 10537 defaultHeadersIteration: 10538 for (defHeaderName in defHeaders) { 10539 lowercaseDefHeaderName = lowercase(defHeaderName); 10540 10541 for (reqHeaderName in reqHeaders) { 10542 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 10543 continue defaultHeadersIteration; 10544 } 10545 } 10546 10547 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 10548 } 10549 10550 // execute if header value is a function for merged headers 10551 return executeHeaderFns(reqHeaders, shallowCopy(config)); 10552 } 10553 } 10554 10555 $http.pendingRequests = []; 10556 10557 /** 10558 * @ngdoc method 10559 * @name $http#get 10560 * 10561 * @description 10562 * Shortcut method to perform `GET` request. 10563 * 10564 * @param {string} url Relative or absolute URL specifying the destination of the request 10565 * @param {Object=} config Optional configuration object 10566 * @returns {HttpPromise} Future object 10567 */ 10568 10569 /** 10570 * @ngdoc method 10571 * @name $http#delete 10572 * 10573 * @description 10574 * Shortcut method to perform `DELETE` request. 10575 * 10576 * @param {string} url Relative or absolute URL specifying the destination of the request 10577 * @param {Object=} config Optional configuration object 10578 * @returns {HttpPromise} Future object 10579 */ 10580 10581 /** 10582 * @ngdoc method 10583 * @name $http#head 10584 * 10585 * @description 10586 * Shortcut method to perform `HEAD` request. 10587 * 10588 * @param {string} url Relative or absolute URL specifying the destination of the request 10589 * @param {Object=} config Optional configuration object 10590 * @returns {HttpPromise} Future object 10591 */ 10592 10593 /** 10594 * @ngdoc method 10595 * @name $http#jsonp 10596 * 10597 * @description 10598 * Shortcut method to perform `JSONP` request. 10599 * 10600 * @param {string} url Relative or absolute URL specifying the destination of the request. 10601 * The name of the callback should be the string `JSON_CALLBACK`. 10602 * @param {Object=} config Optional configuration object 10603 * @returns {HttpPromise} Future object 10604 */ 10605 createShortMethods('get', 'delete', 'head', 'jsonp'); 10606 10607 /** 10608 * @ngdoc method 10609 * @name $http#post 10610 * 10611 * @description 10612 * Shortcut method to perform `POST` request. 10613 * 10614 * @param {string} url Relative or absolute URL specifying the destination of the request 10615 * @param {*} data Request content 10616 * @param {Object=} config Optional configuration object 10617 * @returns {HttpPromise} Future object 10618 */ 10619 10620 /** 10621 * @ngdoc method 10622 * @name $http#put 10623 * 10624 * @description 10625 * Shortcut method to perform `PUT` request. 10626 * 10627 * @param {string} url Relative or absolute URL specifying the destination of the request 10628 * @param {*} data Request content 10629 * @param {Object=} config Optional configuration object 10630 * @returns {HttpPromise} Future object 10631 */ 10632 10633 /** 10634 * @ngdoc method 10635 * @name $http#patch 10636 * 10637 * @description 10638 * Shortcut method to perform `PATCH` request. 10639 * 10640 * @param {string} url Relative or absolute URL specifying the destination of the request 10641 * @param {*} data Request content 10642 * @param {Object=} config Optional configuration object 10643 * @returns {HttpPromise} Future object 10644 */ 10645 createShortMethodsWithData('post', 'put', 'patch'); 10646 10647 /** 10648 * @ngdoc property 10649 * @name $http#defaults 10650 * 10651 * @description 10652 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 10653 * default headers, withCredentials as well as request and response transformations. 10654 * 10655 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 10656 */ 10657 $http.defaults = defaults; 10658 10659 10660 return $http; 10661 10662 10663 function createShortMethods(names) { 10664 forEach(arguments, function(name) { 10665 $http[name] = function(url, config) { 10666 return $http(extend({}, config || {}, { 10667 method: name, 10668 url: url 10669 })); 10670 }; 10671 }); 10672 } 10673 10674 10675 function createShortMethodsWithData(name) { 10676 forEach(arguments, function(name) { 10677 $http[name] = function(url, data, config) { 10678 return $http(extend({}, config || {}, { 10679 method: name, 10680 url: url, 10681 data: data 10682 })); 10683 }; 10684 }); 10685 } 10686 10687 10688 /** 10689 * Makes the request. 10690 * 10691 * !!! ACCESSES CLOSURE VARS: 10692 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 10693 */ 10694 function sendReq(config, reqData) { 10695 var deferred = $q.defer(), 10696 promise = deferred.promise, 10697 cache, 10698 cachedResp, 10699 reqHeaders = config.headers, 10700 url = buildUrl(config.url, config.paramSerializer(config.params)); 10701 10702 $http.pendingRequests.push(config); 10703 promise.then(removePendingReq, removePendingReq); 10704 10705 10706 if ((config.cache || defaults.cache) && config.cache !== false && 10707 (config.method === 'GET' || config.method === 'JSONP')) { 10708 cache = isObject(config.cache) ? config.cache 10709 : isObject(defaults.cache) ? defaults.cache 10710 : defaultCache; 10711 } 10712 10713 if (cache) { 10714 cachedResp = cache.get(url); 10715 if (isDefined(cachedResp)) { 10716 if (isPromiseLike(cachedResp)) { 10717 // cached request has already been sent, but there is no response yet 10718 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 10719 } else { 10720 // serving from cache 10721 if (isArray(cachedResp)) { 10722 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 10723 } else { 10724 resolvePromise(cachedResp, 200, {}, 'OK'); 10725 } 10726 } 10727 } else { 10728 // put the promise for the non-transformed response into cache as a placeholder 10729 cache.put(url, promise); 10730 } 10731 } 10732 10733 10734 // if we won't have the response in cache, set the xsrf headers and 10735 // send the request to the backend 10736 if (isUndefined(cachedResp)) { 10737 var xsrfValue = urlIsSameOrigin(config.url) 10738 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 10739 : undefined; 10740 if (xsrfValue) { 10741 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 10742 } 10743 10744 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 10745 config.withCredentials, config.responseType); 10746 } 10747 10748 return promise; 10749 10750 10751 /** 10752 * Callback registered to $httpBackend(): 10753 * - caches the response if desired 10754 * - resolves the raw $http promise 10755 * - calls $apply 10756 */ 10757 function done(status, response, headersString, statusText) { 10758 if (cache) { 10759 if (isSuccess(status)) { 10760 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 10761 } else { 10762 // remove promise from the cache 10763 cache.remove(url); 10764 } 10765 } 10766 10767 function resolveHttpPromise() { 10768 resolvePromise(response, status, headersString, statusText); 10769 } 10770 10771 if (useApplyAsync) { 10772 $rootScope.$applyAsync(resolveHttpPromise); 10773 } else { 10774 resolveHttpPromise(); 10775 if (!$rootScope.$$phase) $rootScope.$apply(); 10776 } 10777 } 10778 10779 10780 /** 10781 * Resolves the raw $http promise. 10782 */ 10783 function resolvePromise(response, status, headers, statusText) { 10784 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 10785 status = status >= -1 ? status : 0; 10786 10787 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 10788 data: response, 10789 status: status, 10790 headers: headersGetter(headers), 10791 config: config, 10792 statusText: statusText 10793 }); 10794 } 10795 10796 function resolvePromiseWithResult(result) { 10797 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 10798 } 10799 10800 function removePendingReq() { 10801 var idx = $http.pendingRequests.indexOf(config); 10802 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 10803 } 10804 } 10805 10806 10807 function buildUrl(url, serializedParams) { 10808 if (serializedParams.length > 0) { 10809 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 10810 } 10811 return url; 10812 } 10813 }]; 10814} 10815 10816/** 10817 * @ngdoc service 10818 * @name $xhrFactory 10819 * 10820 * @description 10821 * Factory function used to create XMLHttpRequest objects. 10822 * 10823 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 10824 * 10825 * ``` 10826 * angular.module('myApp', []) 10827 * .factory('$xhrFactory', function() { 10828 * return function createXhr(method, url) { 10829 * return new window.XMLHttpRequest({mozSystem: true}); 10830 * }; 10831 * }); 10832 * ``` 10833 * 10834 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 10835 * @param {string} url URL of the request. 10836 */
10837function $xhrFactoryProvider() { 10838 this.$get = function() { 10839 return function createXhr() { 10840 return new window.XMLHttpRequest(); 10841 }; 10842 }; 10843} 10844 10845/** 10846 * @ngdoc service 10847 * @name $httpBackend 10848 * @requires $window 10849 * @requires $document 10850 * @requires $xhrFactory 10851 * 10852 * @description 10853 * HTTP backend used by the {@link ng.$http service} that delegates to 10854 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 10855 * 10856 * You should never need to use this service directly, instead use the higher-level abstractions: 10857 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 10858 * 10859 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 10860 * $httpBackend} which can be trained with responses. 10861 */ 10862function $HttpBackendProvider() { 10863 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) { 10864 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]); 10865 }]; 10866} 10867 10868function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 10869 // TODO(vojta): fix the signature 10870 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 10871 $browser.$$incOutstandingRequestCount(); 10872 url = url || $browser.url(); 10873 10874 if (lowercase(method) == 'jsonp') { 10875 var callbackId = '_' + (callbacks.counter++).toString(36); 10876 callbacks[callbackId] = function(data) { 10877 callbacks[callbackId].data = data; 10878 callbacks[callbackId].called = true; 10879 }; 10880 10881 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 10882 callbackId, function(status, text) { 10883 completeRequest(callback, status, callbacks[callbackId].data, "", text); 10884 callbacks[callbackId] = noop; 10885 }); 10886 } else { 10887 10888 var xhr = createXhr(method, url); 10889 10890 xhr.open(method, url, true); 10891 forEach(headers, function(value, key) { 10892 if (isDefined(value)) { 10893 xhr.setRequestHeader(key, value); 10894 } 10895 }); 10896 10897 xhr.onload = function requestLoaded() { 10898 var statusText = xhr.statusText || ''; 10899 10900 // responseText is the old-school way of retrieving response (supported by IE9) 10901 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 10902 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 10903 10904 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 10905 var status = xhr.status === 1223 ? 204 : xhr.status; 10906 10907 // fix status code when it is 0 (0 status is undocumented). 10908 // Occurs when accessing file resources or on Android 4.1 stock browser 10909 // while retrieving files from application cache. 10910 if (status === 0) { 10911 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 10912 } 10913 10914 completeRequest(callback, 10915 status, 10916 response, 10917 xhr.getAllResponseHeaders(), 10918 statusText); 10919 }; 10920 10921 var requestError = function() { 10922 // The response is always empty 10923 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 10924 completeRequest(callback, -1, null, null, ''); 10925 }; 10926 10927 xhr.onerror = requestError; 10928 xhr.onabort = requestError; 10929 10930 if (withCredentials) { 10931 xhr.withCredentials = true; 10932 } 10933 10934 if (responseType) { 10935 try { 10936 xhr.responseType = responseType; 10937 } catch (e) { 10938 // WebKit added support for the json responseType value on 09/03/2013 10939 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 10940 // known to throw when setting the value "json" as the response type. Other older 10941 // browsers implementing the responseType 10942 // 10943 // The json response type can be ignored if not supported, because JSON payloads are 10944 // parsed on the client-side regardless. 10945 if (responseType !== 'json') { 10946 throw e; 10947 } 10948 } 10949 } 10950
10951 xhr.send(isUndefined(post) ? null : post); 10952 } 10953 10954 if (timeout > 0) { 10955 var timeoutId = $browserDefer(timeoutRequest, timeout); 10956 } else if (isPromiseLike(timeout)) { 10957 timeout.then(timeoutRequest); 10958 } 10959 10960 10961 function timeoutRequest() { 10962 jsonpDone && jsonpDone(); 10963 xhr && xhr.abort(); 10964 } 10965 10966 function completeRequest(callback, status, response, headersString, statusText) { 10967 // cancel timeout and subsequent timeout promise resolution 10968 if (isDefined(timeoutId)) { 10969 $browserDefer.cancel(timeoutId); 10970 } 10971 jsonpDone = xhr = null; 10972 10973 callback(status, response, headersString, statusText); 10974 $browser.$$completeOutstandingRequest(noop); 10975 } 10976 }; 10977 10978 function jsonpReq(url, callbackId, done) { 10979 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 10980 // - fetches local scripts via XHR and evals them 10981 // - adds and immediately removes script elements from the document 10982 var script = rawDocument.createElement('script'), callback = null; 10983 script.type = "text/javascript"; 10984 script.src = url; 10985 script.async = true; 10986 10987 callback = function(event) { 10988 removeEventListenerFn(script, "load", callback); 10989 removeEventListenerFn(script, "error", callback); 10990 rawDocument.body.removeChild(script); 10991 script = null; 10992 var status = -1; 10993 var text = "unknown"; 10994 10995 if (event) { 10996 if (event.type === "load" && !callbacks[callbackId].called) { 10997 event = { type: "error" }; 10998 } 10999 text = event.type; 11000 status = event.type === "error" ? 404 : 200; 11001 } 11002 11003 if (done) { 11004 done(status, text); 11005 } 11006 }; 11007 11008 addEventListenerFn(script, "load", callback); 11009 addEventListenerFn(script, "error", callback); 11010 rawDocument.body.appendChild(script); 11011 return callback; 11012 } 11013} 11014 11015var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 11016$interpolateMinErr.throwNoconcat = function(text) { 11017 throw $interpolateMinErr('noconcat', 11018 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 11019 "interpolations that concatenate multiple expressions when a trusted value is " + 11020 "required. See http://docs.angularjs.org/api/ng.$sce", text); 11021}; 11022 11023$interpolateMinErr.interr = function(text, err) { 11024 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 11025}; 11026 11027/** 11028 * @ngdoc provider 11029 * @name $interpolateProvider 11030 * 11031 * @description 11032 *
11033 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 11034 * 11035 * @example 11036<example module="customInterpolationApp"> 11037<file name="index.html"> 11038<script> 11039 var customInterpolationApp = angular.module('customInterpolationApp', []); 11040 11041 customInterpolationApp.config(function($interpolateProvider) { 11042 $interpolateProvider.startSymbol('//'); 11043 $interpolateProvider.endSymbol('//'); 11044 }); 11045 11046 11047 customInterpolationApp.controller('DemoController', function() { 11048 this.label = "This binding is brought you by // interpolation symbols."; 11049 }); 11050</script> 11051<div ng-app="App" ng-controller="DemoController as demo"> 11052 //demo.label// 11053</div> 11054</file> 11055<file name="protractor.js" type="protractor"> 11056 it('should interpolate binding with custom symbols', function() { 11057 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 11058 }); 11059</file> 11060</example> 11061 */ 11062function $InterpolateProvider() { 11063 var startSymbol = '{{'; 11064 var endSymbol = '}}'; 11065 11066 /** 11067 * @ngdoc method 11068 * @name $interpolateProvider#startSymbol 11069 * @description 11070 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 11071 * 11072 * @param {string=} value new value to set the starting symbol to. 11073 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 11074 */ 11075 this.startSymbol = function(value) { 11076 if (value) { 11077 startSymbol = value; 11078 return this; 11079 } else { 11080 return startSymbol; 11081 } 11082 }; 11083 11084 /** 11085 * @ngdoc method 11086 * @name $interpolateProvider#endSymbol 11087 * @description 11088 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 11089 * 11090 * @param {string=} value new value to set the ending symbol to. 11091 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 11092 */ 11093 this.endSymbol = function(value) { 11094 if (value) { 11095 endSymbol = value; 11096 return this; 11097 } else { 11098 return endSymbol; 11099 } 11100 }; 11101 11102 11103 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 11104 var startSymbolLength = startSymbol.length, 11105 endSymbolLength = endSymbol.length, 11106 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 11107 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 11108 11109 function escape(ch) { 11110 return '\\\\\\' + ch; 11111 } 11112 11113 function unescapeText(text) { 11114 return text.replace(escapedStartRegexp, startSymbol). 11115 replace(escapedEndRegexp, endSymbol); 11116 } 11117 11118 function stringify(value) { 11119 if (value == null) { // null || undefined 11120 return ''; 11121 } 11122 switch (typeof value) { 11123 case 'string': 11124 break; 11125 case 'number': 11126 value = '' + value; 11127 break; 11128 default: 11129 value = toJson(value); 11130 } 11131 11132 return value; 11133 } 11134 11135 /** 11136 * @ngdoc service 11137 * @name $interpolate 11138 * @kind function 11139 * 11140 * @requires $parse 11141 * @requires $sce 11142 * 11143 * @description 11144 * 11145 * Compiles a string with markup into an interpolation function. This service is used by the 11146 * HTML {@link ng.$compile $compile} service for data binding. See 11147 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 11148 * interpolation markup. 11149 * 11150 * 11151 * ```js 11152 * var $interpolate = ...; // injected 11153 * var exp = $interpolate('Hello {{name | uppercase}}!'); 11154 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); 11155 * ``` 11156 * 11157 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 11158 * `true`, the interpolation function will return `undefined` unless all embedded expressions 11159 * evaluate to a value other than `undefined`. 11160 * 11161 * ```js 11162 * var $interpolate = ...; // injected 11163 * var context = {greeting: 'Hello', name: undefined }; 11164 * 11165 * // default "forgiving" mode 11166 * var exp = $interpolate('{{greeting}} {{name}}!'); 11167 * expect(exp(context)).toEqual('Hello !'); 11168 * 11169 * // "allOrNothing" mode 11170 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 11171 * expect(exp(context)).toBeUndefined(); 11172 * context.name = 'Angular'; 11173 * expect(exp(context)).toEqual('Hello Angular!'); 11174 * ``` 11175 * 11176 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 11177 * 11178 * ####Escaped Interpolation 11179 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 11180 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 11181 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 11182 * or binding. 11183 * 11184 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 11185 * degree, while also enabling code examples to work without relying on the 11186 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 11187 * 11188 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 11189 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 11190 * interpolation start/end markers with their escaped counterparts.** 11191 * 11192 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 11193 * output when the $interpolate service processes the text. So, for HTML elements interpolated 11194 * by {@link ng.$compile $compile}
11194, or otherwise interpolated with the `mustHaveExpression` parameter 11195 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 11196 * this is typically useful only when user-data is used in rendering a template from the server, or 11197 * when otherwise untrusted data is used by a directive. 11198 * 11199 * <example> 11200 * <file name="index.html"> 11201 * <div ng-init="username='A user'"> 11202 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 11203 * </p> 11204 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 11205 * application, but fails to accomplish their task, because the server has correctly 11206 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 11207 * characters.</p> 11208 * <p>Instead, the result of the attempted script injection is visible, and can be removed 11209 * from the database by an administrator.</p> 11210 * </div> 11211 * </file> 11212 * </example> 11213 * 11214 * @param {string} text The text with markup to interpolate. 11215 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 11216 * embedded expression in order to return an interpolation function. Strings with no 11217 * embedded expression will return null for the interpolation function. 11218 * @param {string=} trustedContext when provided, the returned function passes the interpolated 11219 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 11220 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 11221 * provides Strict Contextual Escaping for details. 11222 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 11223 * unless all embedded expressions evaluate to a value other than `undefined`. 11224 * @returns {function(context)} an interpolation function which is used to compute the 11225 * interpolated string. The function has these parameters: 11226 * 11227 * - `context`: evaluation context for all expressions embedded in the interpolated text 11228 */ 11229 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 11230 allOrNothing = !!allOrNothing; 11231 var startIndex, 11232 endIndex, 11233 index = 0, 11234 expressions = [], 11235 parseFns = [], 11236 textLength = text.length, 11237 exp, 11238 concat = [], 11239 expressionPositions = []; 11240 11241 while (index < textLength) { 11242 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 11243 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 11244 if (index !== startIndex) { 11245 concat.push(unescapeText(text.substring(index, startIndex))); 11246 } 11247 exp = text.substring(startIndex + startSymbolLength, endIndex); 11248 expressions.push(exp); 11249 parseFns.push($parse(exp, parseStringifyInterceptor)); 11250 index = endIndex + endSymbolLength; 11251 expressionPositions.push(concat.length); 11252 concat.push(''); 11253 } else { 11254 // we did not find an interpolation, so we have to add the remainder to the separators array 11255 if (index !== textLength) { 11256 concat.push(unescapeText(text.substring(index))); 11257 } 11258 break; 11259 } 11260 } 11261 11262 // Concatenating expressions makes it hard to reason about whether some combination of 11263 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 11264 // single expression be used for iframe[src], object[src], etc., we ensure that the value 11265 // that's used is assigned or constructed by some JS code somewhere that is more testable or 11266 // make it obvious that you bound the value to some user controlled value. This helps reduce 11267 // the load when auditing for XSS issues. 11268 if (trustedContext && concat.length > 1) { 11269 $interpolateMinErr.throwNoconcat(text); 11270 } 11271 11272 if (!mustHaveExpression || expressions.length) { 11273 var compute = function(values) { 11274 for (var i = 0, ii = expressions.length; i < ii; i++) { 11275 if (allOrNothing && isUndefined(values[i])) return; 11276 concat[expressionPositions[i]] = values[i]; 11277 } 11278 return concat.join(''); 11279 }; 11280 11281 var getValue = function(value) { 11282 return trustedContext ? 11283 $sce.getTrusted(trustedContext, value) : 11284 $sce.valueOf(value); 11285 }; 11286 11287 return extend(function interpolationFn(context) { 11288 var i = 0; 11289 var ii = expressions.length; 11290 var values = new Array(ii); 11291 11292 try { 11293 for (; i < ii; i++) { 11294 values[i] = parseFns[i](context); 11295 } 11296 11297 return compute(values); 11298 } catch (err) { 11299 $exceptionHandler($interpolateMinErr.interr(text, err)); 11300 } 11301 11302 }, { 11303 // all of these properties are undocumented for now 11304 exp: text, //just for compatibility with regular watchers created via $watch 11305 expressions: expressions, 11306 $$watchDelegate: function(scope, listener) { 11307 var lastValue; 11308 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 11309 var currValue = compute(values); 11310 if (isFunction(listener)) { 11311 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 11312 }
11313 lastValue = currValue; 11314 }); 11315 } 11316 }); 11317 } 11318 11319 function parseStringifyInterceptor(value) { 11320 try { 11321 value = getValue(value); 11322 return allOrNothing && !isDefined(value) ? value : stringify(value); 11323 } catch (err) { 11324 $exceptionHandler($interpolateMinErr.interr(text, err)); 11325 } 11326 } 11327 } 11328 11329 11330 /** 11331 * @ngdoc method 11332 * @name $interpolate#startSymbol 11333 * @description 11334 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 11335 * 11336 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 11337 * the symbol. 11338 * 11339 * @returns {string} start symbol. 11340 */ 11341 $interpolate.startSymbol = function() { 11342 return startSymbol; 11343 }; 11344 11345 11346 /** 11347 * @ngdoc method 11348 * @name $interpolate#endSymbol 11349 * @description 11350 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 11351 * 11352 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 11353 * the symbol. 11354 * 11355 * @returns {string} end symbol. 11356 */ 11357 $interpolate.endSymbol = function() { 11358 return endSymbol; 11359 }; 11360 11361 return $interpolate; 11362 }]; 11363} 11364 11365function $IntervalProvider() { 11366 this.$get = ['$rootScope', '$window', '$q', '$$q', 11367 function($rootScope, $window, $q, $$q) { 11368 var intervals = {}; 11369 11370 11371 /** 11372 * @ngdoc service 11373 * @name $interval 11374 * 11375 * @description 11376 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 11377 * milliseconds. 11378 * 11379 * The return value of registering an interval function is a promise. This promise will be 11380 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 11381 * run indefinitely if `count` is not defined. The value of the notification will be the 11382 * number of iterations that have run. 11383 * To cancel an interval, call `$interval.cancel(promise)`. 11384 * 11385 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 11386 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 11387 * time. 11388 * 11389 * <div class="alert alert-warning"> 11390 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 11391 * with them. In particular they are not automatically destroyed when a controller's scope or a 11392 * directive's element are destroyed. 11393 * You should take this into consideration and make sure to always cancel the interval at the 11394 * appropriate moment. See the example below for more details on how and when to do this. 11395 * </div> 11396 * 11397 * @param {function()} fn A function that should be called repeatedly. 11398 * @param {number} delay Number of milliseconds between each function call. 11399 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 11400 * indefinitely. 11401 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 11402 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 11403 * @param {...*=} Pass additional parameters to the executed function. 11404 * @returns {promise} A promise which will be notified on each iteration. 11405 * 11406 * @example 11407 * <example module="intervalExample"> 11408 * <file name="index.html"> 11409 * <script> 11410 * angular.module('intervalExample', []) 11411 * .controller('ExampleController', ['$scope', '$interval', 11412 * function($scope, $interval) { 11413 * $scope.format = 'M/d/yy h:mm:ss a'; 11414 * $scope.blood_1 = 100; 11415 * $scope.blood_2 = 120; 11416 * 11417 * var stop; 11418 * $scope.fight = function() { 11419 * // Don't start a new fight if we are already fighting 11420 * if ( angular.isDefined(stop) ) return; 11421 * 11422 * stop = $interval(function() { 11423 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 11424 * $scope.blood_1 = $scope.blood_1 - 3; 11425 * $scope.blood_2 = $scope.blood_2 - 4; 11426 * } else { 11427 * $scope.stopFight(); 11428 * } 11429 * }, 100); 11430 * }; 11431 * 11432 * $scope.stopFight = function() { 11433 * if (angular.isDefined(stop)) { 11434 * $interval.cancel(stop); 11435 * stop = undefined; 11436 * } 11437 * }; 11438 * 11439 * $scope.resetFight = function() { 11440 * $scope.blood_1 = 100; 11441 * $scope.blood_2 = 120; 11442 * }; 11443 * 11444 * $scope.$on('$destroy', function() { 11445 * // Make sure that the interval is destroyed too 11446 * $scope.stopFight(); 11447 * }); 11448 * }]) 11449 * // Register the 'myCurrentTime' directive factory method. 11450 * // We inject $interval and dateFilter service since the factory method is DI. 11451 * .directive('myCurrentTime', ['$interval', 'dateFilter', 11452 * function($interval, dateFilter) { 11453 * // return the directive link function. (compile function not needed) 11454 * return function(scope, element, attrs) { 11455 * var format, // date format 11456 * stopTime; // so that we can cancel the time updates 11457 * 11458 * // used to update the UI 11459 * function updateTime() { 11460 * element.text(dateFilter(new Date(), format)); 11461 * } 11462 * 11463 * // watch the expression, and update the UI on change. 11464 * scope.$watch(attrs.myCurrentTime, function(value) { 11465 * format = value; 11466 * updateTime(); 11467 * }); 11468 * 11469 * stopTime = $interval(updateTime, 1000); 11470 * 11471 * // listen on DOM destroy (removal) event, and cancel the next UI update 11472 * // to prevent updating time after the DOM element was removed. 11473 * element.on('$destroy', function() { 11474 * $interval.cancel(stopTime); 11475 * }); 11476 * } 11477 * }]); 11478 * </script> 11479 * 11480 * <div>
11481 * <div ng-controller="ExampleController"> 11482 * <label>Date format: <input ng-model="format"></label> <hr/> 11483 * Current time is: <span my-current-time="format"></span> 11484 * <hr/> 11485 * Blood 1 : <font color='red'>{{blood_1}}</font> 11486 * Blood 2 : <font color='red'>{{blood_2}}</font> 11487 * <button type="button" data-ng-click="fight()">Fight</button> 11488 * <button type="button" data-ng-click="stopFight()">StopFight</button> 11489 * <button type="button" data-ng-click="resetFight()">resetFight</button> 11490 * </div> 11491 * </div> 11492 * 11493 * </file> 11494 * </example> 11495 */ 11496 function interval(fn, delay, count, invokeApply) { 11497 var hasParams = arguments.length > 4, 11498 args = hasParams ? sliceArgs(arguments, 4) : [], 11499 setInterval = $window.setInterval, 11500 clearInterval = $window.clearInterval, 11501 iteration = 0, 11502 skipApply = (isDefined(invokeApply) && !invokeApply), 11503 deferred = (skipApply ? $$q : $q).defer(), 11504 promise = deferred.promise; 11505 11506 count = isDefined(count) ? count : 0; 11507 11508 promise.then(null, null, (!hasParams) ? fn : function() { 11509 fn.apply(null, args); 11510 }); 11511 11512 promise.$$intervalId = setInterval(function tick() { 11513 deferred.notify(iteration++); 11514 11515 if (count > 0 && iteration >= count) { 11516 deferred.resolve(iteration); 11517 clearInterval(promise.$$intervalId); 11518 delete intervals[promise.$$intervalId]; 11519 } 11520 11521 if (!skipApply) $rootScope.$apply(); 11522 11523 }, delay); 11524 11525 intervals[promise.$$intervalId] = deferred; 11526 11527 return promise; 11528 } 11529 11530 11531 /** 11532 * @ngdoc method 11533 * @name $interval#cancel 11534 * 11535 * @description 11536 * Cancels a task associated with the `promise`. 11537 * 11538 * @param {Promise=} promise returned by the `$interval` function. 11539 * @returns {boolean} Returns `true` if the task was successfully canceled. 11540 */ 11541 interval.cancel = function(promise) { 11542 if (promise && promise.$$intervalId in intervals) { 11543 intervals[promise.$$intervalId].reject('canceled'); 11544 $window.clearInterval(promise.$$intervalId); 11545 delete intervals[promise.$$intervalId]; 11546 return true; 11547 } 11548 return false; 11549 }; 11550 11551 return interval; 11552 }]; 11553} 11554 11555/** 11556 * @ngdoc service 11557 * @name $locale 11558 * 11559 * @description 11560 * $locale service provides localization rules for various Angular components. As of right now the 11561 * only public api is: 11562 * 11563 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 11564 */ 11565 11566var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 11567 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 11568var $locationMinErr = minErr('$location'); 11569 11570 11571/** 11572 * Encode path using encodeUriSegment, ignoring forward slashes 11573 * 11574 * @param {string} path Path to encode 11575 * @returns {string} 11576 */ 11577function encodePath(path) { 11578 var segments = path.split('/'), 11579 i = segments.length; 11580 11581 while (i--) { 11582 segments[i] = encodeUriSegment(segments[i]); 11583 } 11584 11585 return segments.join('/'); 11586} 11587 11588function parseAbsoluteUrl(absoluteUrl, locationObj) { 11589 var parsedUrl = urlResolve(absoluteUrl); 11590 11591 locationObj.$$protocol = parsedUrl.protocol; 11592 locationObj.$$host = parsedUrl.hostname; 11593 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 11594} 11595 11596 11597function parseAppUrl(relativeUrl, locationObj) { 11598 var prefixed = (relativeUrl.charAt(0) !== '/'); 11599 if (prefixed) { 11600 relativeUrl = '/' + relativeUrl; 11601 } 11602 var match = urlResolve(relativeUrl); 11603 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 11604 match.pathname.substring(1) : match.pathname); 11605 locationObj.$$search = parseKeyValue(match.search); 11606 locationObj.$$hash = decodeURIComponent(match.hash); 11607 11608 // make sure path starts with '/'; 11609 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 11610 locationObj.$$path = '/' + locationObj.$$path; 11611 } 11612} 11613 11614 11615/** 11616 * 11617 * @param {string} begin 11618 * @param {string} whole 11619 * @returns {string} returns text from whole after begin or undefined if it does not begin with 11620 * expected string. 11621 */ 11622function beginsWith(begin, whole) { 11623 if (whole.indexOf(begin) === 0) { 11624 return whole.substr(begin.length); 11625 } 11626} 11627 11628 11629function stripHash(url) { 11630 var index = url.indexOf('#'); 11631 return index == -1 ? url : url.substr(0, index); 11632} 11633 11634function trimEmptyHash(url) { 11635 return url.replace(/(#.+)|#$/, '$1'); 11636} 11637 11638 11639function stripFile(url) { 11640 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 11641} 11642 11643/* return the server only (scheme://host:port) */ 11644function serverBase(url) { 11645 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 11646} 11647 11648 11649/** 11650 * LocationHtml5Url represents an url 11651 * This object is exposed as $location service when HTML5 mode is enabled and supported 11652 * 11653 * @constructor 11654 * @param {string} appBase application base URL 11655 * @param {string} appBaseNoFile application base URL stripped of any filename 11656 * @param {string} basePrefix url path prefix 11657 */ 11658function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 11659 this.$$html5 = true; 11660 basePrefix = basePrefix || ''; 11661 parseAbsoluteUrl(appBase, this); 11662 11663 11664 /** 11665 * Parse given html5 (regular) url string into properties 11666 * @param {string} url HTML5 url 11667 * @private 11668 */ 11669 this.$$parse = function(url) { 11670 var pathUrl = beginsWith(appBaseNoFile, url); 11671 if (!isString(pathUrl)) { 11672 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 11673 appBaseNoFile); 11674 } 11675 11676 parseAppUrl(pathUrl, this); 11677 11678 if (!this.$$path) { 11679 this.$$path = '/'; 11680 } 11681 11682 this.$$compose(); 11683 }; 11684 11685 /** 11686 * Compose url and update `absUrl` property 11687 * @private 11688 */ 11689 this.$$compose = function() {
vendor: 4,177 bytes, lines 11690-11821
11690 var search = toKeyValue(this.$$search), 11691 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11692 11693 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11694 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 11695 }; 11696 11697 this.$$parseLinkUrl = function(url, relHref) { 11698 if (relHref && relHref[0] === '#') { 11699 // special case for links to hash fragments: 11700 // keep the old url and only replace the hash fragment 11701 this.hash(relHref.slice(1)); 11702 return true; 11703 } 11704 var appUrl, prevAppUrl; 11705 var rewrittenUrl; 11706 11707 if (isDefined(appUrl = beginsWith(appBase, url))) { 11708 prevAppUrl = appUrl; 11709 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) { 11710 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 11711 } else { 11712 rewrittenUrl = appBase + prevAppUrl; 11713 } 11714 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) { 11715 rewrittenUrl = appBaseNoFile + appUrl; 11716 } else if (appBaseNoFile == url + '/') { 11717 rewrittenUrl = appBaseNoFile; 11718 } 11719 if (rewrittenUrl) { 11720 this.$$parse(rewrittenUrl); 11721 } 11722 return !!rewrittenUrl; 11723 }; 11724} 11725 11726 11727/** 11728 * LocationHashbangUrl represents url 11729 * This object is exposed as $location service when developer doesn't opt into html5 mode. 11730 * It also serves as the base class for html5 mode fallback on legacy browsers. 11731 * 11732 * @constructor 11733 * @param {string} appBase application base URL 11734 * @param {string} appBaseNoFile application base URL stripped of any filename 11735 * @param {string} hashPrefix hashbang prefix 11736 */ 11737function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 11738 11739 parseAbsoluteUrl(appBase, this); 11740 11741 11742 /** 11743 * Parse given hashbang url into properties 11744 * @param {string} url Hashbang url 11745 * @private 11746 */ 11747 this.$$parse = function(url) { 11748 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 11749 var withoutHashUrl; 11750 11751 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 11752 11753 // The rest of the url starts with a hash so we have 11754 // got either a hashbang path or a plain hash fragment 11755 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 11756 if (isUndefined(withoutHashUrl)) { 11757 // There was no hashbang prefix so we just have a hash fragment 11758 withoutHashUrl = withoutBaseUrl; 11759 } 11760 11761 } else { 11762 // There was no hashbang path nor hash fragment: 11763 // If we are in HTML5 mode we use what is left as the path; 11764 // Otherwise we ignore what is left 11765 if (this.$$html5) { 11766 withoutHashUrl = withoutBaseUrl; 11767 } else { 11768 withoutHashUrl = ''; 11769 if (isUndefined(withoutBaseUrl)) { 11770 appBase = url; 11771 this.replace(); 11772 } 11773 } 11774 } 11775 11776 parseAppUrl(withoutHashUrl, this); 11777 11778 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 11779 11780 this.$$compose(); 11781 11782 /* 11783 * In Windows, on an anchor node on documents loaded from 11784 * the filesystem, the browser will return a pathname 11785 * prefixed with the drive name ('/C:/path') when a 11786 * pathname without a drive is set: 11787 * * a.setAttribute('href', '/foo') 11788 * * a.pathname === '/C:/foo' //true 11789 * 11790 * Inside of Angular, we're always using pathnames that 11791 * do not include drive names for routing. 11792 */ 11793 function removeWindowsDriveName(path, url, base) { 11794 /* 11795 Matches paths for file protocol on windows, 11796 such as /C:/foo/bar, and captures only /foo/bar. 11797 */ 11798 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 11799 11800 var firstPathSegmentMatch; 11801 11802 //Get the relative path from the input URL. 11803 if (url.indexOf(base) === 0) { 11804 url = url.replace(base, ''); 11805 } 11806 11807 // The input URL intentionally contains a first path segment that ends with a colon. 11808 if (windowsFilePathExp.exec(url)) { 11809 return path; 11810 } 11811 11812 firstPathSegmentMatch = windowsFilePathExp.exec(path); 11813 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 11814 } 11815 }; 11816 11817 /** 11818 * Compose hashbang url and update `absUrl` property 11819 * @private 11820 */ 11821 this.$$compose = function() {
11822 var search = toKeyValue(this.$$search), 11823 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11824 11825 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11826 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 11827 }; 11828 11829 this.$$parseLinkUrl = function(url, relHref) { 11830 if (stripHash(appBase) == stripHash(url)) { 11831 this.$$parse(url); 11832 return true; 11833 } 11834 return false; 11835 }; 11836} 11837 11838 11839/** 11840 * LocationHashbangUrl represents url 11841 * This object is exposed as $location service when html5 history api is enabled but the browser 11842 * does not support it. 11843 * 11844 * @constructor 11845 * @param {string} appBase application base URL 11846 * @param {string} appBaseNoFile application base URL stripped of any filename 11847 * @param {string} hashPrefix hashbang prefix 11848 */ 11849function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 11850 this.$$html5 = true; 11851 LocationHashbangUrl.apply(this, arguments); 11852 11853 this.$$parseLinkUrl = function(url, relHref) { 11854 if (relHref && relHref[0] === '#') { 11855 // special case for links to hash fragments: 11856 // keep the old url and only replace the hash fragment 11857 this.hash(relHref.slice(1)); 11858 return true; 11859 } 11860 11861 var rewrittenUrl; 11862 var appUrl; 11863 11864 if (appBase == stripHash(url)) { 11865 rewrittenUrl = url; 11866 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 11867 rewrittenUrl = appBase + hashPrefix + appUrl; 11868 } else if (appBaseNoFile === url + '/') { 11869 rewrittenUrl = appBaseNoFile; 11870 } 11871 if (rewrittenUrl) { 11872 this.$$parse(rewrittenUrl); 11873 } 11874 return !!rewrittenUrl; 11875 }; 11876 11877 this.$$compose = function() { 11878 var search = toKeyValue(this.$$search), 11879 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11880 11881 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11882 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 11883 this.$$absUrl = appBase + hashPrefix + this.$$url; 11884 }; 11885 11886} 11887 11888 11889var locationPrototype = { 11890 11891 /** 11892 * Are we in html5 mode? 11893 * @private 11894 */ 11895 $$html5: false, 11896 11897 /** 11898 * Has any change been replacing? 11899 * @private 11900 */ 11901 $$replace: false, 11902 11903 /** 11904 * @ngdoc method 11905 * @name $location#absUrl 11906 * 11907 * @description 11908 * This method is getter only. 11909 * 11910 * Return full url representation with all segments encoded according to rules specified in 11911 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 11912 * 11913 * 11914 * ```js 11915 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11916 * var absUrl = $location.absUrl(); 11917 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 11918 * ``` 11919 * 11920 * @return {string} full url 11921 */ 11922 absUrl: locationGetter('$$absUrl'), 11923 11924 /** 11925 * @ngdoc method 11926 * @name $location#url 11927 * 11928 * @description 11929 * This method is getter / setter. 11930 * 11931 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 11932 * 11933 * Change path, search and hash, when called with parameter and return `$location`. 11934 * 11935 * 11936 * ```js 11937 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11938 * var url = $location.url(); 11939 * // => "/some/path?foo=bar&baz=xoxo" 11940 * ``` 11941 * 11942 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 11943 * @return {string} url 11944 */ 11945 url: function(url) { 11946 if (isUndefined(url)) { 11947 return this.$$url; 11948 } 11949 11950 var match = PATH_MATCH.exec(url); 11951 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 11952 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 11953 this.hash(match[5] || ''); 11954 11955 return this; 11956 }, 11957 11958 /** 11959 * @ngdoc method 11960 * @name $location#protocol 11961 * 11962 * @description 11963 * This method is getter only. 11964 * 11965 * Return protocol of current url. 11966 * 11967 * 11968 * ```js 11969 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11970 * var protocol = $location.protocol(); 11971 * // => "http" 11972 * ``` 11973 * 11974 * @return {string} protocol of current url 11975 */ 11976 protocol: locationGetter('$$protocol'), 11977 11978 /** 11979 * @ngdoc method 11980 * @name $location#host 11981 * 11982 * @description 11983 * This method is getter only. 11984 * 11985 * Return host of current url. 11986 * 11987 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 11988 * 11989 * 11990 * ```js
11991 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11992 * var host = $location.host(); 11993 * // => "example.com" 11994 * 11995 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 11996 * host = $location.host(); 11997 * // => "example.com" 11998 * host = location.host; 11999 * // => "example.com:8080" 12000 * ``` 12001 * 12002 * @return {string} host of current url. 12003 */ 12004 host: locationGetter('$$host'), 12005 12006 /** 12007 * @ngdoc method 12008 * @name $location#port 12009 * 12010 * @description 12011 * This method is getter only. 12012 * 12013 * Return port of current url. 12014 * 12015 * 12016 * ```js 12017 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12018 * var port = $location.port(); 12019 * // => 80 12020 * ``` 12021 * 12022 * @return {Number} port 12023 */ 12024 port: locationGetter('$$port'), 12025 12026 /** 12027 * @ngdoc method 12028 * @name $location#path 12029 * 12030 * @description 12031 * This method is getter / setter. 12032 * 12033 * Return path of current url when called without any parameter. 12034 * 12035 * Change path when called with parameter and return `$location`. 12036 * 12037 * Note: Path should always begin with forward slash (/), this method will add the forward slash 12038 * if it is missing. 12039 * 12040 * 12041 * ```js 12042 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12043 * var path = $location.path(); 12044 * // => "/some/path" 12045 * ``` 12046 * 12047 * @param {(string|number)=} path New path 12048 * @return {string} path 12049 */ 12050 path: locationGetterSetter('$$path', function(path) { 12051 path = path !== null ? path.toString() : ''; 12052 return path.charAt(0) == '/' ? path : '/' + path; 12053 }), 12054 12055 /** 12056 * @ngdoc method 12057 * @name $location#search 12058 * 12059 * @description 12060 * This method is getter / setter. 12061 * 12062 * Return search part (as object) of current url when called without any parameter. 12063 * 12064 * Change search part when called with parameter and return `$location`. 12065 * 12066 * 12067 * ```js 12068 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 12069 * var searchObject = $location.search(); 12070 * // => {foo: 'bar', baz: 'xoxo'} 12071 * 12072 * // set foo to 'yipee' 12073 * $location.search('foo', 'yipee'); 12074 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 12075 * ``` 12076 * 12077 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 12078 * hash object. 12079 * 12080 * When called with a single argument the method acts as a setter, setting the `search` component 12081 * of `$location` to the specified value. 12082 * 12083 * If the argument is a hash object containing an array of values, these values will be encoded 12084 * as duplicate search parameters in the url. 12085 * 12086 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 12087 * will override only a single search property. 12088 * 12089 * If `paramValue` is an array, it will override the property of the `search` component of 12090 * `$location` specified via the first argument. 12091 * 12092 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 12093 * 12094 * If `paramValue` is `true`, the property specified via the first argument will be added with no 12095 * value nor trailing equal sign. 12096 * 12097 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 12098 * one or more arguments returns `$location` object itself. 12099 */ 12100 search: function(search, paramValue) { 12101 switch (arguments.length) { 12102 case 0: 12103 return this.$$search; 12104 case 1: 12105 if (isString(search) || isNumber(search)) { 12106 search = search.toString(); 12107 this.$$search = parseKeyValue(search); 12108 } else if (isObject(search)) { 12109 search = copy(search, {}); 12110 // remove object undefined or null properties 12111 forEach(search, function(value, key) { 12112 if (value == null) delete search[key]; 12113 }); 12114 12115 this.$$search = search; 12116 } else { 12117 throw $locationMinErr('isrcharg', 12118 'The first argument of the `$location#search()` call must be a string or an object.'); 12119 } 12120 break; 12121 default: 12122 if (isUndefined(paramValue) || paramValue === null) { 12123 delete this.$$search[search]; 12124 } else { 12125 this.$$search[search] = paramValue; 12126 } 12127 } 12128 12129 this.$$compose(); 12130 return this; 12131 }, 12132 12133 /** 12134 * @ngdoc method 12135 * @name $location#hash 12136 * 12137 * @description 12138 * This method is getter / setter. 12139 * 12140 * Return hash fragment when called without any parameter. 12141 * 12142 * Change hash fragment when called with parameter and return `$location`. 12143 * 12144 * 12145 * ```js
12146 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 12147 * var hash = $location.hash(); 12148 * // => "hashValue" 12149 * ``` 12150 * 12151 * @param {(string|number)=} hash New hash fragment 12152 * @return {string} hash 12153 */ 12154 hash: locationGetterSetter('$$hash', function(hash) { 12155 return hash !== null ? hash.toString() : ''; 12156 }), 12157 12158 /** 12159 * @ngdoc method 12160 * @name $location#replace 12161 * 12162 * @description 12163 * If called, all changes to $location during current `$digest` will be replacing current history 12164 * record, instead of adding new one. 12165 */ 12166 replace: function() { 12167 this.$$replace = true; 12168 return this; 12169 } 12170}; 12171 12172forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 12173 Location.prototype = Object.create(locationPrototype); 12174 12175 /** 12176 * @ngdoc method 12177 * @name $location#state 12178 * 12179 * @description 12180 * This method is getter / setter. 12181 * 12182 * Return the history state object when called without any parameter. 12183 * 12184 * Change the history state object when called with one parameter and return `$location`. 12185 * The state object is later passed to `pushState` or `replaceState`. 12186 * 12187 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 12188 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 12189 * older browsers (like IE9 or Android < 4.0), don't use this method. 12190 * 12191 * @param {object=} state State object for pushState or replaceState 12192 * @return {object} state 12193 */ 12194 Location.prototype.state = function(state) { 12195 if (!arguments.length) { 12196 return this.$$state; 12197 } 12198 12199 if (Location !== LocationHtml5Url || !this.$$html5) { 12200 throw $locationMinErr('nostate', 'History API state support is available only ' + 12201 'in HTML5 mode and only in browsers supporting HTML5 History API'); 12202 } 12203 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 12204 // but we're changing the $$state reference to $browser.state() during the $digest 12205 // so the modification window is narrow. 12206 this.$$state = isUndefined(state) ? null : state; 12207 12208 return this; 12209 }; 12210}); 12211 12212 12213function locationGetter(property) { 12214 return function() { 12215 return this[property]; 12216 }; 12217} 12218 12219 12220function locationGetterSetter(property, preprocess) { 12221 return function(value) { 12222 if (isUndefined(value)) { 12223 return this[property]; 12224 } 12225 12226 this[property] = preprocess(value); 12227 this.$$compose(); 12228 12229 return this; 12230 }; 12231} 12232 12233 12234/** 12235 * @ngdoc service 12236 * @name $location 12237 * 12238 * @requires $rootElement 12239 * 12240 * @description 12241 * The $location service parses the URL in the browser address bar (based on the 12242 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 12243 * available to your application. Changes to the URL in the address bar are reflected into 12244 * $location service and changes to $location are reflected into the browser address bar. 12245 * 12246 * **The $location service:** 12247 * 12248 * - Exposes the current URL in the browser address bar, so you can 12249 * - Watch and observe the URL. 12250 * - Change the URL. 12251 * - Synchronizes the URL with the browser when the user 12252 * - Changes the address bar. 12253 * - Clicks the back or forward button (or clicks a History link). 12254 * - Clicks on a link. 12255 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 12256 * 12257 * For more information see {@link guide/$location Developer Guide: Using $location} 12258 */ 12259 12260/** 12261 * @ngdoc provider 12262 * @name $locationProvider 12263 * @description 12264 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 12265 */ 12266function $LocationProvider() { 12267 var hashPrefix = '', 12268 html5Mode = { 12269 enabled: false, 12270 requireBase: true, 12271 rewriteLinks: true 12272 }; 12273 12274 /** 12275 * @ngdoc method 12276 * @name $locationProvider#hashPrefix 12277 * @description 12278 * @param {string=} prefix Prefix for hash part (containing path and search) 12279 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12280 */ 12281 this.hashPrefix = function(prefix) { 12282 if (isDefined(prefix)) { 12283 hashPrefix = prefix; 12284 return this; 12285 } else { 12286 return hashPrefix; 12287 } 12288 }; 12289 12290 /** 12291 * @ngdoc method 12292 * @name $locationProvider#html5Mode 12293 * @description 12294 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 12295 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 12296 * properties: 12297 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 12298 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 12299 * support `pushState`. 12300 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 12301 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 12302 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 12303 * See the {@link guide/$location $location guide for more information} 12304 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 12305 * enables/disables url rewriting for relative links. 12306 * 12307 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 12308 */ 12309 this.html5Mode = function(mode) { 12310 if (isBoolean(mode)) { 12311 html5Mode.enabled = mode; 12312 return this; 12313 } else if (isObject(mode)) { 12314 12315 if (isBoolean(mode.enabled)) { 12316 html5Mode.enabled = mode.enabled; 12317 } 12318 12319 if (isBoolean(mode.requireBase)) { 12320 html5Mode.requireBase = mode.requireBase; 12321 } 12322 12323 if (isBoolean(mode.rewriteLinks)) { 12324 html5Mode.rewriteLinks = mode.rewriteLinks; 12325 } 12326 12327 return this; 12328 } else { 12329 return html5Mode; 12330 } 12331 }; 12332 12333 /** 12334 * @ngdoc event 12335 * @name $location#$locationChangeStart 12336 * @eventType broadcast on root scope 12337 * @description 12338 * Broadcasted before a URL will change. 12339 * 12340 * This change can be prevented by calling 12341 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 12342 * details about event object. Upon successful change 12343 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 12344 * 12345 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 12346 * the browser supports the HTML5 History API. 12347 * 12348 * @param {Object} angularEvent Synthetic event object. 12349 * @param {string} newUrl New URL 12350 * @param {string=} oldUrl URL that was before it was changed. 12351 * @param {string=} newState New history state object 12352 * @param {string=} oldState History state object that was before it was changed. 12353 */ 12354 12355 /** 12356 * @ngdoc event 12357 * @name $location#$locationChangeSuccess 12358 * @eventType broadcast on root scope 12359 * @description 12360 * Broadcasted after a URL was changed. 12361 * 12362 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 12363 * the browser supports the HTML5 History API. 12364 * 12365 * @param {Object} angularEvent Synthetic event object. 12366 * @param {string} newUrl New URL 12367 * @param {string=} oldUrl URL that was before it was changed. 12368 * @param {string=} newState New history state object 12369 * @param {string=} oldState History state object that was before it was changed. 12370 */ 12371 12372 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 12373 function($rootScope, $browser, $sniffer, $rootElement, $window) { 12374 var $location, 12375 LocationMode, 12376 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 12377 initialUrl = $browser.url(), 12378 appBase; 12379 12380 if (html5Mode.enabled) { 12381 if (!baseHref && html5Mode.requireBase) { 12382 throw $locationMinErr('nobase', 12383 "$location in HTML5 mode requires a <base> tag to be present!"); 12384 } 12385 appBase = serverBase(initialUrl) + (baseHref || '/'); 12386 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 12387 } else { 12388 appBase = stripHash(initialUrl); 12389 LocationMode = LocationHashbangUrl; 12390 } 12391 var appBaseNoFile = stripFile(appBase); 12392
12393 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 12394 $location.$$parseLinkUrl(initialUrl, initialUrl); 12395 12396 $location.$$state = $browser.state(); 12397 12398 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 12399 12400 function setBrowserUrlWithFallback(url, replace, state) { 12401 var oldUrl = $location.url(); 12402 var oldState = $location.$$state; 12403 try { 12404 $browser.url(url, replace, state); 12405 12406 // Make sure $location.state() returns referentially identical (not just deeply equal) 12407 // state object; this makes possible quick checking if the state changed in the digest 12408 // loop. Checking deep equality would be too expensive. 12409 $location.$$state = $browser.state(); 12410 } catch (e) { 12411 // Restore old values if pushState fails 12412 $location.url(oldUrl); 12413 $location.$$state = oldState; 12414 12415 throw e; 12416 } 12417 } 12418 12419 $rootElement.on('click', function(event) { 12420 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 12421 // currently we open nice url link and redirect then 12422 12423 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 12424 12425 var elm = jqLite(event.target); 12426 12427 // traverse the DOM up to find first A tag 12428 while (nodeName_(elm[0]) !== 'a') { 12429 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 12430 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 12431 } 12432 12433 var absHref = elm.prop('href'); 12434 // get the actual href attribute - see 12435 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 12436 var relHref = elm.attr('href') || elm.attr('xlink:href'); 12437 12438 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 12439 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 12440 // an animation. 12441 absHref = urlResolve(absHref.animVal).href; 12442 } 12443 12444 // Ignore when url is started with javascript: or mailto: 12445 if (IGNORE_URI_REGEXP.test(absHref)) return; 12446 12447 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 12448 if ($location.$$parseLinkUrl(absHref, relHref)) { 12449 // We do a preventDefault for all urls that are part of the angular application, 12450 // in html5mode and also without, so that we are able to abort navigation without 12451 // getting double entries in the location history. 12452 event.preventDefault(); 12453 // update location manually 12454 if ($location.absUrl() != $browser.url()) { 12455 $rootScope.$apply(); 12456 // hack to work around FF6 bug 684208 when scenario runner clicks on links 12457 $window.angular['ff-684208-preventDefault'] = true; 12458 } 12459 } 12460 } 12461 }); 12462 12463 12464 // rewrite hashbang url <> html5 url 12465 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 12466 $browser.url($location.absUrl(), true); 12467 } 12468 12469 var initializing = true; 12470 12471 // update $location when $browser url changes 12472 $browser.onUrlChange(function(newUrl, newState) { 12473 12474 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { 12475 // If we are navigating outside of the app then force a reload 12476 $window.location.href = newUrl; 12477 return; 12478 } 12479 12480 $rootScope.$evalAsync(function() { 12481 var oldUrl = $location.absUrl(); 12482 var oldState = $location.$$state; 12483 var defaultPrevented; 12484 12485 $location.$$parse(newUrl); 12486 $location.$$state = newState; 12487 12488 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12489 newState, oldState).defaultPrevented; 12490 12491 // if the location was changed by a `$locationChangeStart` handler then stop 12492 // processing this location change 12493 if ($location.absUrl() !== newUrl) return; 12494 12495 if (defaultPrevented) { 12496 $location.$$parse(oldUrl); 12497 $location.$$state = oldState; 12498 setBrowserUrlWithFallback(oldUrl, false, oldState); 12499 } else { 12500 initializing = false;
vendor: 4,210 bytes, lines 12501-12643
12501 afterLocationChange(oldUrl, oldState); 12502 } 12503 }); 12504 if (!$rootScope.$$phase) $rootScope.$digest(); 12505 }); 12506 12507 // update browser 12508 $rootScope.$watch(function $locationWatch() { 12509 var oldUrl = trimEmptyHash($browser.url()); 12510 var newUrl = trimEmptyHash($location.absUrl()); 12511 var oldState = $browser.state(); 12512 var currentReplace = $location.$$replace; 12513 var urlOrStateChanged = oldUrl !== newUrl || 12514 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 12515 12516 if (initializing || urlOrStateChanged) { 12517 initializing = false; 12518 12519 $rootScope.$evalAsync(function() { 12520 var newUrl = $location.absUrl(); 12521 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12522 $location.$$state, oldState).defaultPrevented; 12523 12524 // if the location was changed by a `$locationChangeStart` handler then stop 12525 // processing this location change 12526 if ($location.absUrl() !== newUrl) return; 12527 12528 if (defaultPrevented) { 12529 $location.$$parse(oldUrl); 12530 $location.$$state = oldState; 12531 } else { 12532 if (urlOrStateChanged) { 12533 setBrowserUrlWithFallback(newUrl, currentReplace, 12534 oldState === $location.$$state ? null : $location.$$state); 12535 } 12536 afterLocationChange(oldUrl, oldState); 12537 } 12538 }); 12539 } 12540 12541 $location.$$replace = false; 12542 12543 // we don't need to return anything because $evalAsync will make the digest loop dirty when 12544 // there is a change 12545 }); 12546 12547 return $location; 12548 12549 function afterLocationChange(oldUrl, oldState) { 12550 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 12551 $location.$$state, oldState); 12552 } 12553}]; 12554} 12555 12556/** 12557 * @ngdoc service 12558 * @name $log 12559 * @requires $window 12560 * 12561 * @description 12562 * Simple service for logging. Default implementation safely writes the message 12563 * into the browser's console (if present). 12564 * 12565 * The main purpose of this service is to simplify debugging and troubleshooting. 12566 * 12567 * The default is to log `debug` messages. You can use 12568 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 12569 * 12570 * @example 12571 <example module="logExample"> 12572 <file name="script.js"> 12573 angular.module('logExample', []) 12574 .controller('LogController', ['$scope', '$log', function($scope, $log) { 12575 $scope.$log = $log; 12576 $scope.message = 'Hello World!'; 12577 }]); 12578 </file> 12579 <file name="index.html"> 12580 <div ng-controller="LogController"> 12581 <p>Reload this page with open console, enter text and hit the log button...</p> 12582 <label>Message: 12583 <input type="text" ng-model="message" /></label> 12584 <button ng-click="$log.log(message)">log</button> 12585 <button ng-click="$log.warn(message)">warn</button> 12586 <button ng-click="$log.info(message)">info</button> 12587 <button ng-click="$log.error(message)">error</button> 12588 <button ng-click="$log.debug(message)">debug</button> 12589 </div> 12590 </file> 12591 </example> 12592 */ 12593 12594/** 12595 * @ngdoc provider 12596 * @name $logProvider 12597 * @description 12598 * Use the `$logProvider` to configure how the application logs messages 12599 */ 12600function $LogProvider() { 12601 var debug = true, 12602 self = this; 12603 12604 /** 12605 * @ngdoc method 12606 * @name $logProvider#debugEnabled 12607 * @description 12608 * @param {boolean=} flag enable or disable debug level messages 12609 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12610 */ 12611 this.debugEnabled = function(flag) { 12612 if (isDefined(flag)) { 12613 debug = flag; 12614 return this; 12615 } else { 12616 return debug; 12617 } 12618 }; 12619 12620 this.$get = ['$window', function($window) { 12621 return { 12622 /** 12623 * @ngdoc method 12624 * @name $log#log 12625 * 12626 * @description 12627 * Write a log message 12628 */ 12629 log: consoleLog('log'), 12630 12631 /** 12632 * @ngdoc method 12633 * @name $log#info 12634 * 12635 * @description 12636 * Write an information message 12637 */ 12638 info: consoleLog('info'), 12639 12640 /** 12641 * @ngdoc method 12642 * @name $log#warn 12643 *
12644 * @description 12645 * Write a warning message 12646 */ 12647 warn: consoleLog('warn'), 12648 12649 /** 12650 * @ngdoc method 12651 * @name $log#error 12652 * 12653 * @description 12654 * Write an error message 12655 */ 12656 error: consoleLog('error'), 12657 12658 /** 12659 * @ngdoc method 12660 * @name $log#debug 12661 * 12662 * @description 12663 * Write a debug message 12664 */ 12665 debug: (function() { 12666 var fn = consoleLog('debug'); 12667 12668 return function() { 12669 if (debug) { 12670 fn.apply(self, arguments); 12671 } 12672 }; 12673 }()) 12674 }; 12675 12676 function formatError(arg) { 12677 if (arg instanceof Error) { 12678 if (arg.stack) { 12679 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 12680 ? 'Error: ' + arg.message + '\n' + arg.stack 12681 : arg.stack; 12682 } else if (arg.sourceURL) { 12683 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 12684 } 12685 } 12686 return arg; 12687 } 12688 12689 function consoleLog(type) { 12690 var console = $window.console || {}, 12691 logFn = console[type] || console.log || noop, 12692 hasApply = false; 12693 12694 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 12695 // The reason behind this is that console.log has type "object" in IE8... 12696 try { 12697 hasApply = !!logFn.apply; 12698 } catch (e) {} 12699 12700 if (hasApply) { 12701 return function() { 12702 var args = []; 12703 forEach(arguments, function(arg) { 12704 args.push(formatError(arg)); 12705 }); 12706 return logFn.apply(console, args); 12707 }; 12708 } 12709 12710 // we are IE which either doesn't have window.console => this is noop and we do nothing, 12711 // or we are IE where console.log doesn't have apply so we log at least first 2 args 12712 return function(arg1, arg2) { 12713 logFn(arg1, arg2 == null ? '' : arg2); 12714 }; 12715 } 12716 }]; 12717} 12718 12719/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 12720 * Any commits to this file should be reviewed with security in mind. * 12721 * Changes to this file can potentially create security vulnerabilities. * 12722 * An approval from 2 Core members with history of modifying * 12723 * this file is required. * 12724 * * 12725 * Does the change somehow allow for arbitrary javascript to be executed? * 12726 * Or allows for someone to change the prototype of built-in objects? * 12727 * Or gives undesired access to variables likes document or window? * 12728 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 12729 12730var $parseMinErr = minErr('$parse'); 12731 12732// Sandboxing Angular Expressions 12733// ------------------------------ 12734// Angular expressions are generally considered safe because these expressions only have direct 12735// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 12736// obtaining a reference to native JS functions such as the Function constructor. 12737// 12738// As an example, consider the following Angular expression: 12739// 12740// {}.toString.constructor('alert("evil JS code")') 12741// 12742// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 12743// against the expression language, but not to prevent exploits that were enabled by exposing 12744// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 12745// practice and therefore we are not even trying to protect against interaction with an object 12746// explicitly exposed in this way. 12747// 12748// In general, it is not possible to access a Window object from an angular expression unless a 12749// window or some DOM object that has a reference to window is published onto a Scope. 12750// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 12751// native objects. 12752// 12753// See https://docs.angularjs.org/guide/security 12754 12755 12756function ensureSafeMemberName(name, fullExpression) { 12757 if (name === "__defineGetter__" || name === "__defineSetter__" 12758 || name === "__lookupGetter__" || name === "__lookupSetter__" 12759 || name === "__proto__") { 12760 throw $parseMinErr('isecfld', 12761 'Attempting to access a disallowed field in Angular expressions! ' 12762 + 'Expression: {0}', fullExpression); 12763 } 12764 return name; 12765} 12766 12767function getStringValue(name, fullExpression) { 12768 // From the JavaScript docs: 12769 // Property names must be strings. This means that non-string objects cannot be used 12770 // as keys in an object. Any non-string object, including a number, is typecasted 12771 // into a string via the toString method. 12772 // 12773 // So, to ensure that we are checking the same `name` that JavaScript would use, 12774 // we cast it to a string, if possible. 12775 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string, 12776 // this is, this will handle objects that misbehave. 12777 name = name + ''; 12778 if (!isString(name)) { 12779 throw $parseMinErr('iseccst', 12780 'Cannot convert object to primitive value! ' 12781 + 'Expression: {0}', fullExpression); 12782 } 12783 return name; 12784} 12785 12786function ensureSafeObject(obj, fullExpression) { 12787 // nifty check if obj is Function that is fast and works across iframes and other contexts 12788 if (obj) { 12789 if (obj.constructor === obj) { 12790 throw $parseMinErr('isecfn', 12791 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12792 fullExpression); 12793 } else if (// isWindow(obj) 12794 obj.window === obj) { 12795 throw $parseMinErr('isecwindow', 12796 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 12797 fullExpression); 12798 } else if (// isElement(obj) 12799 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 12800 throw $parseMinErr('isecdom', 12801 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 12802 fullExpression); 12803 } else if (// block Object so that we can't get hold of dangerous Object.* methods 12804 obj === Object) { 12805 throw $parseMinErr('isecobj', 12806 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 12807 fullExpression); 12808 } 12809 } 12810 return obj; 12811} 12812 12813var CALL = Function.prototype.call; 12814var APPLY = Function.prototype.apply; 12815var BIND = Function.prototype.bind; 12816 12817function ensureSafeFunction(obj, fullExpression) { 12818 if (obj) { 12819 if (obj.constructor === obj) { 12820 throw $parseMinErr('isecfn', 12821 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12822 fullExpression); 12823 } else if (obj === CALL || obj === APPLY || obj === BIND) { 12824 throw $parseMinErr('isecff', 12825 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 12826 fullExpression); 12827 } 12828 } 12829} 12830 12831function ensureSafeAssignContext(obj, fullExpression) { 12832 if (obj) { 12833 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor || 12834 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) { 12835 throw $parseMinErr('isecaf', 12836 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression); 12837 } 12838 } 12839} 12840 12841var OPERATORS = createMap(); 12842forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 12843var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 12844 12845
vendor: 47,856 bytes, lines 12846-14352
12846///////////////////////////////////////// 12847 12848 12849/** 12850 * @constructor 12851 */ 12852var Lexer = function(options) { 12853 this.options = options; 12854}; 12855 12856Lexer.prototype = { 12857 constructor: Lexer, 12858 12859 lex: function(text) { 12860 this.text = text; 12861 this.index = 0; 12862 this.tokens = []; 12863 12864 while (this.index < this.text.length) { 12865 var ch = this.text.charAt(this.index); 12866 if (ch === '"' || ch === "'") { 12867 this.readString(ch); 12868 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 12869 this.readNumber(); 12870 } else if (this.isIdent(ch)) { 12871 this.readIdent(); 12872 } else if (this.is(ch, '(){}[].,;:?')) { 12873 this.tokens.push({index: this.index, text: ch}); 12874 this.index++; 12875 } else if (this.isWhitespace(ch)) { 12876 this.index++; 12877 } else { 12878 var ch2 = ch + this.peek(); 12879 var ch3 = ch2 + this.peek(2); 12880 var op1 = OPERATORS[ch]; 12881 var op2 = OPERATORS[ch2]; 12882 var op3 = OPERATORS[ch3]; 12883 if (op1 || op2 || op3) { 12884 var token = op3 ? ch3 : (op2 ? ch2 : ch); 12885 this.tokens.push({index: this.index, text: token, operator: true}); 12886 this.index += token.length; 12887 } else { 12888 this.throwError('Unexpected next character ', this.index, this.index + 1); 12889 } 12890 } 12891 } 12892 return this.tokens; 12893 }, 12894 12895 is: function(ch, chars) { 12896 return chars.indexOf(ch) !== -1; 12897 }, 12898 12899 peek: function(i) { 12900 var num = i || 1; 12901 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 12902 }, 12903 12904 isNumber: function(ch) { 12905 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 12906 }, 12907 12908 isWhitespace: function(ch) { 12909 // IE treats non-breaking space as \u00A0 12910 return (ch === ' ' || ch === '\r' || ch === '\t' || 12911 ch === '\n' || ch === '\v' || ch === '\u00A0'); 12912 }, 12913 12914 isIdent: function(ch) { 12915 return ('a' <= ch && ch <= 'z' || 12916 'A' <= ch && ch <= 'Z' || 12917 '_' === ch || ch === '$'); 12918 }, 12919 12920 isExpOperator: function(ch) { 12921 return (ch === '-' || ch === '+' || this.isNumber(ch)); 12922 }, 12923 12924 throwError: function(error, start, end) { 12925 end = end || this.index; 12926 var colStr = (isDefined(start) 12927 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 12928 : ' ' + end); 12929 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 12930 error, colStr, this.text); 12931 }, 12932 12933 readNumber: function() { 12934 var number = ''; 12935 var start = this.index; 12936 while (this.index < this.text.length) { 12937 var ch = lowercase(this.text.charAt(this.index)); 12938 if (ch == '.' || this.isNumber(ch)) { 12939 number += ch; 12940 } else { 12941 var peekCh = this.peek(); 12942 if (ch == 'e' && this.isExpOperator(peekCh)) { 12943 number += ch; 12944 } else if (this.isExpOperator(ch) && 12945 peekCh && this.isNumber(peekCh) && 12946 number.charAt(number.length - 1) == 'e') { 12947 number += ch; 12948 } else if (this.isExpOperator(ch) && 12949 (!peekCh || !this.isNumber(peekCh)) && 12950 number.charAt(number.length - 1) == 'e') { 12951 this.throwError('Invalid exponent'); 12952 } else { 12953 break; 12954 } 12955 } 12956 this.index++; 12957 } 12958 this.tokens.push({ 12959 index: start, 12960 text: number, 12961 constant: true, 12962 value: Number(number) 12963 }); 12964 }, 12965 12966 readIdent: function() { 12967 var start = this.index; 12968 while (this.index < this.text.length) { 12969 var ch = this.text.charAt(this.index); 12970 if (!(this.isIdent(ch) || this.isNumber(ch))) { 12971 break; 12972 } 12973 this.index++; 12974 } 12975 this.tokens.push({ 12976 index: start, 12977 text: this.text.slice(start, this.index), 12978 identifier: true 12979 }); 12980 }, 12981 12982 readString: function(quote) { 12983 var start = this.index; 12984 this.index++; 12985 var string = ''; 12986 var rawString = quote; 12987 var escape = false; 12988 while (this.index < this.text.length) { 12989 var ch = this.text.charAt(this.index); 12990 rawString += ch; 12991 if (escape) { 12992 if (ch === 'u') { 12993 var hex = this.text.substring(this.index + 1, this.index + 5); 12994 if (!hex.match(/[\da-f]{4}/i)) { 12995 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 12996 } 12997 this.index += 4; 12998 string += String.fromCharCode(parseInt(hex, 16)); 12999 } else { 13000 var rep = ESCAPE[ch]; 13001 string = string + (rep || ch); 13002 } 13003 escape = false; 13004 } else if (ch === '\\') { 13005 escape = true; 13006 } else if (ch === quote) { 13007 this.index++; 13008 this.tokens.push({ 13009 index: start, 13010 text: rawString, 13011 constant: true, 13012 value: string 13013 }); 13014 return; 13015 } else { 13016 string += ch; 13017 } 13018 this.index++; 13019 } 13020 this.throwError('Unterminated quote', start); 13021 } 13022}; 13023 13024var AST = function(lexer, options) { 13025 this.lexer = lexer; 13026 this.options = options; 13027}; 13028 13029AST.Program = 'Program'; 13030AST.ExpressionStatement = 'ExpressionStatement'; 13031AST.AssignmentExpression = 'AssignmentExpression'; 13032AST.ConditionalExpression = 'ConditionalExpression'; 13033AST.LogicalExpression = 'LogicalExpression'; 13034AST.BinaryExpression = 'BinaryExpression'; 13035AST.UnaryExpression = 'UnaryExpression'; 13036AST.CallExpression = 'CallExpression'; 13037AST.MemberExpression = 'MemberExpression'; 13038AST.Identifier = 'Identifier'; 13039AST.Literal = 'Literal'; 13040AST.ArrayExpression = 'ArrayExpression'; 13041AST.Property = 'Property'; 13042AST.ObjectExpression = 'ObjectExpression'; 13043AST.ThisExpression = 'ThisExpression'; 13044 13045// Internal use only 13046AST.NGValueParameter = 'NGValueParameter'; 13047 13048AST.prototype = { 13049 ast: function(text) { 13050 this.text = text; 13051 this.tokens = this.lexer.lex(text); 13052 13053 var value = this.program(); 13054 13055 if (this.tokens.length !== 0) { 13056 this.throwError('is an unexpected token', this.tokens[0]); 13057 } 13058 13059 return value; 13060 }, 13061 13062 program: function() { 13063 var body = []; 13064 while (true) { 13065 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 13066 body.push(this.expressionStatement()); 13067 if (!this.expect(';')) { 13068 return { type: AST.Program, body: body}; 13069 } 13070 } 13071 }, 13072 13073 expressionStatement: function() { 13074 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 13075 }, 13076 13077 filterChain: function() { 13078 var left = this.expression(); 13079 var token; 13080 while ((token = this.expect('|'))) { 13081 left = this.filter(left); 13082 } 13083 return left; 13084 }, 13085 13086 expression: function() { 13087 return this.assignment(); 13088 }, 13089 13090 assignment: function() { 13091 var result = this.ternary(); 13092 if (this.expect('=')) { 13093 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 13094 } 13095 return result; 13096 }, 13097 13098 ternary: function() { 13099 var test = this.logicalOR(); 13100 var alternate; 13101 var consequent; 13102 if (this.expect('?')) { 13103 alternate = this.expression(); 13104 if (this.consume(':')) { 13105 consequent = this.expression(); 13106 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 13107 } 13108 } 13109 return test; 13110 }, 13111 13112 logicalOR: function() { 13113 var left = this.logicalAND(); 13114 while (this.expect('||')) { 13115 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 13116 } 13117 return left; 13118 }, 13119 13120 logicalAND: function() { 13121 var left = this.equality(); 13122 while (this.expect('&&')) { 13123 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 13124 } 13125 return left; 13126 }, 13127 13128 equality: function() { 13129 var left = this.relational(); 13130 var token; 13131 while ((token = this.expect('==','!=','===','!=='))) { 13132 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 13133 } 13134 return left; 13135 }, 13136 13137 relational: function() { 13138 var left = this.additive(); 13139 var token; 13140 while ((token = this.expect('<', '>', '<=', '>='))) { 13141 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 13142 } 13143 return left; 13144 }, 13145 13146 additive: function() { 13147 var left = this.multiplicative(); 13148 var token; 13149 while ((token = this.expect('+','-'))) { 13150 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 13151 } 13152 return left; 13153 }, 13154 13155 multiplicative: function() { 13156 var left = this.unary(); 13157 var token; 13158 while ((token = this.expect('*','/','%'))) { 13159 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 13160 } 13161 return left; 13162 }, 13163 13164 unary: function() { 13165 var token; 13166 if ((token = this.expect('+', '-', '!'))) { 13167 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 13168 } else { 13169 return this.primary(); 13170 } 13171 }, 13172 13173 primary: function() { 13174 var primary; 13175 if (this.expect('(')) { 13176 primary = this.filterChain(); 13177 this.consume(')'); 13178 } else if (this.expect('[')) { 13179 primary = this.arrayDeclaration(); 13180 } else if (this.expect('{')) { 13181 primary = this.object(); 13182 } else if (this.constants.hasOwnProperty(this.peek().text)) { 13183 primary = copy(this.constants[this.consume().text]); 13184 } else if (this.peek().identifier) { 13185 primary = this.identifier(); 13186 } else if (this.peek().constant) { 13187 primary = this.constant(); 13188 } else { 13189 this.throwError('not a primary expression', this.peek()); 13190 } 13191 13192 var next; 13193 while ((next = this.expect('(', '[', '.'))) { 13194 if (next.text === '(') { 13195 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 13196 this.consume(')'); 13197 } else if (next.text === '[') { 13198 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 13199 this.consume(']'); 13200 } else if (next.text === '.') { 13201 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 13202 } else { 13203 this.throwError('IMPOSSIBLE'); 13204 } 13205 } 13206 return primary; 13207 }, 13208 13209 filter: function(baseExpression) { 13210 var args = [baseExpression]; 13211 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 13212 13213 while (this.expect(':')) { 13214 args.push(this.expression()); 13215 } 13216 13217 return result; 13218 }, 13219 13220 parseArguments: function() { 13221 var args = []; 13222 if (this.peekToken().text !== ')') { 13223 do { 13224 args.push(this.expression()); 13225 } while (this.expect(',')); 13226 } 13227 return args; 13228 }, 13229 13230 identifier: function() { 13231 var token = this.consume(); 13232 if (!token.identifier) { 13233 this.throwError('is not a valid identifier', token); 13234 } 13235 return { type: AST.Identifier, name: token.text }; 13236 }, 13237 13238 constant: function() { 13239 // TODO check that it is a constant 13240 return { type: AST.Literal, value: this.consume().value }; 13241 }, 13242 13243 arrayDeclaration: function() { 13244 var elements = []; 13245 if (this.peekToken().text !== ']') { 13246 do { 13247 if (this.peek(']')) { 13248 // Support trailing commas per ES5.1. 13249 break; 13250 } 13251 elements.push(this.expression()); 13252 } while (this.expect(',')); 13253 } 13254 this.consume(']'); 13255 13256 return { type: AST.ArrayExpression, elements: elements }; 13257 }, 13258 13259 object: function() { 13260 var properties = [], property; 13261 if (this.peekToken().text !== '}') { 13262 do { 13263 if (this.peek('}')) { 13264 // Support trailing commas per ES5.1. 13265 break; 13266 } 13267 property = {type: AST.Property, kind: 'init'}; 13268 if (this.peek().constant) { 13269 property.key = this.constant(); 13270 } else if (this.peek().identifier) { 13271 property.key = this.identifier(); 13272 } else { 13273 this.throwError("invalid key", this.peek()); 13274 } 13275 this.consume(':'); 13276 property.value = this.expression(); 13277 properties.push(property); 13278 } while (this.expect(',')); 13279 } 13280 this.consume('}'); 13281 13282 return {type: AST.ObjectExpression, properties: properties }; 13283 }, 13284 13285 throwError: function(msg, token) { 13286 throw $parseMinErr('syntax', 13287 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 13288 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 13289 }, 13290 13291 consume: function(e1) { 13292 if (this.tokens.length === 0) { 13293 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 13294 } 13295 13296 var token = this.expect(e1); 13297 if (!token) { 13298 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 13299 } 13300 return token; 13301 }, 13302 13303 peekToken: function() { 13304 if (this.tokens.length === 0) { 13305 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 13306 } 13307 return this.tokens[0]; 13308 }, 13309 13310 peek: function(e1, e2, e3, e4) { 13311 return this.peekAhead(0, e1, e2, e3, e4); 13312 }, 13313 13314 peekAhead: function(i, e1, e2, e3, e4) { 13315 if (this.tokens.length > i) { 13316 var token = this.tokens[i]; 13317 var t = token.text; 13318 if (t === e1 || t === e2 || t === e3 || t === e4 || 13319 (!e1 && !e2 && !e3 && !e4)) { 13320 return token; 13321 } 13322 } 13323 return false; 13324 }, 13325 13326 expect: function(e1, e2, e3, e4) { 13327 var token = this.peek(e1, e2, e3, e4); 13328 if (token) { 13329 this.tokens.shift(); 13330 return token; 13331 } 13332 return false; 13333 }, 13334 13335 13336 /* `undefined` is not a constant, it is an identifier, 13337 * but using it as an identifier is not supported 13338 */ 13339 constants: { 13340 'true': { type: AST.Literal, value: true }, 13341 'false': { type: AST.Literal, value: false }, 13342 'null': { type: AST.Literal, value: null }, 13343 'undefined': {type: AST.Literal, value: undefined }, 13344 'this': {type: AST.ThisExpression } 13345 } 13346}; 13347 13348function ifDefined(v, d) { 13349 return typeof v !== 'undefined' ? v : d; 13350} 13351 13352function plusFn(l, r) { 13353 if (typeof l === 'undefined') return r; 13354 if (typeof r === 'undefined') return l; 13355 return l + r; 13356} 13357 13358function isStateless($filter, filterName) { 13359 var fn = $filter(filterName); 13360 return !fn.$stateful; 13361} 13362 13363function findConstantAndWatchExpressions(ast, $filter) { 13364 var allConstants; 13365 var argsToWatch; 13366 switch (ast.type) { 13367 case AST.Program: 13368 allConstants = true; 13369 forEach(ast.body, function(expr) { 13370 findConstantAndWatchExpressions(expr.expression, $filter); 13371 allConstants = allConstants && expr.expression.constant; 13372 }); 13373 ast.constant = allConstants; 13374 break; 13375 case AST.Literal: 13376 ast.constant = true; 13377 ast.toWatch = []; 13378 break; 13379 case AST.UnaryExpression: 13380 findConstantAndWatchExpressions(ast.argument, $filter); 13381 ast.constant = ast.argument.constant; 13382 ast.toWatch = ast.argument.toWatch; 13383 break; 13384 case AST.BinaryExpression: 13385 findConstantAndWatchExpressions(ast.left, $filter); 13386 findConstantAndWatchExpressions(ast.right, $filter); 13387 ast.constant = ast.left.constant && ast.right.constant; 13388 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 13389 break; 13390 case AST.LogicalExpression: 13391 findConstantAndWatchExpressions(ast.left, $filter); 13392 findConstantAndWatchExpressions(ast.right, $filter); 13393 ast.constant = ast.left.constant && ast.right.constant; 13394 ast.toWatch = ast.constant ? [] : [ast]; 13395 break; 13396 case AST.ConditionalExpression: 13397 findConstantAndWatchExpressions(ast.test, $filter); 13398 findConstantAndWatchExpressions(ast.alternate, $filter); 13399 findConstantAndWatchExpressions(ast.consequent, $filter); 13400 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 13401 ast.toWatch = ast.constant ? [] : [ast]; 13402 break; 13403 case AST.Identifier: 13404 ast.constant = false; 13405 ast.toWatch = [ast]; 13406 break; 13407 case AST.MemberExpression: 13408 findConstantAndWatchExpressions(ast.object, $filter); 13409 if (ast.computed) { 13410 findConstantAndWatchExpressions(ast.property, $filter); 13411 } 13412 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 13413 ast.toWatch = [ast]; 13414 break; 13415 case AST.CallExpression: 13416 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 13417 argsToWatch = []; 13418 forEach(ast.arguments, function(expr) { 13419 findConstantAndWatchExpressions(expr, $filter); 13420 allConstants = allConstants && expr.constant; 13421 if (!expr.constant) { 13422 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13423 } 13424 }); 13425 ast.constant = allConstants; 13426 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 13427 break; 13428 case AST.AssignmentExpression: 13429 findConstantAndWatchExpressions(ast.left, $filter); 13430 findConstantAndWatchExpressions(ast.right, $filter); 13431 ast.constant = ast.left.constant && ast.right.constant; 13432 ast.toWatch = [ast]; 13433 break; 13434 case AST.ArrayExpression: 13435 allConstants = true; 13436 argsToWatch = []; 13437 forEach(ast.elements, function(expr) { 13438 findConstantAndWatchExpressions(expr, $filter); 13439 allConstants = allConstants && expr.constant; 13440 if (!expr.constant) { 13441 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13442 } 13443 }); 13444 ast.constant = allConstants; 13445 ast.toWatch = argsToWatch; 13446 break; 13447 case AST.ObjectExpression: 13448 allConstants = true; 13449 argsToWatch = []; 13450 forEach(ast.properties, function(property) { 13451 findConstantAndWatchExpressions(property.value, $filter); 13452 allConstants = allConstants && property.value.constant; 13453 if (!property.value.constant) { 13454 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 13455 } 13456 }); 13457 ast.constant = allConstants; 13458 ast.toWatch = argsToWatch; 13459 break; 13460 case AST.ThisExpression: 13461 ast.constant = false; 13462 ast.toWatch = []; 13463 break; 13464 } 13465} 13466 13467function getInputs(body) { 13468 if (body.length != 1) return; 13469 var lastExpression = body[0].expression; 13470 var candidate = lastExpression.toWatch; 13471 if (candidate.length !== 1) return candidate; 13472 return candidate[0] !== lastExpression ? candidate : undefined; 13473} 13474 13475function isAssignable(ast) { 13476 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 13477} 13478 13479function assignableAST(ast) { 13480 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 13481 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 13482 } 13483} 13484 13485function isLiteral(ast) { 13486 return ast.body.length === 0 || 13487 ast.body.length === 1 && ( 13488 ast.body[0].expression.type === AST.Literal || 13489 ast.body[0].expression.type === AST.ArrayExpression || 13490 ast.body[0].expression.type === AST.ObjectExpression); 13491} 13492 13493function isConstant(ast) { 13494 return ast.constant; 13495} 13496 13497function ASTCompiler(astBuilder, $filter) { 13498 this.astBuilder = astBuilder; 13499 this.$filter = $filter; 13500} 13501 13502ASTCompiler.prototype = { 13503 compile: function(expression, expensiveChecks) { 13504 var self = this; 13505 var ast = this.astBuilder.ast(expression); 13506 this.state = { 13507 nextId: 0, 13508 filters: {}, 13509 expensiveChecks: expensiveChecks, 13510 fn: {vars: [], body: [], own: {}}, 13511 assign: {vars: [], body: [], own: {}}, 13512 inputs: [] 13513 }; 13514 findConstantAndWatchExpressions(ast, self.$filter); 13515 var extra = ''; 13516 var assignable; 13517 this.stage = 'assign'; 13518 if ((assignable = assignableAST(ast))) { 13519 this.state.computing = 'assign'; 13520 var result = this.nextId(); 13521 this.recurse(assignable, result); 13522 this.return_(result); 13523 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 13524 } 13525 var toWatch = getInputs(ast.body); 13526 self.stage = 'inputs'; 13527 forEach(toWatch, function(watch, key) { 13528 var fnKey = 'fn' + key; 13529 self.state[fnKey] = {vars: [], body: [], own: {}}; 13530 self.state.computing = fnKey; 13531 var intoId = self.nextId(); 13532 self.recurse(watch, intoId); 13533 self.return_(intoId); 13534 self.state.inputs.push(fnKey); 13535 watch.watchId = key; 13536 }); 13537 this.state.computing = 'fn'; 13538 this.stage = 'main'; 13539 this.recurse(ast); 13540 var fnString = 13541 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 13542 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 13543 '"' + this.USE + ' ' + this.STRICT + '";\n' + 13544 this.filterPrefix() + 13545 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 13546 extra + 13547 this.watchFns() + 13548 'return fn;'; 13549 13550 /* jshint -W054 */ 13551 var fn = (new Function('$filter', 13552 'ensureSafeMemberName', 13553 'ensureSafeObject', 13554 'ensureSafeFunction', 13555 'getStringValue', 13556 'ensureSafeAssignContext', 13557 'ifDefined', 13558 'plus', 13559 'text', 13560 fnString))( 13561 this.$filter, 13562 ensureSafeMemberName, 13563 ensureSafeObject, 13564 ensureSafeFunction, 13565 getStringValue, 13566 ensureSafeAssignContext, 13567 ifDefined, 13568 plusFn, 13569 expression); 13570 /* jshint +W054 */ 13571 this.state = this.stage = undefined; 13572 fn.literal = isLiteral(ast); 13573 fn.constant = isConstant(ast); 13574 return fn; 13575 }, 13576 13577 USE: 'use', 13578 13579 STRICT: 'strict', 13580 13581 watchFns: function() { 13582 var result = []; 13583 var fns = this.state.inputs; 13584 var self = this; 13585 forEach(fns, function(name) { 13586 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 13587 }); 13588 if (fns.length) { 13589 result.push('fn.inputs=[' + fns.join(',') + '];'); 13590 } 13591 return result.join(''); 13592 }, 13593 13594 generateFunction: function(name, params) { 13595 return 'function(' + params + '){' + 13596 this.varsPrefix(name) + 13597 this.body(name) + 13598 '};'; 13599 }, 13600 13601 filterPrefix: function() { 13602 var parts = []; 13603 var self = this; 13604 forEach(this.state.filters, function(id, filter) { 13605 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 13606 }); 13607 if (parts.length) return 'var ' + parts.join(',') + ';'; 13608 return ''; 13609 }, 13610 13611 varsPrefix: function(section) { 13612 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 13613 }, 13614 13615 body: function(section) { 13616 return this.state[section].body.join(''); 13617 }, 13618 13619 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13620 var left, right, self = this, args, expression; 13621 recursionFn = recursionFn || noop; 13622 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 13623 intoId = intoId || this.nextId(); 13624 this.if_('i', 13625 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 13626 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 13627 ); 13628 return; 13629 } 13630 switch (ast.type) { 13631 case AST.Program: 13632 forEach(ast.body, function(expression, pos) { 13633 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 13634 if (pos !== ast.body.length - 1) { 13635 self.current().body.push(right, ';'); 13636 } else { 13637 self.return_(right); 13638 } 13639 }); 13640 break; 13641 case AST.Literal: 13642 expression = this.escape(ast.value); 13643 this.assign(intoId, expression); 13644 recursionFn(expression); 13645 break; 13646 case AST.UnaryExpression: 13647 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 13648 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 13649 this.assign(intoId, expression); 13650 recursionFn(expression); 13651 break; 13652 case AST.BinaryExpression: 13653 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 13654 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 13655 if (ast.operator === '+') { 13656 expression = this.plus(left, right); 13657 } else if (ast.operator === '-') { 13658 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 13659 } else { 13660 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 13661 } 13662 this.assign(intoId, expression); 13663 recursionFn(expression); 13664 break; 13665 case AST.LogicalExpression: 13666 intoId = intoId || this.nextId(); 13667 self.recurse(ast.left, intoId); 13668 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 13669 recursionFn(intoId); 13670 break; 13671 case AST.ConditionalExpression: 13672 intoId = intoId || this.nextId(); 13673 self.recurse(ast.test, intoId); 13674 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 13675 recursionFn(intoId); 13676 break; 13677 case AST.Identifier: 13678 intoId = intoId || this.nextId(); 13679 if (nameId) { 13680 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 13681 nameId.computed = false; 13682 nameId.name = ast.name; 13683 } 13684 ensureSafeMemberName(ast.name); 13685 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 13686 function() { 13687 self.if_(self.stage === 'inputs' || 's', function() { 13688 if (create && create !== 1) { 13689 self.if_( 13690 self.not(self.nonComputedMember('s', ast.name)), 13691 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 13692 } 13693 self.assign(intoId, self.nonComputedMember('s', ast.name)); 13694 }); 13695 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 13696 ); 13697 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 13698 self.addEnsureSafeObject(intoId); 13699 } 13700 recursionFn(intoId); 13701 break; 13702 case AST.MemberExpression: 13703 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 13704 intoId = intoId || this.nextId(); 13705 self.recurse(ast.object, left, undefined, function() { 13706 self.if_(self.notNull(left), function() { 13707 if (ast.computed) { 13708 right = self.nextId(); 13709 self.recurse(ast.property, right); 13710 self.getStringValue(right); 13711 self.addEnsureSafeMemberName(right); 13712 if (create && create !== 1) { 13713 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 13714 } 13715 expression = self.ensureSafeObject(self.computedMember(left, right)); 13716 self.assign(intoId, expression); 13717 if (nameId) { 13718 nameId.computed = true; 13719 nameId.name = right; 13720 } 13721 } else { 13722 ensureSafeMemberName(ast.property.name); 13723 if (create && create !== 1) { 13724 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 13725 } 13726 expression = self.nonComputedMember(left, ast.property.name); 13727 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 13728 expression = self.ensureSafeObject(expression); 13729 } 13730 self.assign(intoId, expression); 13731 if (nameId) { 13732 nameId.computed = false; 13733 nameId.name = ast.property.name; 13734 } 13735 } 13736 }, function() { 13737 self.assign(intoId, 'undefined'); 13738 }); 13739 recursionFn(intoId); 13740 }, !!create); 13741 break; 13742 case AST.CallExpression: 13743 intoId = intoId || this.nextId(); 13744 if (ast.filter) { 13745 right = self.filter(ast.callee.name); 13746 args = []; 13747 forEach(ast.arguments, function(expr) { 13748 var argument = self.nextId(); 13749 self.recurse(expr, argument); 13750 args.push(argument); 13751 }); 13752 expression = right + '(' + args.join(',') + ')'; 13753 self.assign(intoId, expression); 13754 recursionFn(intoId); 13755 } else { 13756 right = self.nextId(); 13757 left = {}; 13758 args = []; 13759 self.recurse(ast.callee, right, left, function() { 13760 self.if_(self.notNull(right), function() { 13761 self.addEnsureSafeFunction(right); 13762 forEach(ast.arguments, function(expr) { 13763 self.recurse(expr, self.nextId(), undefined, function(argument) { 13764 args.push(self.ensureSafeObject(argument)); 13765 }); 13766 }); 13767 if (left.name) { 13768 if (!self.state.expensiveChecks) { 13769 self.addEnsureSafeObject(left.context); 13770 } 13771 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 13772 } else { 13773 expression = right + '(' + args.join(',') + ')'; 13774 } 13775 expression = self.ensureSafeObject(expression); 13776 self.assign(intoId, expression); 13777 }, function() { 13778 self.assign(intoId, 'undefined'); 13779 }); 13780 recursionFn(intoId); 13781 }); 13782 } 13783 break; 13784 case AST.AssignmentExpression: 13785 right = this.nextId(); 13786 left = {}; 13787 if (!isAssignable(ast.left)) { 13788 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value'); 13789 } 13790 this.recurse(ast.left, undefined, left, function() { 13791 self.if_(self.notNull(left.context), function() { 13792 self.recurse(ast.right, right); 13793 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 13794 self.addEnsureSafeAssignContext(left.context); 13795 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 13796 self.assign(intoId, expression); 13797 recursionFn(intoId || expression); 13798 }); 13799 }, 1); 13800 break; 13801 case AST.ArrayExpression: 13802 args = []; 13803 forEach(ast.elements, function(expr) { 13804 self.recurse(expr, self.nextId(), undefined, function(argument) { 13805 args.push(argument); 13806 }); 13807 }); 13808 expression = '[' + args.join(',') + ']'; 13809 this.assign(intoId, expression); 13810 recursionFn(expression); 13811 break; 13812 case AST.ObjectExpression: 13813 args = []; 13814 forEach(ast.properties, function(property) { 13815 self.recurse(property.value, self.nextId(), undefined, function(expr) { 13816 args.push(self.escape( 13817 property.key.type === AST.Identifier ? property.key.name : 13818 ('' + property.key.value)) + 13819 ':' + expr); 13820 }); 13821 }); 13822 expression = '{' + args.join(',') + '}'; 13823 this.assign(intoId, expression); 13824 recursionFn(expression); 13825 break; 13826 case AST.ThisExpression: 13827 this.assign(intoId, 's'); 13828 recursionFn('s'); 13829 break; 13830 case AST.NGValueParameter: 13831 this.assign(intoId, 'v'); 13832 recursionFn('v'); 13833 break; 13834 } 13835 }, 13836 13837 getHasOwnProperty: function(element, property) { 13838 var key = element + '.' + property; 13839 var own = this.current().own; 13840 if (!own.hasOwnProperty(key)) { 13841 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 13842 } 13843 return own[key]; 13844 }, 13845 13846 assign: function(id, value) { 13847 if (!id) return; 13848 this.current().body.push(id, '=', value, ';'); 13849 return id; 13850 }, 13851 13852 filter: function(filterName) { 13853 if (!this.state.filters.hasOwnProperty(filterName)) { 13854 this.state.filters[filterName] = this.nextId(true); 13855 } 13856 return this.state.filters[filterName]; 13857 }, 13858 13859 ifDefined: function(id, defaultValue) { 13860 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 13861 }, 13862 13863 plus: function(left, right) { 13864 return 'plus(' + left + ',' + right + ')'; 13865 }, 13866 13867 return_: function(id) { 13868 this.current().body.push('return ', id, ';'); 13869 }, 13870 13871 if_: function(test, alternate, consequent) { 13872 if (test === true) { 13873 alternate(); 13874 } else { 13875 var body = this.current().body; 13876 body.push('if(', test, '){'); 13877 alternate(); 13878 body.push('}'); 13879 if (consequent) { 13880 body.push('else{'); 13881 consequent(); 13882 body.push('}'); 13883 } 13884 } 13885 }, 13886 13887 not: function(expression) { 13888 return '!(' + expression + ')'; 13889 }, 13890 13891 notNull: function(expression) { 13892 return expression + '!=null'; 13893 }, 13894 13895 nonComputedMember: function(left, right) { 13896 return left + '.' + right; 13897 }, 13898 13899 computedMember: function(left, right) { 13900 return left + '[' + right + ']'; 13901 }, 13902 13903 member: function(left, right, computed) { 13904 if (computed) return this.computedMember(left, right); 13905 return this.nonComputedMember(left, right); 13906 }, 13907 13908 addEnsureSafeObject: function(item) { 13909 this.current().body.push(this.ensureSafeObject(item), ';'); 13910 }, 13911 13912 addEnsureSafeMemberName: function(item) { 13913 this.current().body.push(this.ensureSafeMemberName(item), ';'); 13914 }, 13915 13916 addEnsureSafeFunction: function(item) { 13917 this.current().body.push(this.ensureSafeFunction(item), ';'); 13918 }, 13919 13920 addEnsureSafeAssignContext: function(item) { 13921 this.current().body.push(this.ensureSafeAssignContext(item), ';'); 13922 }, 13923 13924 ensureSafeObject: function(item) { 13925 return 'ensureSafeObject(' + item + ',text)'; 13926 }, 13927 13928 ensureSafeMemberName: function(item) { 13929 return 'ensureSafeMemberName(' + item + ',text)'; 13930 }, 13931 13932 ensureSafeFunction: function(item) { 13933 return 'ensureSafeFunction(' + item + ',text)'; 13934 }, 13935 13936 getStringValue: function(item) { 13937 this.assign(item, 'getStringValue(' + item + ',text)'); 13938 }, 13939 13940 ensureSafeAssignContext: function(item) { 13941 return 'ensureSafeAssignContext(' + item + ',text)'; 13942 }, 13943 13944 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13945 var self = this; 13946 return function() { 13947 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 13948 }; 13949 }, 13950 13951 lazyAssign: function(id, value) { 13952 var self = this; 13953 return function() { 13954 self.assign(id, value); 13955 }; 13956 }, 13957 13958 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 13959 13960 stringEscapeFn: function(c) { 13961 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 13962 }, 13963 13964 escape: function(value) { 13965 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 13966 if (isNumber(value)) return value.toString(); 13967 if (value === true) return 'true'; 13968 if (value === false) return 'false'; 13969 if (value === null) return 'null'; 13970 if (typeof value === 'undefined') return 'undefined'; 13971 13972 throw $parseMinErr('esc', 'IMPOSSIBLE'); 13973 }, 13974 13975 nextId: function(skip, init) { 13976 var id = 'v' + (this.state.nextId++); 13977 if (!skip) { 13978 this.current().vars.push(id + (init ? '=' + init : '')); 13979 } 13980 return id; 13981 }, 13982 13983 current: function() { 13984 return this.state[this.state.computing]; 13985 } 13986}; 13987 13988 13989function ASTInterpreter(astBuilder, $filter) { 13990 this.astBuilder = astBuilder; 13991 this.$filter = $filter; 13992} 13993 13994ASTInterpreter.prototype = { 13995 compile: function(expression, expensiveChecks) { 13996 var self = this; 13997 var ast = this.astBuilder.ast(expression); 13998 this.expression = expression; 13999 this.expensiveChecks = expensiveChecks; 14000 findConstantAndWatchExpressions(ast, self.$filter); 14001 var assignable; 14002 var assign; 14003 if ((assignable = assignableAST(ast))) { 14004 assign = this.recurse(assignable); 14005 } 14006 var toWatch = getInputs(ast.body); 14007 var inputs; 14008 if (toWatch) { 14009 inputs = []; 14010 forEach(toWatch, function(watch, key) { 14011 var input = self.recurse(watch); 14012 watch.input = input; 14013 inputs.push(input); 14014 watch.watchId = key; 14015 }); 14016 } 14017 var expressions = []; 14018 forEach(ast.body, function(expression) { 14019 expressions.push(self.recurse(expression.expression)); 14020 }); 14021 var fn = ast.body.length === 0 ? function() {} : 14022 ast.body.length === 1 ? expressions[0] : 14023 function(scope, locals) { 14024 var lastValue; 14025 forEach(expressions, function(exp) { 14026 lastValue = exp(scope, locals); 14027 }); 14028 return lastValue; 14029 }; 14030 if (assign) { 14031 fn.assign = function(scope, value, locals) { 14032 return assign(scope, locals, value); 14033 }; 14034 } 14035 if (inputs) { 14036 fn.inputs = inputs; 14037 } 14038 fn.literal = isLiteral(ast); 14039 fn.constant = isConstant(ast); 14040 return fn; 14041 }, 14042 14043 recurse: function(ast, context, create) { 14044 var left, right, self = this, args, expression; 14045 if (ast.input) { 14046 return this.inputs(ast.input, ast.watchId); 14047 } 14048 switch (ast.type) { 14049 case AST.Literal: 14050 return this.value(ast.value, context); 14051 case AST.UnaryExpression: 14052 right = this.recurse(ast.argument); 14053 return this['unary' + ast.operator](right, context); 14054 case AST.BinaryExpression: 14055 left = this.recurse(ast.left); 14056 right = this.recurse(ast.right); 14057 return this['binary' + ast.operator](left, right, context); 14058 case AST.LogicalExpression: 14059 left = this.recurse(ast.left); 14060 right = this.recurse(ast.right); 14061 return this['binary' + ast.operator](left, right, context); 14062 case AST.ConditionalExpression: 14063 return this['ternary?:']( 14064 this.recurse(ast.test), 14065 this.recurse(ast.alternate), 14066 this.recurse(ast.consequent), 14067 context 14068 ); 14069 case AST.Identifier: 14070 ensureSafeMemberName(ast.name, self.expression); 14071 return self.identifier(ast.name, 14072 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 14073 context, create, self.expression); 14074 case AST.MemberExpression: 14075 left = this.recurse(ast.object, false, !!create); 14076 if (!ast.computed) { 14077 ensureSafeMemberName(ast.property.name, self.expression); 14078 right = ast.property.name; 14079 } 14080 if (ast.computed) right = this.recurse(ast.property); 14081 return ast.computed ? 14082 this.computedMember(left, right, context, create, self.expression) : 14083 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 14084 case AST.CallExpression: 14085 args = []; 14086 forEach(ast.arguments, function(expr) { 14087 args.push(self.recurse(expr)); 14088 }); 14089 if (ast.filter) right = this.$filter(ast.callee.name); 14090 if (!ast.filter) right = this.recurse(ast.callee, true); 14091 return ast.filter ? 14092 function(scope, locals, assign, inputs) { 14093 var values = []; 14094 for (var i = 0; i < args.length; ++i) { 14095 values.push(args[i](scope, locals, assign, inputs)); 14096 } 14097 var value = right.apply(undefined, values, inputs); 14098 return context ? {context: undefined, name: undefined, value: value} : value; 14099 } : 14100 function(scope, locals, assign, inputs) { 14101 var rhs = right(scope, locals, assign, inputs); 14102 var value; 14103 if (rhs.value != null) { 14104 ensureSafeObject(rhs.context, self.expression); 14105 ensureSafeFunction(rhs.value, self.expression); 14106 var values = []; 14107 for (var i = 0; i < args.length; ++i) { 14108 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 14109 } 14110 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 14111 } 14112 return context ? {value: value} : value; 14113 }; 14114 case AST.AssignmentExpression: 14115 left = this.recurse(ast.left, true, 1); 14116 right = this.recurse(ast.right); 14117 return function(scope, locals, assign, inputs) { 14118 var lhs = left(scope, locals, assign, inputs); 14119 var rhs = right(scope, locals, assign, inputs); 14120 ensureSafeObject(lhs.value, self.expression); 14121 ensureSafeAssignContext(lhs.context); 14122 lhs.context[lhs.name] = rhs; 14123 return context ? {value: rhs} : rhs; 14124 }; 14125 case AST.ArrayExpression: 14126 args = []; 14127 forEach(ast.elements, function(expr) { 14128 args.push(self.recurse(expr)); 14129 }); 14130 return function(scope, locals, assign, inputs) { 14131 var value = []; 14132 for (var i = 0; i < args.length; ++i) { 14133 value.push(args[i](scope, locals, assign, inputs)); 14134 } 14135 return context ? {value: value} : value; 14136 }; 14137 case AST.ObjectExpression: 14138 args = []; 14139 forEach(ast.properties, function(property) { 14140 args.push({key: property.key.type === AST.Identifier ? 14141 property.key.name : 14142 ('' + property.key.value), 14143 value: self.recurse(property.value) 14144 }); 14145 }); 14146 return function(scope, locals, assign, inputs) { 14147 var value = {}; 14148 for (var i = 0; i < args.length; ++i) { 14149 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 14150 } 14151 return context ? {value: value} : value; 14152 }; 14153 case AST.ThisExpression: 14154 return function(scope) { 14155 return context ? {value: scope} : scope; 14156 }; 14157 case AST.NGValueParameter: 14158 return function(scope, locals, assign, inputs) { 14159 return context ? {value: assign} : assign; 14160 }; 14161 } 14162 }, 14163 14164 'unary+': function(argument, context) { 14165 return function(scope, locals, assign, inputs) { 14166 var arg = argument(scope, locals, assign, inputs); 14167 if (isDefined(arg)) { 14168 arg = +arg; 14169 } else { 14170 arg = 0; 14171 } 14172 return context ? {value: arg} : arg; 14173 }; 14174 }, 14175 'unary-': function(argument, context) { 14176 return function(scope, locals, assign, inputs) { 14177 var arg = argument(scope, locals, assign, inputs); 14178 if (isDefined(arg)) { 14179 arg = -arg; 14180 } else { 14181 arg = 0; 14182 } 14183 return context ? {value: arg} : arg; 14184 }; 14185 }, 14186 'unary!': function(argument, context) { 14187 return function(scope, locals, assign, inputs) { 14188 var arg = !argument(scope, locals, assign, inputs); 14189 return context ? {value: arg} : arg; 14190 }; 14191 }, 14192 'binary+': function(left, right, context) { 14193 return function(scope, locals, assign, inputs) { 14194 var lhs = left(scope, locals, assign, inputs); 14195 var rhs = right(scope, locals, assign, inputs); 14196 var arg = plusFn(lhs, rhs); 14197 return context ? {value: arg} : arg; 14198 }; 14199 }, 14200 'binary-': function(left, right, context) { 14201 return function(scope, locals, assign, inputs) { 14202 var lhs = left(scope, locals, assign, inputs); 14203 var rhs = right(scope, locals, assign, inputs); 14204 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 14205 return context ? {value: arg} : arg; 14206 }; 14207 }, 14208 'binary*': function(left, right, context) { 14209 return function(scope, locals, assign, inputs) { 14210 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 14211 return context ? {value: arg} : arg; 14212 }; 14213 }, 14214 'binary/': function(left, right, context) { 14215 return function(scope, locals, assign, inputs) { 14216 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 14217 return context ? {value: arg} : arg; 14218 }; 14219 }, 14220 'binary%': function(left, right, context) { 14221 return function(scope, locals, assign, inputs) { 14222 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 14223 return context ? {value: arg} : arg; 14224 }; 14225 }, 14226 'binary===': function(left, right, context) { 14227 return function(scope, locals, assign, inputs) { 14228 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 14229 return context ? {value: arg} : arg; 14230 }; 14231 }, 14232 'binary!==': function(left, right, context) { 14233 return function(scope, locals, assign, inputs) { 14234 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 14235 return context ? {value: arg} : arg; 14236 }; 14237 }, 14238 'binary==': function(left, right, context) { 14239 return function(scope, locals, assign, inputs) { 14240 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 14241 return context ? {value: arg} : arg; 14242 }; 14243 }, 14244 'binary!=': function(left, right, context) { 14245 return function(scope, locals, assign, inputs) { 14246 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 14247 return context ? {value: arg} : arg; 14248 }; 14249 }, 14250 'binary<': function(left, right, context) { 14251 return function(scope, locals, assign, inputs) { 14252 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 14253 return context ? {value: arg} : arg; 14254 }; 14255 }, 14256 'binary>': function(left, right, context) { 14257 return function(scope, locals, assign, inputs) { 14258 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 14259 return context ? {value: arg} : arg; 14260 }; 14261 }, 14262 'binary<=': function(left, right, context) { 14263 return function(scope, locals, assign, inputs) { 14264 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 14265 return context ? {value: arg} : arg; 14266 }; 14267 }, 14268 'binary>=': function(left, right, context) { 14269 return function(scope, locals, assign, inputs) { 14270 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 14271 return context ? {value: arg} : arg; 14272 }; 14273 }, 14274 'binary&&': function(left, right, context) { 14275 return function(scope, locals, assign, inputs) { 14276 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 14277 return context ? {value: arg} : arg; 14278 }; 14279 }, 14280 'binary||': function(left, right, context) { 14281 return function(scope, locals, assign, inputs) { 14282 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 14283 return context ? {value: arg} : arg; 14284 }; 14285 }, 14286 'ternary?:': function(test, alternate, consequent, context) { 14287 return function(scope, locals, assign, inputs) { 14288 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 14289 return context ? {value: arg} : arg; 14290 }; 14291 }, 14292 value: function(value, context) { 14293 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 14294 }, 14295 identifier: function(name, expensiveChecks, context, create, expression) { 14296 return function(scope, locals, assign, inputs) { 14297 var base = locals && (name in locals) ? locals : scope; 14298 if (create && create !== 1 && base && !(base[name])) { 14299 base[name] = {}; 14300 } 14301 var value = base ? base[name] : undefined; 14302 if (expensiveChecks) { 14303 ensureSafeObject(value, expression); 14304 } 14305 if (context) { 14306 return {context: base, name: name, value: value}; 14307 } else { 14308 return value; 14309 } 14310 }; 14311 }, 14312 computedMember: function(left, right, context, create, expression) { 14313 return function(scope, locals, assign, inputs) { 14314 var lhs = left(scope, locals, assign, inputs); 14315 var rhs; 14316 var value; 14317 if (lhs != null) { 14318 rhs = right(scope, locals, assign, inputs); 14319 rhs = getStringValue(rhs); 14320 ensureSafeMemberName(rhs, expression); 14321 if (create && create !== 1 && lhs && !(lhs[rhs])) { 14322 lhs[rhs] = {}; 14323 } 14324 value = lhs[rhs]; 14325 ensureSafeObject(value, expression); 14326 } 14327 if (context) { 14328 return {context: lhs, name: rhs, value: value}; 14329 } else { 14330 return value; 14331 } 14332 }; 14333 }, 14334 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 14335 return function(scope, locals, assign, inputs) { 14336 var lhs = left(scope, locals, assign, inputs); 14337 if (create && create !== 1 && lhs && !(lhs[right])) { 14338 lhs[right] = {}; 14339 } 14340 var value = lhs != null ? lhs[right] : undefined; 14341 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 14342 ensureSafeObject(value, expression); 14343 } 14344 if (context) { 14345 return {context: lhs, name: right, value: value}; 14346 } else { 14347 return value; 14348 } 14349 }; 14350 }, 14351 inputs: function(input, watchId) { 14352 return function(scope, value, locals, inputs) {
14353 if (inputs) return inputs[watchId]; 14354 return input(scope, value, locals); 14355 }; 14356 } 14357}; 14358 14359/** 14360 * @constructor 14361 */ 14362var Parser = function(lexer, $filter, options) { 14363 this.lexer = lexer; 14364 this.$filter = $filter; 14365 this.options = options; 14366 this.ast = new AST(this.lexer); 14367 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 14368 new ASTCompiler(this.ast, $filter); 14369}; 14370 14371Parser.prototype = { 14372 constructor: Parser, 14373 14374 parse: function(text) { 14375 return this.astCompiler.compile(text, this.options.expensiveChecks); 14376 } 14377}; 14378 14379var getterFnCacheDefault = createMap(); 14380var getterFnCacheExpensive = createMap(); 14381 14382function isPossiblyDangerousMemberName(name) { 14383 return name == 'constructor'; 14384} 14385 14386var objectValueOf = Object.prototype.valueOf; 14387 14388function getValueOf(value) { 14389 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 14390} 14391 14392/////////////////////////////////// 14393 14394/** 14395 * @ngdoc service 14396 * @name $parse 14397 * @kind function 14398 * 14399 * @description 14400 * 14401 * Converts Angular {@link guide/expression expression} into a function. 14402 * 14403 * ```js 14404 * var getter = $parse('user.name'); 14405 * var setter = getter.assign; 14406 * var context = {user:{name:'angular'}}; 14407 * var locals = {user:{name:'local'}}; 14408 * 14409 * expect(getter(context)).toEqual('angular'); 14410 * setter(context, 'newValue'); 14411 * expect(context.user.name).toEqual('newValue'); 14412 * expect(getter(context, locals)).toEqual('local'); 14413 * ``` 14414 * 14415 * 14416 * @param {string} expression String expression to compile. 14417 * @returns {function(context, locals)} a function which represents the compiled expression: 14418 * 14419 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14420 * are evaluated against (typically a scope object). 14421 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14422 * `context`. 14423 * 14424 * The returned function also has the following properties: 14425 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 14426 * literal. 14427 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 14428 * constant literals. 14429 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 14430 * set to a function to change its value on the given context. 14431 * 14432 */ 14433 14434 14435/** 14436 * @ngdoc provider 14437 * @name $parseProvider 14438 * 14439 * @description 14440 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 14441 * service. 14442 */ 14443function $ParseProvider() { 14444 var cacheDefault = createMap(); 14445 var cacheExpensive = createMap(); 14446 14447 this.$get = ['$filter', function($filter) { 14448 var noUnsafeEval = csp().noUnsafeEval; 14449 var $parseOptions = { 14450 csp: noUnsafeEval, 14451 expensiveChecks: false 14452 }, 14453 $parseOptionsExpensive = { 14454 csp: noUnsafeEval, 14455 expensiveChecks: true 14456 }; 14457 14458 return function $parse(exp, interceptorFn, expensiveChecks) { 14459 var parsedExpression, oneTime, cacheKey; 14460 14461 switch (typeof exp) { 14462 case 'string': 14463 exp = exp.trim(); 14464 cacheKey = exp; 14465 14466 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 14467 parsedExpression = cache[cacheKey]; 14468 14469 if (!parsedExpression) { 14470 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 14471 oneTime = true; 14472 exp = exp.substring(2); 14473 } 14474 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 14475 var lexer = new Lexer(parseOptions); 14476 var parser = new Parser(lexer, $filter, parseOptions); 14477 parsedExpression = parser.parse(exp); 14478 if (parsedExpression.constant) { 14479 parsedExpression.$$watchDelegate = constantWatchDelegate; 14480 } else if (oneTime) { 14481 parsedExpression.$$watchDelegate = parsedExpression.literal ? 14482 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 14483 } else if (parsedExpression.inputs) { 14484 parsedExpression.$$watchDelegate = inputsWatchDelegate; 14485 } 14486 cache[cacheKey] = parsedExpression; 14487 } 14488 return addInterceptor(parsedExpression, interceptorFn); 14489 14490 case 'function': 14491 return addInterceptor(exp, interceptorFn); 14492 14493 default: 14494 return noop; 14495 } 14496 }; 14497 14498 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 14499 14500 if (newValue == null || oldValueOfValue == null) { // null/undefined 14501 return newValue === oldValueOfValue; 14502 } 14503 14504 if (typeof newValue === 'object') { 14505 14506 // attempt to convert the value to a primitive type 14507 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 14508 // be cheaply dirty-checked 14509 newValue = getValueOf(newValue); 14510 14511 if (typeof newValue === 'object') { 14512 // objects/arrays are not supported - deep-watching them would be too expensive 14513 return false;
14514 } 14515 14516 // fall-through to the primitive equality check 14517 } 14518 14519 //Primitive or NaN 14520 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 14521 } 14522 14523 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 14524 var inputExpressions = parsedExpression.inputs; 14525 var lastResult; 14526 14527 if (inputExpressions.length === 1) { 14528 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 14529 inputExpressions = inputExpressions[0]; 14530 return scope.$watch(function expressionInputWatch(scope) { 14531 var newInputValue = inputExpressions(scope); 14532 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 14533 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 14534 oldInputValueOf = newInputValue && getValueOf(newInputValue); 14535 } 14536 return lastResult; 14537 }, listener, objectEquality, prettyPrintExpression); 14538 } 14539 14540 var oldInputValueOfValues = []; 14541 var oldInputValues = []; 14542 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14543 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 14544 oldInputValues[i] = null; 14545 } 14546 14547 return scope.$watch(function expressionInputsWatch(scope) { 14548 var changed = false; 14549 14550 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14551 var newInputValue = inputExpressions[i](scope); 14552 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 14553 oldInputValues[i] = newInputValue; 14554 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 14555 } 14556 } 14557 14558 if (changed) { 14559 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 14560 } 14561 14562 return lastResult; 14563 }, listener, objectEquality, prettyPrintExpression); 14564 } 14565 14566 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14567 var unwatch, lastValue; 14568 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14569 return parsedExpression(scope); 14570 }, function oneTimeListener(value, old, scope) { 14571 lastValue = value; 14572 if (isFunction(listener)) { 14573 listener.apply(this, arguments); 14574 } 14575 if (isDefined(value)) { 14576 scope.$$postDigest(function() { 14577 if (isDefined(lastValue)) { 14578 unwatch(); 14579 } 14580 }); 14581 } 14582 }, objectEquality); 14583 } 14584 14585 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14586 var unwatch, lastValue; 14587 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14588 return parsedExpression(scope); 14589 }, function oneTimeListener(value, old, scope) { 14590 lastValue = value; 14591 if (isFunction(listener)) { 14592 listener.call(this, value, old, scope); 14593 } 14594 if (isAllDefined(value)) { 14595 scope.$$postDigest(function() { 14596 if (isAllDefined(lastValue)) unwatch(); 14597 }); 14598 } 14599 }, objectEquality); 14600 14601 function isAllDefined(value) { 14602 var allDefined = true; 14603 forEach(value, function(val) { 14604 if (!isDefined(val)) allDefined = false; 14605 }); 14606 return allDefined; 14607 } 14608 } 14609 14610 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14611 var unwatch; 14612 return unwatch = scope.$watch(function constantWatch(scope) { 14613 return parsedExpression(scope); 14614 }, function constantListener(value, old, scope) { 14615 if (isFunction(listener)) { 14616 listener.apply(this, arguments); 14617 } 14618 unwatch(); 14619 }, objectEquality); 14620 } 14621 14622 function addInterceptor(parsedExpression, interceptorFn) { 14623 if (!interceptorFn) return parsedExpression; 14624 var watchDelegate = parsedExpression.$$watchDelegate; 14625 14626 var regularWatch = 14627 watchDelegate !== oneTimeLiteralWatchDelegate && 14628 watchDelegate !== oneTimeWatchDelegate; 14629 14630 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { 14631 var value = parsedExpression(scope, locals, assign, inputs); 14632 return interceptorFn(value, scope, locals); 14633 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 14634 var value = parsedExpression(scope, locals, assign, inputs); 14635 var result = interceptorFn(value, scope, locals); 14636 // we only return the interceptor's result if the 14637 // initial value is defined (for bind-once) 14638 return isDefined(value) ? result : value; 14639 }; 14640
14641 // Propagate $$watchDelegates other then inputsWatchDelegate 14642 if (parsedExpression.$$watchDelegate && 14643 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 14644 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 14645 } else if (!interceptorFn.$stateful) { 14646 // If there is an interceptor, but no watchDelegate then treat the interceptor like 14647 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 14648 fn.$$watchDelegate = inputsWatchDelegate; 14649 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 14650 } 14651 14652 return fn; 14653 } 14654 }]; 14655} 14656 14657/** 14658 * @ngdoc service 14659 * @name $q 14660 * @requires $rootScope 14661 * 14662 * @description 14663 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 14664 * when they are done processing. 14665 * 14666 * This is an implementation of promises/deferred objects inspired by 14667 * [Kris Kowal's Q](https://github.com/kriskowal/q). 14668 * 14669 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 14670 * implementations, and the other which resembles ES6 promises to some degree. 14671 * 14672 * # $q constructor 14673 * 14674 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 14675 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, 14676 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 14677 * 14678 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are 14679 * available yet. 14680 * 14681 * It can be used like so: 14682 * 14683 * ```js 14684 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14685 * // are available in the current lexical scope (they could have been injected or passed in). 14686 * 14687 * function asyncGreet(name) { 14688 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 14689 * return $q(function(resolve, reject) { 14690 * setTimeout(function() { 14691 * if (okToGreet(name)) { 14692 * resolve('Hello, ' + name + '!'); 14693 * } else { 14694 * reject('Greeting ' + name + ' is not allowed.'); 14695 * } 14696 * }, 1000); 14697 * }); 14698 * } 14699 * 14700 * var promise = asyncGreet('Robin Hood'); 14701 * promise.then(function(greeting) { 14702 * alert('Success: ' + greeting); 14703 * }, function(reason) { 14704 * alert('Failed: ' + reason); 14705 * }); 14706 * ``` 14707 * 14708 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 14709 * 14710 * However, the more traditional CommonJS-style usage is still available, and documented below. 14711 * 14712 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 14713 * interface for interacting with an object that represents the result of an action that is 14714 * performed asynchronously, and may or may not be finished at any given point in time. 14715 * 14716 * From the perspective of dealing with error handling, deferred and promise APIs are to 14717 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 14718 * 14719 * ```js 14720 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14721 * // are available in the current lexical scope (they could have been injected or passed in). 14722 * 14723 * function asyncGreet(name) { 14724 * var deferred = $q.defer(); 14725 * 14726 * setTimeout(function() { 14727 * deferred.notify('About to greet ' + name + '.'); 14728 * 14729 * if (okToGreet(name)) { 14730 * deferred.resolve('Hello, ' + name + '!'); 14731 * } else { 14732 * deferred.reject('Greeting ' + name + ' is not allowed.'); 14733 * } 14734 * }, 1000); 14735 * 14736 * return deferred.promise; 14737 * } 14738 * 14739 * var promise = asyncGreet('Robin Hood'); 14740 * promise.then(function(greeting) { 14741 * alert('Success: ' + greeting); 14742 * }, function(reason) { 14743 * alert('Failed: ' + reason); 14744 * }, function(update) { 14745 * alert('Got notification: ' + update); 14746 * }); 14747 * ``` 14748 * 14749 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 14750 * comes in the way of guarantees that promise and deferred APIs make, see 14751 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 14752 *
14753 * Additionally the promise api allows for composition that is very hard to do with the 14754 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 14755 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 14756 * section on serial or parallel joining of promises. 14757 * 14758 * # The Deferred API 14759 * 14760 * A new instance of deferred is constructed by calling `$q.defer()`. 14761 * 14762 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 14763 * that can be used for signaling the successful or unsuccessful completion, as well as the status 14764 * of the task. 14765 * 14766 * **Methods** 14767 * 14768 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 14769 * constructed via `$q.reject`, the promise will be rejected instead. 14770 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 14771 * resolving it with a rejection constructed via `$q.reject`. 14772 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 14773 * multiple times before the promise is either resolved or rejected. 14774 * 14775 * **Properties** 14776 * 14777 * - promise â `{Promise}` â promise object associated with this deferred. 14778 * 14779 * 14780 * # The Promise API 14781 * 14782 * A new promise instance is created when a deferred instance is created and can be retrieved by 14783 * calling `deferred.promise`. 14784 * 14785 * The purpose of the promise object is to allow for interested parties to get access to the result 14786 * of the deferred task when it completes. 14787 * 14788 * **Methods** 14789 * 14790 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 14791 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 14792 * as soon as the result is available. The callbacks are called with a single argument: the result 14793 * or rejection reason. Additionally, the notify callback may be called zero or more times to 14794 * provide a progress indication, before the promise is resolved or rejected. 14795 * 14796 * This method *returns a new promise* which is resolved or rejected via the return value of the 14797 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14798 * with the value which is resolved in that promise using 14799 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 14800 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 14801 * resolved or rejected from the notifyCallback method. 14802 * 14803 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 14804 * 14805 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 14806 * but to do so without modifying the final value. This is useful to release resources or do some 14807 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 14808 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 14809 * more information. 14810 * 14811 * # Chaining promises 14812 * 14813 * Because calling the `then` method of a promise returns a new derived promise, it is easily 14814 * possible to create a chain of promises: 14815 * 14816 * ```js 14817 * promiseB = promiseA.then(function(result) { 14818 * return result + 1; 14819 * }); 14820 * 14821 * // promiseB will be resolved immediately after promiseA is resolved and its value 14822 * // will be the result of promiseA incremented by 1 14823 * ``` 14824 * 14825 * It is possible to create chains of any length and since a promise can be resolved with another 14826 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 14827 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 14828 * $http's response interceptors. 14829 * 14830 * 14831 * # Differences between Kris Kowal's Q and $q 14832 * 14833 * There are two main differences: 14834 * 14835 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 14836 * mechanism in angular, which means faster propagation of resolution or rejection into your 14837 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 14838 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14839 * all the important functionality needed for common async tasks. 14840 * 14841 * # Testing 14842 * 14843 * ```js 14844 * it('should simulate promise', inject(function($q, $rootScope) { 14845 * var deferred = $q.defer(); 14846 * var promise = deferred.promise; 14847 * var resolvedValue; 14848 * 14849 * promise.then(function(value) { resolvedValue = value; }); 14850 * expect(resolvedValue).toBeUndefined(); 14851 * 14852 * // Simulate resolving of promise 14853 * deferred.resolve(123); 14854 * // Note that the 'then' function does not get called synchronously. 14855 * // This is because we want the promise API to always be async, whether or not 14856 * // it got called synchronously or asynchronously. 14857 * expect(resolvedValue).toBeUndefined(); 14858 * 14859 * // Propagate promise resolution to 'then' functions using $apply(). 14860 * $rootScope.$apply(); 14861 * expect(resolvedValue).toEqual(123); 14862 * })); 14863 * ``` 14864 * 14865 * @param {function(function, function)} resolver Function which is responsible for resolving or 14866 * rejecting the newly created promise. The first parameter is a function which resolves the 14867 * promise, the second parameter is a function which rejects the promise. 14868 * 14869 * @returns {Promise} The newly created promise. 14870 */ 14871function $QProvider() { 14872 14873 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 14874 return qFactory(function(callback) { 14875 $rootScope.$evalAsync(callback); 14876 }, $exceptionHandler); 14877 }]; 14878} 14879 14880function $$QProvider() { 14881 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 14882 return qFactory(function(callback) { 14883 $browser.defer(callback); 14884 }, $exceptionHandler); 14885 }]; 14886} 14887 14888/** 14889 * Constructs a promise manager. 14890 * 14891 * @param {function(function)} nextTick Function for executing functions in the next turn. 14892 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 14893 * debugging purposes. 14894 * @returns {object} Promise manager. 14895 */ 14896function qFactory(nextTick, exceptionHandler) { 14897 var $qMinErr = minErr('$q', TypeError); 14898 function callOnce(self, resolveFn, rejectFn) { 14899 var called = false; 14900 function wrap(fn) { 14901 return function(value) { 14902 if (called) return; 14903 called = true; 14904 fn.call(self, value); 14905 }; 14906 } 14907 14908 return [wrap(resolveFn), wrap(rejectFn)]; 14909 } 14910 14911 /** 14912 * @ngdoc method 14913 * @name ng.$q#defer 14914 * @kind function 14915 * 14916 * @description 14917 * Creates a `Deferred` object which represents a task which will finish in the future. 14918 * 14919 * @returns {Deferred} Returns a new instance of deferred. 14920 */ 14921 var defer = function() { 14922 return new Deferred(); 14923 }; 14924 14925 function Promise() { 14926 this.$$state = { status: 0 }; 14927 } 14928 14929 extend(Promise.prototype, { 14930 then: function(onFulfilled, onRejected, progressBack) { 14931 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 14932 return this; 14933 } 14934 var result = new Deferred(); 14935 14936 this.$$state.pending = this.$$state.pending || []; 14937 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 14938 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 14939 14940 return result.promise; 14941 }, 14942 14943 "catch": function(callback) { 14944 return this.then(null, callback); 14945 }, 14946 14947 "finally": function(callback, progressBack) { 14948 return this.then(function(value) { 14949 return handleCallback(value, true, callback); 14950 }, function(error) { 14951 return handleCallback(error, false, callback); 14952 }, progressBack); 14953 } 14954 }); 14955 14956 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 14957 function simpleBind(context, fn) { 14958 return function(value) { 14959 fn.call(context, value); 14960 }; 14961 } 14962 14963 function processQueue(state) { 14964 var fn, deferred, pending; 14965 14966 pending = state.pending; 14967 state.processScheduled = false; 14968 state.pending = undefined; 14969 for (var i = 0, ii = pending.length; i < ii; ++i) { 14970 deferred = pending[i][0]; 14971 fn = pending[i][state.status]; 14972 try { 14973 if (isFunction(fn)) { 14974 deferred.resolve(fn(state.value)); 14975 } else if (state.status === 1) { 14976 deferred.resolve(state.value); 14977 } else { 14978 deferred.reject(state.value); 14979 } 14980 } catch (e) { 14981 deferred.reject(e); 14982 exceptionHandler(e); 14983 } 14984 } 14985 } 14986 14987 function scheduleProcessQueue(state) { 14988 if (state.processScheduled || !state.pending) return; 14989 state.processScheduled = true; 14990 nextTick(function() { processQueue(state); }); 14991 } 14992 14993 function Deferred() { 14994 this.promise = new Promise(); 14995 //Necessary to support unbound execution :/ 14996 this.resolve = simpleBind(this, this.resolve); 14997 this.reject = simpleBind(this, this.reject); 14998 this.notify = simpleBind(this, this.notify); 14999 } 15000 15001 extend(Deferred.prototype, { 15002 resolve: function(val) { 15003 if (this.promise.$$state.status) return; 15004 if (val === this.promise) { 15005 this.$$reject($qMinErr( 15006 'qcycle', 15007 "Expected promise to be resolved with value other than
15007itself '{0}'", 15008 val)); 15009 } else { 15010 this.$$resolve(val); 15011 } 15012 15013 }, 15014 15015 $$resolve: function(val) { 15016 var then, fns; 15017 15018 fns = callOnce(this, this.$$resolve, this.$$reject); 15019 try { 15020 if ((isObject(val) || isFunction(val))) then = val && val.then; 15021 if (isFunction(then)) { 15022 this.promise.$$state.status = -1; 15023 then.call(val, fns[0], fns[1], this.notify); 15024 } else { 15025 this.promise.$$state.value = val; 15026 this.promise.$$state.status = 1; 15027 scheduleProcessQueue(this.promise.$$state); 15028 } 15029 } catch (e) { 15030 fns[1](e); 15031 exceptionHandler(e); 15032 } 15033 }, 15034 15035 reject: function(reason) { 15036 if (this.promise.$$state.status) return; 15037 this.$$reject(reason); 15038 }, 15039 15040 $$reject: function(reason) { 15041 this.promise.$$state.value = reason; 15042 this.promise.$$state.status = 2; 15043 scheduleProcessQueue(this.promise.$$state); 15044 }, 15045 15046 notify: function(progress) { 15047 var callbacks = this.promise.$$state.pending; 15048 15049 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 15050 nextTick(function() { 15051 var callback, result; 15052 for (var i = 0, ii = callbacks.length; i < ii; i++) { 15053 result = callbacks[i][0]; 15054 callback = callbacks[i][3]; 15055 try { 15056 result.notify(isFunction(callback) ? callback(progress) : progress); 15057 } catch (e) { 15058 exceptionHandler(e); 15059 } 15060 } 15061 }); 15062 } 15063 } 15064 }); 15065 15066 /** 15067 * @ngdoc method 15068 * @name $q#reject 15069 * @kind function 15070 * 15071 * @description 15072 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 15073 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 15074 * a promise chain, you don't need to worry about it. 15075 * 15076 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 15077 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 15078 * a promise error callback and you want to forward the error to the promise derived from the 15079 * current promise, you have to "rethrow" the error by returning a rejection constructed via 15080 * `reject`. 15081 * 15082 * ```js 15083 * promiseB = promiseA.then(function(result) { 15084 * // success: do something and resolve promiseB 15085 * // with the old or a new result 15086 * return result; 15087 * }, function(reason) { 15088 * // error: handle the error if possible and 15089 * // resolve promiseB with newPromiseOrValue, 15090 * // otherwise forward the rejection to promiseB 15091 * if (canHandle(reason)) { 15092 * // handle the error and recover 15093 * return newPromiseOrValue; 15094 * } 15095 * return $q.reject(reason); 15096 * }); 15097 * ``` 15098 * 15099 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 15100 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 15101 */ 15102 var reject = function(reason) { 15103 var result = new Deferred(); 15104 result.reject(reason); 15105 return result.promise; 15106 }; 15107 15108 var makePromise = function makePromise(value, resolved) { 15109 var result = new Deferred(); 15110 if (resolved) { 15111 result.resolve(value); 15112 } else { 15113 result.reject(value); 15114 } 15115 return result.promise; 15116 }; 15117 15118 var handleCallback = function handleCallback(value, isResolved, callback) { 15119 var callbackOutput = null; 15120 try { 15121 if (isFunction(callback)) callbackOutput = callback(); 15122 } catch (e) { 15123 return makePromise(e, false); 15124 } 15125 if (isPromiseLike(callbackOutput)) { 15126 return callbackOutput.then(function() { 15127 return makePromise(value, isResolved); 15128 }, function(error) { 15129 return makePromise(error, false); 15130 }); 15131 } else { 15132 return makePromise(value, isResolved); 15133 } 15134 }; 15135 15136 /** 15137 * @ngdoc method 15138 * @name $q#when 15139 * @kind function 15140 * 15141 * @description 15142 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 15143 * This is useful when you are dealing with an object that might or might not be a promise, or if 15144 * the promise comes from a source that can't be trusted. 15145 * 15146 * @param {*} value Value or a promise 15147 * @param {Function=} successCallback 15148 * @param {Function=} errorCallback 15149 * @param {Function=} progressCallback 15150 * @returns {Promise} Returns a promise of the passed value or promise 15151 */ 15152 15153 15154 var when = function(value, callback, errback, progressBack) { 15155 var result = new Deferred(); 15156 result.resolve(value); 15157 return result.promise.then(callback, errback, progressBack); 15158 }; 15159 15160 /** 15161 * @ngdoc method 15162 * @name $q#resolve 15163 * @kind function 15164 * 15165 * @description 15166 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 15167 * 15168 * @param {*} value Value or a promise 15169 * @param {Function=} successCallback 15170 * @param {Function=} errorCallback 15171 * @param {Function=} progressCallback 15172 * @returns {Promise} Returns a promise of the passed value or promise 15173 */ 15174 var resolve = when; 15175 15176 /** 15177 * @ngdoc method 15178 * @name $q#all 15179 * @kind function 15180 * 15181 * @description 15182 * Combines multiple promises into a single promise that is resolved when all of the input 15183 * promises are resolved. 15184 * 15185 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 15186 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 15187 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 15188 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 15189 * with the same rejection value. 15190 */ 15191 15192 function all(promises) { 15193 var deferred = new Deferred(), 15194 counter = 0, 15195 results = isArray(promises) ? [] : {}; 15196
15197 forEach(promises, function(promise, key) { 15198 counter++; 15199 when(promise).then(function(value) { 15200 if (results.hasOwnProperty(key)) return; 15201 results[key] = value; 15202 if (!(--counter)) deferred.resolve(results); 15203 }, function(reason) { 15204 if (results.hasOwnProperty(key)) return; 15205 deferred.reject(reason); 15206 }); 15207 }); 15208 15209 if (counter === 0) { 15210 deferred.resolve(results); 15211 } 15212 15213 return deferred.promise; 15214 } 15215 15216 var $Q = function Q(resolver) { 15217 if (!isFunction(resolver)) { 15218 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 15219 } 15220 15221 if (!(this instanceof Q)) { 15222 // More useful when $Q is the Promise itself. 15223 return new Q(resolver); 15224 } 15225 15226 var deferred = new Deferred(); 15227 15228 function resolveFn(value) { 15229 deferred.resolve(value); 15230 } 15231 15232 function rejectFn(reason) { 15233 deferred.reject(reason); 15234 } 15235 15236 resolver(resolveFn, rejectFn); 15237 15238 return deferred.promise; 15239 }; 15240 15241 $Q.defer = defer; 15242 $Q.reject = reject; 15243 $Q.when = when; 15244 $Q.resolve = resolve; 15245 $Q.all = all; 15246 15247 return $Q; 15248} 15249 15250function $$RAFProvider() { //rAF 15251 this.$get = ['$window', '$timeout', function($window, $timeout) { 15252 var requestAnimationFrame = $window.requestAnimationFrame || 15253 $window.webkitRequestAnimationFrame; 15254 15255 var cancelAnimationFrame = $window.cancelAnimationFrame || 15256 $window.webkitCancelAnimationFrame || 15257 $window.webkitCancelRequestAnimationFrame; 15258 15259 var rafSupported = !!requestAnimationFrame; 15260 var raf = rafSupported 15261 ? function(fn) { 15262 var id = requestAnimationFrame(fn); 15263 return function() { 15264 cancelAnimationFrame(id); 15265 }; 15266 } 15267 : function(fn) { 15268 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 15269 return function() { 15270 $timeout.cancel(timer); 15271 }; 15272 }; 15273 15274 raf.supported = rafSupported; 15275 15276 return raf; 15277 }]; 15278} 15279 15280/** 15281 * DESIGN NOTES 15282 * 15283 * The design decisions behind the scope are heavily favored for speed and memory consumption. 15284 * 15285 * The typical use of scope is to watch the expressions, which most of the time return the same 15286 * value as last time so we optimize the operation. 15287 * 15288 * Closures construction is expensive in terms of speed as well as memory: 15289 * - No closures, instead use prototypical inheritance for API 15290 * - Internal state needs to be stored on scope directly, which means that private state is 15291 * exposed as $$____ properties 15292 * 15293 * Loop operations are optimized by using while(count--) { ... } 15294 * - this means that in order to keep the same order of execution as addition we have to add 15295 * items to the array at the beginning (unshift) instead of at the end (push) 15296 * 15297 * Child scopes are created and removed often 15298 * - Using an array would be slow since inserts in middle are expensive so we use linked list 15299 * 15300 * There are few watches then a lot of observers. This is why you don't want the observer to be 15301 * implemented in the same way as watch. Watch requires return of initialization function which 15302 * are expensive to construct. 15303 */ 15304 15305 15306/** 15307 * @ngdoc provider 15308 * @name $rootScopeProvider 15309 * @description 15310 * 15311 * Provider for the $rootScope service. 15312 */ 15313 15314/** 15315 * @ngdoc method 15316 * @name $rootScopeProvider#digestTtl 15317 * @description 15318 * 15319 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 15320 * assuming that the model is unstable. 15321 * 15322 * The current default is 10 iterations. 15323 * 15324 * In complex applications it's possible that the dependencies between `$watch`s will result in 15325 * several digest iterations. However if an application needs more than the default 10 digest 15326 * iterations for its model to stabilize then you should investigate what is causing the model to 15327 * continuously change during the digest. 15328 * 15329 * Increasing the TTL could have performance implications, so you should not change it without 15330 * proper justification. 15331 * 15332 * @param {number} limit The number of digest iterations. 15333 */ 15334 15335 15336/** 15337 * @ngdoc service 15338 * @name $rootScope 15339 * @description 15340 * 15341 * Every application has a single root {@link ng.$rootScope.Scope scope}. 15342 * All other scopes are descendant scopes of the root scope. Scopes provide separation 15343 * between the model and the view, via a mechanism for watching the model for changes. 15344 * They also provide an event emission/broadcast and subscription facility. See the 15345 * {@link guide/scope developer guide on scopes}. 15346 */ 15347function $RootScopeProvider() { 15348 var TTL = 10; 15349 var $rootScopeMinErr = minErr('$rootScope'); 15350 var lastDirtyWatch = null; 15351 var applyAsyncId = null; 15352 15353 this.digestTtl = function(value) { 15354 if (arguments.length) { 15355 TTL = value; 15356 } 15357 return TTL; 15358 }; 15359 15360 function createChildScopeClass(parent) { 15361 function ChildScope() {
vendor: 4,085 bytes, lines 15362-15475
15362 this.$$watchers = this.$$nextSibling = 15363 this.$$childHead = this.$$childTail = null; 15364 this.$$listeners = {}; 15365 this.$$listenerCount = {}; 15366 this.$$watchersCount = 0; 15367 this.$id = nextUid(); 15368 this.$$ChildScope = null; 15369 } 15370 ChildScope.prototype = parent; 15371 return ChildScope; 15372 } 15373 15374 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 15375 function($injector, $exceptionHandler, $parse, $browser) { 15376 15377 function destroyChildScope($event) { 15378 $event.currentScope.$$destroyed = true; 15379 } 15380 15381 /** 15382 * @ngdoc type 15383 * @name $rootScope.Scope 15384 * 15385 * @description 15386 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 15387 * {@link auto.$injector $injector}. Child scopes are created using the 15388 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 15389 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 15390 * an in-depth introduction and usage examples. 15391 * 15392 * 15393 * # Inheritance 15394 * A scope can inherit from a parent scope, as in this example: 15395 * ```js 15396 var parent = $rootScope; 15397 var child = parent.$new(); 15398 15399 parent.salutation = "Hello"; 15400 expect(child.salutation).toEqual('Hello'); 15401 15402 child.salutation = "Welcome"; 15403 expect(child.salutation).toEqual('Welcome'); 15404 expect(parent.salutation).toEqual('Hello'); 15405 * ``` 15406 * 15407 * When interacting with `Scope` in tests, additional helper methods are available on the 15408 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 15409 * details. 15410 * 15411 * 15412 * @param {Object.<string, function()>=} providers Map of service factory which need to be 15413 * provided for the current scope. Defaults to {@link ng}. 15414 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 15415 * append/override services provided by `providers`. This is handy 15416 * when unit-testing and having the need to override a default 15417 * service. 15418 * @returns {Object} Newly created scope. 15419 * 15420 */ 15421 function Scope() { 15422 this.$id = nextUid(); 15423 this.$$phase = this.$parent = this.$$watchers = 15424 this.$$nextSibling = this.$$prevSibling = 15425 this.$$childHead = this.$$childTail = null; 15426 this.$root = this; 15427 this.$$destroyed = false; 15428 this.$$listeners = {}; 15429 this.$$listenerCount = {}; 15430 this.$$watchersCount = 0; 15431 this.$$isolateBindings = null; 15432 } 15433 15434 /** 15435 * @ngdoc property 15436 * @name $rootScope.Scope#$id 15437 * 15438 * @description 15439 * Unique scope ID (monotonically increasing) useful for debugging. 15440 */ 15441 15442 /** 15443 * @ngdoc property 15444 * @name $rootScope.Scope#$parent 15445 * 15446 * @description 15447 * Reference to the parent scope. 15448 */ 15449 15450 /** 15451 * @ngdoc property 15452 * @name $rootScope.Scope#$root 15453 * 15454 * @description 15455 * Reference to the root scope. 15456 */ 15457 15458 Scope.prototype = { 15459 constructor: Scope, 15460 /** 15461 * @ngdoc method 15462 * @name $rootScope.Scope#$new 15463 * @kind function 15464 * 15465 * @description 15466 * Creates a new child {@link ng.$rootScope.Scope scope}. 15467 * 15468 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 15469 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 15470 * 15471 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 15472 * desired for the scope and its child scopes to be permanently detached from the parent and 15473 * thus stop participating in model change detection and listener notification by invoking. 15474 * 15475 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15476 * parent scope. The scope is isolated, as it can not see parent scope properties. 15477 * When creating widgets, it is useful for the widget to not accidentally read parent 15478 * state. 15479 * 15480 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 15481 * of the newly created scope. Defaults to `this` scope if not provided. 15482 * This is used when creating a transclude scope to correctly place it 15483 * in the scope hierarchy while maintaining the correct prototypical 15484 * inheritance. 15485 * 15486 * @returns {Object} The newly created child scope. 15487 * 15488 */ 15489 $new: function(isolate, parent) { 15490 var child; 15491 15492 parent = parent || this; 15493 15494 if (isolate) { 15495 child = new Scope(); 15496 child.$root = this.$root; 15497 } else { 15498 // Only create a child scope class if somebody asks for one, 15499 // but cache it to allow the VM to optimize lookups. 15500 if (!this.$$ChildScope) { 15501 this.$$ChildScope = createChildScopeClass(this); 15502 } 15503 child = new this.$$ChildScope(); 15504 } 15505 child.$parent = parent; 15506 child.$$prevSibling = parent.$$childTail; 15507 if (parent.$$childHead) { 15508 parent.$$childTail.$$nextSibling = child; 15509 parent.$$childTail = child; 15510 } else { 15511 parent.$$childHead = parent.$$childTail = child; 15512 } 15513 15514 // When the new scope is not isolated or we inherit from `this`, and 15515 // the parent scope is destroyed, the property `$$destroyed` is inherited 15516 // prototypically. In all other cases, this property needs to be set 15517 // when the parent scope is destroyed. 15518 // The listener needs to be added after the parent is set 15519 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 15520 15521 return child; 15522 }, 15523 15524 /** 15525 * @ngdoc method 15526 * @name $rootScope.Scope#$watch 15527 * @kind function 15528 * 15529 * @description 15530 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 15531 * 15532 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 15533 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 15534 * its value when executed multiple times with the same input because it may be executed multiple 15535 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 15536 * [idempotent](http://en.wikipedia.org/wiki/Idempotence). 15537 * - The `listener` is called only when the value from the current `watchExpression` and the 15538 * previous call to `watchExpression` are not equal (with the exception of the initial run, 15539 * see below). Inequality is determined according to reference inequality, 15540 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 15541 * via the `!==` Javascript operator, unless `objectEquality == true` 15542 * (see next point) 15543 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 15544 * according to the {@link angular.equals} function. To save the value of the object for 15545 * later comparison, the {@link angular.copy} function is used. This therefore means that 15546 * watching complex objects will have adverse memory and performance implications. 15547 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 15548 * This is achieved by rerunning the watchers until no changes are detected. The rerun 15549 * iteration limit is 10 to prevent an infinite loop deadlock. 15550 * 15551 * 15552 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 15553 * you can register a `watchExpression` function with no `listener`. (Be prepared for 15554 * multiple calls to your `watchExpression` because it will execute multiple times in a 15555 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 15556 * 15557 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 15558 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 15559 * watcher. In rare cases, this is undesirable because the listener is called when the result 15560 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 15561 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 15562 * listener was called due to initialization. 15563 * 15564 * 15565 * 15566 * # Example 15567 * ```js 15568 // let's assume that scope was dependency injected as the $rootScope 15569 var scope = $rootScope; 15570 scope.name = 'misko'; 15571 scope.counter = 0; 15572 15573 expect(scope.counter).toEqual(0); 15574 scope.$watch('name', function(newValue, oldValue) { 15575 scope.counter = scope.counter + 1; 15576 }); 15577 expect(scope.counter).toEqual(0); 15578 15579 scope.$digest(); 15580 // the listener is always called during the first $digest loop after it was registered 15581 expect(scope.counter).toEqual(1); 15582 15583 scope.$digest(); 15584 // but now it will not be called unless the value changes 15585 expect(scope.counter).toEqual(1); 15586 15587 scope.name = 'adam'; 15588 scope.$digest(); 15589 expect(scope.counter).toEqual(2); 15590 15591 15592
15593 // Using a function as a watchExpression 15594 var food; 15595 scope.foodCounter = 0; 15596 expect(scope.foodCounter).toEqual(0); 15597 scope.$watch( 15598 // This function returns the value being watched. It is called for each turn of the $digest loop 15599 function() { return food; }, 15600 // This is the change listener, called when the value returned from the above function changes 15601 function(newValue, oldValue) { 15602 if ( newValue !== oldValue ) { 15603 // Only increment the counter if the value changed 15604 scope.foodCounter = scope.foodCounter + 1; 15605 } 15606 } 15607 ); 15608 // No digest has been run so the counter will be zero 15609 expect(scope.foodCounter).toEqual(0); 15610 15611 // Run the digest but since food has not changed count will still be zero 15612 scope.$digest(); 15613 expect(scope.foodCounter).toEqual(0); 15614 15615 // Update food and run digest. Now the counter will increment 15616 food = 'cheeseburger'; 15617 scope.$digest(); 15618 expect(scope.foodCounter).toEqual(1); 15619 15620 * ``` 15621 * 15622 * 15623 * 15624 * @param {(function()|string)} watchExpression Expression that is evaluated on each 15625 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 15626 * a call to the `listener`. 15627 * 15628 * - `string`: Evaluated as {@link guide/expression expression} 15629 * - `function(scope)`: called with current `scope` as a parameter. 15630 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 15631 * of `watchExpression` changes. 15632 * 15633 * - `newVal` contains the current value of the `watchExpression` 15634 * - `oldVal` contains the previous value of the `watchExpression` 15635 * - `scope` refers to the current scope 15636 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 15637 * comparing for reference equality. 15638 * @returns {function()} Returns a deregistration function for this listener. 15639 */ 15640 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 15641 var get = $parse(watchExp); 15642 15643 if (get.$$watchDelegate) { 15644 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 15645 } 15646 var scope = this, 15647 array = scope.$$watchers, 15648 watcher = { 15649 fn: listener, 15650 last: initWatchVal, 15651 get: get, 15652 exp: prettyPrintExpression || watchExp, 15653 eq: !!objectEquality 15654 }; 15655 15656 lastDirtyWatch = null; 15657 15658 if (!isFunction(listener)) { 15659 watcher.fn = noop; 15660 } 15661 15662 if (!array) { 15663 array = scope.$$watchers = []; 15664 } 15665 // we use unshift since we use a while loop in $digest for speed. 15666 // the while loop reads in reverse order. 15667 array.unshift(watcher); 15668 incrementWatchersCount(this, 1); 15669 15670 return function deregisterWatch() { 15671 if (arrayRemove(array, watcher) >= 0) { 15672 incrementWatchersCount(scope, -1); 15673 } 15674 lastDirtyWatch = null; 15675 }; 15676 }, 15677 15678 /** 15679 * @ngdoc method 15680 * @name $rootScope.Scope#$watchGroup 15681 * @kind function 15682 * 15683 * @description 15684 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 15685 * If any one expression in the collection changes the `listener` is executed. 15686 * 15687 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 15688 * call to $digest() to see if any items changes. 15689 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 15690 * 15691 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 15692 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 15693 * 15694 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 15695 * expression in `watchExpressions` changes 15696 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 15697 * those of `watchExpression` 15698 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 15699 * those of `watchExpression` 15700 * The `scope` refers to the current scope. 15701 * @returns {function()} Returns a de-registration function for all listeners. 15702 */ 15703 $watchGroup: function(watchExpressions, listener) { 15704 var oldValues = new Array(watchExpressions.length); 15705 var newValues = new Array(watchExpressions.length); 15706 var deregisterFns = []; 15707 var self = this; 15708 var changeReactionScheduled = false;
vendor: 10,192 bytes, lines 15709-15971
15709 var firstRun = true; 15710 15711 if (!watchExpressions.length) { 15712 // No expressions means we call the listener ASAP 15713 var shouldCall = true; 15714 self.$evalAsync(function() { 15715 if (shouldCall) listener(newValues, newValues, self); 15716 }); 15717 return function deregisterWatchGroup() { 15718 shouldCall = false; 15719 }; 15720 } 15721 15722 if (watchExpressions.length === 1) { 15723 // Special case size of one 15724 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 15725 newValues[0] = value; 15726 oldValues[0] = oldValue; 15727 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 15728 }); 15729 } 15730 15731 forEach(watchExpressions, function(expr, i) { 15732 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 15733 newValues[i] = value; 15734 oldValues[i] = oldValue; 15735 if (!changeReactionScheduled) { 15736 changeReactionScheduled = true; 15737 self.$evalAsync(watchGroupAction); 15738 } 15739 }); 15740 deregisterFns.push(unwatchFn); 15741 }); 15742 15743 function watchGroupAction() { 15744 changeReactionScheduled = false; 15745 15746 if (firstRun) { 15747 firstRun = false; 15748 listener(newValues, newValues, self); 15749 } else { 15750 listener(newValues, oldValues, self); 15751 } 15752 } 15753 15754 return function deregisterWatchGroup() { 15755 while (deregisterFns.length) { 15756 deregisterFns.shift()(); 15757 } 15758 }; 15759 }, 15760 15761 15762 /** 15763 * @ngdoc method 15764 * @name $rootScope.Scope#$watchCollection 15765 * @kind function 15766 * 15767 * @description 15768 * Shallow watches the properties of an object and fires whenever any of the properties change 15769 * (for arrays, this implies watching the array items; for object maps, this implies watching 15770 * the properties). If a change is detected, the `listener` callback is fired. 15771 * 15772 * - The `obj` collection is observed via standard $watch operation and is examined on every 15773 * call to $digest() to see if any items have been added, removed, or moved. 15774 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 15775 * adding, removing, and moving items belonging to an object or array. 15776 * 15777 * 15778 * # Example 15779 * ```js 15780 $scope.names = ['igor', 'matias', 'misko', 'james']; 15781 $scope.dataCount = 4; 15782 15783 $scope.$watchCollection('names', function(newNames, oldNames) { 15784 $scope.dataCount = newNames.length; 15785 }); 15786 15787 expect($scope.dataCount).toEqual(4); 15788 $scope.$digest(); 15789 15790 //still at 4 ... no changes 15791 expect($scope.dataCount).toEqual(4); 15792 15793 $scope.names.pop(); 15794 $scope.$digest(); 15795 15796 //now there's been a change 15797 expect($scope.dataCount).toEqual(3); 15798 * ``` 15799 * 15800 * 15801 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 15802 * expression value should evaluate to an object or an array which is observed on each 15803 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 15804 * collection will trigger a call to the `listener`. 15805 * 15806 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 15807 * when a change is detected. 15808 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 15809 * - The `oldCollection` object is a copy of the former collection data. 15810 * Due to performance considerations, the`oldCollection` value is computed only if the 15811 * `listener` function declares two or more arguments. 15812 * - The `scope` argument refers to the current scope. 15813 * 15814 * @returns {function()} Returns a de-registration function for this listener. When the 15815 * de-registration function is executed, the internal watch operation is terminated. 15816 */ 15817 $watchCollection: function(obj, listener) { 15818 $watchCollectionInterceptor.$stateful = true; 15819 15820 var self = this; 15821 // the current value, updated on each dirty-check run 15822 var newValue; 15823 // a shallow copy of the newValue from the last dirty-check run, 15824 // updated to match newValue during dirty-check run 15825 var oldValue; 15826 // a shallow copy of the newValue from when the last change happened 15827 var veryOldValue; 15828 // only track veryOldValue if the listener is asking for it 15829 var trackVeryOldValue = (listener.length > 1); 15830 var changeDetected = 0; 15831 var changeDetector = $parse(obj, $watchCollectionInterceptor); 15832 var internalArray = []; 15833 var internalObject = {}; 15834 var initRun = true; 15835 var oldLength = 0; 15836 15837 function $watchCollectionInterceptor(_value) { 15838 newValue = _value; 15839 var newLength, key, bothNaN, newItem, oldItem; 15840 15841 // If the new value is undefined, then return undefined as the watch may be a one-time watch 15842 if (isUndefined(newValue)) return; 15843 15844 if (!isObject(newValue)) { // if primitive 15845 if (oldValue !== newValue) { 15846 oldValue = newValue; 15847 changeDetected++; 15848 } 15849 } else if (isArrayLike(newValue)) { 15850 if (oldValue !== internalArray) { 15851 // we are transitioning from something which was not an array into array. 15852 oldValue = internalArray; 15853 oldLength = oldValue.length = 0; 15854 changeDetected++; 15855 } 15856 15857 newLength = newValue.length; 15858 15859 if (oldLength !== newLength) { 15860 // if lengths do not match we need to trigger change notification 15861 changeDetected++; 15862 oldValue.length = oldLength = newLength; 15863 } 15864 // copy the items to oldValue and look for changes. 15865 for (var i = 0; i < newLength; i++) { 15866 oldItem = oldValue[i]; 15867 newItem = newValue[i]; 15868 15869 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15870 if (!bothNaN && (oldItem !== newItem)) { 15871 changeDetected++; 15872 oldValue[i] = newItem; 15873 } 15874 } 15875 } else { 15876 if (oldValue !== internalObject) { 15877 // we are transitioning from something which was not an object into object. 15878 oldValue = internalObject = {}; 15879 oldLength = 0; 15880 changeDetected++; 15881 } 15882 // copy the items to oldValue and look for changes. 15883 newLength = 0; 15884 for (key in newValue) { 15885 if (hasOwnProperty.call(newValue, key)) { 15886 newLength++; 15887 newItem = newValue[key]; 15888 oldItem = oldValue[key]; 15889 15890 if (key in oldValue) { 15891 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15892 if (!bothNaN && (oldItem !== newItem)) { 15893 changeDetected++; 15894 oldValue[key] = newItem; 15895 } 15896 } else { 15897 oldLength++; 15898 oldValue[key] = newItem; 15899 changeDetected++; 15900 } 15901 } 15902 } 15903 if (oldLength > newLength) { 15904 // we used to have more keys, need to find them and destroy them. 15905 changeDetected++; 15906 for (key in oldValue) { 15907 if (!hasOwnProperty.call(newValue, key)) { 15908 oldLength--; 15909 delete oldValue[key]; 15910 } 15911 } 15912 } 15913 } 15914 return changeDetected; 15915 } 15916 15917 function $watchCollectionAction() { 15918 if (initRun) { 15919 initRun = false; 15920 listener(newValue, newValue, self); 15921 } else { 15922 listener(newValue, veryOldValue, self); 15923 } 15924 15925 // make a copy for the next time a collection is changed 15926 if (trackVeryOldValue) { 15927 if (!isObject(newValue)) { 15928 //primitive 15929 veryOldValue = newValue; 15930 } else if (isArrayLike(newValue)) { 15931 veryOldValue = new Array(newValue.length); 15932 for (var i = 0; i < newValue.length; i++) { 15933 veryOldValue[i] = newValue[i]; 15934 } 15935 } else { // if object 15936 veryOldValue = {}; 15937 for (var key in newValue) { 15938 if (hasOwnProperty.call(newValue, key)) { 15939 veryOldValue[key] = newValue[key]; 15940 } 15941 } 15942 } 15943 } 15944 } 15945 15946 return this.$watch(changeDetector, $watchCollectionAction); 15947 }, 15948 15949 /** 15950 * @ngdoc method 15951 * @name $rootScope.Scope#$digest 15952 * @kind function 15953 * 15954 * @description 15955 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 15956 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 15957 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 15958 * until no more listeners are firing. This means that it is possible to get into an infinite 15959 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 15960 * iterations exceeds 10. 15961 * 15962 * Usually, you don't call `$digest()` directly in 15963 * {@link ng.directive:ngController controllers} or in 15964 * {@link ng.$compileProvider#directive directives}. 15965 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 15966 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 15967 * 15968 * If you want to be notified whenever `$digest()` is called, 15969 * you can register a `watchExpression` function with 15970 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 15971 *
15972 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 15973 * 15974 * # Example 15975 * ```js 15976 var scope = ...; 15977 scope.name = 'misko'; 15978 scope.counter = 0; 15979 15980 expect(scope.counter).toEqual(0); 15981 scope.$watch('name', function(newValue, oldValue) { 15982 scope.counter = scope.counter + 1; 15983 }); 15984 expect(scope.counter).toEqual(0); 15985 15986 scope.$digest(); 15987 // the listener is always called during the first $digest loop after it was registered 15988 expect(scope.counter).toEqual(1); 15989 15990 scope.$digest(); 15991 // but now it will not be called unless the value changes 15992 expect(scope.counter).toEqual(1); 15993 15994 scope.name = 'adam'; 15995 scope.$digest(); 15996 expect(scope.counter).toEqual(2); 15997 * ``` 15998 * 15999 */ 16000 $digest: function() { 16001 var watch, value, last, 16002 watchers, 16003 length, 16004 dirty, ttl = TTL, 16005 next, current, target = this, 16006 watchLog = [], 16007 logIdx, logMsg, asyncTask; 16008 16009 beginPhase('$digest'); 16010 // Check for changes to browser url that happened in sync before the call to $digest 16011 $browser.$$checkUrlChange(); 16012 16013 if (this === $rootScope && applyAsyncId !== null) { 16014 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 16015 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 16016 $browser.defer.cancel(applyAsyncId); 16017 flushApplyAsync(); 16018 } 16019 16020 lastDirtyWatch = null; 16021 16022 do { // "while dirty" loop 16023 dirty = false; 16024 current = target; 16025 16026 while (asyncQueue.length) { 16027 try { 16028 asyncTask = asyncQueue.shift(); 16029 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 16030 } catch (e) { 16031 $exceptionHandler(e); 16032 } 16033 lastDirtyWatch = null; 16034 } 16035 16036 traverseScopesLoop: 16037 do { // "traverse the scopes" loop 16038 if ((watchers = current.$$watchers)) { 16039 // process our watches 16040 length = watchers.length; 16041 while (length--) { 16042 try { 16043 watch = watchers[length]; 16044 // Most common watches are on primitives, in which case we can short 16045 // circuit it with === operator, only when === fails do we use .equals 16046 if (watch) { 16047 if ((value = watch.get(current)) !== (last = watch.last) && 16048 !(watch.eq 16049 ? equals(value, last) 16050 : (typeof value === 'number' && typeof last === 'number' 16051 && isNaN(value) && isNaN(last)))) { 16052 dirty = true; 16053 lastDirtyWatch = watch; 16054 watch.last = watch.eq ? copy(value, null) : value; 16055 watch.fn(value, ((last === initWatchVal) ? value : last), current); 16056 if (ttl < 5) { 16057 logIdx = 4 - ttl; 16058 if (!watchLog[logIdx]) watchLog[logIdx] = []; 16059 watchLog[logIdx].push({ 16060 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 16061 newVal: value, 16062 oldVal: last 16063 }); 16064 } 16065 } else if (watch === lastDirtyWatch) { 16066 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 16067 // have already been tested. 16068 dirty = false; 16069 break traverseScopesLoop; 16070 } 16071 } 16072 } catch (e) { 16073 $exceptionHandler(e); 16074 } 16075 } 16076 } 16077 16078 // Insanity Warning: scope depth-first traversal 16079 // yes, this code is a bit crazy, but it works and we have tests to prove it! 16080 // this piece should be kept in sync with the traversal in $broadcast 16081 if (!(next = ((current.$$watchersCount && current.$$childHead) || 16082 (current !== target && current.$$nextSibling)))) { 16083 while (current !== target && !(next = current.$$nextSibling)) { 16084 current = current.$parent; 16085 } 16086 } 16087 } while ((current = next)); 16088 16089 // `break traverseScopesLoop;` takes us to here 16090 16091 if ((dirty || asyncQueue.length) && !(ttl--)) { 16092 clearPhase(); 16093 throw $rootScopeMinErr('infdig', 16094 '{0} $digest() iterations reached. Aborting!\n' + 16095 'Watchers fired in the last 5 iterations: {1}', 16096 TTL, watchLog); 16097 } 16098 16099 }
vendor: 5,032 bytes, lines 16099-16225
16099 while (dirty || asyncQueue.length); 16100 16101 clearPhase(); 16102 16103 while (postDigestQueue.length) { 16104 try { 16105 postDigestQueue.shift()(); 16106 } catch (e) { 16107 $exceptionHandler(e); 16108 } 16109 } 16110 }, 16111 16112 16113 /** 16114 * @ngdoc event 16115 * @name $rootScope.Scope#$destroy 16116 * @eventType broadcast on scope being destroyed 16117 * 16118 * @description 16119 * Broadcasted when a scope and its children are being destroyed. 16120 * 16121 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 16122 * clean up DOM bindings before an element is removed from the DOM. 16123 */ 16124 16125 /** 16126 * @ngdoc method 16127 * @name $rootScope.Scope#$destroy 16128 * @kind function 16129 * 16130 * @description 16131 * Removes the current scope (and all of its children) from the parent scope. Removal implies 16132 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 16133 * propagate to the current scope and its children. Removal also implies that the current 16134 * scope is eligible for garbage collection. 16135 * 16136 * The `$destroy()` is usually used by directives such as 16137 * {@link ng.directive:ngRepeat ngRepeat} for managing the 16138 * unrolling of the loop. 16139 * 16140 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 16141 * Application code can register a `$destroy` event handler that will give it a chance to 16142 * perform any necessary cleanup. 16143 * 16144 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 16145 * clean up DOM bindings before an element is removed from the DOM. 16146 */ 16147 $destroy: function() { 16148 // We can't destroy a scope that has been already destroyed. 16149 if (this.$$destroyed) return; 16150 var parent = this.$parent; 16151 16152 this.$broadcast('$destroy'); 16153 this.$$destroyed = true; 16154 16155 if (this === $rootScope) { 16156 //Remove handlers attached to window when $rootScope is removed 16157 $browser.$$applicationDestroyed(); 16158 } 16159 16160 incrementWatchersCount(this, -this.$$watchersCount); 16161 for (var eventName in this.$$listenerCount) { 16162 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 16163 } 16164 16165 // sever all the references to parent scopes (after this cleanup, the current scope should 16166 // not be retained by any of our references and should be eligible for garbage collection) 16167 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 16168 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 16169 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 16170 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 16171 16172 // Disable listeners, watchers and apply/digest methods 16173 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 16174 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 16175 this.$$listeners = {}; 16176 16177 // All of the code below is bogus code that works around V8's memory leak via optimized code 16178 // and inline caches. 16179 // 16180 // see: 16181 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 16182 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 16183 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 16184 16185 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 16186 this.$$childTail = this.$root = this.$$watchers = null; 16187 }, 16188 16189 /** 16190 * @ngdoc method 16191 * @name $rootScope.Scope#$eval 16192 * @kind function 16193 * 16194 * @description 16195 * Executes the `expression` on the current scope and returns the result. Any exceptions in 16196 * the expression are propagated (uncaught). This is useful when evaluating Angular 16197 * expressions. 16198 * 16199 * # Example 16200 * ```js 16201 var scope = ng.$rootScope.Scope(); 16202 scope.a = 1; 16203 scope.b = 2; 16204 16205 expect(scope.$eval('a+b')).toEqual(3); 16206 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 16207 * ``` 16208 * 16209 * @param {(string|function())=} expression An angular expression to be executed. 16210 * 16211 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16212 * - `function(scope)`: execute the function with the current `scope` parameter. 16213 * 16214 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 16215 * @returns {*} The result of evaluating the expression. 16216 */ 16217 $eval: function(expr, locals) { 16218 return $parse(expr)(this, locals); 16219 }, 16220 16221 /** 16222 * @ngdoc method 16223 * @name $rootScope.Scope#$evalAsync 16224 * @kind function 16225 *
16226 * @description 16227 * Executes the expression on the current scope at a later point in time. 16228 * 16229 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 16230 * that: 16231 * 16232 * - it will execute after the function that scheduled the evaluation (preferably before DOM 16233 * rendering). 16234 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 16235 * `expression` execution. 16236 * 16237 * Any exceptions from the execution of the expression are forwarded to the 16238 * {@link ng.$exceptionHandler $exceptionHandler} service. 16239 * 16240 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 16241 * will be scheduled. However, it is encouraged to always call code that changes the model 16242 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 16243 * 16244 * @param {(string|function())=} expression An angular expression to be executed. 16245 * 16246 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16247 * - `function(scope)`: execute the function with the current `scope` parameter. 16248 * 16249 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 16250 */ 16251 $evalAsync: function(expr, locals) { 16252 // if we are outside of an $digest loop and this is the first time we are scheduling async 16253 // task also schedule async auto-flush 16254 if (!$rootScope.$$phase && !asyncQueue.length) { 16255 $browser.defer(function() { 16256 if (asyncQueue.length) { 16257 $rootScope.$digest(); 16258 } 16259 }); 16260 } 16261 16262 asyncQueue.push({scope: this, expression: expr, locals: locals}); 16263 }, 16264 16265 $$postDigest: function(fn) { 16266 postDigestQueue.push(fn); 16267 }, 16268 16269 /** 16270 * @ngdoc method 16271 * @name $rootScope.Scope#$apply 16272 * @kind function 16273 * 16274 * @description 16275 * `$apply()` is used to execute an expression in angular from outside of the angular 16276 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 16277 * Because we are calling into the angular framework we need to perform proper scope life 16278 * cycle of {@link ng.$exceptionHandler exception handling}, 16279 * {@link ng.$rootScope.Scope#$digest executing watches}. 16280 * 16281 * ## Life cycle 16282 * 16283 * # Pseudo-Code of `$apply()` 16284 * ```js 16285 function $apply(expr) { 16286 try { 16287 return $eval(expr); 16288 } catch (e) { 16289 $exceptionHandler(e); 16290 } finally { 16291 $root.$digest(); 16292 } 16293 } 16294 * ``` 16295 * 16296 * 16297 * Scope's `$apply()` method transitions through the following stages: 16298 * 16299 * 1. The {@link guide/expression expression} is executed using the 16300 * {@link ng.$rootScope.Scope#$eval $eval()} method. 16301 * 2. Any exceptions from the execution of the expression are forwarded to the 16302 * {@link ng.$exceptionHandler $exceptionHandler} service. 16303 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 16304 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 16305 * 16306 * 16307 * @param {(string|function())=} exp An angular expression to be executed. 16308 * 16309 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16310 * - `function(scope)`: execute the function with current `scope` parameter. 16311 * 16312 * @returns {*} The result of evaluating the expression. 16313 */ 16314 $apply: function(expr) { 16315 try { 16316 beginPhase('$apply'); 16317 try { 16318 return this.$eval(expr); 16319 } finally { 16320 clearPhase(); 16321 } 16322 } catch (e) { 16323 $exceptionHandler(e); 16324 } finally { 16325 try { 16326 $rootScope.$digest(); 16327 } catch (e) { 16328 $exceptionHandler(e); 16329 throw e; 16330 } 16331 } 16332 }, 16333 16334 /** 16335 * @ngdoc method 16336 * @name $rootScope.Scope#$applyAsync 16337 * @kind function 16338 *
16339 * @description 16340 * Schedule the invocation of $apply to occur at a later time. The actual time difference 16341 * varies across browsers, but is typically around ~10 milliseconds. 16342 * 16343 * This can be used to queue up multiple expressions which need to be evaluated in the same 16344 * digest. 16345 * 16346 * @param {(string|function())=} exp An angular expression to be executed. 16347 * 16348 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16349 * - `function(scope)`: execute the function with current `scope` parameter. 16350 */ 16351 $applyAsync: function(expr) { 16352 var scope = this; 16353 expr && applyAsyncQueue.push($applyAsyncExpression); 16354 scheduleApplyAsync(); 16355 16356 function $applyAsyncExpression() { 16357 scope.$eval(expr); 16358 } 16359 }, 16360 16361 /** 16362 * @ngdoc method 16363 * @name $rootScope.Scope#$on 16364 * @kind function 16365 * 16366 * @description 16367 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 16368 * discussion of event life cycle. 16369 * 16370 * The event listener function format is: `function(event, args...)`. The `event` object 16371 * passed into the listener has the following attributes: 16372 * 16373 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 16374 * `$broadcast`-ed. 16375 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 16376 * event propagates through the scope hierarchy, this property is set to null. 16377 * - `name` - `{string}`: name of the event. 16378 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 16379 * further event propagation (available only for events that were `$emit`-ed). 16380 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 16381 * to true. 16382 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 16383 * 16384 * @param {string} name Event name to listen on. 16385 * @param {function(event, ...args)} listener Function to call when the event is emitted. 16386 * @returns {function()} Returns a deregistration function for this listener. 16387 */ 16388 $on: function(name, listener) { 16389 var namedListeners = this.$$listeners[name]; 16390 if (!namedListeners) { 16391 this.$$listeners[name] = namedListeners = []; 16392 } 16393 namedListeners.push(listener); 16394 16395 var current = this; 16396 do { 16397 if (!current.$$listenerCount[name]) { 16398 current.$$listenerCount[name] = 0; 16399 } 16400 current.$$listenerCount[name]++; 16401 } while ((current = current.$parent)); 16402 16403 var self = this; 16404 return function() { 16405 var indexOfListener = namedListeners.indexOf(listener); 16406 if (indexOfListener !== -1) { 16407 namedListeners[indexOfListener] = null; 16408 decrementListenerCount(self, 1, name); 16409 } 16410 }; 16411 }, 16412 16413 16414 /** 16415 * @ngdoc method 16416 * @name $rootScope.Scope#$emit 16417 * @kind function 16418 * 16419 * @description 16420 * Dispatches an event `name` upwards through the scope hierarchy notifying the 16421 * registered {@link ng.$rootScope.Scope#$on} listeners. 16422 * 16423 * The event life cycle starts at the scope on which `$emit` was called. All 16424 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16425 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 16426 * registered listeners along the way. The event will stop propagating if one of the listeners 16427 * cancels it. 16428 * 16429 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16430 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16431 * 16432 * @param {string} name Event name to emit. 16433 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16434 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 16435 */ 16436 $emit: function(name, args) { 16437 var empty = [], 16438 namedListeners, 16439 scope = this, 16440 stopPropagation = false, 16441 event = { 16442 name: name, 16443 targetScope: scope, 16444 stopPropagation: function() {stopPropagation = true;}, 16445 preventDefault: function() { 16446 event.defaultPrevented = true; 16447 }, 16448 defaultPrevented: false 16449 }, 16450 listenerArgs = concat([event], arguments, 1), 16451 i, length; 16452 16453 do { 16454 namedListeners = scope.$$listeners[name] || empty; 16455 event.currentScope = scope; 16456 for (i = 0, length = namedListeners.length; i < length; i++) { 16457 16458 // if listeners were deregistered, defragment the array 16459 if (!namedListeners[i]) { 16460 namedListeners.splice(i, 1); 16461 i--; 16462 length--; 16463 continue; 16464 } 16465 try { 16466 //allow all listeners attached to the current scope to run 16467 namedListeners[i].apply(null, listenerArgs); 16468 } catch (e) { 16469 $exceptionHandler(e); 16470 } 16471 } 16472 //if any listener on the current scope stops propagation, prevent bubbling 16473 if (stopPropagation) { 16474 event.currentScope = null; 16475 return event; 16476 } 16477 //traverse upwards 16478 scope = scope.$parent; 16479 } while (scope); 16480 16481 event.currentScope = null; 16482 16483 return event; 16484 }, 16485 16486 16487 /** 16488 * @ngdoc method 16489 * @name $rootScope.Scope#$broadcast 16490 * @kind function 16491 *
16492 * @description 16493 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 16494 * registered {@link ng.$rootScope.Scope#$on} listeners. 16495 * 16496 * The event life cycle starts at the scope on which `$broadcast` was called. All 16497 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16498 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 16499 * scope and calls all registered listeners along the way. The event cannot be canceled. 16500 * 16501 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16502 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16503 * 16504 * @param {string} name Event name to broadcast. 16505 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16506 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 16507 */ 16508 $broadcast: function(name, args) { 16509 var target = this, 16510 current = target, 16511 next = target, 16512 event = { 16513 name: name, 16514 targetScope: target, 16515 preventDefault: function() { 16516 event.defaultPrevented = true; 16517 }, 16518 defaultPrevented: false 16519 }; 16520 16521 if (!target.$$listenerCount[name]) return event; 16522 16523 var listenerArgs = concat([event], arguments, 1), 16524 listeners, i, length; 16525 16526 //down while you can, then up and next sibling or up and next sibling until back at root 16527 while ((current = next)) { 16528 event.currentScope = current; 16529 listeners = current.$$listeners[name] || []; 16530 for (i = 0, length = listeners.length; i < length; i++) { 16531 // if listeners were deregistered, defragment the array 16532 if (!listeners[i]) { 16533 listeners.splice(i, 1); 16534 i--; 16535 length--; 16536 continue; 16537 } 16538 16539 try { 16540 listeners[i].apply(null, listenerArgs); 16541 } catch (e) { 16542 $exceptionHandler(e); 16543 } 16544 } 16545 16546 // Insanity Warning: scope depth-first traversal 16547 // yes, this code is a bit crazy, but it works and we have tests to prove it! 16548 // this piece should be kept in sync with the traversal in $digest 16549 // (though it differs due to having the extra check for $$listenerCount) 16550 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 16551 (current !== target && current.$$nextSibling)))) { 16552 while (current !== target && !(next = current.$$nextSibling)) { 16553 current = current.$parent; 16554 } 16555 } 16556 } 16557 16558 event.currentScope = null; 16559 return event; 16560 } 16561 }; 16562 16563 var $rootScope = new Scope(); 16564 16565 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 16566 var asyncQueue = $rootScope.$$asyncQueue = []; 16567 var postDigestQueue = $rootScope.$$postDigestQueue = []; 16568 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 16569 16570 return $rootScope; 16571 16572 16573 function beginPhase(phase) { 16574 if ($rootScope.$$phase) { 16575 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 16576 } 16577 16578 $rootScope.$$phase = phase; 16579 } 16580 16581 function clearPhase() { 16582 $rootScope.$$phase = null; 16583 } 16584 16585 function incrementWatchersCount(current, count) { 16586 do { 16587 current.$$watchersCount += count; 16588 } while ((current = current.$parent)); 16589 } 16590 16591 function decrementListenerCount(current, count, name) { 16592 do { 16593 current.$$listenerCount[name] -= count; 16594 16595 if (current.$$listenerCount[name] === 0) { 16596 delete current.$$listenerCount[name]; 16597 } 16598 } while ((current = current.$parent)); 16599 } 16600 16601 /** 16602 * function used as an initial value for watchers. 16603 * because it's unique we can easily tell it apart from other values 16604 */ 16605 function initWatchVal() {} 16606 16607 function flushApplyAsync() {
vendor: 4,838 bytes, lines 16608-16735
16608 while (applyAsyncQueue.length) { 16609 try { 16610 applyAsyncQueue.shift()(); 16611 } catch (e) { 16612 $exceptionHandler(e); 16613 } 16614 } 16615 applyAsyncId = null; 16616 } 16617 16618 function scheduleApplyAsync() { 16619 if (applyAsyncId === null) { 16620 applyAsyncId = $browser.defer(function() { 16621 $rootScope.$apply(flushApplyAsync); 16622 }); 16623 } 16624 } 16625 }]; 16626} 16627 16628/** 16629 * @description 16630 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 16631 */ 16632function $$SanitizeUriProvider() { 16633 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 16634 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 16635 16636 /** 16637 * @description 16638 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16639 * urls during a[href] sanitization. 16640 * 16641 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16642 * 16643 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 16644 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 16645 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16646 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16647 * 16648 * @param {RegExp=} regexp New regexp to whitelist urls with. 16649 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16650 * chaining otherwise. 16651 */ 16652 this.aHrefSanitizationWhitelist = function(regexp) { 16653 if (isDefined(regexp)) { 16654 aHrefSanitizationWhitelist = regexp; 16655 return this; 16656 } 16657 return aHrefSanitizationWhitelist; 16658 }; 16659 16660 16661 /** 16662 * @description 16663 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16664 * urls during img[src] sanitization. 16665 * 16666 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16667 * 16668 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 16669 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 16670 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16671 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16672 * 16673 * @param {RegExp=} regexp New regexp to whitelist urls with. 16674 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16675 * chaining otherwise. 16676 */ 16677 this.imgSrcSanitizationWhitelist = function(regexp) { 16678 if (isDefined(regexp)) { 16679 imgSrcSanitizationWhitelist = regexp; 16680 return this; 16681 } 16682 return imgSrcSanitizationWhitelist; 16683 }; 16684 16685 this.$get = function() { 16686 return function sanitizeUri(uri, isImage) { 16687 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 16688 var normalizedVal; 16689 normalizedVal = urlResolve(uri).href; 16690 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 16691 return 'unsafe:' + normalizedVal; 16692 } 16693 return uri; 16694 }; 16695 }; 16696} 16697 16698/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16699 * Any commits to this file should be reviewed with security in mind. * 16700 * Changes to this file can potentially create security vulnerabilities. * 16701 * An approval from 2 Core members with history of modifying * 16702 * this file is required. * 16703 * * 16704 * Does the change somehow allow for arbitrary javascript to be executed? * 16705 * Or allows for someone to change the prototype of built-in objects? * 16706 * Or gives undesired access to variables likes document or window? * 16707 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 16708 16709var $sceMinErr = minErr('$sce'); 16710 16711var SCE_CONTEXTS = { 16712 HTML: 'html', 16713 CSS: 'css', 16714 URL: 'url', 16715 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 16716 // url. (e.g. ng-include, script src, templateUrl) 16717 RESOURCE_URL: 'resourceUrl', 16718 JS: 'js' 16719}; 16720 16721// Helper functions follow. 16722 16723function adjustMatcher(matcher) { 16724 if (matcher === 'self') { 16725 return matcher; 16726 } else if (isString(matcher)) { 16727 // Strings match exactly except for 2 wildcards - '*' and '**'. 16728 // '*' matches any character except those from the set ':/.?&'. 16729 // '**' matches any character (like .* in a RegExp). 16730 // More than 2 *'s raises an error as it's ill defined. 16731 if (matcher.indexOf('***') > -1) { 16732 throw $sceMinErr('iwcard', 16733 'Illegal sequence *** in string matcher. String: {0}', matcher); 16734 } 16735 matcher = escapeForRegexp(matcher).
16736 replace('\\*\\*', '.*'). 16737 replace('\\*', '[^:/.?&;]*'); 16738 return new RegExp('^' + matcher + '$'); 16739 } else if (isRegExp(matcher)) { 16740 // The only other type of matcher allowed is a Regexp. 16741 // Match entire URL / disallow partial matches. 16742 // Flags are reset (i.e. no global, ignoreCase or multiline) 16743 return new RegExp('^' + matcher.source + '$'); 16744 } else { 16745 throw $sceMinErr('imatcher', 16746 'Matchers may only be "self", string patterns or RegExp objects'); 16747 } 16748} 16749 16750 16751function adjustMatchers(matchers) { 16752 var adjustedMatchers = []; 16753 if (isDefined(matchers)) { 16754 forEach(matchers, function(matcher) { 16755 adjustedMatchers.push(adjustMatcher(matcher)); 16756 }); 16757 } 16758 return adjustedMatchers; 16759} 16760 16761 16762/** 16763 * @ngdoc service 16764 * @name $sceDelegate 16765 * @kind function 16766 * 16767 * @description 16768 * 16769 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 16770 * Contextual Escaping (SCE)} services to AngularJS. 16771 * 16772 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 16773 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 16774 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 16775 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 16776 * work because `$sce` delegates to `$sceDelegate` for these operations. 16777 * 16778 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 16779 * 16780 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 16781 * can override it completely to change the behavior of `$sce`, the common case would 16782 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 16783 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 16784 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 16785 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 16786 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16787 */ 16788 16789/** 16790 * @ngdoc provider 16791 * @name $sceDelegateProvider 16792 * @description 16793 * 16794 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 16795 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 16796 * that the URLs used for sourcing Angular templates are safe. Refer {@link 16797 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 16798 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16799 * 16800 * For the general details about this service in Angular, read the main page for {@link ng.$sce 16801 * Strict Contextual Escaping (SCE)}. 16802 * 16803 * **Example**: Consider the following case. <a name="example"></a> 16804 * 16805 * - your app is hosted at url `http://myapp.example.com/` 16806 * - but some of your templates are hosted on other domains you control such as 16807 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 16808 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 16809 * 16810 * Here is what a secure configuration for this scenario might look like: 16811 * 16812 * ``` 16813 * angular.module('myApp', []).config(function($sceDelegateProvider) { 16814 * $sceDelegateProvider.resourceUrlWhitelist([ 16815 * // Allow same origin resource loads. 16816 * 'self', 16817 * // Allow loading from our assets domain. Notice the difference between * and **. 16818 * 'http://srv*.assets.example.com/**' 16819 * ]); 16820 * 16821 * // The blacklist overrides the whitelist so the open redirect here is blocked. 16822 * $sceDelegateProvider.resourceUrlBlacklist([ 16823 * 'http://myapp.example.com/clickThru**' 16824 * ]); 16825 * }); 16826 * ``` 16827 */ 16828 16829function $SceDelegateProvider() { 16830 this.SCE_CONTEXTS = SCE_CONTEXTS; 16831 16832 // Resource URLs can also be trusted by policy. 16833 var resourceUrlWhitelist = ['self'], 16834 resourceUrlBlacklist = []; 16835 16836 /** 16837 * @ngdoc method 16838 * @name $sceDelegateProvider#resourceUrlWhitelist 16839 * @kind function 16840 * 16841 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 16842 * provided. This must be an array or null. A snapshot of this array is used so further 16843 * changes to the array are ignored. 16844 * 16845 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16846 * allowed in this array.
vendor: 4,148 bytes, lines 16847-16971
16847 * 16848 * Note: **an empty whitelist array will block all URLs**! 16849 * 16850 * @return {Array} the currently set whitelist array. 16851 * 16852 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 16853 * same origin resource requests. 16854 * 16855 * @description 16856 * Sets/Gets the whitelist of trusted resource URLs. 16857 */ 16858 this.resourceUrlWhitelist = function(value) { 16859 if (arguments.length) { 16860 resourceUrlWhitelist = adjustMatchers(value); 16861 } 16862 return resourceUrlWhitelist; 16863 }; 16864 16865 /** 16866 * @ngdoc method 16867 * @name $sceDelegateProvider#resourceUrlBlacklist 16868 * @kind function 16869 * 16870 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 16871 * provided. This must be an array or null. A snapshot of this array is used so further 16872 * changes to the array are ignored. 16873 * 16874 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16875 * allowed in this array. 16876 * 16877 * The typical usage for the blacklist is to **block 16878 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 16879 * these would otherwise be trusted but actually return content from the redirected domain. 16880 * 16881 * Finally, **the blacklist overrides the whitelist** and has the final say. 16882 * 16883 * @return {Array} the currently set blacklist array. 16884 * 16885 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 16886 * is no blacklist.) 16887 * 16888 * @description 16889 * Sets/Gets the blacklist of trusted resource URLs. 16890 */ 16891 16892 this.resourceUrlBlacklist = function(value) { 16893 if (arguments.length) { 16894 resourceUrlBlacklist = adjustMatchers(value); 16895 } 16896 return resourceUrlBlacklist; 16897 }; 16898 16899 this.$get = ['$injector', function($injector) { 16900 16901 var htmlSanitizer = function htmlSanitizer(html) { 16902 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 16903 }; 16904 16905 if ($injector.has('$sanitize')) { 16906 htmlSanitizer = $injector.get('$sanitize'); 16907 } 16908 16909 16910 function matchUrl(matcher, parsedUrl) { 16911 if (matcher === 'self') { 16912 return urlIsSameOrigin(parsedUrl); 16913 } else { 16914 // definitely a regex. See adjustMatchers() 16915 return !!matcher.exec(parsedUrl.href); 16916 } 16917 } 16918 16919 function isResourceUrlAllowedByPolicy(url) { 16920 var parsedUrl = urlResolve(url.toString()); 16921 var i, n, allowed = false; 16922 // Ensure that at least one item from the whitelist allows this url. 16923 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 16924 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 16925 allowed = true; 16926 break; 16927 } 16928 } 16929 if (allowed) { 16930 // Ensure that no item from the blacklist blocked this url. 16931 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 16932 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 16933 allowed = false; 16934 break; 16935 } 16936 } 16937 } 16938 return allowed; 16939 } 16940 16941 function generateHolderType(Base) { 16942 var holderType = function TrustedValueHolderType(trustedValue) { 16943 this.$$unwrapTrustedValue = function() { 16944 return trustedValue; 16945 }; 16946 }; 16947 if (Base) { 16948 holderType.prototype = new Base(); 16949 } 16950 holderType.prototype.valueOf = function sceValueOf() { 16951 return this.$$unwrapTrustedValue(); 16952 }; 16953 holderType.prototype.toString = function sceToString() { 16954 return this.$$unwrapTrustedValue().toString(); 16955 }; 16956 return holderType; 16957 } 16958 16959 var trustedValueHolderBase = generateHolderType(), 16960 byType = {}; 16961 16962 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 16963 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 16964 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 16965 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 16966 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 16967 16968 /** 16969 * @ngdoc method 16970 * @name $sceDelegate#trustAs 16971 *
16972 * @description 16973 * Returns an object that is trusted by angular for use in specified strict 16974 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 16975 * attribute interpolation, any dom event binding attribute interpolation 16976 * such as for onclick, etc.) that uses the provided value. 16977 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 16978 * 16979 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 16980 * resourceUrl, html, js and css. 16981 * @param {*} value The value that that should be considered trusted/safe. 16982 * @returns {*} A value that can be used to stand in for the provided `value` in places 16983 * where Angular expects a $sce.trustAs() return value. 16984 */ 16985 function trustAs(type, trustedValue) { 16986 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 16987 if (!Constructor) { 16988 throw $sceMinErr('icontext', 16989 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 16990 type, trustedValue); 16991 } 16992 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 16993 return trustedValue; 16994 } 16995 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 16996 // mutable objects, we ensure here that the value passed in is actually a string. 16997 if (typeof trustedValue !== 'string') { 16998 throw $sceMinErr('itype', 16999 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 17000 type); 17001 } 17002 return new Constructor(trustedValue); 17003 } 17004 17005 /** 17006 * @ngdoc method 17007 * @name $sceDelegate#valueOf 17008 * 17009 * @description 17010 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 17011 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 17012 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 17013 * 17014 * If the passed parameter is not a value that had been returned by {@link 17015 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 17016 * 17017 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 17018 * call or anything else. 17019 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 17020 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 17021 * `value` unchanged. 17022 */ 17023 function valueOf(maybeTrusted) { 17024 if (maybeTrusted instanceof trustedValueHolderBase) { 17025 return maybeTrusted.$$unwrapTrustedValue(); 17026 } else { 17027 return maybeTrusted; 17028 } 17029 } 17030 17031 /** 17032 * @ngdoc method 17033 * @name $sceDelegate#getTrusted 17034 * 17035 * @description 17036 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 17037 * returns the originally supplied value if the queried context type is a supertype of the 17038 * created type. If this condition isn't satisfied, throws an exception. 17039 * 17040 * @param {string} type The kind of context in which this value is to be used. 17041 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 17042 * `$sceDelegate.trustAs`} call. 17043 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 17044 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 17045 */ 17046 function getTrusted(type, maybeTrusted) { 17047 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 17048 return maybeTrusted; 17049 } 17050 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 17051 if (constructor && maybeTrusted instanceof constructor) { 17052 return maybeTrusted.$$unwrapTrustedValue(); 17053 } 17054 // If we get here, then we may only take one of two actions. 17055 // 1. sanitize the value for the requested type, or 17056 // 2. throw an exception. 17057 if (type === SCE_CONTEXTS.RESOURCE_URL) { 17058 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 17059 return maybeTrusted; 17060 } else { 17061 throw $sceMinErr('insecurl', 17062 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 17063 maybeTrusted.toString()); 17064 } 17065 } else if (type === SCE_CONTEXTS.HTML) { 17066 return htmlSanitizer(maybeTrusted); 17067 } 17068 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 17069 } 17070 17071 return { trustAs: trustAs, 17072 getTrusted: getTrusted,
17073 valueOf: valueOf }; 17074 }]; 17075} 17076 17077 17078/** 17079 * @ngdoc provider 17080 * @name $sceProvider 17081 * @description 17082 * 17083 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 17084 * - enable/disable Strict Contextual Escaping (SCE) in a module 17085 * - override the default implementation with a custom delegate 17086 * 17087 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 17088 */ 17089 17090/* jshint maxlen: false*/ 17091 17092/** 17093 * @ngdoc service 17094 * @name $sce 17095 * @kind function 17096 * 17097 * @description 17098 * 17099 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 17100 * 17101 * # Strict Contextual Escaping 17102 * 17103 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 17104 * contexts to result in a value that is marked as safe to use for that context. One example of 17105 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 17106 * to these contexts as privileged or SCE contexts. 17107 * 17108 * As of version 1.2, Angular ships with SCE enabled by default. 17109 * 17110 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 17111 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 17112 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17113 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 17114 * to the top of your HTML document. 17115 * 17116 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 17117 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 17118 * 17119 * Here's an example of a binding in a privileged context: 17120 * 17121 * ``` 17122 * <input ng-model="userHtml" aria-label="User input"> 17123 * <div ng-bind-html="userHtml"></div> 17124 * ``` 17125 * 17126 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 17127 * disabled, this application allows the user to render arbitrary HTML into the DIV. 17128 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 17129 * bindings. (HTML is just one example of a context where rendering user controlled input creates 17130 * security vulnerabilities.) 17131 * 17132 * For the case of HTML, you might use a library, either on the client side, or on the server side, 17133 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 17134 * 17135 * How would you ensure that every place that used these types of bindings was bound to a value that 17136 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 17137 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 17138 * properties/fields and forgot to update the binding to the sanitized value? 17139 * 17140 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 17141 * determine that something explicitly says it's safe to use a value for binding in that
17142 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 17143 * for those values that you can easily tell are safe - because they were received from your server, 17144 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 17145 * allowing only the files in a specific directory to do this. Ensuring that the internal API 17146 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 17147 * 17148 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 17149 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 17150 * obtain values that will be accepted by SCE / privileged contexts. 17151 * 17152 * 17153 * ## How does it work? 17154 * 17155 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 17156 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 17157 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 17158 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 17159 * 17160 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 17161 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 17162 * simplified): 17163 * 17164 * ``` 17165 * var ngBindHtmlDirective = ['$sce', function($sce) { 17166 * return function(scope, element, attr) { 17167 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 17168 * element.html(value || ''); 17169 * }); 17170 * }; 17171 * }]; 17172 * ``` 17173 * 17174 * ## Impact on loading templates 17175 * 17176 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 17177 * `templateUrl`'s specified by {@link guide/directive directives}. 17178 * 17179 * By default, Angular only loads templates from the same domain and protocol as the application 17180 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 17181 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 17182 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 17183 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 17184 * 17185 * *Please note*: 17186 * The browser's 17187 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 17188 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 17189 * policy apply in addition to this and may further restrict whether the template is successfully 17190 * loaded. This means that without the right CORS policy, loading templates from a different domain 17191 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 17192 * browsers. 17193 * 17194 * ## This feels like too much overhead 17195 * 17196 * It's important to remember that SCE only applies to interpolation expressions. 17197 * 17198 * If your expressions are constant literals, they're automatically trusted and you don't need to 17199 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 17200 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 17201 * 17202 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 17203 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 17204 * 17205 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 17206 * templates in `ng-include` from your application's domain without having to even know about SCE. 17207 * It blocks loading templates from other domains or loading templates over http from an https 17208 * served document. You can change these by setting your own custom {@link 17209 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 17210 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 17211 * 17212 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 17213 * application that's secure and can be audited to verify that with much more ease than bolting 17214 * security onto an application later. 17215 * 17216 * <a name="contexts"></a> 17217 * ## What trusted context types are supported? 17218 * 17219 * | Context | Notes | 17220 * |---------------------|----------------| 17221 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | 17222 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 17223 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. | 17224 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | 17225 * | `$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. | 17226 * 17227 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 17228 *
17229 * Each element in these arrays must be one of the following: 17230 * 17231 * - **'self'** 17232 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 17233 * domain** as the application document using the **same protocol**. 17234 * - **String** (except the special value `'self'`) 17235 * - The string is matched against the full *normalized / absolute URL* of the resource 17236 * being tested (substring matches are not good enough.) 17237 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 17238 * match themselves. 17239 * - `*`: matches zero or more occurrences of any character other than one of the following 6 17240 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use 17241 * in a whitelist. 17242 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 17243 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 17244 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 17245 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 17246 * http://foo.example.com/templates/**). 17247 * - **RegExp** (*see caveat below*) 17248 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 17249 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 17250 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 17251 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 17252 * small number of cases. A `.` character in the regex used when matching the scheme or a 17253 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 17254 * is highly recommended to use the string patterns and only fall back to regular expressions 17255 * as a last resort. 17256 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 17257 * matched against the **entire** *normalized / absolute URL* of the resource being tested 17258 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 17259 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 17260 * - If you are generating your JavaScript from some other templating engine (not 17261 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 17262 * remember to escape your regular expression (and be aware that you might need more than 17263 * one level of escaping depending on your templating engine and the way you interpolated 17264 * the value.) Do make use of your platform's escaping mechanism as it might be good 17265 * enough before coding your own. E.g. Ruby has 17266 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 17267 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 17268 * Javascript lacks a similar built in function for escaping. Take a look at Google 17269 * Closure library's [goog.string.regExpEscape(s)]( 17270 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 17271 * 17272 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 17273 * 17274 * ## Show me an example using SCE. 17275 * 17276 * <example module="mySceApp" deps="angular-sanitize.js"> 17277 * <file name="index.html"> 17278 * <div ng-controller="AppController as myCtrl"> 17279 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 17280 * <b>User comments</b><br> 17281 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 17282 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 17283 * exploit. 17284 * <div class="well"> 17285 * <div ng-repeat="userComment in myCtrl.userComments"> 17286 * <b>{{userComment.name}}</b>: 17287 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 17288 * <br> 17289 * </div>
17290 * </div> 17291 * </div> 17292 * </file> 17293 * 17294 * <file name="script.js"> 17295 * angular.module('mySceApp', ['ngSanitize']) 17296 * .controller('AppController', ['$http', '$templateCache', '$sce', 17297 * function($http, $templateCache, $sce) { 17298 * var self = this; 17299 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 17300 * self.userComments = userComments; 17301 * }); 17302 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 17303 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 17304 * 'sanitization."">Hover over this text.</span>'); 17305 * }]); 17306 * </file> 17307 * 17308 * <file name="test_data.json"> 17309 * [ 17310 * { "name": "Alice", 17311 * "htmlComment": 17312 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 17313 * }, 17314 * { "name": "Bob", 17315 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 17316 * } 17317 * ] 17318 * </file> 17319 * 17320 * <file name="protractor.js" type="protractor"> 17321 * describe('SCE doc demo', function() { 17322 * it('should sanitize untrusted values', function() { 17323 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 17324 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 17325 * }); 17326 * 17327 * it('should NOT sanitize explicitly trusted values', function() { 17328 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 17329 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 17330 * 'sanitization."">Hover over this text.</span>'); 17331 * }); 17332 * }); 17333 * </file> 17334 * </example> 17335 * 17336 * 17337 * 17338 * ## Can I disable SCE completely? 17339 * 17340 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 17341 * for little coding overhead. It will be much harder to take an SCE disabled application and 17342 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 17343 * for cases where you have a lot of existing code that was written before SCE was introduced and 17344 * you're migrating them a module at a time. 17345 * 17346 * That said, here's how you can completely disable SCE: 17347 * 17348 * ``` 17349 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 17350 * // Completely disable SCE. For demonstration purposes only! 17351 * // Do not use in new projects. 17352 * $sceProvider.enabled(false); 17353 * }); 17354 * ``` 17355 * 17356 */ 17357/* jshint maxlen: 100 */ 17358 17359function $SceProvider() { 17360 var enabled = true; 17361 17362 /** 17363 * @ngdoc method 17364 * @name $sceProvider#enabled 17365 * @kind function 17366 * 17367 * @param {boolean=} value If provided, then enables/disables SCE. 17368 * @return {boolean} true if SCE is enabled, false otherwise. 17369 * 17370 * @description 17371 * Enables/disables SCE and returns the current value. 17372 */ 17373 this.enabled = function(value) { 17374 if (arguments.length) { 17375 enabled = !!value; 17376 } 17377 return enabled; 17378 }; 17379 17380 17381 /* Design notes on the default implementation for SCE. 17382 * 17383 * The API contract for the SCE delegate 17384 * ------------------------------------- 17385 * The SCE delegate object must provide the following 3 methods: 17386 * 17387 * - trustAs(contextEnum, value) 17388 * This method is used to tell the SCE service that the provided value is OK to use in the 17389 * contexts specified by contextEnum. It must return an object that will be accepted by 17390 * getTrusted() for a compatible contextEnum and return this value. 17391 * 17392 * - valueOf(value) 17393 * For values that were not produced by trustAs(), return them as is. For values that were 17394 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 17395 * trustAs is wrapping the given values into some type, this operation unwraps it when given 17396 * such a value. 17397 * 17398 * - getTrusted(contextEnum, value) 17399 * This function should return the a value that is safe to use in the context specified by 17400 * contextEnum or throw and exception otherwise. 17401 * 17402 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 17403 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 17404 * instance, an implementation could maintain a registry of all trusted objects by context. In 17405 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 17406 * return the same object passed in if it was found in the registry under a compatible context or 17407 * throw an exception otherwise. An implementation might only wrap values some of the time based 17408 * on some criteria. getTrusted() might return a value and not throw an exception for special 17409 * constants or objects even if not wrapped. All such implementations fulfill this contract. 17410 * 17411 * 17412 * A note on the inheritance model for SCE contexts 17413 * ------------------------------------------------ 17414 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 17415 * is purely an implementation details. 17416 * 17417 * The contract is simply this: 17418 * 17419 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 17420 * will also succeed. 17421 * 17422 * Inheritance happens to capture this in a natural way. In some future, we
17423 * may not use inheritance anymore. That is OK because no code outside of 17424 * sce.js and sceSpecs.js would need to be aware of this detail. 17425 */ 17426 17427 this.$get = ['$parse', '$sceDelegate', function( 17428 $parse, $sceDelegate) { 17429 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 17430 // the "expression(javascript expression)" syntax which is insecure. 17431 if (enabled && msie < 8) { 17432 throw $sceMinErr('iequirks', 17433 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 17434 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 17435 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 17436 } 17437 17438 var sce = shallowCopy(SCE_CONTEXTS); 17439 17440 /** 17441 * @ngdoc method 17442 * @name $sce#isEnabled 17443 * @kind function 17444 * 17445 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 17446 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 17447 * 17448 * @description 17449 * Returns a boolean indicating if SCE is enabled. 17450 */ 17451 sce.isEnabled = function() { 17452 return enabled; 17453 }; 17454 sce.trustAs = $sceDelegate.trustAs; 17455 sce.getTrusted = $sceDelegate.getTrusted; 17456 sce.valueOf = $sceDelegate.valueOf; 17457 17458 if (!enabled) { 17459 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 17460 sce.valueOf = identity; 17461 } 17462 17463 /** 17464 * @ngdoc method 17465 * @name $sce#parseAs 17466 * 17467 * @description 17468 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 17469 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 17470 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 17471 * *result*)} 17472 * 17473 * @param {string} type The kind of SCE context in which this result will be used. 17474 * @param {string} expression String expression to compile. 17475 * @returns {function(context, locals)} a function which represents the compiled expression: 17476 * 17477 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17478 * are evaluated against (typically a scope object). 17479 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17480 * `context`. 17481 */ 17482 sce.parseAs = function sceParseAs(type, expr) { 17483 var parsed = $parse(expr); 17484 if (parsed.literal && parsed.constant) { 17485 return parsed; 17486 } else { 17487 return $parse(expr, function(value) { 17488 return sce.getTrusted(type, value); 17489 }); 17490 } 17491 }; 17492 17493 /** 17494 * @ngdoc method 17495 * @name $sce#trustAs 17496 * 17497 * @description 17498 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 17499 * returns an object that is trusted by angular for use in specified strict contextual 17500 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 17501 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 17502 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 17503 * escaping. 17504 * 17505 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 17506 * resourceUrl, html, js and css. 17507 * @param {*} value The value that that should be considered trusted/safe. 17508 * @returns {*} A value that can be used to stand in for the provided `value` in places 17509 * where Angular expects a $sce.trustAs() return value. 17510 */ 17511 17512 /** 17513 * @ngdoc method 17514 * @name $sce#trustAsHtml 17515 * 17516 * @description 17517 * Shorthand method. `$sce.trustAsHtml(value)` â 17518 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 17519 * 17520 * @param {*} value The value to trustAs. 17521 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 17522 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 17523 * only accept expressions that are either literal constants or are the 17524 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17525 */ 17526 17527 /** 17528 * @ngdoc method 17529 * @name $sce#trustAsUrl 17530 *
17531 * @description 17532 * Shorthand method. `$sce.trustAsUrl(value)` â 17533 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 17534 * 17535 * @param {*} value The value to trustAs. 17536 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 17537 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 17538 * only accept expressions that are either literal constants or are the 17539 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17540 */ 17541 17542 /** 17543 * @ngdoc method 17544 * @name $sce#trustAsResourceUrl 17545 * 17546 * @description 17547 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 17548 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 17549 * 17550 * @param {*} value The value to trustAs. 17551 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 17552 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 17553 * only accept expressions that are either literal constants or are the return 17554 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 17555 */ 17556 17557 /** 17558 * @ngdoc method 17559 * @name $sce#trustAsJs 17560 * 17561 * @description 17562 * Shorthand method. `$sce.trustAsJs(value)` â 17563 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 17564 * 17565 * @param {*} value The value to trustAs. 17566 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 17567 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 17568 * only accept expressions that are either literal constants or are the 17569 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17570 */ 17571 17572 /** 17573 * @ngdoc method 17574 * @name $sce#getTrusted 17575 * 17576 * @description 17577 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 17578 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 17579 * originally supplied value if the queried context type is a supertype of the created type. 17580 * If this condition isn't satisfied, throws an exception. 17581 * 17582 * @param {string} type The kind of context in which this value is to be used. 17583 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 17584 * call. 17585 * @returns {*} The value the was originally provided to 17586 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 17587 * Otherwise, throws an exception. 17588 */ 17589 17590 /** 17591 * @ngdoc method 17592 * @name $sce#getTrustedHtml 17593 * 17594 * @description 17595 * Shorthand method. `$sce.getTrustedHtml(value)` â 17596 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 17597 * 17598 * @param {*} value The value to pass to `$sce.getTrusted`. 17599 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 17600 */ 17601 17602 /** 17603 * @ngdoc method 17604 * @name $sce#getTrustedCss 17605 * 17606 * @description 17607 * Shorthand method. `$sce.getTrustedCss(value)` â 17608 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 17609 * 17610 * @param {*} value The value to pass to `$sce.getTrusted`. 17611 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 17612 */ 17613 17614 /** 17615 * @ngdoc method 17616 * @name $sce#getTrustedUrl 17617 * 17618 * @description 17619 * Shorthand method. `$sce.getTrustedUrl(value)` â 17620 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 17621 * 17622 * @param {*} value The value to pass to `$sce.getTrusted`. 17623 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 17624 */ 17625 17626 /** 17627 * @ngdoc method 17628 * @name $sce#getTrustedResourceUrl 17629 * 17630 * @description 17631 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 17632 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 17633 * 17634 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 17635 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 17636 */ 17637 17638 /** 17639 * @ngdoc method 17640 * @name $sce#getTrustedJs 17641 *
17642 * @description 17643 * Shorthand method. `$sce.getTrustedJs(value)` â 17644 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 17645 * 17646 * @param {*} value The value to pass to `$sce.getTrusted`. 17647 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 17648 */ 17649 17650 /** 17651 * @ngdoc method 17652 * @name $sce#parseAsHtml 17653 * 17654 * @description 17655 * Shorthand method. `$sce.parseAsHtml(expression string)` â 17656 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 17657 * 17658 * @param {string} expression String expression to compile. 17659 * @returns {function(context, locals)} a function which represents the compiled expression: 17660 * 17661 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17662 * are evaluated against (typically a scope object). 17663 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17664 * `context`. 17665 */ 17666 17667 /** 17668 * @ngdoc method 17669 * @name $sce#parseAsCss 17670 * 17671 * @description 17672 * Shorthand method. `$sce.parseAsCss(value)` â 17673 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 17674 * 17675 * @param {string} expression String expression to compile. 17676 * @returns {function(context, locals)} a function which represents the compiled expression: 17677 * 17678 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17679 * are evaluated against (typically a scope object). 17680 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17681 * `context`. 17682 */ 17683 17684 /** 17685 * @ngdoc method 17686 * @name $sce#parseAsUrl 17687 * 17688 * @description 17689 * Shorthand method. `$sce.parseAsUrl(value)` â 17690 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 17691 * 17692 * @param {string} expression String expression to compile. 17693 * @returns {function(context, locals)} a function which represents the compiled expression: 17694 * 17695 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17696 * are evaluated against (typically a scope object). 17697 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17698 * `context`. 17699 */ 17700 17701 /** 17702 * @ngdoc method 17703 * @name $sce#parseAsResourceUrl 17704 * 17705 * @description 17706 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 17707 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 17708 * 17709 * @param {string} expression String expression to compile. 17710 * @returns {function(context, locals)} a function which represents the compiled expression: 17711 * 17712 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17713 * are evaluated against (typically a scope object). 17714 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17715 * `context`. 17716 */ 17717 17718 /** 17719 * @ngdoc method 17720 * @name $sce#parseAsJs 17721 * 17722 * @description 17723 * Shorthand method. `$sce.parseAsJs(value)` â 17724 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 17725 * 17726 * @param {string} expression String expression to compile. 17727 * @returns {function(context, locals)} a function which represents the compiled expression: 17728 * 17729 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17730 * are evaluated against (typically a scope object). 17731 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17732 * `context`. 17733 */ 17734 17735 // Shorthand delegations. 17736 var parse = sce.parseAs, 17737 getTrusted = sce.getTrusted, 17738 trustAs = sce.trustAs; 17739 17740 forEach(SCE_CONTEXTS, function(enumValue, name) { 17741 var lName = lowercase(name); 17742 sce[camelCase("parse_as_" + lName)] = function(expr) { 17743 return parse(enumValue, expr); 17744 }; 17745 sce[camelCase("get_trusted_" + lName)] = function(value) { 17746 return getTrusted(enumValue, value); 17747 }; 17748 sce[camelCase("trust_as_" + lName)] = function(value) { 17749 return trustAs(enumValue, value); 17750 }; 17751 }); 17752 17753 return sce; 17754 }]; 17755} 17756 17757/** 17758 * !!! This is an undocumented "private" service !!! 17759 * 17760 * @name $sniffer 17761 * @requires $window 17762 * @requires $document 17763 * 17764 * @property {boolean} history Does the browser support html5 history api ? 17765 * @property {boolean} transitions Does the browser support CSS transition events ? 17766 * @property {boolean} animations Does the browser support CSS animation events ? 17767 * 17768 * @description 17769 * This is very simple implementation of testing browser's features. 17770 */ 17771function $SnifferProvider() { 17772 this.$get = ['$window', '$document', function($window, $document) { 17773 var eventSupport = {}, 17774 android = 17775 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 17776 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 17777 document = $document[0] || {}, 17778 vendorPrefix, 17779 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 17780 bodyStyle = document.body && document.body.style, 17781 transitions = false, 17782 animations = false, 17783 match; 17784 17785 if (bodyStyle) { 17786 for (var prop in bodyStyle) {
17787 if (match = vendorRegex.exec(prop)) { 17788 vendorPrefix = match[0]; 17789 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 17790 break; 17791 } 17792 } 17793 17794 if (!vendorPrefix) { 17795 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 17796 } 17797 17798 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 17799 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 17800 17801 if (android && (!transitions || !animations)) { 17802 transitions = isString(bodyStyle.webkitTransition); 17803 animations = isString(bodyStyle.webkitAnimation); 17804 } 17805 } 17806 17807 17808 return { 17809 // Android has history.pushState, but it does not update location correctly 17810 // so let's not use the history API at all. 17811 // http://code.google.com/p/android/issues/detail?id=17471 17812 // https://github.com/angular/angular.js/issues/904 17813 17814 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 17815 // so let's not use the history API also 17816 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 17817 // jshint -W018 17818 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 17819 // jshint +W018 17820 hasEvent: function(event) { 17821 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 17822 // it. In particular the event is not fired when backspace or delete key are pressed or 17823 // when cut operation is performed. 17824 // IE10+ implements 'input' event but it erroneously fires under various situations, 17825 // e.g. when placeholder changes, or a form is focused. 17826 if (event === 'input' && msie <= 11) return false; 17827 17828 if (isUndefined(eventSupport[event])) { 17829 var divElm = document.createElement('div'); 17830 eventSupport[event] = 'on' + event in divElm; 17831 } 17832 17833 return eventSupport[event]; 17834 }, 17835 csp: csp(), 17836 vendorPrefix: vendorPrefix, 17837 transitions: transitions, 17838 animations: animations, 17839 android: android 17840 }; 17841 }]; 17842} 17843 17844var $compileMinErr = minErr('$compile'); 17845 17846/** 17847 * @ngdoc service 17848 * @name $templateRequest 17849 * 17850 * @description 17851 * The `$templateRequest` service runs security checks then downloads the provided template using 17852 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 17853 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 17854 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 17855 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 17856 * when `tpl` is of type string and `$templateCache` has the matching entry. 17857 * 17858 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 17859 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 17860 * 17861 * @return {Promise} a promise for the HTTP response data of the given URL. 17862 * 17863 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 17864 */ 17865function $TemplateRequestProvider() { 17866 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { 17867 function handleRequestFn(tpl, ignoreRequestError) { 17868 handleRequestFn.totalPendingRequests++; 17869 17870 // We consider the template cache holds only trusted templates, so 17871 // there's no need to go through whitelisting again for keys that already 17872 // are included in there. This also makes Angular accept any script 17873 // directive, no matter its name. However, we still need to unwrap trusted 17874 // types. 17875 if (!isString(tpl) || !$templateCache.get(tpl)) { 17876 tpl = $sce.getTrustedResourceUrl(tpl); 17877 } 17878 17879 var transformResponse = $http.defaults && $http.defaults.transformResponse; 17880 17881 if (isArray(transformResponse)) { 17882 transformResponse = transformResponse.filter(function(transformer) { 17883 return transformer !== defaultHttpResponseTransform; 17884 }); 17885 } else if (transformResponse === defaultHttpResponseTransform) { 17886 transformResponse = null; 17887 } 17888 17889 var httpOptions = { 17890 cache: $templateCache, 17891 transformResponse: transformResponse 17892 }; 17893 17894 return $http.get(tpl, httpOptions) 17895 ['finally'](function() { 17896 handleRequestFn.totalPendingRequests--; 17897 }) 17898 .then(function(response) { 17899 $templateCache.put(tpl, response.data); 17900 return response.data; 17901 }, handleError); 17902 17903 function handleError(resp) { 17904 if (!ignoreRequestError) { 17905 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 17906 tpl, resp.status, resp.statusText); 17907 } 17908 return $q.reject(resp); 17909 } 17910 } 17911 17912 handleRequestFn.totalPendingRequests = 0; 17913 17914 return handleRequestFn; 17915 }]; 17916} 17917
vendor: 5,440 bytes, lines 17918-18071
17918function $$TestabilityProvider() { 17919 this.$get = ['$rootScope', '$browser', '$location', 17920 function($rootScope, $browser, $location) { 17921 17922 /** 17923 * @name $testability 17924 * 17925 * @description 17926 * The private $$testability service provides a collection of methods for use when debugging 17927 * or by automated test and debugging tools. 17928 */ 17929 var testability = {}; 17930 17931 /** 17932 * @name $$testability#findBindings 17933 * 17934 * @description 17935 * Returns an array of elements that are bound (via ng-bind or {{}}) 17936 * to expressions matching the input. 17937 * 17938 * @param {Element} element The element root to search from. 17939 * @param {string} expression The binding expression to match. 17940 * @param {boolean} opt_exactMatch If true, only returns exact matches 17941 * for the expression. Filters and whitespace are ignored. 17942 */ 17943 testability.findBindings = function(element, expression, opt_exactMatch) { 17944 var bindings = element.getElementsByClassName('ng-binding'); 17945 var matches = []; 17946 forEach(bindings, function(binding) { 17947 var dataBinding = angular.element(binding).data('$binding'); 17948 if (dataBinding) { 17949 forEach(dataBinding, function(bindingName) { 17950 if (opt_exactMatch) { 17951 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 17952 if (matcher.test(bindingName)) { 17953 matches.push(binding); 17954 } 17955 } else { 17956 if (bindingName.indexOf(expression) != -1) { 17957 matches.push(binding); 17958 } 17959 } 17960 }); 17961 } 17962 }); 17963 return matches; 17964 }; 17965 17966 /** 17967 * @name $$testability#findModels 17968 * 17969 * @description 17970 * Returns an array of elements that are two-way found via ng-model to 17971 * expressions matching the input. 17972 * 17973 * @param {Element} element The element root to search from. 17974 * @param {string} expression The model expression to match. 17975 * @param {boolean} opt_exactMatch If true, only returns exact matches 17976 * for the expression. 17977 */ 17978 testability.findModels = function(element, expression, opt_exactMatch) { 17979 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 17980 for (var p = 0; p < prefixes.length; ++p) { 17981 var attributeEquals = opt_exactMatch ? '=' : '*='; 17982 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 17983 var elements = element.querySelectorAll(selector); 17984 if (elements.length) { 17985 return elements; 17986 } 17987 } 17988 }; 17989 17990 /** 17991 * @name $$testability#getLocation 17992 * 17993 * @description 17994 * Shortcut for getting the location in a browser agnostic way. Returns 17995 * the path, search, and hash. (e.g. /path?a=b#hash) 17996 */ 17997 testability.getLocation = function() { 17998 return $location.url(); 17999 }; 18000 18001 /** 18002 * @name $$testability#setLocation 18003 * 18004 * @description 18005 * Shortcut for navigating to a location without doing a full page reload. 18006 * 18007 * @param {string} url The location url (path, search and hash, 18008 * e.g. /path?a=b#hash) to go to. 18009 */ 18010 testability.setLocation = function(url) { 18011 if (url !== $location.url()) { 18012 $location.url(url); 18013 $rootScope.$digest(); 18014 } 18015 }; 18016 18017 /** 18018 * @name $$testability#whenStable 18019 * 18020 * @description 18021 * Calls the callback when $timeout and $http requests are completed. 18022 * 18023 * @param {function} callback 18024 */ 18025 testability.whenStable = function(callback) { 18026 $browser.notifyWhenNoOutstandingRequests(callback); 18027 }; 18028 18029 return testability; 18030 }]; 18031} 18032 18033function $TimeoutProvider() { 18034 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 18035 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 18036 18037 var deferreds = {}; 18038 18039 18040 /** 18041 * @ngdoc service 18042 * @name $timeout 18043 * 18044 * @description 18045 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 18046 * block and delegates any exceptions to 18047 * {@link ng.$exceptionHandler $exceptionHandler} service. 18048 * 18049 * The return value of calling `$timeout` is a promise, which will be resolved when 18050 * the delay has passed and the timeout function, if provided, is executed. 18051 * 18052 * To cancel a timeout request, call `$timeout.cancel(promise)`. 18053 * 18054 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 18055 * synchronously flush the queue of deferred functions. 18056 * 18057 * If you only want a promise that will be resolved after some specified delay 18058 * then you can call `$timeout` without the `fn` function. 18059 * 18060 * @param {function()=} fn A function, whose execution should be delayed. 18061 * @param {number=} [delay=0] Delay in milliseconds. 18062 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 18063 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 18064 * @param {...*=} Pass additional parameters to the executed function. 18065 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 18066 * promise will be resolved with is the return value of the `fn` function. 18067 * 18068 */ 18069 function timeout(fn, delay, invokeApply) { 18070 if (!isFunction(fn)) { 18071 invokeApply = delay;
18072 delay = fn; 18073 fn = noop; 18074 } 18075 18076 var args = sliceArgs(arguments, 3), 18077 skipApply = (isDefined(invokeApply) && !invokeApply), 18078 deferred = (skipApply ? $$q : $q).defer(), 18079 promise = deferred.promise, 18080 timeoutId; 18081 18082 timeoutId = $browser.defer(function() { 18083 try { 18084 deferred.resolve(fn.apply(null, args)); 18085 } catch (e) { 18086 deferred.reject(e); 18087 $exceptionHandler(e); 18088 } 18089 finally { 18090 delete deferreds[promise.$$timeoutId]; 18091 } 18092 18093 if (!skipApply) $rootScope.$apply(); 18094 }, delay); 18095 18096 promise.$$timeoutId = timeoutId; 18097 deferreds[timeoutId] = deferred; 18098 18099 return promise; 18100 } 18101 18102 18103 /** 18104 * @ngdoc method 18105 * @name $timeout#cancel 18106 * 18107 * @description 18108 * Cancels a task associated with the `promise`. As a result of this, the promise will be 18109 * resolved with a rejection. 18110 * 18111 * @param {Promise=} promise Promise returned by the `$timeout` function. 18112 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 18113 * canceled. 18114 */ 18115 timeout.cancel = function(promise) { 18116 if (promise && promise.$$timeoutId in deferreds) { 18117 deferreds[promise.$$timeoutId].reject('canceled'); 18118 delete deferreds[promise.$$timeoutId]; 18119 return $browser.defer.cancel(promise.$$timeoutId); 18120 } 18121 return false; 18122 }; 18123 18124 return timeout; 18125 }]; 18126} 18127 18128// NOTE: The usage of window and document instead of $window and $document here is 18129// deliberate. This service depends on the specific behavior of anchor nodes created by the 18130// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 18131// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 18132// doesn't know about mocked locations and resolves URLs to the real document - which is 18133// exactly the behavior needed here. There is little value is mocking these out for this 18134// service. 18135var urlParsingNode = document.createElement("a"); 18136var originUrl = urlResolve(window.location.href); 18137 18138 18139/** 18140 * 18141 * Implementation Notes for non-IE browsers 18142 * ---------------------------------------- 18143 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 18144 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 18145 * URL will be resolved into an absolute URL in the context of the application document. 18146 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 18147 * properties are all populated to reflect the normalized URL. This approach has wide 18148 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 18149 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 18150 * 18151 * Implementation Notes for IE 18152 * --------------------------- 18153 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 18154 * browsers. However, the parsed components will not be set if the URL assigned did not specify 18155 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 18156 * work around that by performing the parsing in a 2nd step by taking a previously normalized 18157 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 18158 * properties such as protocol, hostname, port, etc. 18159 * 18160 * References: 18161 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 18162 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 18163 * http://url.spec.whatwg.org/#urlutils 18164 * https://github.com/angular/angular.js/pull/2902 18165 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 18166 * 18167 * @kind function 18168 * @param {string} url The URL to be parsed. 18169 * @description Normalizes and parses a URL. 18170 * @returns {object} Returns the normalized URL as a dictionary. 18171 * 18172 * | member name | Description | 18173 * |---------------|----------------| 18174 * | href | A normalized version of the provided URL if it was not an absolute URL | 18175 * | protocol | The protocol including the trailing colon | 18176 * | host | The host and port (if the port is non-default) of the normalizedUrl | 18177 * | search | The search params, minus the question mark | 18178 * | hash | The hash string, minus the hash symbol 18179 * | hostname | The hostname 18180 * | port | The port, without ":" 18181 * | pathname | The pathname, beginning with "/" 18182 * 18183 */ 18184function urlResolve(url) { 18185 var href = url; 18186 18187 if (msie) { 18188 // Normalize before parse. Refer Implementation Notes on why this is 18189 // done in two steps on IE. 18190 urlParsingNode.setAttribute("href", href); 18191 href = urlParsingNode.href; 18192 } 18193 18194 urlParsingNode.setAttribute('href', href); 18195 18196 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 18197 return { 18198 href: urlParsingNode.href, 18199 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 18200 host: urlParsingNode.host, 18201 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 18202 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 18203 hostname: urlParsingNode.hostname, 18204 port: urlParsingNode.port, 18205 pathname: (urlParsingNode.pathname.charAt(0) === '/') 18206 ? urlParsingNode.pathname 18207 : '/' + urlParsingNode.pathname 18208 }; 18209} 18210 18211/** 18212 * Parse a request URL and determine whether this is a same-origin request as the application document. 18213 * 18214 * @param {string|object} requestUrl The url of the request as a string that will be resolved 18215 * or a parsed URL object. 18216 * @returns {boolean} Whether the request is for the same origin as the application document. 18217 */ 18218function urlIsSameOrigin(requestUrl) { 18219 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 18220 return (parsed.protocol === originUrl.protocol && 18221 parsed.host === originUrl.host); 18222} 18223 18224/** 18225 * @ngdoc service 18226 * @name $window 18227 * 18228 * @description 18229 * A reference to the browser's `window` object. While `window` 18230 * is globally available in JavaScript, it causes testability problems, because 18231 * it is a global variable. In angular we always refer to it through the 18232 * `$window` service, so it may be overridden, removed or mocked for testing. 18233 * 18234 * Expressions, like the one defined for the `ngClick` directive in the example 18235 * below, are evaluated with respect to the current scope. Therefore, there is 18236 * no risk of inadvertently coding in a dependency on a global value in such an 18237 * expression. 18238 * 18239 * @example 18240 <example module="windowExample"> 18241 <file name="index.html"> 18242 <script> 18243 angular.module('windowExample', [])
18244 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 18245 $scope.greeting = 'Hello, World!'; 18246 $scope.doGreeting = function(greeting) { 18247 $window.alert(greeting); 18248 }; 18249 }]); 18250 </script> 18251 <div ng-controller="ExampleController"> 18252 <input type="text" ng-model="greeting" aria-label="greeting" /> 18253 <button ng-click="doGreeting(greeting)">ALERT</button> 18254 </div> 18255 </file> 18256 <file name="protractor.js" type="protractor"> 18257 it('should display the greeting in the input box', function() { 18258 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 18259 // If we click the button it will block the test runner 18260 // element(':button').click(); 18261 }); 18262 </file> 18263 </example> 18264 */ 18265function $WindowProvider() { 18266 this.$get = valueFn(window); 18267} 18268 18269/** 18270 * @name $$cookieReader 18271 * @requires $document 18272 * 18273 * @description 18274 * This is a private service for reading cookies used by $http and ngCookies 18275 * 18276 * @return {Object} a key/value map of the current cookies 18277 */ 18278function $$CookieReader($document) { 18279 var rawDocument = $document[0] || {}; 18280 var lastCookies = {}; 18281 var lastCookieString = ''; 18282 18283 function safeDecodeURIComponent(str) { 18284 try { 18285 return decodeURIComponent(str); 18286 } catch (e) { 18287 return str; 18288 } 18289 } 18290 18291 return function() { 18292 var cookieArray, cookie, i, index, name; 18293 var currentCookieString = rawDocument.cookie || ''; 18294 18295 if (currentCookieString !== lastCookieString) { 18296 lastCookieString = currentCookieString; 18297 cookieArray = lastCookieString.split('; '); 18298 lastCookies = {}; 18299 18300 for (i = 0; i < cookieArray.length; i++) { 18301 cookie = cookieArray[i]; 18302 index = cookie.indexOf('='); 18303 if (index > 0) { //ignore nameless cookies 18304 name = safeDecodeURIComponent(cookie.substring(0, index)); 18305 // the first value that is seen for a cookie is the most 18306 // specific one. values for the same cookie name that 18307 // follow are for less specific paths. 18308 if (isUndefined(lastCookies[name])) { 18309 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 18310 } 18311 } 18312 } 18313 } 18314 return lastCookies; 18315 }; 18316} 18317 18318$$CookieReader.$inject = ['$document']; 18319 18320function $$CookieReaderProvider() { 18321 this.$get = $$CookieReader; 18322} 18323 18324/* global currencyFilter: true, 18325 dateFilter: true, 18326 filterFilter: true, 18327 jsonFilter: true, 18328 limitToFilter: true, 18329 lowercaseFilter: true, 18330 numberFilter: true, 18331 orderByFilter: true, 18332 uppercaseFilter: true, 18333 */ 18334 18335/** 18336 * @ngdoc provider 18337 * @name $filterProvider 18338 * @description 18339 * 18340 * Filters are just functions which transform input to an output. However filters need to be 18341 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 18342 * annotated with dependencies and is responsible for creating a filter function. 18343 * 18344 * <div class="alert alert-warning"> 18345 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18346 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18347 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18348 * (`myapp_subsection_filterx`). 18349 * </div> 18350 * 18351 * ```js 18352 * // Filter registration 18353 * function MyModule($provide, $filterProvider) { 18354 * // create a service to demonstrate injection (not always needed) 18355 * $provide.value('greet', function(name){ 18356 * return 'Hello ' + name + '!'; 18357 * }); 18358 * 18359 * // register a filter factory which uses the 18360 * // greet service to demonstrate DI. 18361 * $filterProvider.register('greet', function(greet){ 18362 * // return the filter function which uses the greet service 18363 * // to generate salutation 18364 * return function(text) { 18365 * // filters need to be forgiving so check input validity 18366 * return text && greet(text) || text; 18367 * }; 18368 * }); 18369 * } 18370 * ``` 18371 * 18372 * The filter function is registered with the `$injector` under the filter name suffix with 18373 * `Filter`. 18374 * 18375 * ```js 18376 * it('should be the same instance', inject( 18377 * function($filterProvider) { 18378 * $filterProvider.register('reverse', function(){ 18379 * return ...; 18380 * }); 18381 * }, 18382 * function($filter, reverseFilter) { 18383 * expect($filter('reverse')).toBe(reverseFilter); 18384 * }); 18385 * ``` 18386 * 18387 * 18388 * For more information about how angular filters work, and how to create your own filters, see 18389 * {@link guide/filter Filters} in the Angular Developer Guide. 18390 */ 18391 18392/** 18393 * @ngdoc service 18394 * @name $filter 18395 * @kind function 18396 * @description 18397 * Filters are used for formatting data displayed to the user. 18398 * 18399 * The general syntax in templates is as follows: 18400 * 18401 * {{ expression [| filter_name[:parameter_value] ... ] }} 18402 * 18403 * @param {String} name Name of the filter function to retrieve 18404 * @return {Function} the filter function 18405 * @example 18406 <example name="$filter" module="filterExample"> 18407 <file name="index.html"> 18408 <div ng-controller="MainCtrl"> 18409 <h3>{{ originalText }}</h3> 18410 <h3>{{ filteredText }}</h3> 18411 </div> 18412 </file> 18413 18414 <file name="script.js"> 18415 angular.module('filterExample', []) 18416 .controller('MainCtrl', function($scope, $filter) { 18417 $scope.originalText = 'hello'; 18418 $scope.filteredText = $filter('uppercase')($scope.originalText); 18419 }); 18420 </file> 18421 </example> 18422 */ 18423$FilterProvider.$inject = ['$provide']; 18424function $FilterProvider($provide) { 18425 var suffix = 'Filter'; 18426 18427 /** 18428 * @ngdoc method 18429 * @name $filterProvider#register 18430 * @param {string|Object} name Name of the filter function, or an object map of filters where 18431 * the keys are the filter names and the values are the filter factories. 18432 * 18433 * <div class="alert alert-warning"> 18434 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18435 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18436 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18437 * (`myapp_subsection_filterx`). 18438 * </div> 18439 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 18440 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 18441 * of the registered filter instances. 18442 */ 18443 function register(name, factory) { 18444 if (isObject(name)) { 18445 var filters = {};
18446 forEach(name, function(filter, key) { 18447 filters[key] = register(key, filter); 18448 }); 18449 return filters; 18450 } else { 18451 return $provide.factory(name + suffix, factory); 18452 } 18453 } 18454 this.register = register; 18455 18456 this.$get = ['$injector', function($injector) { 18457 return function(name) { 18458 return $injector.get(name + suffix); 18459 }; 18460 }]; 18461 18462 //////////////////////////////////////// 18463 18464 /* global 18465 currencyFilter: false, 18466 dateFilter: false, 18467 filterFilter: false, 18468 jsonFilter: false, 18469 limitToFilter: false, 18470 lowercaseFilter: false, 18471 numberFilter: false, 18472 orderByFilter: false, 18473 uppercaseFilter: false, 18474 */ 18475 18476 register('currency', currencyFilter); 18477 register('date', dateFilter); 18478 register('filter', filterFilter); 18479 register('json', jsonFilter); 18480 register('limitTo', limitToFilter); 18481 register('lowercase', lowercaseFilter); 18482 register('number', numberFilter); 18483 register('orderBy', orderByFilter); 18484 register('uppercase', uppercaseFilter); 18485} 18486 18487/** 18488 * @ngdoc filter 18489 * @name filter 18490 * @kind function 18491 * 18492 * @description 18493 * Selects a subset of items from `array` and returns it as a new array. 18494 * 18495 * @param {Array} array The source array. 18496 * @param {string|Object|function()} expression The predicate to be used for selecting items from 18497 * `array`. 18498 * 18499 * Can be one of: 18500 * 18501 * - `string`: The string is used for matching against the contents of the `array`. All strings or 18502 * objects with string properties in `array` that match this string will be returned. This also 18503 * applies to nested object properties. 18504 * The predicate can be negated by prefixing the string with `!`. 18505 * 18506 * - `Object`: A pattern object can be used to filter specific properties on objects contained 18507 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 18508 * which have property `name` containing "M" and property `phone` containing "1". A special 18509 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 18510 * property of the object or its nested object properties. That's equivalent to the simple 18511 * substring match with a `string` as described above. The predicate can be negated by prefixing 18512 * the string with `!`. 18513 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 18514 * not containing "M". 18515 * 18516 * Note that a named property will match properties on the same level only, while the special 18517 * `$` property will match properties on the same level or deeper. E.g. an array item like 18518 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 18519 * **will** be matched by `{$: 'John'}`. 18520 * 18521 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 18522 * The function is called for each element of the array, with the element, its index, and 18523 * the entire array itself as arguments. 18524 * 18525 * The final result is an array of those elements that the predicate returned true for. 18526 * 18527 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 18528 * determining if the expected value (from the filter expression) and actual value (from 18529 * the object in the array) should be considered a match. 18530 * 18531 * Can be one of: 18532 * 18533 * - `function(actual, expected)`: 18534 * The function will be given the object value and the predicate value to compare and 18535 * should return true if both values should be considered equal. 18536 * 18537 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 18538 * This is essentially strict comparison of expected and actual. 18539 * 18540 * - `false|undefined`: A short hand for a function which will look for a substring match in case 18541 * insensitive way. 18542 * 18543 * Primitive values are converted to strings. Objects are not compared against primitives, 18544 * unless they have a custom `toString` method (e.g. `Date` objects). 18545 * 18546 * @example 18547 <example> 18548 <file name="index.html"> 18549 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 18550 {name:'Mary', phone:'800-BIG-MARY'}, 18551 {name:'Mike', phone:'555-4321'}, 18552 {name:'Adam', phone:'555-5678'}, 18553 {name:'Julie', phone:'555-8765'}, 18554 {name:'Juliette', phone:'555-5678'}]"></div> 18555 18556 <label>Search: <input ng-model="searchText"></label> 18557 <table id="searchTextResults"> 18558 <tr><th>
18558Name</th><th>Phone</th></tr> 18559 <tr ng-repeat="friend in friends | filter:searchText"> 18560 <td>{{friend.name}}</td> 18561 <td>{{friend.phone}}</td> 18562 </tr> 18563 </table> 18564 <hr> 18565 <label>Any: <input ng-model="search.$"></label> <br> 18566 <label>Name only <input ng-model="search.name"></label><br> 18567 <label>Phone only <input ng-model="search.phone"></label><br> 18568 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 18569 <table id="searchObjResults"> 18570 <tr><th>Name</th><th>Phone</th></tr> 18571 <tr ng-repeat="friendObj in friends | filter:search:strict"> 18572 <td>{{friendObj.name}}</td> 18573 <td>{{friendObj.phone}}</td> 18574 </tr> 18575 </table> 18576 </file> 18577 <file name="protractor.js" type="protractor"> 18578 var expectFriendNames = function(expectedNames, key) { 18579 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 18580 arr.forEach(function(wd, i) { 18581 expect(wd.getText()).toMatch(expectedNames[i]); 18582 }); 18583 }); 18584 }; 18585 18586 it('should search across all fields when filtering with a string', function() { 18587 var searchText = element(by.model('searchText')); 18588 searchText.clear(); 18589 searchText.sendKeys('m'); 18590 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 18591 18592 searchText.clear(); 18593 searchText.sendKeys('76'); 18594 expectFriendNames(['John', 'Julie'], 'friend'); 18595 }); 18596 18597 it('should search in specific fields when filtering with a predicate object', function() { 18598 var searchAny = element(by.model('search.$')); 18599 searchAny.clear(); 18600 searchAny.sendKeys('i'); 18601 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 18602 }); 18603 it('should use a equal comparison when comparator is true', function() { 18604 var searchName = element(by.model('search.name')); 18605 var strict = element(by.model('strict')); 18606 searchName.clear(); 18607 searchName.sendKeys('Julie'); 18608 strict.click(); 18609 expectFriendNames(['Julie'], 'friendObj'); 18610 }); 18611 </file> 18612 </example> 18613 */ 18614function filterFilter() { 18615 return function(array, expression, comparator) { 18616 if (!isArrayLike(array)) { 18617 if (array == null) { 18618 return array; 18619 } else { 18620 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 18621 } 18622 } 18623 18624 var expressionType = getTypeForFilter(expression); 18625 var predicateFn; 18626 var matchAgainstAnyProp; 18627 18628 switch (expressionType) { 18629 case 'function': 18630 predicateFn = expression; 18631 break; 18632 case 'boolean': 18633 case 'null': 18634 case 'number': 18635 case 'string': 18636 matchAgainstAnyProp = true; 18637 //jshint -W086 18638 case 'object': 18639 //jshint +W086 18640 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 18641 break; 18642 default: 18643 return array; 18644 } 18645 18646 return Array.prototype.filter.call(array, predicateFn); 18647 }; 18648} 18649 18650// Helper functions for `filterFilter` 18651function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 18652 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 18653 var predicateFn; 18654 18655 if (comparator === true) { 18656 comparator = equals; 18657 } else if (!isFunction(comparator)) { 18658 comparator = function(actual, expected) { 18659 if (isUndefined(actual)) { 18660 // No substring matching against `undefined` 18661 return false; 18662 } 18663 if ((actual === null) || (expected === null)) { 18664 // No substring matching against `null`; only match against `null` 18665 return actual === expected; 18666 } 18667 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 18668 // Should not compare primitives against objects, unless they have custom `toString` method 18669 return false; 18670 } 18671 18672 actual = lowercase('' + actual); 18673 expected = lowercase('' + expected); 18674 return actual.indexOf(expected) !== -1; 18675 }; 18676 } 18677 18678 predicateFn = function(item) { 18679 if (shouldMatchPrimitives && !isObject(item)) { 18680 return deepCompare(item, expression.$, comparator, false); 18681 } 18682 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 18683 }; 18684 18685 return predicateFn; 18686} 18687 18688function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 18689 var actualType = getTypeForFilter(actual); 18690 var expectedType = getTypeForFilter(expected); 18691 18692 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 18693 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 18694 } else if (isArray(actual)) { 18695 // In case `actual` is an array, consider it a match 18696 // if ANY of it's items matches `expected` 18697 return actual.some(function(item) { 18698 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 18699 }); 18700 } 18701 18702 switch (actualType) { 18703 case 'object': 18704 var key; 18705 if (matchAgainstAnyProp) { 18706 for (key in actual) { 18707 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 18708 return true; 18709 } 18710 } 18711 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 18712 } else if (expectedType === 'object') { 18713 for (key in expected) { 18714 var expectedVal = expected[key]; 18715 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 18716 continue; 18717 } 18718 18719 var matchAnyProperty = key === '$'; 18720 var actualVal = matchAnyProperty ? actual : actual[key]; 18721 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 18722 return false;
18723 } 18724 } 18725 return true; 18726 } else { 18727 return comparator(actual, expected); 18728 } 18729 break; 18730 case 'function': 18731 return false; 18732 default: 18733 return comparator(actual, expected); 18734 } 18735} 18736 18737// Used for easily differentiating between `null` and actual `object` 18738function getTypeForFilter(val) { 18739 return (val === null) ? 'null' : typeof val; 18740} 18741 18742/** 18743 * @ngdoc filter 18744 * @name currency 18745 * @kind function 18746 * 18747 * @description 18748 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 18749 * symbol for current locale is used. 18750 * 18751 * @param {number} amount Input to filter. 18752 * @param {string=} symbol Currency symbol or identifier to be displayed. 18753 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 18754 * @returns {string} Formatted number. 18755 * 18756 * 18757 * @example 18758 <example module="currencyExample"> 18759 <file name="index.html"> 18760 <script> 18761 angular.module('currencyExample', []) 18762 .controller('ExampleController', ['$scope', function($scope) { 18763 $scope.amount = 1234.56; 18764 }]); 18765 </script>
18766 <div ng-controller="ExampleController"> 18767 <input type="number" ng-model="amount" aria-label="amount"> <br> 18768 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 18769 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 18770 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 18771 </div> 18772 </file> 18773 <file name="protractor.js" type="protractor"> 18774 it('should init with 1234.56', function() { 18775 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 18776 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 18777 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 18778 }); 18779 it('should update', function() { 18780 if (browser.params.browser == 'safari') { 18781 // Safari does not understand the minus key. See 18782 // https://github.com/angular/protractor/issues/481 18783 return; 18784 } 18785 element(by.model('amount')).clear(); 18786 element(by.model('amount')).sendKeys('-1234'); 18787 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 18788 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 18789 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 18790 }); 18791 </file> 18792 </example> 18793 */ 18794currencyFilter.$inject = ['$locale']; 18795function currencyFilter($locale) { 18796 var formats = $locale.NUMBER_FORMATS; 18797 return function(amount, currencySymbol, fractionSize) { 18798 if (isUndefined(currencySymbol)) { 18799 currencySymbol = formats.CURRENCY_SYM; 18800 } 18801 18802 if (isUndefined(fractionSize)) { 18803 fractionSize = formats.PATTERNS[1].maxFrac; 18804 } 18805 18806 // if null or undefined pass it through 18807 return (amount == null) 18808 ? amount 18809 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 18810 replace(/\u00A4/g, currencySymbol); 18811 }; 18812} 18813 18814/** 18815 * @ngdoc filter 18816 * @name number 18817 * @kind function 18818 * 18819 * @description 18820 * Formats a number as text. 18821 * 18822 * If the input is null or undefined, it will just be returned. 18823 * If the input is infinite (Infinity/-Infinity) the Infinity symbol 'â' is returned. 18824 * If the input is not a number an empty string is returned. 18825 * 18826 * 18827 * @param {number|string} number Number to format. 18828 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 18829 * If this is not provided then the fraction size is computed from the current locale's number 18830 * formatting pattern. In the case of the default locale, it will be 3. 18831 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 18832 * 18833 * @example 18834 <example module="numberFilterExample"> 18835 <file name="index.html"> 18836 <script> 18837 angular.module('numberFilterExample', []) 18838 .controller('ExampleController', ['$scope', function($scope) { 18839 $scope.val = 1234.56789; 18840 }]); 18841 </script>
18842 <div ng-controller="ExampleController"> 18843 <label>Enter number: <input ng-model='val'></label><br> 18844 Default formatting: <span id='number-default'>{{val | number}}</span><br> 18845 No fractions: <span>{{val | number:0}}</span><br> 18846 Negative number: <span>{{-val | number:4}}</span> 18847 </div> 18848 </file> 18849 <file name="protractor.js" type="protractor"> 18850 it('should format numbers', function() { 18851 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 18852 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 18853 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 18854 }); 18855 18856 it('should update', function() { 18857 element(by.model('val')).clear(); 18858 element(by.model('val')).sendKeys('3374.333'); 18859 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 18860 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 18861 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 18862 }); 18863 </file> 18864 </example> 18865 */ 18866 18867 18868numberFilter.$inject = ['$locale']; 18869function numberFilter($locale) { 18870 var formats = $locale.NUMBER_FORMATS; 18871 return function(number, fractionSize) { 18872 18873 // if null or undefined pass it through 18874 return (number == null) 18875 ? number 18876 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 18877 fractionSize); 18878 }; 18879} 18880 18881var DECIMAL_SEP = '.'; 18882function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 18883 if (isObject(number)) return ''; 18884 18885 var isNegative = number < 0; 18886 number = Math.abs(number); 18887 18888 var isInfinity = number === Infinity; 18889 if (!isInfinity && !isFinite(number)) return ''; 18890 18891 var numStr = number + '', 18892 formatedText = '', 18893 hasExponent = false, 18894 parts = []; 18895 18896 if (isInfinity) formatedText = '\u221e'; 18897 18898 if (!isInfinity && numStr.indexOf('e') !== -1) { 18899 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 18900 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 18901 number = 0; 18902 } else { 18903 formatedText = numStr; 18904 hasExponent = true; 18905 } 18906 } 18907 18908 if (!isInfinity && !hasExponent) { 18909 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 18910 18911 // determine fractionSize if it is not specified 18912 if (isUndefined(fractionSize)) { 18913 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 18914 } 18915 18916 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics 18917 // inspired by: 18918 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 18919 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); 18920 18921 var fraction = ('' + number).split(DECIMAL_SEP); 18922 var whole = fraction[0]; 18923 fraction = fraction[1] || ''; 18924 18925 var i, pos = 0, 18926 lgroup = pattern.lgSize, 18927 group = pattern.gSize; 18928 18929 if (whole.length >= (lgroup + group)) { 18930 pos = whole.length - lgroup; 18931 for (i = 0; i < pos; i++) { 18932 if ((pos - i) % group === 0 && i !== 0) { 18933 formatedText += groupSep; 18934 } 18935 formatedText += whole.charAt(i); 18936 } 18937 } 18938 18939 for (i = pos; i < whole.length; i++) { 18940 if ((whole.length - i) % lgroup === 0 && i !== 0) { 18941 formatedText += groupSep; 18942 } 18943 formatedText += whole.charAt(i); 18944 } 18945 18946 // format fraction part. 18947 while (fraction.length < fractionSize) { 18948 fraction += '0'; 18949 } 18950 18951 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 18952 } else { 18953 if (fractionSize > 0 && number < 1) { 18954 formatedText = number.toFixed(fractionSize); 18955 number = parseFloat(formatedText); 18956 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep); 18957 } 18958 } 18959 18960 if (number === 0) { 18961 isNegative = false; 18962 } 18963 18964 parts.push(isNegative ? pattern.negPre : pattern.posPre, 18965 formatedText, 18966 isNegative ? pattern.negSuf : pattern.posSuf); 18967 return parts.join(''); 18968} 18969 18970function padNumber(num, digits, trim) { 18971 var neg = ''; 18972 if (num < 0) { 18973 neg = '-'; 18974 num = -num; 18975 } 18976 num = '' + num; 18977 while (num.length < digits) num = '0' + num; 18978 if (trim) { 18979 num = num.substr(num.length - digits); 18980 } 18981 return neg + num; 18982} 18983 18984 18985function dateGetter(name, size, offset, trim) { 18986 offset = offset || 0; 18987 return function(date) { 18988 var value = date['get' + name](); 18989 if (offset > 0 || value > -offset) { 18990 value += offset; 18991 } 18992 if (value === 0 && offset == -12) value = 12; 18993 return padNumber(value, size, trim); 18994 }; 18995} 18996 18997function dateStrGetter(name, shortForm) { 18998 return function(date, formats) { 18999 var value = date['get' + name](); 19000 var get = uppercase(shortForm ? ('SHORT' + name) : name); 19001 19002 return formats[get][value]; 19003 }; 19004} 19005 19006function timeZoneGetter(date, formats, offset) { 19007 var zone = -1 * offset; 19008 var paddedZone = (zone >= 0) ? "+" : ""; 19009 19010 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 19011 padNumber(Math.abs(zone % 60), 2); 19012 19013 return paddedZone; 19014} 19015 19016function getFirstThursdayOfYear(year) { 19017 // 0 = index of January 19018 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 19019 // 4 = index of Thursday (+1 to account for 1st = 5) 19020 // 11 = index of *next* Thursday (+1 account for 1st = 12) 19021 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 19022} 19023 19024function getThursdayThisWeek(datetime) { 19025 return new Date(datetime.getFullYear(), datetime.getMonth(), 19026 // 4 = index of Thursday 19027 datetime.getDate() + (4 - datetime.getDay())); 19028} 19029 19030function weekGetter(size) { 19031 return function(date) { 19032 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 19033 thisThurs = getThursdayThisWeek(date); 19034 19035 var diff = +thisThurs - +firstThurs, 19036 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 19037 19038 return padNumber(result, size); 19039 }; 19040} 19041 19042function ampmGetter(date, formats) { 19043 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 19044} 19045 19046function eraGetter(date, formats) {
vendor: 5,157 bytes, lines 19047-19159
19047 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 19048} 19049 19050function longEraGetter(date, formats) { 19051 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 19052} 19053 19054var DATE_FORMATS = { 19055 yyyy: dateGetter('FullYear', 4), 19056 yy: dateGetter('FullYear', 2, 0, true), 19057 y: dateGetter('FullYear', 1), 19058 MMMM: dateStrGetter('Month'), 19059 MMM: dateStrGetter('Month', true), 19060 MM: dateGetter('Month', 2, 1), 19061 M: dateGetter('Month', 1, 1), 19062 dd: dateGetter('Date', 2), 19063 d: dateGetter('Date', 1), 19064 HH: dateGetter('Hours', 2), 19065 H: dateGetter('Hours', 1), 19066 hh: dateGetter('Hours', 2, -12), 19067 h: dateGetter('Hours', 1, -12), 19068 mm: dateGetter('Minutes', 2), 19069 m: dateGetter('Minutes', 1), 19070 ss: dateGetter('Seconds', 2), 19071 s: dateGetter('Seconds', 1), 19072 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 19073 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 19074 sss: dateGetter('Milliseconds', 3), 19075 EEEE: dateStrGetter('Day'), 19076 EEE: dateStrGetter('Day', true), 19077 a: ampmGetter, 19078 Z: timeZoneGetter, 19079 ww: weekGetter(2), 19080 w: weekGetter(1), 19081 G: eraGetter, 19082 GG: eraGetter, 19083 GGG: eraGetter, 19084 GGGG: longEraGetter 19085}; 19086 19087var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 19088 NUMBER_STRING = /^\-?\d+$/; 19089 19090/** 19091 * @ngdoc filter 19092 * @name date 19093 * @kind function 19094 * 19095 * @description 19096 * Formats `date` to a string based on the requested `format`. 19097 * 19098 * `format` string can be composed of the following elements: 19099 * 19100 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 19101 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 19102 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 19103 * * `'MMMM'`: Month in year (January-December) 19104 * * `'MMM'`: Month in year (Jan-Dec) 19105 * * `'MM'`: Month in year, padded (01-12) 19106 * * `'M'`: Month in year (1-12) 19107 * * `'dd'`: Day in month, padded (01-31) 19108 * * `'d'`: Day in month (1-31) 19109 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 19110 * * `'EEE'`: Day in Week, (Sun-Sat) 19111 * * `'HH'`: Hour in day, padded (00-23) 19112 * * `'H'`: Hour in day (0-23) 19113 * * `'hh'`: Hour in AM/PM, padded (01-12) 19114 * * `'h'`: Hour in AM/PM, (1-12) 19115 * * `'mm'`: Minute in hour, padded (00-59) 19116 * * `'m'`: Minute in hour (0-59) 19117 * * `'ss'`: Second in minute, padded (00-59) 19118 * * `'s'`: Second in minute (0-59) 19119 * * `'sss'`: Millisecond in second, padded (000-999) 19120 * * `'a'`: AM/PM marker 19121 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 19122 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 19123 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 19124 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 19125 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 19126 * 19127 * `format` string can also be one of the following predefined 19128 * {@link guide/i18n localizable formats}: 19129 * 19130 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 19131 * (e.g. Sep 3, 2010 12:05:08 PM) 19132 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 19133 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 19134 * (e.g. Friday, September 3, 2010) 19135 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 19136 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 19137 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 19138 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 19139 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 19140 * 19141 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 19142 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 19143 * (e.g. `"h 'o''clock'"`). 19144 * 19145 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 19146 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 19147 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 19148 * specified in the string input, the time is considered to be in the local timezone. 19149 * @param {string=} format Formatting rules (see Description). If not specified, 19150 * `mediumDate` is used. 19151 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 19152 * continental US time zone abbreviations, but for general use, use a time zone offset, for 19153 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 19154 * If not specified, the timezone of the browser will be used. 19155 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 19156 * 19157 * @example 19158 <example> 19159 <file name="index.html">
vendor: 4,078 bytes, lines 19160-19272
19160 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 19161 <span>{{1288323623006 | date:'medium'}}</span><br> 19162 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 19163 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 19164 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 19165 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 19166 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 19167 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 19168 </file> 19169 <file name="protractor.js" type="protractor"> 19170 it('should format date', function() { 19171 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 19172 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 19173 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 19174 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 19175 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 19176 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 19177 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 19178 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 19179 }); 19180 </file> 19181 </example> 19182 */ 19183dateFilter.$inject = ['$locale']; 19184function dateFilter($locale) { 19185 19186 19187 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 19188 // 1 2 3 4 5 6 7 8 9 10 11 19189 function jsonStringToDate(string) { 19190 var match; 19191 if (match = string.match(R_ISO8601_STR)) { 19192 var date = new Date(0), 19193 tzHour = 0, 19194 tzMin = 0, 19195 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 19196 timeSetter = match[8] ? date.setUTCHours : date.setHours; 19197 19198 if (match[9]) { 19199 tzHour = toInt(match[9] + match[10]); 19200 tzMin = toInt(match[9] + match[11]); 19201 } 19202 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 19203 var h = toInt(match[4] || 0) - tzHour; 19204 var m = toInt(match[5] || 0) - tzMin; 19205 var s = toInt(match[6] || 0); 19206 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 19207 timeSetter.call(date, h, m, s, ms); 19208 return date; 19209 } 19210 return string; 19211 } 19212 19213 19214 return function(date, format, timezone) { 19215 var text = '', 19216 parts = [], 19217 fn, match; 19218 19219 format = format || 'mediumDate'; 19220 format = $locale.DATETIME_FORMATS[format] || format; 19221 if (isString(date)) { 19222 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 19223 } 19224 19225 if (isNumber(date)) { 19226 date = new Date(date); 19227 } 19228 19229 if (!isDate(date) || !isFinite(date.getTime())) { 19230 return date; 19231 } 19232 19233 while (format) { 19234 match = DATE_FORMATS_SPLIT.exec(format); 19235 if (match) { 19236 parts = concat(parts, match, 1); 19237 format = parts.pop(); 19238 } else { 19239 parts.push(format); 19240 format = null; 19241 } 19242 } 19243 19244 var dateTimezoneOffset = date.getTimezoneOffset(); 19245 if (timezone) { 19246 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 19247 date = convertTimezoneToLocal(date, timezone, true); 19248 } 19249 forEach(parts, function(value) { 19250 fn = DATE_FORMATS[value]; 19251 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 19252 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 19253 }); 19254 19255 return text; 19256 }; 19257} 19258 19259 19260/** 19261 * @ngdoc filter 19262 * @name json 19263 * @kind function 19264 * 19265 * @description 19266 * Allows you to convert a JavaScript object into JSON string. 19267 * 19268 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 19269 * the binding is automatically converted to JSON. 19270 * 19271 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 19272 * @param {number=}
19272 spacing The number of spaces to use per indentation, defaults to 2. 19273 * @returns {string} JSON string. 19274 * 19275 * 19276 * @example 19277 <example> 19278 <file name="index.html"> 19279 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 19280 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 19281 </file> 19282 <file name="protractor.js" type="protractor"> 19283 it('should jsonify filtered objects', function() { 19284 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 19285 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 19286 }); 19287 </file> 19288 </example> 19289 * 19290 */ 19291function jsonFilter() { 19292 return function(object, spacing) { 19293 if (isUndefined(spacing)) { 19294 spacing = 2; 19295 } 19296 return toJson(object, spacing); 19297 }; 19298} 19299 19300 19301/** 19302 * @ngdoc filter 19303 * @name lowercase 19304 * @kind function 19305 * @description 19306 * Converts string to lowercase. 19307 * @see angular.lowercase 19308 */ 19309var lowercaseFilter = valueFn(lowercase); 19310 19311 19312/** 19313 * @ngdoc filter 19314 * @name uppercase 19315 * @kind function 19316 * @description 19317 * Converts string to uppercase. 19318 * @see angular.uppercase 19319 */ 19320var uppercaseFilter = valueFn(uppercase); 19321 19322/** 19323 * @ngdoc filter 19324 * @name limitTo 19325 * @kind function 19326 * 19327 * @description 19328 * Creates a new array or string containing only a specified number of elements. The elements 19329 * are taken from either the beginning or the end of the source array, string or number, as specified by 19330 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 19331 * converted to a string. 19332 * 19333 * @param {Array|string|number} input Source array, string or number to be limited. 19334 * @param {string|number} limit The length of the returned array or string. If the `limit` number 19335 * is positive, `limit` number of items from the beginning of the source array/string are copied. 19336 * If the number is negative, `limit` number of items from the end of the source array/string 19337 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 19338 * the input will be returned unchanged. 19339 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 19340 * indicates an offset from the end of `input`. Defaults to `0`. 19341 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 19342 * had less than `limit` elements. 19343 * 19344 * @example 19345 <example module="limitToExample"> 19346 <file name="index.html"> 19347 <script> 19348 angular.module('limitToExample', []) 19349 .controller('ExampleController', ['$scope', function($scope) { 19350 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 19351 $scope.letters = "abcdefghi"; 19352 $scope.longNumber = 2345432342; 19353 $scope.numLimit = 3; 19354 $scope.letterLimit = 3; 19355 $scope.longNumberLimit = 3; 19356 }]); 19357 </script>
19358 <div ng-controller="ExampleController"> 19359 <label> 19360 Limit {{numbers}} to: 19361 <input type="number" step="1" ng-model="numLimit"> 19362 </label> 19363 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 19364 <label> 19365 Limit {{letters}} to: 19366 <input type="number" step="1" ng-model="letterLimit"> 19367 </label> 19368 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 19369 <label> 19370 Limit {{longNumber}} to: 19371 <input type="number" step="1" ng-model="longNumberLimit"> 19372 </label> 19373 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 19374 </div> 19375 </file> 19376 <file name="protractor.js" type="protractor"> 19377 var numLimitInput = element(by.model('numLimit')); 19378 var letterLimitInput = element(by.model('letterLimit')); 19379 var longNumberLimitInput = element(by.model('longNumberLimit')); 19380 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 19381 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 19382 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 19383 19384 it('should limit the number array to first three items', function() { 19385 expect(numLimitInput.getAttribute('value')).toBe('3'); 19386 expect(letterLimitInput.getAttribute('value')).toBe('3'); 19387 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 19388 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 19389 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 19390 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 19391 }); 19392 19393 // There is a bug in safari and protractor that doesn't like the minus key 19394 // it('should update the output when -3 is entered', function() { 19395 // numLimitInput.clear(); 19396 // numLimitInput.sendKeys('-3'); 19397 // letterLimitInput.clear(); 19398 // letterLimitInput.sendKeys('-3'); 19399 // longNumberLimitInput.clear(); 19400 // longNumberLimitInput.sendKeys('-3'); 19401 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 19402 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 19403 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 19404 // }); 19405 19406 it('should not exceed the maximum size of input array', function() { 19407 numLimitInput.clear(); 19408 numLimitInput.sendKeys('100'); 19409 letterLimitInput.clear(); 19410 letterLimitInput.sendKeys('100'); 19411 longNumberLimitInput.clear(); 19412 longNumberLimitInput.sendKeys('100'); 19413 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 19414 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 19415 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 19416 }); 19417 </file> 19418 </example> 19419*/ 19420function limitToFilter() { 19421 return function(input, limit, begin) { 19422 if (Math.abs(Number(limit)) === Infinity) { 19423 limit = Number(limit); 19424 } else { 19425 limit = toInt(limit); 19426 } 19427 if (isNaN(limit)) return input; 19428 19429 if (isNumber(input)) input = input.toString(); 19430 if (!isArray(input) && !isString(input)) return input; 19431 19432 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 19433 begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin; 19434 19435 if (limit >= 0) { 19436 return input.slice(begin, begin + limit); 19437 } else { 19438 if (begin === 0) { 19439 return input.slice(limit, input.length); 19440 } else { 19441 return input.slice(Math.max(0, begin + limit), begin); 19442 } 19443 } 19444 }; 19445} 19446 19447/** 19448 * @ngdoc filter 19449 * @name orderBy 19450 * @kind function 19451 * 19452 * @description 19453 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 19454 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 19455 * as expected, make sure they are actually being saved as numbers and not strings. 19456 * 19457 * @param {Array} array The array to sort. 19458 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 19459 * used by the comparator to determine the order of elements. 19460 * 19461 * Can be one of: 19462 * 19463 * - `function`: Getter function. The result of this function will be sorted using the 19464 * `<`, `===`, `>` operator. 19465 * - `string`: An Angular expression. The result of this expression is used to compare elements 19466 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 19467 * 3 first characters of a property called `name`). The result of a constant expression 19468 * is interpreted as a property name to be used in comparisons (for example `"special name"` 19469 * to sort object by the value of their `special name` property). An expression can be 19470 * optionally prefixed with `+` or `-` to control ascending or descending sort order 19471 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 19472 * element itself is used to compare where sorting. 19473 * - `Array`: An array of function or string predicates. The first predicate in the array 19474 * is used for sorting, but when two items are equivalent, the next predicate is used. 19475 * 19476 * If the predicate is missing or empty then it defaults to `'+'`. 19477 * 19478 * @param {boolean=} reverse Reverse the order of the array. 19479 * @returns {Array} Sorted copy of the source array. 19480 * 19481 * 19482 * @example 19483 * The example below demonstrates a simple ngRepeat, where the data is sorted 19484 * by age in descending order (predicate is set to `'-age'`). 19485 * `reverse` is not set, which means it defaults to `false`. 19486 <example module="orderByExample"> 19487 <file name="index.html"> 19488 <script> 19489 angular.module('orderByExample', [])
19490 .controller('ExampleController', ['$scope', function($scope) { 19491 $scope.friends = 19492 [{name:'John', phone:'555-1212', age:10}, 19493 {name:'Mary', phone:'555-9876', age:19}, 19494 {name:'Mike', phone:'555-4321', age:21}, 19495 {name:'Adam', phone:'555-5678', age:35}, 19496 {name:'Julie', phone:'555-8765', age:29}]; 19497 }]); 19498 </script> 19499 <div ng-controller="ExampleController"> 19500 <table class="friend"> 19501 <tr> 19502 <th>Name</th> 19503 <th>Phone Number</th> 19504 <th>Age</th> 19505 </tr> 19506 <tr ng-repeat="friend in friends | orderBy:'-age'"> 19507 <td>{{friend.name}}</td> 19508 <td>{{friend.phone}}</td> 19509 <td>{{friend.age}}</td> 19510 </tr> 19511 </table> 19512 </div> 19513 </file> 19514 </example> 19515 * 19516 * The predicate and reverse parameters can be controlled dynamically through scope properties, 19517 * as shown in the next example. 19518 * @example 19519 <example module="orderByExample"> 19520 <file name="index.html"> 19521 <script> 19522 angular.module('orderByExample', []) 19523 .controller('ExampleController', ['$scope', function($scope) { 19524 $scope.friends = 19525 [{name:'John', phone:'555-1212', age:10}, 19526 {name:'Mary', phone:'555-9876', age:19}, 19527 {name:'Mike', phone:'555-4321', age:21}, 19528 {name:'Adam', phone:'555-5678', age:35}, 19529 {name:'Julie', phone:'555-8765', age:29}]; 19530 $scope.predicate = 'age'; 19531 $scope.reverse = true; 19532 $scope.order = function(predicate) { 19533 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 19534 $scope.predicate = predicate; 19535 }; 19536 }]); 19537 </script> 19538 <style type="text/css"> 19539 .sortorder:after { 19540 content: '\25b2'; 19541 } 19542 .sortorder.reverse:after { 19543 content: '\25bc'; 19544 } 19545 </style>
19546 <div ng-controller="ExampleController"> 19547 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 19548 <hr/> 19549 [ <a href="" ng-click="predicate=''">unsorted</a> ] 19550 <table class="friend"> 19551 <tr> 19552 <th> 19553 <a href="" ng-click="order('name')">Name</a> 19554 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 19555 </th> 19556 <th> 19557 <a href="" ng-click="order('phone')">Phone Number</a> 19558 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 19559 </th> 19560 <th> 19561 <a href="" ng-click="order('age')">Age</a> 19562 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 19563 </th> 19564 </tr> 19565 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 19566 <td>{{friend.name}}</td> 19567 <td>{{friend.phone}}</td> 19568 <td>{{friend.age}}</td> 19569 </tr> 19570 </table> 19571 </div> 19572 </file> 19573 </example> 19574 * 19575 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 19576 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 19577 * desired parameters. 19578 * 19579 * Example: 19580 * 19581 * @example 19582 <example module="orderByExample"> 19583 <file name="index.html">
19584 <div ng-controller="ExampleController"> 19585 <table class="friend"> 19586 <tr> 19587 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> 19588 (<a href="" ng-click="order('-name',false)">^</a>)</th> 19589 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> 19590 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> 19591 </tr> 19592 <tr ng-repeat="friend in friends"> 19593 <td>{{friend.name}}</td> 19594 <td>{{friend.phone}}</td> 19595 <td>{{friend.age}}</td> 19596 </tr> 19597 </table> 19598 </div> 19599 </file> 19600 19601 <file name="script.js"> 19602 angular.module('orderByExample', []) 19603 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 19604 var orderBy = $filter('orderBy'); 19605 $scope.friends = [ 19606 { name: 'John', phone: '555-1212', age: 10 }, 19607 { name: 'Mary', phone: '555-9876', age: 19 }, 19608 { name: 'Mike', phone: '555-4321', age: 21 }, 19609 { name: 'Adam', phone: '555-5678', age: 35 }, 19610 { name: 'Julie', phone: '555-8765', age: 29 } 19611 ]; 19612 $scope.order = function(predicate, reverse) { 19613 $scope.friends = orderBy($scope.friends, predicate, reverse); 19614 }; 19615 $scope.order('-age',false); 19616 }]); 19617 </file> 19618</example> 19619 */ 19620orderByFilter.$inject = ['$parse']; 19621function orderByFilter($parse) { 19622 return function(array, sortPredicate, reverseOrder) { 19623 19624 if (!(isArrayLike(array))) return array; 19625 19626 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 19627 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 19628 19629 var predicates = processPredicates(sortPredicate, reverseOrder); 19630 // Add a predicate at the end that evaluates to the element index. This makes the 19631 // sort stable as it works as a tie-breaker when all the input predicates cannot 19632 // distinguish between two elements. 19633 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1}); 19634 19635 // The next three lines are a version of a Swartzian Transform idiom from Perl 19636 // (sometimes called the Decorate-Sort-Undecorate idiom) 19637 // See https://en.wikipedia.org/wiki/Schwartzian_transform 19638 var compareValues = Array.prototype.map.call(array, getComparisonObject); 19639 compareValues.sort(doComparison); 19640 array = compareValues.map(function(item) { return item.value; }); 19641 19642 return array; 19643 19644 function getComparisonObject(value, index) { 19645 return { 19646 value: value, 19647 predicateValues: predicates.map(function(predicate) { 19648 return getPredicateValue(predicate.get(value), index); 19649 }) 19650 }; 19651 } 19652 19653 function doComparison(v1, v2) { 19654 var result = 0; 19655 for (var index=0, length = predicates.length; index < length; ++index) { 19656 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; 19657 if (result) break; 19658 } 19659 return result; 19660 } 19661 }; 19662 19663 function processPredicates(sortPredicate, reverseOrder) { 19664 reverseOrder = reverseOrder ? -1 : 1; 19665 return sortPredicate.map(function(predicate) { 19666 var descending = 1, get = identity; 19667 19668 if (isFunction(predicate)) { 19669 get = predicate; 19670 } else if (isString(predicate)) { 19671 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 19672 descending = predicate.charAt(0) == '-' ? -1 : 1; 19673 predicate = predicate.substring(1); 19674 } 19675 if (predicate !== '') { 19676 get = $parse(predicate); 19677 if (get.constant) { 19678 var key = get(); 19679 get = function(value) { return value[key]; }; 19680 } 19681 } 19682 } 19683 return { get: get, descending: descending * reverseOrder }; 19684 }); 19685 } 19686 19687 function isPrimitive(value) { 19688 switch (typeof value) { 19689 case 'number': /* falls through */ 19690 case 'boolean': /* falls through */ 19691 case 'string': 19692 return true; 19693 default: 19694 return false;
19695 } 19696 } 19697 19698 function objectValue(value, index) { 19699 // If `valueOf` is a valid function use that 19700 if (typeof value.valueOf === 'function') { 19701 value = value.valueOf(); 19702 if (isPrimitive(value)) return value; 19703 } 19704 // If `toString` is a valid function and not the one from `Object.prototype` use that 19705 if (hasCustomToString(value)) { 19706 value = value.toString(); 19707 if (isPrimitive(value)) return value; 19708 } 19709 // We have a basic object so we use the position of the object in the collection 19710 return index; 19711 } 19712 19713 function getPredicateValue(value, index) { 19714 var type = typeof value; 19715 if (value === null) { 19716 type = 'string'; 19717 value = 'null'; 19718 } else if (type === 'string') { 19719 value = value.toLowerCase(); 19720 } else if (type === 'object') { 19721 value = objectValue(value, index); 19722 } 19723 return { value: value, type: type }; 19724 } 19725 19726 function compare(v1, v2) { 19727 var result = 0; 19728 if (v1.type === v2.type) { 19729 if (v1.value !== v2.value) { 19730 result = v1.value < v2.value ? -1 : 1; 19731 } 19732 } else { 19733 result = v1.type < v2.type ? -1 : 1; 19734 } 19735 return result; 19736 } 19737} 19738 19739function ngDirective(directive) { 19740 if (isFunction(directive)) { 19741 directive = { 19742 link: directive 19743 }; 19744 } 19745 directive.restrict = directive.restrict || 'AC'; 19746 return valueFn(directive); 19747} 19748 19749/** 19750 * @ngdoc directive 19751 * @name a 19752 * @restrict E 19753 * 19754 * @description 19755 * Modifies the default behavior of the html A tag so that the default action is prevented when 19756 * the href attribute is empty. 19757 * 19758 * This change permits the easy creation of action links with the `ngClick` directive 19759 * without changing the location or causing page reloads, e.g.: 19760 * `<a href="" ng-click="list.addItem()">Add Item</a>` 19761 */ 19762var htmlAnchorDirective = valueFn({ 19763 restrict: 'E', 19764 compile: function(element, attr) { 19765 if (!attr.href && !attr.xlinkHref) { 19766 return function(scope, element) { 19767 // If the linked element is not an anchor tag anymore, do nothing 19768 if (element[0].nodeName.toLowerCase() !== 'a') return; 19769 19770 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 19771 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 19772 'xlink:href' : 'href'; 19773 element.on('click', function(event) { 19774 // if we have no href url, then don't navigate anywhere. 19775 if (!element.attr(href)) { 19776 event.preventDefault(); 19777 } 19778 }); 19779 }; 19780 } 19781 } 19782}); 19783 19784/** 19785 * @ngdoc directive 19786 * @name ngHref 19787 * @restrict A 19788 * @priority 99 19789 * 19790 * @description 19791 * Using Angular markup like `{{hash}}` in an href attribute will 19792 * make the link go to the wrong URL if the user clicks it before 19793 * Angular has a chance to replace the `{{hash}}` markup with its 19794 * value. Until Angular replaces the markup the link will be broken 19795 * and will most likely return a 404 error. The `ngHref` directive 19796 * solves this problem. 19797 * 19798 * The wrong way to write it: 19799 * ```html 19800 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19801 * ``` 19802 * 19803 * The correct way to write it: 19804 * ```html 19805 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19806 * ``` 19807 * 19808 * @element A 19809 * @param {template} ngHref any string which can contain `{{}}` markup. 19810 * 19811 * @example 19812 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 19813 * in links and their different behaviors: 19814 <example> 19815 <file name="index.html"> 19816 <input ng-model="value" /><br /> 19817 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 19818 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 19819 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 19820 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 19821 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 19822 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 19823 </file> 19824 <file name="protractor.js" type="protractor"> 19825 it('should execute ng-click but not reload when href without value', function() {
19826 element(by.id('link-1')).click(); 19827 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 19828 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 19829 }); 19830 19831 it('should execute ng-click but not reload when href empty string', function() { 19832 element(by.id('link-2')).click(); 19833 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 19834 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 19835 }); 19836 19837 it('should execute ng-click and change url when ng-href specified', function() { 19838 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 19839 19840 element(by.id('link-3')).click(); 19841 19842 // At this point, we navigate away from an Angular page, so we need 19843 // to use browser.driver to get the base webdriver. 19844 19845 browser.wait(function() { 19846 return browser.driver.getCurrentUrl().then(function(url) { 19847 return url.match(/\/123$/); 19848 }); 19849 }, 5000, 'page should navigate to /123'); 19850 }); 19851 19852 it('should execute ng-click but not reload when href empty string and name specified', function() { 19853 element(by.id('link-4')).click(); 19854 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 19855 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 19856 }); 19857 19858 it('should execute ng-click but not reload when no href but name specified', function() { 19859 element(by.id('link-5')).click(); 19860 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 19861 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 19862 }); 19863 19864 it('should only change url when only ng-href', function() { 19865 element(by.model('value')).clear(); 19866 element(by.model('value')).sendKeys('6'); 19867 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 19868 19869 element(by.id('link-6')).click(); 19870 19871 // At this point, we navigate away from an Angular page, so we need 19872 // to use browser.driver to get the base webdriver. 19873 browser.wait(function() { 19874 return browser.driver.getCurrentUrl().then(function(url) { 19875 return url.match(/\/6$/); 19876 }); 19877 }, 5000, 'page should navigate to /6'); 19878 }); 19879 </file> 19880 </example> 19881 */ 19882 19883/** 19884 * @ngdoc directive 19885 * @name ngSrc 19886 * @restrict A 19887 * @priority 99 19888 * 19889 * @description 19890 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 19891 * work right: The browser will fetch from the URL with the literal 19892 * text `{{hash}}` until Angular replaces the expression inside 19893 * `{{hash}}`. The `ngSrc` directive solves this problem. 19894 * 19895 * The buggy way to write it: 19896 * ```html 19897 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 19898 * ``` 19899 * 19900 * The correct way to write it: 19901 * ```html 19902 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 19903 * ``` 19904 * 19905 * @element IMG 19906 * @param {template} ngSrc any string which can contain `{{}}` markup. 19907 */ 19908 19909/** 19910 * @ngdoc directive 19911 * @name ngSrcset 19912 * @restrict A 19913 * @priority 99 19914 * 19915 * @description 19916 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 19917 * work right: The browser will fetch from the URL with the literal 19918 * text `{{hash}}` until Angular replaces the expression inside 19919 * `{{hash}}`. The `ngSrcset` directive solves this problem. 19920 * 19921 * The buggy way to write it: 19922 * ```html 19923 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 19924 * ``` 19925 * 19926 * The correct way to write it: 19927 * ```html 19928 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 19929 * ``` 19930 * 19931 * @element IMG 19932 * @param {template} ngSrcset any string which can contain `{{}}` markup. 19933 */ 19934 19935/** 19936 * @ngdoc directive 19937 * @name ngDisabled 19938 * @restrict A 19939 * @priority 100 19940 * 19941 * @description 19942 * 19943 * This directive sets the `disabled` attribute on the element if the 19944 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 19945 * 19946 * A special directive is necessary because we cannot use interpolation inside the `disabled` 19947 * attribute. The following example would make the button enabled on Chrome/Firefox 19948 * but not on older IEs: 19949 * 19950 * ```html 19951 * <!-- See below for an example of ng-disabled being used correctly --> 19952 * <div ng-init="isDisabled = false"> 19953 * <button disabled="{{isDisabled}}">Disabled</button> 19954 * </div> 19955 * ``` 19956 * 19957 * This is because the HTML specification does not require browsers to preserve the values of 19958 * boolean attributes such as `disabled` (Their presence means true and their absence means false.) 19959 * If we put an Angular interpolation expression into such an attribute then the 19960 * binding information would be lost when the browser removes the attribute. 19961 * 19962 * @example 19963 <example> 19964 <file name="index.html"> 19965 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 19966 <button ng-model="button" ng-disabled="checked">Button</button> 19967 </file> 19968 <file name="protractor.js" type="protractor"> 19969 it('should toggle button', function() { 19970 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19971 element(by.model('checked')).click(); 19972 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 19973 }); 19974 </file> 19975 </example> 19976 * 19977 * @element INPUT 19978 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 19979 * then the `disabled` attribute will be set on the element 19980 */ 19981 19982 19983/** 19984 * @ngdoc directive 19985 * @name ngChecked 19986 * @restrict A 19987 * @priority 100 19988 * 19989 * @description 19990 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 19991 * 19992 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 19993 * as this can lead to unexpected behavior. 19994 * 19995 * ### Why do we need `ngChecked`? 19996 * 19997 * The HTML specification does not require browsers to preserve the values of boolean attributes 19998 * such as checked. (Their presence means true and their absence means false.) 19999 * If we put an Angular interpolation expression into such an attribute then the 20000 * binding information would be lost when the browser removes the attribute. 20001 * The `ngChecked` directive solves this problem for the `checked` attribute. 20002 * This complementary directive is not removed by the browser and so provides 20003 * a permanent reliable place to store the binding information. 20004 * @example 20005 <example> 20006 <file name="index.html"> 20007 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 20008 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 20009 </file> 20010 <file name="protractor.js" type="protractor"> 20011 it('should check both checkBoxes', function() { 20012 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 20013 element(by.model('master')).click(); 20014 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 20015 }); 20016 </file> 20017 </example> 20018 * 20019 * @element INPUT 20020 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 20021 * then the `checked` attribute will be set on the element 20022 */ 20023 20024 20025/** 20026 * @ngdoc directive 20027 * @name ngReadonly 20028 * @restrict A 20029 * @priority 100 20030 * 20031 * @description 20032 * The HTML specification does not require browsers to preserve the values of boolean attributes 20033 * such as readonly. (Their presence means true and their absence means false.) 20034 * If we put an Angular interpolation expression into such an attribute then the 20035 * binding information would be lost when the browser removes the attribute. 20036 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 20037 * This complementary directive is not removed by the browser and so provides 20038 * a permanent reliable place to store the binding information. 20039 * @example 20040 <example> 20041 <file name="index.html"> 20042 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 20043 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 20044 </file> 20045 <file name="protractor.js" type="protractor"> 20046 it('should toggle readonly attr', function() { 20047 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 20048 element(by.model('checked')).click(); 20049 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 20050 }); 20051 </file> 20052 </example> 20053 * 20054 * @element INPUT 20055 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 20056 * then special attribute "readonly" will be set on the element 20057 */ 20058 20059 20060/** 20061 * @ngdoc directive 20062 * @name ngSelected 20063 * @restrict A 20064 * @priority 100 20065 * 20066 * @description 20067 * The HTML specification does not require browsers to preserve the values of boolean attributes 20068 * such as selected. (Their presence means true and their absence means false.) 20069 * If we put an Angular interpolation expression into such an attribute then the 20070 * binding information would be lost when the browser removes the attribute. 20071 * The `ngSelected` directive solves this problem for the `selected` attribute. 20072 * This complementary directive is not removed by the browser and so provides 20073 * a permanent reliable place to store the binding information. 20074 * 20075 * @example 20076 <example> 20077 <file name="index.html"> 20078 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 20079 <select aria-label="ngSelected demo"> 20080 <option>Hello!</option> 20081 <option id="greet" ng-selected="selected">Greetings!</option> 20082 </select> 20083 </file> 20084 <file name="protractor.js" type="protractor"> 20085 it('should select Greetings!', function() { 20086 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
20087 element(by.model('selected')).click(); 20088 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 20089 }); 20090 </file> 20091 </example> 20092 * 20093 * @element OPTION 20094 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 20095 * then special attribute "selected" will be set on the element 20096 */ 20097 20098/** 20099 * @ngdoc directive 20100 * @name ngOpen 20101 * @restrict A 20102 * @priority 100 20103 * 20104 * @description 20105 * The HTML specification does not require browsers to preserve the values of boolean attributes 20106 * such as open. (Their presence means true and their absence means false.) 20107 * If we put an Angular interpolation expression into such an attribute then the 20108 * binding information would be lost when the browser removes the attribute. 20109 * The `ngOpen` directive solves this problem for the `open` attribute. 20110 * This complementary directive is not removed by the browser and so provides 20111 * a permanent reliable place to store the binding information. 20112 * @example 20113 <example> 20114 <file name="index.html"> 20115 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 20116 <details id="details" ng-open="open"> 20117 <summary>Show/Hide me</summary> 20118 </details> 20119 </file> 20120 <file name="protractor.js" type="protractor"> 20121 it('should toggle open', function() { 20122 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 20123 element(by.model('open')).click(); 20124 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 20125 }); 20126 </file> 20127 </example> 20128 * 20129 * @element DETAILS 20130 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 20131 * then special attribute "open" will be set on the element 20132 */ 20133 20134var ngAttributeAliasDirectives = {}; 20135 20136// boolean attrs are evaluated 20137forEach(BOOLEAN_ATTR, function(propName, attrName) { 20138 // binding to multiple is not supported 20139 if (propName == "multiple") return; 20140 20141 function defaultLinkFn(scope, element, attr) { 20142 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 20143 attr.$set(attrName, !!value); 20144 }); 20145 } 20146 20147 var normalized = directiveNormalize('ng-' + attrName); 20148 var linkFn = defaultLinkFn; 20149 20150 if (propName === 'checked') { 20151 linkFn = function(scope, element, attr) { 20152 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 20153 if (attr.ngModel !== attr[normalized]) { 20154 defaultLinkFn(scope, element, attr); 20155 } 20156 }; 20157 } 20158 20159 ngAttributeAliasDirectives[normalized] = function() { 20160 return { 20161 restrict: 'A', 20162 priority: 100, 20163 link: linkFn 20164 }; 20165 }; 20166}); 20167 20168// aliased input attrs are evaluated 20169forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 20170 ngAttributeAliasDirectives[ngAttr] = function() { 20171 return { 20172 priority: 100, 20173 link: function(scope, element, attr) { 20174 //special case ngPattern when a literal regular expression value 20175 //is used as the expression (this way we don't have to watch anything). 20176 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 20177 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 20178 if (match) { 20179 attr.$set("ngPattern", new RegExp(match[1], match[2])); 20180 return; 20181 } 20182 } 20183 20184 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 20185 attr.$set(ngAttr, value); 20186 }); 20187 } 20188 }; 20189 }; 20190}); 20191 20192// ng-src, ng-srcset, ng-href are interpolated 20193forEach(['src', 'srcset', 'href'], function(attrName) { 20194 var normalized = directiveNormalize('ng-' + attrName); 20195 ngAttributeAliasDirectives[normalized] = function() { 20196 return { 20197 priority: 99, // it needs to run after the attributes are interpolated 20198 link: function(scope, element, attr) { 20199 var propName = attrName, 20200 name = attrName; 20201 20202 if (attrName === 'href' && 20203 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 20204 name = 'xlinkHref'; 20205 attr.$attr[name] = 'xlink:href'; 20206 propName = null; 20207 } 20208 20209 attr.$observe(normalized, function(value) { 20210 if (!value) { 20211 if (attrName === 'href') { 20212 attr.$set(name, null); 20213 } 20214 return; 20215 } 20216 20217 attr.$set(name, value); 20218 20219 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 20220 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 20221 // to set the property as well to achieve the desired effect. 20222 // we use attr[attrName] value since $set can sanitize the url. 20223 if (msie && propName) element.prop(propName, attr[name]); 20224 }); 20225 } 20226 }; 20227 }; 20228}); 20229 20230/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 20231 */ 20232var nullFormCtrl = { 20233 $addControl: noop, 20234 $$renameControl: nullFormRenameControl, 20235 $removeControl: noop, 20236 $setValidity: noop, 20237 $setDirty: noop, 20238 $setPristine: noop, 20239 $setSubmitted: noop 20240}, 20241SUBMITTED_CLASS = 'ng-submitted'; 20242 20243function nullFormRenameControl(control, name) { 20244 control.$name = name; 20245} 20246
20247/** 20248 * @ngdoc type 20249 * @name form.FormController 20250 * 20251 * @property {boolean} $pristine True if user has not interacted with the form yet. 20252 * @property {boolean} $dirty True if user has already interacted with the form. 20253 * @property {boolean} $valid True if all of the containing forms and controls are valid. 20254 * @property {boolean} $invalid True if at least one containing control or form is invalid. 20255 * @property {boolean} $pending True if at least one containing control or form is pending. 20256 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 20257 * 20258 * @property {Object} $error Is an object hash, containing references to controls or 20259 * forms with failing validators, where: 20260 * 20261 * - keys are validation tokens (error names), 20262 * - values are arrays of controls or forms that have a failing validator for given error name. 20263 * 20264 * Built-in validation tokens: 20265 * 20266 * - `email` 20267 * - `max` 20268 * - `maxlength` 20269 * - `min` 20270 * - `minlength` 20271 * - `number` 20272 * - `pattern` 20273 * - `required` 20274 * - `url` 20275 * - `date` 20276 * - `datetimelocal` 20277 * - `time` 20278 * - `week` 20279 * - `month` 20280 * 20281 * @description 20282 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 20283 * such as being valid/invalid or dirty/pristine. 20284 * 20285 * Each {@link ng.directive:form form} directive creates an instance 20286 * of `FormController`. 20287 * 20288 */ 20289//asks for $scope to fool the BC controller module 20290FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 20291function FormController(element, attrs, $scope, $animate, $interpolate) { 20292 var form = this, 20293 controls = []; 20294 20295 // init state 20296 form.$error = {}; 20297 form.$$success = {}; 20298 form.$pending = undefined; 20299 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 20300 form.$dirty = false; 20301 form.$pristine = true; 20302 form.$valid = true; 20303 form.$invalid = false; 20304 form.$submitted = false; 20305 form.$$parentForm = nullFormCtrl; 20306 20307 /** 20308 * @ngdoc method 20309 * @name form.FormController#$rollbackViewValue 20310 * 20311 * @description 20312 * Rollback all form controls pending updates to the `$modelValue`. 20313 * 20314 * Updates may be pending by a debounced event or because the input is waiting for a some future 20315 * event defined in `ng-model-options`. This method is typically needed by the reset button of 20316 * a form that uses `ng-model-options` to pend updates. 20317 */ 20318 form.$rollbackViewValue = function() { 20319 forEach(controls, function(control) { 20320 control.$rollbackViewValue(); 20321 }); 20322 }; 20323 20324 /** 20325 * @ngdoc method 20326 * @name form.FormController#$commitViewValue 20327 * 20328 * @description 20329 * Commit all form controls pending updates to the `$modelValue`. 20330 * 20331 * Updates may be pending by a debounced event or because the input is waiting for a some future 20332 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 20333 * usually handles calling this in response to input events. 20334 */ 20335 form.$commitViewValue = function() { 20336 forEach(controls, function(control) { 20337 control.$commitViewValue(); 20338 }); 20339 }; 20340 20341 /** 20342 * @ngdoc method 20343 * @name form.FormController#$addControl 20344 * @param {object} control control object, either a {@link form.FormController} or an 20345 * {@link ngModel.NgModelController} 20346 * 20347 * @description 20348 * Register a control with the form. Input elements using ngModelController do this automatically 20349 * when they are linked. 20350 * 20351 * Note that the current state of the control will not be reflected on the new parent form. This 20352 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 20353 * state. 20354 * 20355 * However, if the method is used programmatically, for example by adding dynamically created controls, 20356 * or controls that have been previously removed without destroying their corresponding DOM element, 20357 * it's the developers responsiblity to make sure the current state propagates to the parent form. 20358 * 20359 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 20360 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 20361 */ 20362 form.$addControl = function(control) { 20363 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 20364 // and not added to the scope. Now we throw an error. 20365 assertNotHasOwnProperty(control.$name, 'input'); 20366 controls.push(control); 20367 20368 if (control.$name) { 20369 form[control.$name] = control; 20370 } 20371 20372 control.$$parentForm = form; 20373 }; 20374 20375 // Private API: rename a form control 20376 form.$$renameControl = function(control, newName) { 20377 var oldName = control.$name; 20378 20379 if (form[oldName] === control) { 20380 delete form[oldName]; 20381 } 20382 form[newName] = control; 20383 control.$name = newName; 20384 }; 20385 20386 /** 20387 * @ngdoc method
20388 * @name form.FormController#$removeControl 20389 * @param {object} control control object, either a {@link form.FormController} or an 20390 * {@link ngModel.NgModelController} 20391 * 20392 * @description 20393 * Deregister a control from the form. 20394 * 20395 * Input elements using ngModelController do this automatically when they are destroyed. 20396 * 20397 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 20398 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 20399 * different from case to case. For example, removing the only `$dirty` control from a form may or 20400 * may not mean that the form is still `$dirty`. 20401 */ 20402 form.$removeControl = function(control) { 20403 if (control.$name && form[control.$name] === control) { 20404 delete form[control.$name]; 20405 } 20406 forEach(form.$pending, function(value, name) { 20407 form.$setValidity(name, null, control); 20408 }); 20409 forEach(form.$error, function(value, name) { 20410 form.$setValidity(name, null, control); 20411 }); 20412 forEach(form.$$success, function(value, name) { 20413 form.$setValidity(name, null, control); 20414 }); 20415 20416 arrayRemove(controls, control); 20417 control.$$parentForm = nullFormCtrl; 20418 }; 20419 20420 20421 /** 20422 * @ngdoc method 20423 * @name form.FormController#$setValidity 20424 * 20425 * @description 20426 * Sets the validity of a form control. 20427 * 20428 * This method will also propagate to parent forms. 20429 */ 20430 addSetValidityMethod({ 20431 ctrl: this, 20432 $element: element, 20433 set: function(object, property, controller) { 20434 var list = object[property]; 20435 if (!list) { 20436 object[property] = [controller]; 20437 } else { 20438 var index = list.indexOf(controller); 20439 if (index === -1) { 20440 list.push(controller); 20441 } 20442 } 20443 }, 20444 unset: function(object, property, controller) { 20445 var list = object[property]; 20446 if (!list) { 20447 return; 20448 } 20449 arrayRemove(list, controller); 20450 if (list.length === 0) { 20451 delete object[property]; 20452 } 20453 }, 20454 $animate: $animate 20455 }); 20456 20457 /** 20458 * @ngdoc method 20459 * @name form.FormController#$setDirty 20460 * 20461 * @description 20462 * Sets the form to a dirty state. 20463 * 20464 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 20465 * state (ng-dirty class). This method will also propagate to parent forms. 20466 */ 20467 form.$setDirty = function() { 20468 $animate.removeClass(element, PRISTINE_CLASS); 20469 $animate.addClass(element, DIRTY_CLASS); 20470 form.$dirty = true; 20471 form.$pristine = false; 20472 form.$$parentForm.$setDirty(); 20473 }; 20474 20475 /** 20476 * @ngdoc method 20477 * @name form.FormController#$setPristine 20478 * 20479 * @description 20480 * Sets the form to its pristine state. 20481 * 20482 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 20483 * state (ng-pristine class). This method will also propagate to all the controls contained 20484 * in this form. 20485 * 20486 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 20487 * saving or resetting it. 20488 */ 20489 form.$setPristine = function() { 20490 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 20491 form.$dirty = false; 20492 form.$pristine = true; 20493 form.$submitted = false; 20494 forEach(controls, function(control) { 20495 control.$setPristine(); 20496 }); 20497 }; 20498 20499 /** 20500 * @ngdoc method 20501 * @name form.FormController#$setUntouched 20502 * 20503 * @description 20504 * Sets the form to its untouched state. 20505 * 20506 * This method can be called to remove the 'ng-touched' class and set the form controls to their 20507 * untouched state (ng-untouched class). 20508 * 20509 * Setting a form controls back to their untouched state is often useful when setting the form 20510 * back to its pristine state. 20511 */ 20512 form.$setUntouched = function() { 20513 forEach(controls, function(control) { 20514 control.$setUntouched(); 20515 }); 20516 }; 20517 20518 /** 20519 * @ngdoc method 20520 * @name form.FormController#$setSubmitted 20521 * 20522 * @description 20523 * Sets the form to its submitted state. 20524 */ 20525 form.$setSubmitted = function() {
20526 $animate.addClass(element, SUBMITTED_CLASS); 20527 form.$submitted = true; 20528 form.$$parentForm.$setSubmitted(); 20529 }; 20530} 20531 20532/** 20533 * @ngdoc directive 20534 * @name ngForm 20535 * @restrict EAC 20536 * 20537 * @description 20538 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 20539 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 20540 * sub-group of controls needs to be determined. 20541 * 20542 * Note: the purpose of `ngForm` is to group controls, 20543 * but not to be a replacement for the `<form>` tag with all of its capabilities 20544 * (e.g. posting to the server, ...). 20545 * 20546 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 20547 * related scope, under this name. 20548 * 20549 */ 20550 20551 /** 20552 * @ngdoc directive 20553 * @name form 20554 * @restrict E 20555 * 20556 * @description 20557 * Directive that instantiates 20558 * {@link form.FormController FormController}. 20559 * 20560 * If the `name` attribute is specified, the form controller is published onto the current scope under 20561 * this name. 20562 * 20563 * # Alias: {@link ng.directive:ngForm `ngForm`} 20564 * 20565 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 20566 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 20567 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 20568 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 20569 * using Angular validation directives in forms that are dynamically generated using the 20570 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 20571 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 20572 * `ngForm` directive and nest these in an outer `form` element. 20573 * 20574 * 20575 * # CSS classes 20576 * - `ng-valid` is set if the form is valid. 20577 * - `ng-invalid` is set if the form is invalid. 20578 * - `ng-pending` is set if the form is pending. 20579 * - `ng-pristine` is set if the form is pristine. 20580 * - `ng-dirty` is set if the form is dirty. 20581 * - `ng-submitted` is set if the form was submitted. 20582 * 20583 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 20584 * 20585 * 20586 * # Submitting a form and preventing the default action 20587 * 20588 * Since the role of forms in client-side Angular applications is different than in classical 20589 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 20590 * page reload that sends the data to the server. Instead some javascript logic should be triggered 20591 * to handle the form submission in an application-specific way. 20592 * 20593 * For this reason, Angular prevents the default action (form submission to the server) unless the 20594 * `<form>` element has an `action` attribute specified. 20595 * 20596 * You can use one of the following two ways to specify what javascript method should be called when 20597 * a form is submitted: 20598 * 20599 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 20600 * - {@link ng.directive:ngClick ngClick} directive on the first 20601 * button or input field of type submit (input[type=submit]) 20602 * 20603 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 20604 * or {@link ng.directive:ngClick ngClick} directives. 20605 * This is because of the following form submission rules in the HTML specification: 20606 * 20607 * - If a form has only one input field then hitting enter in this field triggers form submit 20608 * (`ngSubmit`) 20609 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 20610 * doesn't trigger submit 20611 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 20612 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 20613 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 20614 * 20615 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 20616 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 20617 * to have access to the updated model. 20618 * 20619 * ## Animation Hooks 20620 * 20621 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 20622 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 20623 * other validations that are performed within the form. Animations in ngForm are similar to how 20624 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 20625 * as JS animations. 20626 * 20627 * The following example shows a simple way to utilize CSS transitions to style a form element
20628 * that has been rendered as invalid after it has been validated: 20629 * 20630 * <pre> 20631 * //be sure to include ngAnimate as a module to hook into more 20632 * //advanced animations 20633 * .my-form { 20634 * transition:0.5s linear all; 20635 * background: white; 20636 * } 20637 * .my-form.ng-invalid { 20638 * background: red; 20639 * color:white; 20640 * } 20641 * </pre> 20642 * 20643 * @example 20644 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 20645 <file name="index.html"> 20646 <script> 20647 angular.module('formExample', []) 20648 .controller('FormController', ['$scope', function($scope) { 20649 $scope.userType = 'guest'; 20650 }]); 20651 </script> 20652 <style> 20653 .my-form { 20654 transition:all linear 0.5s; 20655 background: transparent; 20656 } 20657 .my-form.ng-invalid { 20658 background: red; 20659 } 20660 </style> 20661 <form name="myForm" ng-controller="FormController" class="my-form"> 20662 userType: <input name="input" ng-model="userType" required> 20663 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 20664 <code>userType = {{userType}}</code><br> 20665 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 20666 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 20667 <code>myForm.$valid = {{myForm.$valid}}</code><br> 20668 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 20669 </form> 20670 </file> 20671 <file name="protractor.js" type="protractor"> 20672 it('should initialize to model', function() { 20673 var userType = element(by.binding('userType')); 20674 var valid = element(by.binding('myForm.input.$valid')); 20675 20676 expect(userType.getText()).toContain('guest'); 20677 expect(valid.getText()).toContain('true'); 20678 }); 20679 20680 it('should be invalid if empty', function() { 20681 var userType = element(by.binding('userType')); 20682 var valid = element(by.binding('myForm.input.$valid')); 20683 var userInput = element(by.model('userType')); 20684 20685 userInput.clear(); 20686 userInput.sendKeys(''); 20687 20688 expect(userType.getText()).toEqual('userType ='); 20689 expect(valid.getText()).toContain('false'); 20690 }); 20691 </file> 20692 </example> 20693 * 20694 * @param {string=} name Name of the form. If specified, the form controller will be published into 20695 * related scope, under this name. 20696 */ 20697var formDirectiveFactory = function(isNgForm) { 20698 return ['$timeout', '$parse', function($timeout, $parse) { 20699 var formDirective = { 20700 name: 'form', 20701 restrict: isNgForm ? 'EAC' : 'E', 20702 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 20703 controller: FormController, 20704 compile: function ngFormCompile(formElement, attr) { 20705 // Setup initial state of the control 20706 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 20707 20708 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 20709 20710 return { 20711 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 20712 var controller = ctrls[0]; 20713 20714 // if `action` attr is not present on the form, prevent the default action (submission) 20715 if (!('action' in attr)) { 20716 // we can't use jq events because if a form is destroyed during submission the default 20717 // action is not prevented. see #1238 20718 // 20719 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 20720 // page reload if the form was destroyed by submission of the form via a click handler 20721 // on a button in the form. Looks like an IE9 specific bug. 20722 var handleFormSubmission = function(event) { 20723 scope.$apply(function() { 20724 controller.$commitViewValue(); 20725 controller.$setSubmitted(); 20726 }); 20727 20728 event.preventDefault(); 20729 }; 20730 20731 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20732 20733 // unregister the preventDefault listener so that we don't not leak memory but in a 20734 // way that will achieve the prevention of the default action. 20735 formElement.on('$destroy', function() { 20736 $timeout(function() { 20737 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20738 }, 0, false); 20739 }); 20740 } 20741 20742 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 20743 parentFormCtrl.$addControl(controller); 20744 20745 var setter = nameAttr ? getSetter(controller.$name) : noop; 20746 20747 if (nameAttr) { 20748 setter(scope, controller); 20749 attr.$observe(nameAttr, function(newValue) { 20750 if (controller.$name === newValue) return; 20751 setter(scope, undefined); 20752 controller.$$parentForm.$$renameControl(controller, newValue); 20753 setter = getSetter(controller.$name); 20754 setter(scope, controller); 20755 }); 20756 } 20757 formElement.on('$destroy', function() { 20758 controller.$$parentForm.$removeControl(controller); 20759 setter(scope, undefined); 20760 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 20761 }); 20762 } 20763 }; 20764 } 20765 }; 20766 20767 return formDirective; 20768 20769 function getSetter(expression) { 20770 if (expression === '') { 20771 //create an assignable expression, so forms with an empty name can be renamed later 20772 return $parse('this[""]').assign; 20773 } 20774 return $parse(expression).assign || noop; 20775 } 20776 }]; 20777}; 20778 20779var formDirective = formDirectiveFactory(); 20780var ngFormDirective = formDirectiveFactory(true); 20781 20782/* global VALID_CLASS: false, 20783 INVALID_CLASS: false, 20784 PRISTINE_CLASS: false, 20785 DIRTY_CLASS: false, 20786 UNTOUCHED_CLASS: false, 20787 TOUCHED_CLASS: false, 20788 ngModelMinErr: false, 20789*/ 20790 20791// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 20792var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/; 20793var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 20794var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
20795var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 20796var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; 20797var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20798var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; 20799var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; 20800var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20801 20802var inputType = { 20803 20804 /** 20805 * @ngdoc input 20806 * @name input[text] 20807 * 20808 * @description 20809 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 20810 * 20811 * 20812 * @param {string} ngModel Assignable angular expression to data-bind to. 20813 * @param {string=} name Property name of the form under which the control is published. 20814 * @param {string=} required Adds `required` validation error key if the value is not entered. 20815 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20816 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20817 * `required` when you want to data-bind to the `required` attribute. 20818 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20819 * minlength. 20820 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20821 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 20822 * any length. 20823 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 20824 * that contains the regular expression body that will be converted to a regular expression 20825 * as in the ngPattern directive. 20826 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 20827 * a RegExp found by evaluating the Angular expression given in the attribute value. 20828 * If the expression evaluates to a RegExp object, then this is used directly. 20829 * If the expression evaluates to a string, then it will be converted to a RegExp 20830 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 20831 * `new RegExp('^abc$')`.<br /> 20832 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 20833 * start at the index of the last search's match, thus not taking the whole input value into 20834 * account. 20835 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20836 * interaction with the input element. 20837 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 20838 * This parameter is ignored for input[type=password] controls, which will never trim the 20839 * input. 20840 * 20841 * @example 20842 <example name="text-input-directive" module="textInputExample"> 20843 <file name="index.html"> 20844 <script> 20845 angular.module('textInputExample', []) 20846 .controller('ExampleController', ['$scope', function($scope) { 20847 $scope.example = { 20848 text: 'guest', 20849 word: /^\s*\w*\s*$/ 20850 }; 20851 }]); 20852 </script>
20853 <form name="myForm" ng-controller="ExampleController"> 20854 <label>Single word: 20855 <input type="text" name="input" ng-model="example.text" 20856 ng-pattern="example.word" required ng-trim="false"> 20857 </label> 20858 <div role="alert"> 20859 <span class="error" ng-show="myForm.input.$error.required"> 20860 Required!</span> 20861 <span class="error" ng-show="myForm.input.$error.pattern"> 20862 Single word only!</span> 20863 </div> 20864 <tt>text = {{example.text}}</tt><br/> 20865 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20866 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20867 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20868 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20869 </form> 20870 </file> 20871 <file name="protractor.js" type="protractor"> 20872 var text = element(by.binding('example.text')); 20873 var valid = element(by.binding('myForm.input.$valid')); 20874 var input = element(by.model('example.text')); 20875 20876 it('should initialize to model', function() { 20877 expect(text.getText()).toContain('guest'); 20878 expect(valid.getText()).toContain('true'); 20879 }); 20880 20881 it('should be invalid if empty', function() { 20882 input.clear(); 20883 input.sendKeys(''); 20884 20885 expect(text.getText()).toEqual('text ='); 20886 expect(valid.getText()).toContain('false'); 20887 }); 20888 20889 it('should be invalid if multi word', function() { 20890 input.clear(); 20891 input.sendKeys('hello world'); 20892 20893 expect(valid.getText()).toContain('false'); 20894 }); 20895 </file> 20896 </example> 20897 */ 20898 'text': textInputType, 20899 20900 /** 20901 * @ngdoc input 20902 * @name input[date] 20903 * 20904 * @description 20905 * Input with date validation and transformation. In browsers that do not yet support 20906 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 20907 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 20908 * modern browsers do not yet support this input type, it is important to provide cues to users on the 20909 * expected input format via a placeholder or label. 20910 * 20911 * The model must always be a Date object, otherwise Angular will throw an error. 20912 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20913 * 20914 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20915 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20916 * 20917 * @param {string} ngModel Assignable angular expression to data-bind to. 20918 * @param {string=} name Property name of the form under which the control is published. 20919 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20920 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 20921 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 20922 * constraint validation. 20923 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 20924 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 20925 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 20926 * constraint validation. 20927 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 20928 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 20929 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 20930 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 20931 * @param {string=} required Sets `required` validation error key if the value is not entered. 20932 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20933 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20934 * `required` when you want to data-bind to the `required` attribute. 20935 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20936 * interaction with the input element. 20937 * 20938 * @example 20939 <example name="date-input-directive" module="dateInputExample"> 20940 <file name="index.html"> 20941 <script> 20942 angular.module('dateInputExample', []) 20943 .controller('DateController', ['$scope', function($scope) { 20944 $scope.example = { 20945 value: new Date(2013, 9, 22) 20946 }; 20947 }]); 20948 </script> 20949 <form name="myForm" ng-controller="DateController as dateCtrl"> 20950 <label for="exampleInput">Pick a date in 2013:</label> 20951 <input type="date" id="exampleInput" name="input" ng-model="example.value" 20952 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 20953 <div role="alert">
20954 <span class="error" ng-show="myForm.input.$error.required"> 20955 Required!</span> 20956 <span class="error" ng-show="myForm.input.$error.date"> 20957 Not a valid date!</span> 20958 </div> 20959 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 20960 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20961 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20962 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20963 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20964 </form> 20965 </file> 20966 <file name="protractor.js" type="protractor"> 20967 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 20968 var valid = element(by.binding('myForm.input.$valid')); 20969 var input = element(by.model('example.value')); 20970 20971 // currently protractor/webdriver does not support 20972 // sending keys to all known HTML5 input controls 20973 // for various browsers (see https://github.com/angular/protractor/issues/562). 20974 function setInput(val) { 20975 // set the value of the element and force validation. 20976 var scr = "var ipt = document.getElementById('exampleInput'); " + 20977 "ipt.value = '" + val + "';" + 20978 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20979 browser.executeScript(scr); 20980 } 20981 20982 it('should initialize to model', function() { 20983 expect(value.getText()).toContain('2013-10-22'); 20984 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20985 }); 20986 20987 it('should be invalid if empty', function() { 20988 setInput(''); 20989 expect(value.getText()).toEqual('value ='); 20990 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20991 }); 20992 20993 it('should be invalid if over max', function() { 20994 setInput('2015-01-01'); 20995 expect(value.getText()).toContain(''); 20996 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20997 }); 20998 </file> 20999 </example> 21000 */ 21001 'date': createDateInputType('date', DATE_REGEXP, 21002 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 21003 'yyyy-MM-dd'), 21004 21005 /** 21006 * @ngdoc input 21007 * @name input[datetime-local] 21008 * 21009 * @description 21010 * Input with datetime validation and transformation. In browsers that do not yet support 21011 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21012 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 21013 * 21014 * The model must always be a Date object, otherwise Angular will throw an error. 21015 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21016 * 21017 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21018 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21019 * 21020 * @param {string} ngModel Assignable angular expression to data-bind to. 21021 * @param {string=} name Property name of the form under which the control is published. 21022 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21023 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 21024 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 21025 * Note that `min` will also add native HTML5 constraint validation. 21026 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21027 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 21028 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 21029 * Note that `max` will also add native HTML5 constraint validation. 21030 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 21031 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21032 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 21033 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21034 * @param {string=} required Sets `required` validation error key if the value is not entered. 21035 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21036 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21037 * `required` when you want to data-bind to the `required` attribute. 21038 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21039 * interaction with the input element. 21040 * 21041 * @example 21042 <example name="datetimelocal-input-directive" module="dateExample"> 21043 <file name="index.html"> 21044 <script> 21045 angular.module('dateExample', []) 21046 .controller('DateController', ['$scope', function($scope) { 21047 $scope.example = { 21048 value: new Date(2010, 11, 28, 14, 57) 21049 }; 21050 }]); 21051 </script> 21052 <form name="myForm" ng-controller="DateController as dateCtrl"> 21053 <label for="exampleInput">Pick a date between in 2013:</label> 21054 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 21055 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 21056 <div role="alert">
21057 <span class="error" ng-show="myForm.input.$error.required"> 21058 Required!</span> 21059 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 21060 Not a valid date!</span> 21061 </div> 21062 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 21063 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21064 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21065 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21066 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21067 </form> 21068 </file> 21069 <file name="protractor.js" type="protractor"> 21070 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 21071 var valid = element(by.binding('myForm.input.$valid')); 21072 var input = element(by.model('example.value')); 21073 21074 // currently protractor/webdriver does not support 21075 // sending keys to all known HTML5 input controls 21076 // for various browsers (https://github.com/angular/protractor/issues/562). 21077 function setInput(val) { 21078 // set the value of the element and force validation. 21079 var scr = "var ipt = document.getElementById('exampleInput'); " + 21080 "ipt.value = '" + val + "';" + 21081 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21082 browser.executeScript(scr); 21083 } 21084 21085 it('should initialize to model', function() { 21086 expect(value.getText()).toContain('2010-12-28T14:57:00'); 21087 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21088 }); 21089 21090 it('should be invalid if empty', function() { 21091 setInput(''); 21092 expect(value.getText()).toEqual('value ='); 21093 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21094 }); 21095 21096 it('should be invalid if over max', function() { 21097 setInput('2015-01-01T23:59:00'); 21098 expect(value.getText()).toContain(''); 21099 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21100 }); 21101 </file> 21102 </example> 21103 */ 21104 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 21105 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 21106 'yyyy-MM-ddTHH:mm:ss.sss'), 21107 21108 /** 21109 * @ngdoc input 21110 * @name input[time] 21111 * 21112 * @description 21113 * Input with time validation and transformation. In browsers that do not yet support 21114 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21115 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 21116 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 21117 * 21118 * The model must always be a Date object, otherwise Angular will throw an error. 21119 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21120 * 21121 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21122 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21123 * 21124 * @param {string} ngModel Assignable angular expression to data-bind to. 21125 * @param {string=} name Property name of the form under which the control is published. 21126 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21127 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 21128 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 21129 * native HTML5 constraint validation. 21130 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21131 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 21132 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 21133 * native HTML5 constraint validation. 21134 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 21135 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21136 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 21137 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21138 * @param {string=} required Sets `required` validation error key if the value is not entered. 21139 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21140 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21141 * `required` when you want to data-bind to the `required` attribute. 21142 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21143 * interaction with the input element. 21144 * 21145 * @example 21146 <example name="time-input-directive" module="timeExample"> 21147 <file name="index.html"> 21148 <script> 21149 angular.module('timeExample', []) 21150 .controller('DateController', ['$scope', function($scope) { 21151 $scope.example = { 21152 value: new Date(1970, 0, 1, 14, 57, 0) 21153 }; 21154 }]); 21155 </script> 21156 <form name="myForm" ng-controller="DateController as dateCtrl"> 21157 <label for="exampleInput">Pick a between 8am and 5pm:</label> 21158 <input type="time" id="exampleInput" name="input" ng-model="example.value" 21159 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 21160 <div role="alert">
21161 <span class="error" ng-show="myForm.input.$error.required"> 21162 Required!</span> 21163 <span class="error" ng-show="myForm.input.$error.time"> 21164 Not a valid date!</span> 21165 </div> 21166 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 21167 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21168 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21169 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21170 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21171 </form> 21172 </file> 21173 <file name="protractor.js" type="protractor"> 21174 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 21175 var valid = element(by.binding('myForm.input.$valid')); 21176 var input = element(by.model('example.value')); 21177 21178 // currently protractor/webdriver does not support 21179 // sending keys to all known HTML5 input controls 21180 // for various browsers (https://github.com/angular/protractor/issues/562). 21181 function setInput(val) { 21182 // set the value of the element and force validation. 21183 var scr = "var ipt = document.getElementById('exampleInput'); " + 21184 "ipt.value = '" + val + "';" + 21185 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21186 browser.executeScript(scr); 21187 } 21188 21189 it('should initialize to model', function() { 21190 expect(value.getText()).toContain('14:57:00'); 21191 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21192 }); 21193 21194 it('should be invalid if empty', function() { 21195 setInput(''); 21196 expect(value.getText()).toEqual('value ='); 21197 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21198 }); 21199 21200 it('should be invalid if over max', function() { 21201 setInput('23:59:00'); 21202 expect(value.getText()).toContain(''); 21203 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21204 }); 21205 </file> 21206 </example> 21207 */ 21208 'time': createDateInputType('time', TIME_REGEXP, 21209 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 21210 'HH:mm:ss.sss'), 21211 21212 /** 21213 * @ngdoc input 21214 * @name input[week] 21215 * 21216 * @description 21217 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 21218 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21219 * week format (yyyy-W##), for example: `2013-W02`. 21220 * 21221 * The model must always be a Date object, otherwise Angular will throw an error. 21222 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21223 * 21224 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21225 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21226 * 21227 * @param {string} ngModel Assignable angular expression to data-bind to. 21228 * @param {string=} name Property name of the form under which the control is published. 21229 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21230 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 21231 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 21232 * native HTML5 constraint validation. 21233 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21234 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 21235 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 21236 * native HTML5 constraint validation. 21237 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 21238 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21239 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 21240 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21241 * @param {string=} required Sets `required` validation error key if the value is not entered. 21242 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21243 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21244 * `required` when you want to data-bind to the `required` attribute. 21245 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21246 * interaction with the input element. 21247 * 21248 * @example 21249 <example name="week-input-directive" module="weekExample"> 21250 <file name="index.html"> 21251 <script> 21252 angular.module('weekExample', []) 21253 .controller('DateController', ['$scope', function($scope) { 21254 $scope.example = { 21255 value: new Date(2013, 0, 3) 21256 }; 21257 }]); 21258 </script> 21259 <form name="myForm" ng-controller="DateController as dateCtrl"> 21260 <label>Pick a date between in 2013: 21261 <input id="exampleInput" type="week" name="input" ng-model="example.value" 21262 placeholder="YYYY-W##" min="2012-W32" 21263 max="2013-W52" required /> 21264 </label> 21265 <div role="alert">
21266 <span class="error" ng-show="myForm.input.$error.required"> 21267 Required!</span> 21268 <span class="error" ng-show="myForm.input.$error.week"> 21269 Not a valid date!</span> 21270 </div> 21271 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 21272 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21273 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21274 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21275 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21276 </form> 21277 </file> 21278 <file name="protractor.js" type="protractor"> 21279 var value = element(by.binding('example.value | date: "yyyy-Www"')); 21280 var valid = element(by.binding('myForm.input.$valid')); 21281 var input = element(by.model('example.value')); 21282 21283 // currently protractor/webdriver does not support 21284 // sending keys to all known HTML5 input controls 21285 // for various browsers (https://github.com/angular/protractor/issues/562). 21286 function setInput(val) { 21287 // set the value of the element and force validation. 21288 var scr = "var ipt = document.getElementById('exampleInput'); " + 21289 "ipt.value = '" + val + "';" + 21290 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21291 browser.executeScript(scr); 21292 } 21293 21294 it('should initialize to model', function() { 21295 expect(value.getText()).toContain('2013-W01'); 21296 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21297 }); 21298 21299 it('should be invalid if empty', function() { 21300 setInput(''); 21301 expect(value.getText()).toEqual('value ='); 21302 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21303 }); 21304 21305 it('should be invalid if over max', function() { 21306 setInput('2015-W01'); 21307 expect(value.getText()).toContain(''); 21308 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21309 }); 21310 </file> 21311 </example> 21312 */ 21313 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 21314 21315 /** 21316 * @ngdoc input 21317 * @name input[month] 21318 * 21319 * @description 21320 * Input with month validation and transformation. In browsers that do not yet support 21321 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21322 * month format (yyyy-MM), for example: `2009-01`. 21323 * 21324 * The model must always be a Date object, otherwise Angular will throw an error. 21325 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21326 * If the model is not set to the first of the month, the next view to model update will set it 21327 * to the first of the month. 21328 * 21329 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21330 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21331 * 21332 * @param {string} ngModel Assignable angular expression to data-bind to. 21333 * @param {string=} name Property name of the form under which the control is published. 21334 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21335 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 21336 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 21337 * native HTML5 constraint validation. 21338 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21339 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 21340 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 21341 * native HTML5 constraint validation. 21342 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 21343 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21344 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 21345 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21346 21347 * @param {string=} required Sets `required` validation error key if the value is not entered. 21348 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21349 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21350 * `required` when you want to data-bind to the `required` attribute. 21351 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21352 * interaction with the input element. 21353 * 21354 * @example 21355 <example name="month-input-directive" module="monthExample"> 21356 <file name="index.html"> 21357 <script> 21358 angular.module('monthExample', []) 21359 .controller('DateController', ['$scope', function($scope) { 21360 $scope.example = { 21361 value: new Date(2013, 9, 1) 21362 }; 21363 }]); 21364 </script> 21365 <form name="myForm" ng-controller="DateController as dateCtrl"> 21366 <label for="exampleInput">Pick a month in 2013:</label> 21367 <input id="exampleInput" type="month" name="input" ng-model="example.value" 21368 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 21369 <div role="alert">
21370 <span class="error" ng-show="myForm.input.$error.required"> 21371 Required!</span> 21372 <span class="error" ng-show="myForm.input.$error.month"> 21373 Not a valid month!</span> 21374 </div> 21375 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 21376 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21377 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21378 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21379 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21380 </form> 21381 </file> 21382 <file name="protractor.js" type="protractor"> 21383 var value = element(by.binding('example.value | date: "yyyy-MM"')); 21384 var valid = element(by.binding('myForm.input.$valid')); 21385 var input = element(by.model('example.value')); 21386 21387 // currently protractor/webdriver does not support 21388 // sending keys to all known HTML5 input controls 21389 // for various browsers (https://github.com/angular/protractor/issues/562). 21390 function setInput(val) { 21391 // set the value of the element and force validation. 21392 var scr = "var ipt = document.getElementById('exampleInput'); " + 21393 "ipt.value = '" + val + "';" + 21394 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21395 browser.executeScript(scr); 21396 } 21397 21398 it('should initialize to model', function() { 21399 expect(value.getText()).toContain('2013-10'); 21400 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21401 }); 21402 21403 it('should be invalid if empty', function() { 21404 setInput(''); 21405 expect(value.getText()).toEqual('value ='); 21406 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21407 }); 21408 21409 it('should be invalid if over max', function() { 21410 setInput('2015-01'); 21411 expect(value.getText()).toContain(''); 21412 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21413 }); 21414 </file> 21415 </example> 21416 */ 21417 'month': createDateInputType('month', MONTH_REGEXP, 21418 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 21419 'yyyy-MM'), 21420 21421 /** 21422 * @ngdoc input 21423 * @name input[number] 21424 * 21425 * @description 21426 * Text input with number validation and transformation. Sets the `number` validation 21427 * error if not a valid number. 21428 * 21429 * <div class="alert alert-warning"> 21430 * The model must always be of type `number` otherwise Angular will throw an error. 21431 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 21432 * error docs for more information and an example of how to convert your model if necessary. 21433 * </div> 21434 * 21435 * ## Issues with HTML5 constraint validation 21436 * 21437 * In browsers that follow the 21438 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 21439 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 21440 * If a non-number is entered in the input, the browser will report the value as an empty string, 21441 * which means the view / model values in `ngModel` and subsequently the scope value 21442 * will also be an empty string. 21443 * 21444 * 21445 * @param {string} ngModel Assignable angular expression to data-bind to. 21446 * @param {string=} name Property name of the form under which the control is published. 21447 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21448 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21449 * @param {string=} required Sets `required` validation error key if the value is not entered. 21450 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21451 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21452 * `required` when you want to data-bind to the `required` attribute. 21453 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21454 * minlength. 21455 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21456 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21457 * any length. 21458 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21459 * that contains the regular expression body that will be converted to a regular expression 21460 * as in the ngPattern directive. 21461 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21462 * a RegExp found by evaluating the Angular expression given in the attribute value. 21463 * If the expression evaluates to a RegExp object, then this is used directly. 21464 * If the expression evaluates to a string, then it will be converted to a RegExp 21465 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21466 * `new RegExp('^abc$')`.<br /> 21467 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21468 * start at the index of the last search's match, thus not taking the whole input value into 21469 * account. 21470 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21471 * interaction with the input element. 21472 * 21473 * @example 21474 <example name="number-input-directive" module="numberExample"> 21475 <file name="index.html"> 21476 <script> 21477 angular.module('numberExample', [])
21478 .controller('ExampleController', ['$scope', function($scope) { 21479 $scope.example = { 21480 value: 12 21481 }; 21482 }]); 21483 </script> 21484 <form name="myForm" ng-controller="ExampleController"> 21485 <label>Number: 21486 <input type="number" name="input" ng-model="example.value" 21487 min="0" max="99" required> 21488 </label> 21489 <div role="alert"> 21490 <span class="error" ng-show="myForm.input.$error.required"> 21491 Required!</span> 21492 <span class="error" ng-show="myForm.input.$error.number"> 21493 Not valid number!</span> 21494 </div> 21495 <tt>value = {{example.value}}</tt><br/> 21496 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21497 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21498 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21499 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21500 </form> 21501 </file> 21502 <file name="protractor.js" type="protractor"> 21503 var value = element(by.binding('example.value')); 21504 var valid = element(by.binding('myForm.input.$valid')); 21505 var input = element(by.model('example.value')); 21506 21507 it('should initialize to model', function() { 21508 expect(value.getText()).toContain('12'); 21509 expect(valid.getText()).toContain('true'); 21510 }); 21511 21512 it('should be invalid if empty', function() { 21513 input.clear(); 21514 input.sendKeys(''); 21515 expect(value.getText()).toEqual('value ='); 21516 expect(valid.getText()).toContain('false'); 21517 }); 21518 21519 it('should be invalid if over max', function() { 21520 input.clear(); 21521 input.sendKeys('123'); 21522 expect(value.getText()).toEqual('value ='); 21523 expect(valid.getText()).toContain('false'); 21524 }); 21525 </file> 21526 </example> 21527 */ 21528 'number': numberInputType, 21529 21530 21531 /** 21532 * @ngdoc input 21533 * @name input[url] 21534 * 21535 * @description 21536 * Text input with URL validation. Sets the `url` validation error key if the content is not a 21537 * valid URL. 21538 * 21539 * <div class="alert alert-warning"> 21540 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 21541 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 21542 * the built-in validators (see the {@link guide/forms Forms guide}) 21543 * </div> 21544 * 21545 * @param {string} ngModel Assignable angular expression to data-bind to. 21546 * @param {string=} name Property name of the form under which the control is published. 21547 * @param {string=} required Sets `required` validation error key if the value is not entered. 21548 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21549 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21550 * `required` when you want to data-bind to the `required` attribute. 21551 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21552 * minlength. 21553 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21554 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21555 * any length. 21556 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21557 * that contains the regular expression body that will be converted to a regular expression 21558 * as in the ngPattern directive. 21559 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21560 * a RegExp found by evaluating the Angular expression given in the attribute value. 21561 * If the expression evaluates to a RegExp object, then this is used directly. 21562 * If the expression evaluates to a string, then it will be converted to a RegExp 21563 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21564 * `new RegExp('^abc$')`.<br /> 21565 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21566 * start at the index of the last search's match, thus not taking the whole input value into 21567 * account. 21568 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21569 * interaction with the input element. 21570 * 21571 * @example 21572 <example name="url-input-directive" module="urlExample"> 21573 <file name="index.html"> 21574 <script> 21575 angular.module('urlExample', [])
21576 .controller('ExampleController', ['$scope', function($scope) { 21577 $scope.url = { 21578 text: 'http://google.com' 21579 }; 21580 }]); 21581 </script> 21582 <form name="myForm" ng-controller="ExampleController"> 21583 <label>URL: 21584 <input type="url" name="input" ng-model="url.text" required> 21585 <label> 21586 <div role="alert"> 21587 <span class="error" ng-show="myForm.input.$error.required"> 21588 Required!</span> 21589 <span class="error" ng-show="myForm.input.$error.url"> 21590 Not valid url!</span> 21591 </div> 21592 <tt>text = {{url.text}}</tt><br/> 21593 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21594 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21595 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21596 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21597 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 21598 </form> 21599 </file> 21600 <file name="protractor.js" type="protractor"> 21601 var text = element(by.binding('url.text')); 21602 var valid = element(by.binding('myForm.input.$valid')); 21603 var input = element(by.model('url.text')); 21604 21605 it('should initialize to model', function() { 21606 expect(text.getText()).toContain('http://google.com'); 21607 expect(valid.getText()).toContain('true'); 21608 }); 21609 21610 it('should be invalid if empty', function() { 21611 input.clear(); 21612 input.sendKeys(''); 21613 21614 expect(text.getText()).toEqual('text ='); 21615 expect(valid.getText()).toContain('false'); 21616 }); 21617 21618 it('should be invalid if not url', function() { 21619 input.clear(); 21620 input.sendKeys('box'); 21621 21622 expect(valid.getText()).toContain('false'); 21623 }); 21624 </file> 21625 </example> 21626 */ 21627 'url': urlInputType, 21628 21629 21630 /** 21631 * @ngdoc input 21632 * @name input[email] 21633 * 21634 * @description 21635 * Text input with email validation. Sets the `email` validation error key if not a valid email 21636 * address. 21637 * 21638 * <div class="alert alert-warning"> 21639 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 21640 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 21641 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 21642 * </div> 21643 * 21644 * @param {string} ngModel Assignable angular expression to data-bind to. 21645 * @param {string=} name Property name of the form under which the control is published. 21646 * @param {string=} required Sets `required` validation error key if the value is not entered. 21647 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21648 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21649 * `required` when you want to data-bind to the `required` attribute. 21650 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21651 * minlength. 21652 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21653 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21654 * any length. 21655 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21656 * that contains the regular expression body that will be converted to a regular expression 21657 * as in the ngPattern directive. 21658 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21659 * a RegExp found by evaluating the Angular expression given in the attribute value. 21660 * If the expression evaluates to a RegExp object, then this is used directly. 21661 * If the expression evaluates to a string, then it will be converted to a RegExp 21662 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21663 * `new RegExp('^abc$')`.<br /> 21664 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21665 * start at the index of the last search's match, thus not taking the whole input value into 21666 * account. 21667 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21668 * interaction with the input element. 21669 * 21670 * @example 21671 <example name="email-input-directive" module="emailExample"> 21672 <file name="index.html"> 21673 <script> 21674 angular.module('emailExample', [])
21675 .controller('ExampleController', ['$scope', function($scope) { 21676 $scope.email = { 21677 text: '[email protected]' 21678 }; 21679 }]); 21680 </script> 21681 <form name="myForm" ng-controller="ExampleController"> 21682 <label>Email: 21683 <input type="email" name="input" ng-model="email.text" required> 21684 </label> 21685 <div role="alert"> 21686 <span class="error" ng-show="myForm.input.$error.required"> 21687 Required!</span> 21688 <span class="error" ng-show="myForm.input.$error.email"> 21689 Not valid email!</span> 21690 </div> 21691 <tt>text = {{email.text}}</tt><br/> 21692 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21693 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21694 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21695 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21696 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 21697 </form> 21698 </file> 21699 <file name="protractor.js" type="protractor"> 21700 var text = element(by.binding('email.text')); 21701 var valid = element(by.binding('myForm.input.$valid')); 21702 var input = element(by.model('email.text')); 21703 21704 it('should initialize to model', function() { 21705 expect(text.getText()).toContain('[email protected]'); 21706 expect(valid.getText()).toContain('true'); 21707 }); 21708 21709 it('should be invalid if empty', function() { 21710 input.clear(); 21711 input.sendKeys(''); 21712 expect(text.getText()).toEqual('text ='); 21713 expect(valid.getText()).toContain('false'); 21714 }); 21715 21716 it('should be invalid if not email', function() { 21717 input.clear(); 21718 input.sendKeys('xxx'); 21719 21720 expect(valid.getText()).toContain('false'); 21721 }); 21722 </file> 21723 </example> 21724 */ 21725 'email': emailInputType, 21726 21727 21728 /** 21729 * @ngdoc input 21730 * @name input[radio] 21731 * 21732 * @description 21733 * HTML radio button. 21734 * 21735 * @param {string} ngModel Assignable angular expression to data-bind to. 21736 * @param {string} value The value to which the `ngModel` expression should be set when selected. 21737 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 21738 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 21739 * @param {string=} name Property name of the form under which the control is published. 21740 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21741 * interaction with the input element. 21742 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio 21743 * is selected. Should be used instead of the `value` attribute if you need 21744 * a non-string `ngModel` (`boolean`, `array`, ...). 21745 * 21746 * @example 21747 <example name="radio-input-directive" module="radioExample"> 21748 <file name="index.html"> 21749 <script> 21750 angular.module('radioExample', []) 21751 .controller('ExampleController', ['$scope', function($scope) { 21752 $scope.color = { 21753 name: 'blue' 21754 }; 21755 $scope.specialValue = { 21756 "id": "12345", 21757 "value": "green" 21758 }; 21759 }]); 21760 </script>
21761 <form name="myForm" ng-controller="ExampleController"> 21762 <label> 21763 <input type="radio" ng-model="color.name" value="red"> 21764 Red 21765 </label><br/> 21766 <label> 21767 <input type="radio" ng-model="color.name" ng-value="specialValue"> 21768 Green 21769 </label><br/> 21770 <label> 21771 <input type="radio" ng-model="color.name" value="blue"> 21772 Blue 21773 </label><br/> 21774 <tt>color = {{color.name | json}}</tt><br/> 21775 </form> 21776 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 21777 </file> 21778 <file name="protractor.js" type="protractor"> 21779 it('should change state', function() { 21780 var color = element(by.binding('color.name')); 21781 21782 expect(color.getText()).toContain('blue'); 21783 21784 element.all(by.model('color.name')).get(0).click(); 21785 21786 expect(color.getText()).toContain('red'); 21787 }); 21788 </file> 21789 </example> 21790 */ 21791 'radio': radioInputType, 21792 21793 21794 /** 21795 * @ngdoc input 21796 * @name input[checkbox] 21797 * 21798 * @description 21799 * HTML checkbox. 21800 * 21801 * @param {string} ngModel Assignable angular expression to data-bind to. 21802 * @param {string=} name Property name of the form under which the control is published. 21803 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 21804 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 21805 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21806 * interaction with the input element. 21807 * 21808 * @example 21809 <example name="checkbox-input-directive" module="checkboxExample"> 21810 <file name="index.html"> 21811 <script> 21812 angular.module('checkboxExample', []) 21813 .controller('ExampleController', ['$scope', function($scope) {
21814 $scope.checkboxModel = { 21815 value1 : true, 21816 value2 : 'YES' 21817 }; 21818 }]); 21819 </script> 21820 <form name="myForm" ng-controller="ExampleController"> 21821 <label>Value1: 21822 <input type="checkbox" ng-model="checkboxModel.value1"> 21823 </label><br/> 21824 <label>Value2: 21825 <input type="checkbox" ng-model="checkboxModel.value2" 21826 ng-true-value="'YES'" ng-false-value="'NO'"> 21827 </label><br/> 21828 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 21829 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 21830 </form> 21831 </file> 21832 <file name="protractor.js" type="protractor"> 21833 it('should change state', function() { 21834 var value1 = element(by.binding('checkboxModel.value1')); 21835 var value2 = element(by.binding('checkboxModel.value2')); 21836 21837 expect(value1.getText()).toContain('true'); 21838 expect(value2.getText()).toContain('YES'); 21839 21840 element(by.model('checkboxModel.value1')).click(); 21841 element(by.model('checkboxModel.value2')).click(); 21842 21843 expect(value1.getText()).toContain('false'); 21844 expect(value2.getText()).toContain('NO'); 21845 }); 21846 </file> 21847 </example> 21848 */ 21849 'checkbox': checkboxInputType, 21850 21851 'hidden': noop, 21852 'button': noop, 21853 'submit': noop, 21854 'reset': noop, 21855 'file': noop 21856}; 21857 21858function stringBasedInputType(ctrl) { 21859 ctrl.$formatters.push(function(value) { 21860 return ctrl.$isEmpty(value) ? value : value.toString(); 21861 }); 21862} 21863 21864function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21865 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21866 stringBasedInputType(ctrl); 21867} 21868 21869function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21870 var type = lowercase(element[0].type); 21871 21872 // In composition mode, users are still inputing intermediate text buffer, 21873 // hold the listener until composition is done. 21874 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 21875 if (!$sniffer.android) { 21876 var composing = false; 21877 21878 element.on('compositionstart', function(data) { 21879 composing = true; 21880 }); 21881 21882 element.on('compositionend', function() { 21883 composing = false; 21884 listener(); 21885 }); 21886 } 21887 21888 var listener = function(ev) { 21889 if (timeout) { 21890 $browser.defer.cancel(timeout); 21891 timeout = null; 21892 } 21893 if (composing) return; 21894 var value = element.val(), 21895 event = ev && ev.type; 21896 21897 // By default we will trim the value 21898 // If the attribute ng-trim exists we will avoid trimming 21899 // If input type is 'password', the value is never trimmed 21900 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 21901 value = trim(value); 21902 } 21903 21904 // If a control is suffering from bad input (due to native validators), browsers discard its 21905 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 21906 // control's value is the same empty value twice in a row. 21907 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 21908 ctrl.$setViewValue(value, event); 21909 } 21910 }; 21911 21912 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 21913 // input event on backspace, delete or cut 21914 if ($sniffer.hasEvent('input')) { 21915 element.on('input', listener); 21916 } else { 21917 var timeout; 21918 21919 var deferListener = function(ev, input, origValue) { 21920 if (!timeout) { 21921 timeout = $browser.defer(function() { 21922 timeout = null; 21923 if (!input || input.value !== origValue) { 21924 listener(ev); 21925 } 21926 }); 21927 } 21928 }; 21929 21930 element.on('keydown', function(event) { 21931 var key = event.keyCode; 21932 21933 // ignore 21934 // command modifiers arrows 21935 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 21936 21937 deferListener(event, this, this.value); 21938 }); 21939 21940 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 21941 if ($sniffer.hasEvent('paste')) { 21942 element.on('paste cut', deferListener); 21943 } 21944 } 21945 21946 // if user paste into input using mouse on older browser 21947 // or form autocomplete on newer browser, we need "change" event to catch it 21948 element.on('change', listener); 21949 21950 ctrl.$render = function() { 21951 // Workaround for Firefox validation #12102. 21952 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 21953 if (element.val() !== value) { 21954 element.val(value); 21955 } 21956 }; 21957} 21958 21959function weekParser(isoWeek, existingDate) { 21960 if (isDate(isoWeek)) { 21961 return isoWeek; 21962 } 21963 21964 if (isString(isoWeek)) { 21965 WEEK_REGEXP.lastIndex = 0; 21966 var parts = WEEK_REGEXP.exec(isoWeek); 21967 if (parts) { 21968 var year = +parts[1], 21969 week = +parts[2], 21970 hours = 0, 21971 minutes = 0, 21972 seconds = 0, 21973 milliseconds = 0, 21974 firstThurs = getFirstThursdayOfYear(year), 21975 addDays = (week - 1) * 7; 21976 21977 if (existingDate) { 21978 hours = existingDate.getHours(); 21979 minutes = existingDate.getMinutes(); 21980 seconds = existingDate.getSeconds(); 21981 milliseconds = existingDate.getMilliseconds(); 21982 } 21983 21984 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 21985 } 21986 } 21987 21988 return NaN; 21989} 21990 21991function createDateParser(regexp, mapping) { 21992 return function(iso, date) { 21993 var parts, map; 21994 21995 if (isDate(iso)) { 21996 return iso; 21997 } 21998 21999 if (isString(iso)) { 22000 // When a date is JSON'ified to wraps itself inside of an extra 22001 // set of double quotes. This makes the date parsing code unable 22002 // to match the date string and parse it as a date. 22003 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 22004 iso = iso.substring(1, iso.length - 1); 22005 }
22006 if (ISO_DATE_REGEXP.test(iso)) { 22007 return new Date(iso); 22008 } 22009 regexp.lastIndex = 0; 22010 parts = regexp.exec(iso); 22011 22012 if (parts) { 22013 parts.shift(); 22014 if (date) { 22015 map = { 22016 yyyy: date.getFullYear(), 22017 MM: date.getMonth() + 1, 22018 dd: date.getDate(), 22019 HH: date.getHours(), 22020 mm: date.getMinutes(), 22021 ss: date.getSeconds(), 22022 sss: date.getMilliseconds() / 1000 22023 }; 22024 } else { 22025 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 22026 } 22027 22028 forEach(parts, function(part, index) { 22029 if (index < mapping.length) { 22030 map[mapping[index]] = +part; 22031 } 22032 }); 22033 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 22034 } 22035 } 22036 22037 return NaN; 22038 }; 22039} 22040 22041function createDateInputType(type, regexp, parseDate, format) { 22042 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 22043 badInputChecker(scope, element, attr, ctrl); 22044 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22045 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 22046 var previousDate; 22047 22048 ctrl.$$parserName = type; 22049 ctrl.$parsers.push(function(value) { 22050 if (ctrl.$isEmpty(value)) return null; 22051 if (regexp.test(value)) { 22052 // Note: We cannot read ctrl.$modelValue, as there might be a different 22053 // parser/formatter in the processing chain so that the model 22054 // contains some different data format! 22055 var parsedDate = parseDate(value, previousDate); 22056 if (timezone) { 22057 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 22058 } 22059 return parsedDate; 22060 } 22061 return undefined; 22062 }); 22063 22064 ctrl.$formatters.push(function(value) { 22065 if (value && !isDate(value)) { 22066 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 22067 } 22068 if (isValidDate(value)) { 22069 previousDate = value; 22070 if (previousDate && timezone) { 22071 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 22072 } 22073 return $filter('date')(value, format, timezone); 22074 } else { 22075 previousDate = null; 22076 return ''; 22077 } 22078 }); 22079 22080 if (isDefined(attr.min) || attr.ngMin) { 22081 var minVal; 22082 ctrl.$validators.min = function(value) { 22083 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 22084 }; 22085 attr.$observe('min', function(val) { 22086 minVal = parseObservedDateValue(val); 22087 ctrl.$validate(); 22088 }); 22089 } 22090 22091 if (isDefined(attr.max) || attr.ngMax) { 22092 var maxVal; 22093 ctrl.$validators.max = function(value) { 22094 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 22095 }; 22096 attr.$observe('max', function(val) { 22097 maxVal = parseObservedDateValue(val); 22098 ctrl.$validate(); 22099 }); 22100 } 22101 22102 function isValidDate(value) { 22103 // Invalid Date: getTime() returns NaN 22104 return value && !(value.getTime && value.getTime() !== value.getTime()); 22105 } 22106 22107 function parseObservedDateValue(val) { 22108 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; 22109 } 22110 }; 22111} 22112 22113function badInputChecker(scope, element, attr, ctrl) { 22114 var node = element[0]; 22115 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 22116 if (nativeValidation) { 22117 ctrl.$parsers.push(function(value) { 22118 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 22119 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): 22120 // - also sets validity.badInput (should only be validity.typeMismatch). 22121 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) 22122 // - can ignore this case as we can still read out the erroneous email... 22123 return validity.badInput && !validity.typeMismatch ? undefined : value; 22124 }); 22125 } 22126} 22127 22128function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22129 badInputChecker(scope, element, attr, ctrl); 22130 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22131 22132 ctrl.$$parserName = 'number'; 22133 ctrl.$parsers.push(function(value) { 22134 if (ctrl.$isEmpty(value)) return null; 22135 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 22136 return undefined; 22137 }); 22138 22139 ctrl.$formatters.push(function(value) { 22140 if (!ctrl.$isEmpty(value)) { 22141 if (!isNumber(value)) { 22142 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 22143 } 22144 value = value.toString(); 22145 } 22146 return value; 22147 }); 22148 22149 if (isDefined(attr.min) || attr.ngMin) { 22150 var minVal; 22151 ctrl.$validators.min = function(value) { 22152 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 22153 }; 22154 22155 attr.$observe('min', function(val) { 22156 if (isDefined(val) && !isNumber(val)) { 22157 val = parseFloat(val, 10); 22158 } 22159 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 22160 // TODO(matsko): implement validateLater to reduce number of validations 22161 ctrl.$validate(); 22162 }); 22163 } 22164 22165 if (isDefined(attr.max) || attr.ngMax) { 22166 var maxVal; 22167 ctrl.$validators.max = function(value) { 22168 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 22169 }; 22170 22171 attr.$observe('max', function(val) { 22172 if (isDefined(val) && !isNumber(val)) { 22173 val = parseFloat(val, 10); 22174 } 22175 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 22176 // TODO(matsko): implement validateLater to reduce number of validations 22177 ctrl.$validate(); 22178 }); 22179 } 22180} 22181 22182function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22183 // Note: no badInputChecker here by purpose as `url` is only a validation 22184 // in browsers, i.e. we can always read out input.value even if it is not val
vendor: 6,862 bytes, lines 22184-22345
22184id! 22185 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22186 stringBasedInputType(ctrl); 22187 22188 ctrl.$$parserName = 'url'; 22189 ctrl.$validators.url = function(modelValue, viewValue) { 22190 var value = modelValue || viewValue; 22191 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 22192 }; 22193} 22194 22195function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 22196 // Note: no badInputChecker here by purpose as `url` is only a validation 22197 // in browsers, i.e. we can always read out input.value even if it is not valid! 22198 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 22199 stringBasedInputType(ctrl); 22200 22201 ctrl.$$parserName = 'email'; 22202 ctrl.$validators.email = function(modelValue, viewValue) { 22203 var value = modelValue || viewValue; 22204 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 22205 }; 22206} 22207 22208function radioInputType(scope, element, attr, ctrl) { 22209 // make the name unique, if not defined 22210 if (isUndefined(attr.name)) { 22211 element.attr('name', nextUid()); 22212 } 22213 22214 var listener = function(ev) { 22215 if (element[0].checked) { 22216 ctrl.$setViewValue(attr.value, ev && ev.type); 22217 } 22218 }; 22219 22220 element.on('click', listener); 22221 22222 ctrl.$render = function() { 22223 var value = attr.value; 22224 element[0].checked = (value == ctrl.$viewValue); 22225 }; 22226 22227 attr.$observe('value', ctrl.$render); 22228} 22229 22230function parseConstantExpr($parse, context, name, expression, fallback) { 22231 var parseFn; 22232 if (isDefined(expression)) { 22233 parseFn = $parse(expression); 22234 if (!parseFn.constant) { 22235 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 22236 '`{1}`.', name, expression); 22237 } 22238 return parseFn(context); 22239 } 22240 return fallback; 22241} 22242 22243function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 22244 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 22245 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 22246 22247 var listener = function(ev) { 22248 ctrl.$setViewValue(element[0].checked, ev && ev.type); 22249 }; 22250 22251 element.on('click', listener); 22252 22253 ctrl.$render = function() { 22254 element[0].checked = ctrl.$viewValue; 22255 }; 22256 22257 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 22258 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 22259 // it to a boolean. 22260 ctrl.$isEmpty = function(value) { 22261 return value === false; 22262 }; 22263 22264 ctrl.$formatters.push(function(value) { 22265 return equals(value, trueValue); 22266 }); 22267 22268 ctrl.$parsers.push(function(value) { 22269 return value ? trueValue : falseValue; 22270 }); 22271} 22272 22273 22274/** 22275 * @ngdoc directive 22276 * @name textarea 22277 * @restrict E 22278 * 22279 * @description 22280 * HTML textarea element control with angular data-binding. The data-binding and validation 22281 * properties of this element are exactly the same as those of the 22282 * {@link ng.directive:input input element}. 22283 * 22284 * @param {string} ngModel Assignable angular expression to data-bind to. 22285 * @param {string=} name Property name of the form under which the control is published. 22286 * @param {string=} required Sets `required` validation error key if the value is not entered. 22287 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22288 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22289 * `required` when you want to data-bind to the `required` attribute. 22290 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22291 * minlength. 22292 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22293 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 22294 * length. 22295 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 22296 * a RegExp found by evaluating the Angular expression given in the attribute value. 22297 * If the expression evaluates to a RegExp object, then this is used directly. 22298 * If the expression evaluates to a string, then it will be converted to a RegExp 22299 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22300 * `new RegExp('^abc$')`.<br /> 22301 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22302 * start at the index of the last search's match, thus not taking the whole input value into 22303 * account. 22304 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22305 * interaction with the input element. 22306 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 22307 */ 22308 22309 22310/** 22311 * @ngdoc directive 22312 * @name input 22313 * @restrict E 22314 * 22315 * @description 22316 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 22317 * input state control, and validation. 22318 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 22319 * 22320 * <div class="alert alert-warning"> 22321 * **Note:** Not every feature offered is available for all input types. 22322 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 22323 * </div> 22324 * 22325 * @param {string} ngModel Assignable angular expression to data-bind to. 22326 * @param {string=} name Property name of the form under which the control is published. 22327 * @param {string=} required Sets `required` validation error key if the value is not entered. 22328 * @param {boolean=} ngRequired Sets `required` attribute if set to true 22329 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22330 * minlength. 22331 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22332 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 22333 * length. 22334 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 22335 * a RegExp found by evaluating the Angular expression given in the attribute value. 22336 * If the expression evaluates to a RegExp object, then this is used directly. 22337 * If the expression evaluates to a string, then it will be converted to a RegExp 22338 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22339 * `new RegExp('^abc$')`.<br /> 22340 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22341 * start at the index of the last search's match, thus not taking the whole input value into 22342 * account. 22343 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22344 * interaction with the input element. 22345 * @param {boolean=} [ngTrim=true] If set to false Angular
vendor: 4,842 bytes, lines 22345-22463
22345 will not automatically trim the input. 22346 * This parameter is ignored for input[type=password] controls, which will never trim the 22347 * input. 22348 * 22349 * @example 22350 <example name="input-directive" module="inputExample"> 22351 <file name="index.html"> 22352 <script> 22353 angular.module('inputExample', []) 22354 .controller('ExampleController', ['$scope', function($scope) { 22355 $scope.user = {name: 'guest', last: 'visitor'}; 22356 }]); 22357 </script> 22358 <div ng-controller="ExampleController"> 22359 <form name="myForm"> 22360 <label> 22361 User name: 22362 <input type="text" name="userName" ng-model="user.name" required> 22363 </label> 22364 <div role="alert"> 22365 <span class="error" ng-show="myForm.userName.$error.required"> 22366 Required!</span> 22367 </div> 22368 <label> 22369 Last name: 22370 <input type="text" name="lastName" ng-model="user.last" 22371 ng-minlength="3" ng-maxlength="10"> 22372 </label> 22373 <div role="alert"> 22374 <span class="error" ng-show="myForm.lastName.$error.minlength"> 22375 Too short!</span> 22376 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 22377 Too long!</span> 22378 </div> 22379 </form> 22380 <hr> 22381 <tt>user = {{user}}</tt><br/> 22382 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 22383 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 22384 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 22385 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 22386 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22387 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22388 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 22389 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 22390 </div> 22391 </file> 22392 <file name="protractor.js" type="protractor"> 22393 var user = element(by.exactBinding('user')); 22394 var userNameValid = element(by.binding('myForm.userName.$valid')); 22395 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 22396 var lastNameError = element(by.binding('myForm.lastName.$error')); 22397 var formValid = element(by.binding('myForm.$valid')); 22398 var userNameInput = element(by.model('user.name')); 22399 var userLastInput = element(by.model('user.last')); 22400 22401 it('should initialize to model', function() { 22402 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 22403 expect(userNameValid.getText()).toContain('true'); 22404 expect(formValid.getText()).toContain('true'); 22405 }); 22406 22407 it('should be invalid if empty when required', function() { 22408 userNameInput.clear(); 22409 userNameInput.sendKeys(''); 22410 22411 expect(user.getText()).toContain('{"last":"visitor"}'); 22412 expect(userNameValid.getText()).toContain('false'); 22413 expect(formValid.getText()).toContain('false'); 22414 }); 22415 22416 it('should be valid if empty when min length is set', function() { 22417 userLastInput.clear(); 22418 userLastInput.sendKeys(''); 22419 22420 expect(user.getText()).toContain('{"name":"guest","last":""}'); 22421 expect(lastNameValid.getText()).toContain('true'); 22422 expect(formValid.getText()).toContain('true'); 22423 }); 22424 22425 it('should be invalid if less than required min length', function() { 22426 userLastInput.clear(); 22427 userLastInput.sendKeys('xx'); 22428 22429 expect(user.getText()).toContain('{"name":"guest"}'); 22430 expect(lastNameValid.getText()).toContain('false'); 22431 expect(lastNameError.getText()).toContain('minlength'); 22432 expect(formValid.getText()).toContain('false'); 22433 }); 22434 22435 it('should be invalid if longer than max length', function() { 22436 userLastInput.clear(); 22437 userLastInput.sendKeys('some ridiculously long name'); 22438 22439 expect(user.getText()).toContain('{"name":"guest"}'); 22440 expect(lastNameValid.getText()).toContain('false'); 22441 expect(lastNameError.getText()).toContain('maxlength'); 22442 expect(formValid.getText()).toContain('false'); 22443 }); 22444 </file> 22445 </example> 22446 */ 22447var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 22448 function($browser, $sniffer, $filter, $parse) { 22449 return { 22450 restrict: 'E', 22451 require: ['?ngModel'], 22452 link: { 22453 pre: function(scope, element, attr, ctrls) { 22454 if (ctrls[0]) { 22455 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 22456 $browser, $filter, $parse); 22457 } 22458 } 22459 } 22460 }; 22461}]; 22462 22463
22464 22465var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 22466/** 22467 * @ngdoc directive 22468 * @name ngValue 22469 * 22470 * @description 22471 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 22472 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 22473 * the bound value. 22474 * 22475 * `ngValue` is useful when dynamically generating lists of radio buttons using 22476 * {@link ngRepeat `ngRepeat`}, as shown below. 22477 * 22478 * Likewise, `ngValue` can be used to generate `<option>` elements for 22479 * the {@link select `select`} element. In that case however, only strings are supported 22480 * for the `value `attribute, so the resulting `ngModel` will always be a string. 22481 * Support for `select` models with non-string values is available via `ngOptions`. 22482 * 22483 * @element input 22484 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 22485 * of the `input` element 22486 * 22487 * @example 22488 <example name="ngValue-directive" module="valueExample"> 22489 <file name="index.html"> 22490 <script> 22491 angular.module('valueExample', []) 22492 .controller('ExampleController', ['$scope', function($scope) { 22493 $scope.names = ['pizza', 'unicorns', 'robots']; 22494 $scope.my = { favorite: 'unicorns' }; 22495 }]); 22496 </script>
22497 <form ng-controller="ExampleController"> 22498 <h2>Which is your favorite?</h2> 22499 <label ng-repeat="name in names" for="{{name}}"> 22500 {{name}} 22501 <input type="radio" 22502 ng-model="my.favorite" 22503 ng-value="name" 22504 id="{{name}}" 22505 name="favorite"> 22506 </label> 22507 <div>You chose {{my.favorite}}</div> 22508 </form> 22509 </file> 22510 <file name="protractor.js" type="protractor"> 22511 var favorite = element(by.binding('my.favorite')); 22512 22513 it('should initialize to model', function() { 22514 expect(favorite.getText()).toContain('unicorns'); 22515 }); 22516 it('should bind the values to the inputs', function() { 22517 element.all(by.model('my.favorite')).get(0).click(); 22518 expect(favorite.getText()).toContain('pizza'); 22519 }); 22520 </file> 22521 </example> 22522 */ 22523var ngValueDirective = function() { 22524 return { 22525 restrict: 'A', 22526 priority: 100, 22527 compile: function(tpl, tplAttr) { 22528 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 22529 return function ngValueConstantLink(scope, elm, attr) { 22530 attr.$set('value', scope.$eval(attr.ngValue)); 22531 }; 22532 } else { 22533 return function ngValueLink(scope, elm, attr) { 22534 scope.$watch(attr.ngValue, function valueWatchAction(value) { 22535 attr.$set('value', value); 22536 }); 22537 }; 22538 } 22539 } 22540 }; 22541}; 22542 22543/** 22544 * @ngdoc directive 22545 * @name ngBind 22546 * @restrict AC 22547 * 22548 * @description 22549 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 22550 * with the value of a given expression, and to update the text content when the value of that 22551 * expression changes. 22552 * 22553 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 22554 * `{{ expression }}` which is similar but less verbose. 22555 * 22556 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 22557 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 22558 * element attribute, it makes the bindings invisible to the user while the page is loading. 22559 * 22560 * An alternative solution to this problem would be using the 22561 * {@link ng.directive:ngCloak ngCloak} directive. 22562 * 22563 * 22564 * @element ANY 22565 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 22566 * 22567 * @example 22568 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 22569 <example module="bindExample"> 22570 <file name="index.html"> 22571 <script> 22572 angular.module('bindExample', []) 22573 .controller('ExampleController', ['$scope', function($scope) { 22574 $scope.name = 'Whirled'; 22575 }]); 22576 </script>
22577 <div ng-controller="ExampleController"> 22578 <label>Enter name: <input type="text" ng-model="name"></label><br> 22579 Hello <span ng-bind="name"></span>! 22580 </div> 22581 </file> 22582 <file name="protractor.js" type="protractor"> 22583 it('should check ng-bind', function() { 22584 var nameInput = element(by.model('name')); 22585 22586 expect(element(by.binding('name')).getText()).toBe('Whirled'); 22587 nameInput.clear(); 22588 nameInput.sendKeys('world'); 22589 expect(element(by.binding('name')).getText()).toBe('world'); 22590 }); 22591 </file> 22592 </example> 22593 */ 22594var ngBindDirective = ['$compile', function($compile) { 22595 return { 22596 restrict: 'AC', 22597 compile: function ngBindCompile(templateElement) { 22598 $compile.$$addBindingClass(templateElement); 22599 return function ngBindLink(scope, element, attr) { 22600 $compile.$$addBindingInfo(element, attr.ngBind); 22601 element = element[0]; 22602 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 22603 element.textContent = isUndefined(value) ? '' : value; 22604 }); 22605 }; 22606 } 22607 }; 22608}]; 22609 22610 22611/** 22612 * @ngdoc directive 22613 * @name ngBindTemplate 22614 * 22615 * @description 22616 * The `ngBindTemplate` directive specifies that the element 22617 * text content should be replaced with the interpolation of the template 22618 * in the `ngBindTemplate` attribute. 22619 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 22620 * expressions. This directive is needed since some HTML elements 22621 * (such as TITLE and OPTION) cannot contain SPAN elements. 22622 * 22623 * @element ANY 22624 * @param {string} ngBindTemplate template of form 22625 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 22626 * 22627 * @example 22628 * Try it here: enter text in text box and watch the greeting change. 22629 <example module="bindExample"> 22630 <file name="index.html"> 22631 <script> 22632 angular.module('bindExample', []) 22633 .controller('ExampleController', ['$scope', function($scope) { 22634 $scope.salutation = 'Hello'; 22635 $scope.name = 'World'; 22636 }]); 22637 </script>
22638 <div ng-controller="ExampleController"> 22639 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 22640 <label>Name: <input type="text" ng-model="name"></label><br> 22641 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 22642 </div> 22643 </file> 22644 <file name="protractor.js" type="protractor"> 22645 it('should check ng-bind', function() { 22646 var salutationElem = element(by.binding('salutation')); 22647 var salutationInput = element(by.model('salutation')); 22648 var nameInput = element(by.model('name')); 22649 22650 expect(salutationElem.getText()).toBe('Hello World!'); 22651 22652 salutationInput.clear(); 22653 salutationInput.sendKeys('Greetings'); 22654 nameInput.clear(); 22655 nameInput.sendKeys('user'); 22656 22657 expect(salutationElem.getText()).toBe('Greetings user!'); 22658 }); 22659 </file> 22660 </example> 22661 */ 22662var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 22663 return { 22664 compile: function ngBindTemplateCompile(templateElement) { 22665 $compile.$$addBindingClass(templateElement); 22666 return function ngBindTemplateLink(scope, element, attr) { 22667 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 22668 $compile.$$addBindingInfo(element, interpolateFn.expressions); 22669 element = element[0]; 22670 attr.$observe('ngBindTemplate', function(value) { 22671 element.textContent = isUndefined(value) ? '' : value; 22672 }); 22673 }; 22674 } 22675 }; 22676}]; 22677 22678 22679/** 22680 * @ngdoc directive 22681 * @name ngBindHtml 22682 * 22683 * @description 22684 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 22685 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 22686 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 22687 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 22688 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 22689 * 22690 * You may also bypass sanitization for values you know are safe. To do so, bind to 22691 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 22692 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 22693 * 22694 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 22695 * will have an exception (instead of an exploit.) 22696 * 22697 * @element ANY 22698 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 22699 * 22700 * @example 22701 22702 <example module="bindHtmlExample" deps="angular-sanitize.js"> 22703 <file name="index.html">
22704 <div ng-controller="ExampleController"> 22705 <p ng-bind-html="myHTML"></p> 22706 </div> 22707 </file> 22708 22709 <file name="script.js"> 22710 angular.module('bindHtmlExample', ['ngSanitize']) 22711 .controller('ExampleController', ['$scope', function($scope) { 22712 $scope.myHTML = 22713 'I am an <code>HTML</code>string with ' + 22714 '<a href="#">links!</a> and other <em>stuff</em>'; 22715 }]); 22716 </file> 22717 22718 <file name="protractor.js" type="protractor"> 22719 it('should check ng-bind-html', function() { 22720 expect(element(by.binding('myHTML')).getText()).toBe( 22721 'I am an HTMLstring with links! and other stuff'); 22722 }); 22723 </file> 22724 </example> 22725 */ 22726var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 22727 return { 22728 restrict: 'A', 22729 compile: function ngBindHtmlCompile(tElement, tAttrs) { 22730 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 22731 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 22732 return (value || '').toString(); 22733 }); 22734 $compile.$$addBindingClass(tElement); 22735 22736 return function ngBindHtmlLink(scope, element, attr) { 22737 $compile.$$addBindingInfo(element, attr.ngBindHtml); 22738 22739 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 22740 // we re-evaluate the expr because we want a TrustedValueHolderType 22741 // for $sce, not a string 22742 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 22743 }); 22744 }; 22745 } 22746 }; 22747}]; 22748 22749/** 22750 * @ngdoc directive 22751 * @name ngChange 22752 * 22753 * @description 22754 * Evaluate the given expression when the user changes the input. 22755 * The expression is evaluated immediately, unlike the JavaScript onchange event 22756 * which only triggers at the end of a change (usually, when the user leaves the 22757 * form element or presses the return key). 22758 * 22759 * The `ngChange` expression is only evaluated when a change in the input value causes 22760 * a new value to be committed to the model. 22761 * 22762 * It will not be evaluated: 22763 * * if the value returned from the `$parsers` transformation pipeline has not changed 22764 * * if the input has continued to be invalid since the model will stay `null` 22765 * * if the model is changed programmatically and not by a change to the input value 22766 * 22767 * 22768 * Note, this directive requires `ngModel` to be present. 22769 * 22770 * @element input 22771 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 22772 * in input value. 22773 * 22774 * @example 22775 * <example name="ngChange-directive" module="changeExample"> 22776 * <file name="index.html"> 22777 * <script> 22778 * angular.module('changeExample', []) 22779 * .controller('ExampleController', ['$scope', function($scope) { 22780 * $scope.counter = 0; 22781 * $scope.change = function() { 22782 * $scope.counter++; 22783 * }; 22784 * }]); 22785 * </script>
22786 * <div ng-controller="ExampleController"> 22787 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 22788 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 22789 * <label for="ng-change-example2">Confirmed</label><br /> 22790 * <tt>debug = {{confirmed}}</tt><br/> 22791 * <tt>counter = {{counter}}</tt><br/> 22792 * </div> 22793 * </file> 22794 * <file name="protractor.js" type="protractor"> 22795 * var counter = element(by.binding('counter')); 22796 * var debug = element(by.binding('confirmed')); 22797 * 22798 * it('should evaluate the expression if changing from view', function() { 22799 * expect(counter.getText()).toContain('0'); 22800 * 22801 * element(by.id('ng-change-example1')).click(); 22802 * 22803 * expect(counter.getText()).toContain('1'); 22804 * expect(debug.getText()).toContain('true'); 22805 * }); 22806 * 22807 * it('should not evaluate the expression if changing from model', function() { 22808 * element(by.id('ng-change-example2')).click(); 22809 22810 * expect(counter.getText()).toContain('0'); 22811 * expect(debug.getText()).toContain('true'); 22812 * }); 22813 * </file> 22814 * </example> 22815 */ 22816var ngChangeDirective = valueFn({ 22817 restrict: 'A', 22818 require: 'ngModel', 22819 link: function(scope, element, attr, ctrl) { 22820 ctrl.$viewChangeListeners.push(function() { 22821 scope.$eval(attr.ngChange); 22822 }); 22823 } 22824}); 22825 22826function classDirective(name, selector) { 22827 name = 'ngClass' + name; 22828 return ['$animate', function($animate) { 22829 return { 22830 restrict: 'AC', 22831 link: function(scope, element, attr) { 22832 var oldVal; 22833 22834 scope.$watch(attr[name], ngClassWatchAction, true); 22835 22836 attr.$observe('class', function(value) { 22837 ngClassWatchAction(scope.$eval(attr[name])); 22838 }); 22839 22840 22841 if (name !== 'ngClass') { 22842 scope.$watch('$index', function($index, old$index) { 22843 // jshint bitwise: false 22844 var mod = $index & 1; 22845 if (mod !== (old$index & 1)) { 22846 var classes = arrayClasses(scope.$eval(attr[name])); 22847 mod === selector ? 22848 addClasses(classes) : 22849 removeClasses(classes); 22850 } 22851 }); 22852 } 22853 22854 function addClasses(classes) { 22855 var newClasses = digestClassCounts(classes, 1); 22856 attr.$addClass(newClasses); 22857 } 22858 22859 function removeClasses(classes) { 22860 var newClasses = digestClassCounts(classes, -1); 22861 attr.$removeClass(newClasses); 22862 } 22863 22864 function digestClassCounts(classes, count) { 22865 // Use createMap() to prevent class assumptions involving property 22866 // names in Object.prototype 22867 var classCounts = element.data('$classCounts') || createMap(); 22868 var classesToUpdate = []; 22869 forEach(classes, function(className) { 22870 if (count > 0 || classCounts[className]) { 22871 classCounts[className] = (classCounts[className] || 0) + count; 22872 if (classCounts[className] === +(count > 0)) { 22873 classesToUpdate.push(className); 22874 } 22875 } 22876 }); 22877 element.data('$classCounts', classCounts); 22878 return classesToUpdate.join(' '); 22879 } 22880 22881 function updateClasses(oldClasses, newClasses) { 22882 var toAdd = arrayDifference(newClasses, oldClasses); 22883 var toRemove = arrayDifference(oldClasses, newClasses); 22884 toAdd = digestClassCounts(toAdd, 1); 22885 toRemove = digestClassCounts(toRemove, -1); 22886 if (toAdd && toAdd.length) { 22887 $animate.addClass(element, toAdd); 22888 } 22889 if (toRemove && toRemove.length) { 22890 $animate.removeClass(element, toRemove); 22891 } 22892 } 22893 22894 function ngClassWatchAction(newVal) { 22895 if (selector === true || scope.$index % 2 === selector) { 22896 var newClasses = arrayClasses(newVal || []); 22897 if (!oldVal) { 22898 addClasses(newClasses); 22899 } else if (!equals(newVal,oldVal)) { 22900 var oldClasses = arrayClasses(oldVal);
22901 updateClasses(oldClasses, newClasses); 22902 } 22903 } 22904 oldVal = shallowCopy(newVal); 22905 } 22906 } 22907 }; 22908 22909 function arrayDifference(tokens1, tokens2) { 22910 var values = []; 22911 22912 outer: 22913 for (var i = 0; i < tokens1.length; i++) { 22914 var token = tokens1[i]; 22915 for (var j = 0; j < tokens2.length; j++) { 22916 if (token == tokens2[j]) continue outer; 22917 } 22918 values.push(token); 22919 } 22920 return values; 22921 } 22922 22923 function arrayClasses(classVal) { 22924 var classes = []; 22925 if (isArray(classVal)) { 22926 forEach(classVal, function(v) { 22927 classes = classes.concat(arrayClasses(v)); 22928 }); 22929 return classes; 22930 } else if (isString(classVal)) { 22931 return classVal.split(' '); 22932 } else if (isObject(classVal)) { 22933 forEach(classVal, function(v, k) { 22934 if (v) { 22935 classes = classes.concat(k.split(' ')); 22936 } 22937 }); 22938 return classes; 22939 } 22940 return classVal; 22941 } 22942 }]; 22943} 22944 22945/** 22946 * @ngdoc directive 22947 * @name ngClass 22948 * @restrict AC 22949 * 22950 * @description 22951 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 22952 * an expression that represents all classes to be added. 22953 * 22954 * The directive operates in three different ways, depending on which of three types the expression 22955 * evaluates to: 22956 * 22957 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 22958 * names. 22959 * 22960 * 2. If the expression evaluates to an object, then for each key-value pair of the 22961 * object with a truthy value the corresponding key is used as a class name. 22962 * 22963 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 22964 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 22965 * to give you more control over what CSS classes appear. See the code below for an example of this. 22966 * 22967 * 22968 * The directive won't add duplicate classes if a particular class was already set. 22969 * 22970 * When the expression changes, the previously added classes are removed and only then are the 22971 * new classes added. 22972 * 22973 * @animations 22974 * **add** - happens just before the class is applied to the elements 22975 * 22976 * **remove** - happens just before the class is removed from the element 22977 * 22978 * @element ANY 22979 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 22980 * of the evaluation can be a string representing space delimited class 22981 * names, an array, or a map of class names to boolean values. In the case of a map, the 22982 * names of the properties whose values are truthy will be added as css classes to the 22983 * element. 22984 * 22985 * @example Example that demonstrates basic bindings via ngClass directive. 22986 <example> 22987 <file name="index.html"> 22988 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 22989 <label> 22990 <input type="checkbox" ng-model="deleted"> 22991 deleted (apply "strike" class) 22992 </label><br> 22993 <label> 22994 <input type="checkbox" ng-model="important"> 22995 important (apply "bold" class) 22996 </label><br> 22997 <label> 22998 <input type="checkbox" ng-model="error"> 22999 error (apply "has-error" class) 23000 </label> 23001 <hr> 23002 <p ng-class="style">Using String Syntax</p> 23003 <input type="text" ng-model="style" 23004 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 23005 <hr> 23006 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 23007 <input ng-model="style1" 23008 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 23009 <input ng-model="style2" 23010 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 23011 <input ng-model="style3" 23012 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 23013 <hr> 23014 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 23015 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 23016 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 23017 </file> 23018 <file name="style.css"> 23019 .strike { 23020 text-decoration: line-through; 23021 } 23022 .bold { 23023 font-weight: bold; 23024 } 23025 .red { 23026 color: red; 23027 } 23028 .has-error { 23029 color: red; 23030 background-color: yellow; 23031 } 23032 .orange { 23033 color: orange; 23034 } 23035 </file> 23036 <file name="protractor.js" type="protractor"> 23037 var ps = element.all(by.css('p')); 23038 23039 it('should let you toggle the class', function() { 23040 23041 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 23042 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 23043
23044 element(by.model('important')).click(); 23045 expect(ps.first().getAttribute('class')).toMatch(/bold/); 23046 23047 element(by.model('error')).click(); 23048 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 23049 }); 23050 23051 it('should let you toggle string example', function() { 23052 expect(ps.get(1).getAttribute('class')).toBe(''); 23053 element(by.model('style')).clear(); 23054 element(by.model('style')).sendKeys('red'); 23055 expect(ps.get(1).getAttribute('class')).toBe('red'); 23056 }); 23057 23058 it('array example should have 3 classes', function() { 23059 expect(ps.get(2).getAttribute('class')).toBe(''); 23060 element(by.model('style1')).sendKeys('bold'); 23061 element(by.model('style2')).sendKeys('strike'); 23062 element(by.model('style3')).sendKeys('red'); 23063 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 23064 }); 23065 23066 it('array with map example should have 2 classes', function() { 23067 expect(ps.last().getAttribute('class')).toBe(''); 23068 element(by.model('style4')).sendKeys('bold'); 23069 element(by.model('warning')).click(); 23070 expect(ps.last().getAttribute('class')).toBe('bold orange'); 23071 }); 23072 </file> 23073 </example> 23074 23075 ## Animations 23076 23077 The example below demonstrates how to perform animations using ngClass. 23078 23079 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 23080 <file name="index.html"> 23081 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 23082 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 23083 <br> 23084 <span class="base-class" ng-class="myVar">Sample Text</span> 23085 </file> 23086 <file name="style.css"> 23087 .base-class { 23088 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23089 } 23090 23091 .base-class.my-class { 23092 color: red; 23093 font-size:3em; 23094 } 23095 </file> 23096 <file name="protractor.js" type="protractor"> 23097 it('should check ng-class', function() { 23098 expect(element(by.css('.base-class')).getAttribute('class')).not. 23099 toMatch(/my-class/); 23100 23101 element(by.id('setbtn')).click(); 23102 23103 expect(element(by.css('.base-class')).getAttribute('class')). 23104 toMatch(/my-class/); 23105 23106 element(by.id('clearbtn')).click(); 23107 23108 expect(element(by.css('.base-class')).getAttribute('class')).not. 23109 toMatch(/my-class/); 23110 }); 23111 </file> 23112 </example> 23113 23114 23115 ## ngClass and pre-existing CSS3 Transitions/Animations 23116 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 23117 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 23118 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 23119 to view the step by step details of {@link $animate#addClass $animate.addClass} and 23120 {@link $animate#removeClass $animate.removeClass}. 23121 */ 23122var ngClassDirective = classDirective('', true); 23123 23124/** 23125 * @ngdoc directive 23126 * @name ngClassOdd 23127 * @restrict AC 23128 * 23129 * @description 23130 * The `ngClassOdd` and `ngClassEven` directives work exactly as 23131 * {@link ng.directive:ngClass ngClass}, except they work in 23132 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 23133 * 23134 * This directive can be applied only within the scope of an 23135 * {@link ng.directive:ngRepeat ngRepeat}. 23136 * 23137 * @element ANY 23138 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 23139 * of the evaluation can be a string representing space delimited class names or an array. 23140 * 23141 * @example 23142 <example> 23143 <file name="index.html"> 23144 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 23145 <li ng-repeat="name in names"> 23146 <span ng-class-odd="'odd'" ng-class-even="'even'"> 23147 {{name}} 23148 </span> 23149 </li> 23150 </ol> 23151 </file> 23152 <file name="style.css"> 23153 .odd { 23154 color: red; 23155 } 23156 .even { 23157 color: blue; 23158 } 23159 </file> 23160 <file name="protractor.js" type="protractor"> 23161 it('should check ng-class-odd and ng-class-even', function() { 23162 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 23163 toMatch(/odd/); 23164 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 23165 toMatch(/even/); 23166 }); 23167 </file> 23168 </example> 23169 */ 23170var ngClassOddDirective = classDirective('Odd', 0); 23171 23172/** 23173 * @ngdoc directive 23174 * @name ngClassEven 23175 * @restrict AC 23176 * 23177 * @description 23178 * The `ngClassOdd` and `ngClassEven` directives work exactly as 23179 * {@link ng.directive:ngClass ngClass}, except they work in 23180 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 23181 * 23182 * This directive can be applied only within the scope of an 23183 * {@link ng.directive:ngRepeat ngRepeat}. 23184 * 23185 * @element ANY 23186 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 23187 * result of the evaluation can be a string representing space delimited class names or an array.
23188 * 23189 * @example 23190 <example> 23191 <file name="index.html"> 23192 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 23193 <li ng-repeat="name in names"> 23194 <span ng-class-odd="'odd'" ng-class-even="'even'"> 23195 {{name}} 23196 </span> 23197 </li> 23198 </ol> 23199 </file> 23200 <file name="style.css"> 23201 .odd { 23202 color: red; 23203 } 23204 .even { 23205 color: blue; 23206 } 23207 </file> 23208 <file name="protractor.js" type="protractor"> 23209 it('should check ng-class-odd and ng-class-even', function() { 23210 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 23211 toMatch(/odd/); 23212 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 23213 toMatch(/even/); 23214 }); 23215 </file> 23216 </example> 23217 */ 23218var ngClassEvenDirective = classDirective('Even', 1); 23219 23220/** 23221 * @ngdoc directive 23222 * @name ngCloak 23223 * @restrict AC 23224 * 23225 * @description 23226 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 23227 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 23228 * directive to avoid the undesirable flicker effect caused by the html template display. 23229 * 23230 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 23231 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 23232 * of the browser view. 23233 * 23234 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 23235 * `angular.min.js`. 23236 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 23237 * 23238 * ```css 23239 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 23240 * display: none !important; 23241 * } 23242 * ``` 23243 * 23244 * When this css rule is loaded by the browser, all html elements (including their children) that 23245 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 23246 * during the compilation of the template it deletes the `ngCloak` element attribute, making 23247 * the compiled element visible. 23248 * 23249 * For the best result, the `angular.js` script must be loaded in the head section of the html 23250 * document; alternatively, the css rule above must be included in the external stylesheet of the 23251 * application. 23252 * 23253 * @element ANY 23254 * 23255 * @example 23256 <example> 23257 <file name="index.html"> 23258 <div id="template1" ng-cloak>{{ 'hello' }}</div> 23259 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 23260 </file> 23261 <file name="protractor.js" type="protractor"> 23262 it('should remove the template directive and css class', function() { 23263 expect($('#template1').getAttribute('ng-cloak')). 23264 toBeNull(); 23265 expect($('#template2').getAttribute('ng-cloak')). 23266 toBeNull(); 23267 }); 23268 </file> 23269 </example> 23270 * 23271 */ 23272var ngCloakDirective = ngDirective({ 23273 compile: function(element, attr) { 23274 attr.$set('ngCloak', undefined); 23275 element.removeClass('ng-cloak'); 23276 } 23277}); 23278 23279/** 23280 * @ngdoc directive 23281 * @name ngController 23282 * 23283 * @description 23284 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 23285 * supports the principles behind the Model-View-Controller design pattern. 23286 * 23287 * MVC components in angular: 23288 * 23289 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 23290 * are accessed through bindings. 23291 * * View â The template (HTML with data bindings) that is rendered into the View. 23292 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 23293 * logic behind the application to decorate the scope with functions and values 23294 * 23295 * Note that you can also attach controllers to the DOM by declaring it in a route definition 23296 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 23297 * again using `ng-controller` in the template itself. This will cause the controller to be attached 23298 * and executed twice. 23299 * 23300 * @element ANY 23301 * @scope 23302 * @priority 500 23303 * @param {expression} ngController Name of a constructor function registered with the current 23304 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 23305 * that on the current scope evaluates to a constructor function. 23306 * 23307 * The controller instance can be published into a scope property by specifying 23308 * `ng-controller="as propertyName"`. 23309 * 23310 * If the current `$controllerProvider` is configured to use globals (via 23311 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 23312 * also be the name of a globally accessible constructor function (not recommended). 23313 * 23314 * @example 23315 * Here is a simple form for editing user contact information. Adding, remov
23315ing, clearing, and 23316 * greeting are methods declared on the controller (see source tab). These methods can 23317 * easily be called from the angular markup. Any changes to the data are automatically reflected 23318 * in the View without the need for a manual update. 23319 * 23320 * Two different declaration styles are included below: 23321 * 23322 * * one binds methods and properties directly onto the controller using `this`: 23323 * `ng-controller="SettingsController1 as settings"` 23324 * * one injects `$scope` into the controller: 23325 * `ng-controller="SettingsController2"` 23326 * 23327 * The second option is more common in the Angular community, and is generally used in boilerplates 23328 * and in this guide. However, there are advantages to binding properties directly to the controller 23329 * and avoiding scope. 23330 * 23331 * * Using `controller as` makes it obvious which controller you are accessing in the template when 23332 * multiple controllers apply to an element. 23333 * * If you are writing your controllers as classes you have easier access to the properties and 23334 * methods, which will appear on the scope, from inside the controller code. 23335 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 23336 * inheritance masking primitives. 23337 * 23338 * This example demonstrates the `controller as` syntax. 23339 * 23340 * <example name="ngControllerAs" module="controllerAsExample"> 23341 * <file name="index.html"> 23342 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 23343 * <label>Name: <input type="text" ng-model="settings.name"/></label> 23344 * <button ng-click="settings.greet()">greet</button><br/> 23345 * Contact: 23346 * <ul> 23347 * <li ng-repeat="contact in settings.contacts"> 23348 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 23349 * <option>phone</option> 23350 * <option>email</option> 23351 * </select> 23352 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 23353 * <button ng-click="settings.clearContact(contact)">clear</button> 23354 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 23355 * </li> 23356 * <li><button ng-click="settings.addContact()">add</button></li> 23357 * </ul> 23358 * </div> 23359 * </file> 23360 * <file name="app.js"> 23361 * angular.module('controllerAsExample', []) 23362 * .controller('SettingsController1', SettingsController1); 23363 * 23364 * function SettingsController1() { 23365 * this.name = "John Smith"; 23366 * this.contacts = [ 23367 * {type: 'phone', value: '408 555 1212'}, 23368 * {type: 'email', value: '[email protected]'} ]; 23369 * } 23370 * 23371 * SettingsController1.prototype.greet = function() { 23372 * alert(this.name); 23373 * }; 23374 * 23375 * SettingsController1.prototype.addContact = function() { 23376 * this.contacts.push({type: 'email', value: '[email protected]'}); 23377 * }; 23378 * 23379 * SettingsController1.prototype.removeContact = function(contactToRemove) { 23380 * var index = this.contacts.indexOf(contactToRemove); 23381 * this.contacts.splice(index, 1); 23382 * }; 23383 * 23384 * SettingsController1.prototype.clearContact = function(contact) { 23385 * contact.type = 'phone'; 23386 * contact.value = ''; 23387 * }; 23388 * </file> 23389 * <file name="protractor.js" type="protractor"> 23390 * it('should check controller as', function() { 23391 * var container = element(by.id('ctrl-as-exmpl')); 23392 * expect(container.element(by.model('settings.name')) 23393 * .getAttribute('value')).toBe('John Smith'); 23394 * 23395 * var firstRepeat = 23396 * container.element(by.repeater('contact in settings.contacts').row(0)); 23397 * var secondRepeat = 23398 * container.element(by.repeater('contact in settings.contacts').row(1)); 23399 * 23400 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23401 * .toBe('408 555 1212'); 23402 * 23403 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 23404 * .toBe('[email protected]'); 23405 * 23406 * firstRepeat.element(by.buttonText('clear')).click(); 23407 * 23408 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23409 * .toBe(''); 23410 * 23411 * container.element(by.buttonText('add')).click(); 23412 * 23413 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 23414 * .element(by.model('contact.value')) 23415 * .getAttribute('value')) 23416 * .toBe('[email protected]'); 23417 * }); 23418 * </file> 23419 * </example> 23420 * 23421 * This example demonstrates the "attach to `$scope`" style of controller. 23422 * 23423 * <example name="ngController" module="controllerExample"> 23424 * <file name="index.html"> 23425 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 23426 * <label>Name: <input type="text" ng-model="name"/></label> 23427 * <button ng-click="greet()">greet</button><br/> 23428 * Contact: 23429 * <ul> 23430 * <li ng-repeat="contact in contacts"> 23431 * <select ng-model="contact.type" id="select_{{$index}}"> 23432 * <option>phone</option> 23433 * <option>email</option> 23434 * </select> 23435 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 23436 * <button ng-click="clearContact(contact)">clear</button> 23437 * <button ng-click="removeContact(contact)">X</button> 23438 * </li> 23439 * <li>[ <button ng-click="addContact()">add</button> ]</li> 23440 * </ul> 23441 * </div> 23442 * </file> 23443 * <file name="app.js">
23444 * angular.module('controllerExample', []) 23445 * .controller('SettingsController2', ['$scope', SettingsController2]); 23446 * 23447 * function SettingsController2($scope) { 23448 * $scope.name = "John Smith"; 23449 * $scope.contacts = [ 23450 * {type:'phone', value:'408 555 1212'}, 23451 * {type:'email', value:'[email protected]'} ]; 23452 * 23453 * $scope.greet = function() { 23454 * alert($scope.name); 23455 * }; 23456 * 23457 * $scope.addContact = function() { 23458 * $scope.contacts.push({type:'email', value:'[email protected]'}); 23459 * }; 23460 * 23461 * $scope.removeContact = function(contactToRemove) { 23462 * var index = $scope.contacts.indexOf(contactToRemove); 23463 * $scope.contacts.splice(index, 1); 23464 * }; 23465 * 23466 * $scope.clearContact = function(contact) { 23467 * contact.type = 'phone'; 23468 * contact.value = ''; 23469 * }; 23470 * } 23471 * </file> 23472 * <file name="protractor.js" type="protractor"> 23473 * it('should check controller', function() { 23474 * var container = element(by.id('ctrl-exmpl')); 23475 * 23476 * expect(container.element(by.model('name')) 23477 * .getAttribute('value')).toBe('John Smith'); 23478 * 23479 * var firstRepeat = 23480 * container.element(by.repeater('contact in contacts').row(0)); 23481 * var secondRepeat = 23482 * container.element(by.repeater('contact in contacts').row(1)); 23483 * 23484 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23485 * .toBe('408 555 1212'); 23486 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 23487 * .toBe('[email protected]'); 23488 * 23489 * firstRepeat.element(by.buttonText('clear')).click(); 23490 * 23491 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23492 * .toBe(''); 23493 * 23494 * container.element(by.buttonText('add')).click(); 23495 * 23496 * expect(container.element(by.repeater('contact in contacts').row(2)) 23497 * .element(by.model('contact.value')) 23498 * .getAttribute('value')) 23499 * .toBe('[email protected]'); 23500 * }); 23501 * </file> 23502 *</example> 23503 23504 */ 23505var ngControllerDirective = [function() { 23506 return { 23507 restrict: 'A', 23508 scope: true, 23509 controller: '@', 23510 priority: 500 23511 }; 23512}]; 23513 23514/** 23515 * @ngdoc directive 23516 * @name ngCsp 23517 * 23518 * @element html 23519 * @description 23520 * 23521 * Angular has some features that can break certain 23522 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 23523 * 23524 * If you intend to implement these rules then you must tell Angular not to use these features. 23525 * 23526 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 23527 * 23528 * 23529 * The following rules affect Angular: 23530 * 23531 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions 23532 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30% 23533 * increase in the speed of evaluating Angular expressions. 23534 * 23535 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular 23536 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). 23537 * To make these directives work when a CSP rule is blocking inline styles, you must link to the 23538 * `angular-csp.css` in your HTML manually. 23539 * 23540 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval 23541 * and automatically deactivates this feature in the {@link $parse} service. This autodetection, 23542 * however, triggers a CSP error to be logged in the console: 23543 * 23544 * ``` 23545 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 23546 * script in the following Content Security Policy directive: "default-src 'self'". Note that 23547 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 23548 * ``` 23549 * 23550 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 23551 * directive on an element of the HTML document that appears before the `<script>` tag that loads 23552 * the `angular.js` file. 23553 * 23554 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 23555 * 23556 * You can specify which of the CSP related Angular features should be deactivated by providing 23557 * a value for the `ng-csp` attribute. The options are as follows: 23558 * 23559 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM 23560 *
23561 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings 23562 * 23563 * You can use these values in the following combinations: 23564 * 23565 * 23566 * * No declaration means that Angular will assume that you can do inline styles, but it will do 23567 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions 23568 * of Angular. 23569 * 23570 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline 23571 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions 23572 * of Angular. 23573 * 23574 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject 23575 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 23576 * 23577 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can 23578 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 23579 * 23580 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject 23581 * styles nor use eval, which is the same as an empty: ng-csp. 23582 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 23583 * 23584 * @example 23585 * This example shows how to apply the `ngCsp` directive to the `html` tag. 23586 ```html 23587 <!doctype html> 23588 <html ng-app ng-csp> 23589 ... 23590 ... 23591 </html> 23592 ``` 23593 * @example 23594 // Note: the suffix `.csp` in the example name triggers 23595 // csp mode in our http server! 23596 <example name="example.csp" module="cspExample" ng-csp="true"> 23597 <file name="index.html"> 23598 <div ng-controller="MainController as ctrl"> 23599 <div> 23600 <button ng-click="ctrl.inc()" id="inc">Increment</button> 23601 <span id="counter"> 23602 {{ctrl.counter}} 23603 </span> 23604 </div> 23605 23606 <div> 23607 <button ng-click="ctrl.evil()" id="evil">Evil</button> 23608 <span id="evilError"> 23609 {{ctrl.evilError}} 23610 </span> 23611 </div> 23612 </div> 23613 </file> 23614 <file name="script.js"> 23615 angular.module('cspExample', []) 23616 .controller('MainController', function() { 23617 this.counter = 0; 23618 this.inc = function() { 23619 this.counter++; 23620 }; 23621 this.evil = function() { 23622 // jshint evil:true 23623 try { 23624 eval('1+2'); 23625 } catch (e) { 23626 this.evilError = e.message; 23627 } 23628 }; 23629 }); 23630 </file> 23631 <file name="protractor.js" type="protractor"> 23632 var util, webdriver; 23633 23634 var incBtn = element(by.id('inc')); 23635 var counter = element(by.id('counter')); 23636 var evilBtn = element(by.id('evil')); 23637 var evilError = element(by.id('evilError')); 23638 23639 function getAndClearSevereErrors() { 23640 return browser.manage().logs().get('browser').then(function(browserLog) { 23641 return browserLog.filter(function(logEntry) { 23642 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 23643 }); 23644 }); 23645 } 23646 23647 function clearErrors() { 23648 getAndClearSevereErrors(); 23649 } 23650 23651 function expectNoErrors() { 23652 getAndClearSevereErrors().then(function(filteredLog) { 23653 expect(filteredLog.length).toEqual(0); 23654 if (filteredLog.length) { 23655 console.log('browser console errors: ' + util.inspect(filteredLog)); 23656 } 23657 }); 23658 } 23659 23660 function expectError(regex) { 23661 getAndClearSevereErrors().then(function(filteredLog) { 23662 var found = false; 23663 filteredLog.forEach(function(log) { 23664 if (log.message.match(regex)) { 23665 found = true; 23666 } 23667 }); 23668 if (!found) { 23669 throw new Error('expected an error that matches ' + regex); 23670 } 23671 }); 23672 } 23673 23674 beforeEach(function() { 23675 util = require('util'); 23676 webdriver = require('protractor/node_modules/selenium-webdriver'); 23677 }); 23678 23679 // For now, we only test on Chrome, 23680 // as Safari does not load the page with Protractor's injected scripts, 23681 // and Firefox webdriver always disables content security policy (#6358) 23682 if (browser.params.browser !== 'chrome') { 23683 return; 23684 } 23685 23686 it('should not report errors when the page is loaded', function() { 23687 // clear errors so we are not dependent on previous tests 23688 clearErrors(); 23689 // Need to reload the page as the page is already loaded when 23690 // we come here 23691 browser.driver.getCurrentUrl().then(function(url) { 23692 browser.get(url); 23693 }); 23694 expectNoErrors(); 23695 }); 23696 23697 it('should evaluate expressions', function() { 23698 expect(counter.getText()).toEqual('0'); 23699 incBtn.click(); 23700 expect(counter.getText()).toEqual('1'); 23701 expectNoErrors(); 23702 }); 23703 23704 it('should throw and report an error when using "eval"', function() { 23705 evilBtn.click(); 23706 expect(evilError.getText()).toMatch(/Content Security Policy/); 23707 expectError(/Content Security Policy/); 23708 }); 23709 </file> 23710 </example> 23711 */ 23712 23713// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23714// bootstrap the system (before $parse is instantiated), for this reason we just have 23715// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc 23716 23717/** 23718 * @ngdoc directive 23719 * @name ngClick 23720 * 23721 * @description 23722 * The ngClick directive allows you to specify custom behavior when 23723 * an element is clicked. 23724 * 23725 * @element ANY 23726 * @priority 0 23727 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 23728 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 23729 * 23730 * @example 23731 <example> 23732 <file name="index.html"> 23733 <button ng-click="count = count + 1" ng-init="count=0"> 23734 Increment 23735 </button> 23736 <span> 23737 count: {{count}} 23738 </span> 23739 </file> 23740 <file name="protractor.js" type="protractor"> 23741 it('should check ng-click', function() { 23742 expect(element(by.binding('count')).getText()).toMatch('0'); 23743 element(by.css('button')).click(); 23744 expect(element(by.binding('count')).getText()).toMatch('1'); 23745 }); 23746 </file> 23747 </example> 23748 */ 23749/* 23750 * A collection of directives that allows creation of custom event handlers that are defined as 23751 * angular expressions and are compiled and executed within the current scope. 23752 */ 23753var ngEventDirectives = {}; 23754 23755// For events that might fire synchronously during DOM manipulation 23756// we need to execute their event handlers asynchronously using $evalAsync, 23757// so that they are not executed in an inconsistent state. 23758var forceAsyncEvents = { 23759 'blur': true, 23760 'focus': true 23761}; 23762forEach( 23763 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 23764 function(eventName) { 23765 var directiveName = directiveNormalize('ng-' + eventName); 23766 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 23767 return { 23768 restrict: 'A', 23769 compile: function($element, attr) { 23770 // We expose the powerful $event object on the scope that provides access to the Window, 23771 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 23772 // checks at the cost of speed since event handler expressions are not executed as 23773 // frequently as regular change detection. 23774 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 23775 return function ngEventHandler(scope, element) { 23776 element.on(eventName, function(event) { 23777 var callback = function() { 23778 fn(scope, {$event:event}); 23779 }; 23780 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 23781 scope.$evalAsync(callback); 23782 } else { 23783 scope.$apply(callback); 23784 } 23785 }); 23786 }; 23787 } 23788 }; 23789 }]; 23790 } 23791); 23792 23793/** 23794 * @ngdoc directive 23795 * @name ngDblclick 23796 * 23797 * @description 23798 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 23799 * 23800 * @element ANY 23801 * @priority 0 23802 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 23803 * a dblclick. (The Event object is available as `$event`) 23804 * 23805 * @example 23806 <example> 23807 <file name="index.html"> 23808 <button ng-dblclick="count = count + 1" ng-init="count=0"> 23809 Increment (on double click) 23810 </button> 23811 count: {{count}} 23812 </file> 23813 </example> 23814 */ 23815 23816 23817/** 23818 * @ngdoc directive 23819 * @name ngMousedown 23820 * 23821 * @description 23822 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 23823 * 23824 * @element ANY 23825 * @priority 0 23826 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 23827 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 23828 * 23829 * @example 23830 <example> 23831 <file name="index.html"> 23832 <button ng-mousedown="count = count + 1" ng-init="count=0"> 23833 Increment (on mouse down) 23834 </button> 23835 count: {{count}} 23836 </file> 23837 </example> 23838 */ 23839 23840 23841/** 23842 * @ngdoc directive 23843 * @name ngMouseup 23844 * 23845 * @description 23846 * Specify custom behavior on mouseup event. 23847 * 23848 * @element ANY 23849 * @priority 0 23850 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 23851 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 23852 * 23853 * @example 23854 <example> 23855 <file name="index.html"> 23856 <button ng-mouseup="count = count + 1" ng-init="count=0"> 23857 Increment (on mouse up) 23858 </button> 23859 count: {{count}} 23860 </file> 23861 </example> 23862 */ 23863 23864/** 23865 * @ngdoc directive 23866 * @name ngMouseover 23867 * 23868 * @description 23869 * Specify custom behavior on mouseover event. 23870 * 23871 * @element ANY 23872 * @priority 0 23873 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 23874 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 23875 * 23876 * @example 23877 <example> 23878 <file name="index.html"> 23879 <button ng-mouseover="count = count + 1" ng-init="count=0"> 23880 Increment (when mouse is over) 23881 </button> 23882 count: {{count}} 23883 </file> 23884 </example> 23885 */ 23886 23887 23888/** 23889 * @ngdoc directive 23890 * @name ngMouseenter 23891 * 23892 * @description 23893 * Specify custom behavior on mouseenter event. 23894 * 23895 * @element ANY 23896 * @priority 0 23897 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 23898 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 23899 * 23900 * @example 23901 <example> 23902 <file name="index.html"> 23903 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 23904 Increment (when mouse enters) 23905 </button> 23906 count: {{count}} 23907 </file> 23908 </example> 23909 */ 23910 23911 23912/** 23913 * @ngdoc directive 23914 * @name ngMouseleave 23915 * 23916 * @description 23917 * Specify custom behavior on mouseleave event. 23918 * 23919 * @element ANY 23920 * @priority 0 23921 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 23922 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 23923 * 23924 * @example 23925 <example> 23926 <file name="index.html"> 23927 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 23928 Increment (when mouse leaves) 23929 </button> 23930 count: {{count}} 23931 </file> 23932 </example> 23933 */ 23934 23935 23936/** 23937 * @ngdoc directive 23938 * @name ngMousemove 23939 * 23940 * @description 23941 * Specify custom behavior on mousemove event. 23942 * 23943 * @element ANY 23944 * @priority 0 23945 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 23946 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 23947 * 23948 * @example 23949 <example> 23950 <file name="index.html"> 23951 <button ng-mousemove="count = count + 1" ng-init="count=0"> 23952 Increment (when mouse moves) 23953 </button> 23954 count: {{count}} 23955 </file> 23956 </example> 23957 */ 23958 23959 23960/** 23961 * @ngdoc directive 23962 * @name ngKeydown 23963 * 23964 * @description 23965 * Specify custom behavior on keydown event. 23966 * 23967 * @element ANY 23968 * @priority 0 23969 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 23970 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 23971 * 23972 * @example 23973 <example> 23974 <file name="index.html"> 23975 <input ng-keydown="count = count + 1" ng-init="count=0"> 23976 key down count: {{count}} 23977 </file> 23978 </example> 23979 */ 23980 23981 23982/** 23983 * @ngdoc directive 23984 * @name ngKeyup 23985 * 23986 * @description 23987 * Specify custom behavior on keyup event. 23988 * 23989 * @element ANY 23990 * @priority 0 23991 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 23992 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 23993 * 23994 * @example 23995 <example> 23996 <file name="index.html"> 23997 <p>Typing in the input box below updates the key count</p> 23998 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 23999 24000 <p>Typing in the input box below updates the keycode</p> 24001 <input ng-keyup="event=$event"> 24002 <p>event keyCode: {{ event.keyCode }}</p> 24003 <p>event altKey: {{ event.altKey }}</p> 24004 </file> 24005 </example> 24006 */ 24007 24008 24009/** 24010 * @ngdoc directive 24011 * @name ngKeypress 24012 * 24013 * @description 24014 * Specify custom behavior on keypress event. 24015 * 24016 * @element ANY 24017 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 24018 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 24019 * and can be interrogated for keyCode, altKey, etc.) 24020 * 24021 * @example 24022 <example> 24023 <file name="index.html"> 24024 <input ng-keypress="count = count + 1" ng-init="count=0"> 24025 key press count: {{count}} 24026 </file> 24027 </example> 24028 */ 24029 24030 24031/** 24032 * @ngdoc directive 24033 * @name ngSubmit 24034 * 24035 * @description 24036 * Enables binding angular expressions to onsubmit events. 24037 * 24038 * Additionally it prevents the default action (which for form means
24038sending the request to the 24039 * server and reloading the current page), but only if the form does not contain `action`, 24040 * `data-action`, or `x-action` attributes. 24041 * 24042 * <div class="alert alert-warning"> 24043 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 24044 * `ngSubmit` handlers together. See the 24045 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 24046 * for a detailed discussion of when `ngSubmit` may be triggered. 24047 * </div> 24048 * 24049 * @element form 24050 * @priority 0 24051 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 24052 * ({@link guide/expression#-event- Event object is available as `$event`}) 24053 * 24054 * @example 24055 <example module="submitExample"> 24056 <file name="index.html"> 24057 <script> 24058 angular.module('submitExample', []) 24059 .controller('ExampleController', ['$scope', function($scope) { 24060 $scope.list = []; 24061 $scope.text = 'hello'; 24062 $scope.submit = function() { 24063 if ($scope.text) { 24064 $scope.list.push(this.text); 24065 $scope.text = ''; 24066 } 24067 }; 24068 }]); 24069 </script>
24070 <form ng-submit="submit()" ng-controller="ExampleController"> 24071 Enter text and hit enter: 24072 <input type="text" ng-model="text" name="text" /> 24073 <input type="submit" id="submit" value="Submit" /> 24074 <pre>list={{list}}</pre> 24075 </form> 24076 </file> 24077 <file name="protractor.js" type="protractor"> 24078 it('should check ng-submit', function() { 24079 expect(element(by.binding('list')).getText()).toBe('list=[]'); 24080 element(by.css('#submit')).click(); 24081 expect(element(by.binding('list')).getText()).toContain('hello'); 24082 expect(element(by.model('text')).getAttribute('value')).toBe(''); 24083 }); 24084 it('should ignore empty strings', function() { 24085 expect(element(by.binding('list')).getText()).toBe('list=[]'); 24086 element(by.css('#submit')).click(); 24087 element(by.css('#submit')).click(); 24088 expect(element(by.binding('list')).getText()).toContain('hello'); 24089 }); 24090 </file> 24091 </example> 24092 */ 24093 24094/** 24095 * @ngdoc directive 24096 * @name ngFocus 24097 * 24098 * @description 24099 * Specify custom behavior on focus event. 24100 * 24101 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 24102 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 24103 * during an `$apply` to ensure a consistent state. 24104 * 24105 * @element window, input, select, textarea, a 24106 * @priority 0 24107 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 24108 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 24109 * 24110 * @example 24111 * See {@link ng.directive:ngClick ngClick} 24112 */ 24113 24114/** 24115 * @ngdoc directive 24116 * @name ngBlur 24117 * 24118 * @description 24119 * Specify custom behavior on blur event. 24120 * 24121 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 24122 * an element has lost focus. 24123 * 24124 * Note: As the `blur` event is executed synchronously also during DOM manipulations 24125 * (e.g. removing a focussed input), 24126 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 24127 * during an `$apply` to ensure a consistent state. 24128 * 24129 * @element window, input, select, textarea, a 24130 * @priority 0 24131 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 24132 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 24133 * 24134 * @example 24135 * See {@link ng.directive:ngClick ngClick} 24136 */ 24137 24138/** 24139 * @ngdoc directive 24140 * @name ngCopy 24141 * 24142 * @description 24143 * Specify custom behavior on copy event. 24144 * 24145 * @element window, input, select, textarea, a 24146 * @priority 0 24147 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 24148 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 24149 * 24150 * @example 24151 <example> 24152 <file name="index.html"> 24153 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 24154 copied: {{copied}} 24155 </file> 24156 </example> 24157 */ 24158 24159/** 24160 * @ngdoc directive 24161 * @name ngCut 24162 * 24163 * @description 24164 * Specify custom behavior on cut event. 24165 * 24166 * @element window, input, select, textarea, a 24167 * @priority 0 24168 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 24169 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 24170 * 24171 * @example 24172 <example> 24173 <file name="index.html"> 24174 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 24175 cut: {{cut}} 24176 </file> 24177 </example> 24178 */ 24179 24180/** 24181 * @ngdoc directive 24182 * @name ngPaste 24183 * 24184 * @description 24185 * Specify custom behavior on paste event. 24186 * 24187 * @element window, input, select, textarea, a 24188 * @priority 0 24189 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 24190 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 24191 * 24192 * @example 24193 <example> 24194 <file name="index.html"> 24195 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 24196 pasted: {{paste}} 24197 </file> 24198 </example> 24199 */ 24200 24201/** 24202 * @ngdoc directive 24203 * @name ngIf 24204 * @restrict A 24205 * @multiElement 24206 * 24207 * @description 24208 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 24209 * {expression}. If the expression assigned to `ngIf` evaluates to a false 24210 * value then the element is removed from the DOM, otherwise a clone of the 24211 * element is reinserted into the DOM. 24212 * 24213 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 24214 * element in the DOM rather than changing its visibility via the `display` css property. A common 24215 * case when this difference is significant is when using css selectors that rely on an element's 24216 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
24216s. 24217 * 24218 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 24219 * is created when the element is restored. The scope created within `ngIf` inherits from 24220 * its parent scope using 24221 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 24222 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 24223 * a javascript primitive defined in the parent scope. In this case any modifications made to the 24224 * variable within the child scope will override (hide) the value in the parent scope. 24225 * 24226 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 24227 * is if an element's class attribute is directly modified after it's compiled, using something like 24228 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 24229 * the added class will be lost because the original compiled state is used to regenerate the element. 24230 * 24231 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 24232 * and `leave` effects. 24233 * 24234 * @animations 24235 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container 24236 * leave - happens just before the `ngIf` contents are removed from the DOM 24237 * 24238 * @element ANY 24239 * @scope 24240 * @priority 600 24241 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 24242 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 24243 * element is added to the DOM tree. 24244 * 24245 * @example 24246 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 24247 <file name="index.html"> 24248 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 24249 Show when checked: 24250 <span ng-if="checked" class="animate-if"> 24251 This is removed when the checkbox is unchecked. 24252 </span> 24253 </file> 24254 <file name="animations.css"> 24255 .animate-if { 24256 background:white; 24257 border:1px solid black; 24258 padding:10px; 24259 } 24260 24261 .animate-if.ng-enter, .animate-if.ng-leave { 24262 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 24263 } 24264 24265 .animate-if.ng-enter, 24266 .animate-if.ng-leave.ng-leave-active { 24267 opacity:0; 24268 } 24269 24270 .animate-if.ng-leave, 24271 .animate-if.ng-enter.ng-enter-active { 24272 opacity:1; 24273 } 24274 </file> 24275 </example> 24276 */ 24277var ngIfDirective = ['$animate', function($animate) { 24278 return { 24279 multiElement: true, 24280 transclude: 'element', 24281 priority: 600, 24282 terminal: true, 24283 restrict: 'A', 24284 $$tlb: true, 24285 link: function($scope, $element, $attr, ctrl, $transclude) { 24286 var block, childScope, previousElements; 24287 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 24288 24289 if (value) { 24290 if (!childScope) { 24291 $transclude(function(clone, newScope) { 24292 childScope = newScope; 24293 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 24294 // Note: We only need the first/last node of the cloned nodes. 24295 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 24296 // by a directive with templateUrl when its template arrives. 24297 block = { 24298 clone: clone 24299 }; 24300 $animate.enter(clone, $element.parent(), $element); 24301 }); 24302 } 24303 } else { 24304 if (previousElements) { 24305 previousElements.remove(); 24306 previousElements = null; 24307 } 24308 if (childScope) { 24309 childScope.$destroy(); 24310 childScope = null; 24311 } 24312 if (block) { 24313 previousElements = getBlockNodes(block.clone); 24314 $animate.leave(previousElements).then(function() { 24315 previousElements = null; 24316 }); 24317 block = null; 24318 } 24319 } 24320 }); 24321 } 24322 }; 24323}]; 24324 24325/** 24326 * @ngdoc directive 24327 * @name ngInclude 24328 * @restrict ECA 24329 * 24330 * @description 24331 * Fetches, compiles and includes an external HTML fragment. 24332 * 24333 * By default, the template URL is restricted to the same domain and protocol as the 24334 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 24335 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 24336 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 24337 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 24338 * ng.$sce Strict Contextual Escaping}. 24339 * 24340 * In addition, the browser's 24341 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 24342 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 24343 * policy may further restrict whether the template is successfully loaded. 24344 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 24345 * access on some browsers. 24346 * 24347 * @animations 24348 * enter - animation is used to bring new content into the browser. 24349 * leave - animation is used to animate existing content away. 24350 * 24351 * The enter and leave animation occur concurrently. 24352 * 24353 * @scope 24354 * @priority 400 24355 * 24356 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 24357 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 24358 * @param {string=} onload Expression to evaluate when a new partial is loaded. 24359 * 24360 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 24361 * $anchorScroll} to scroll the viewport after the content is loaded. 24362 * 24363 * - If the attribute is not set, disable scrolling. 24364 * - If the attribute is set without value, enable scrolling. 24365 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 24366 * 24367 * @example 24368 <example module="includeExample" deps="angular-animate.js" animations="true"> 24369 <file name="index.html">
24370 <div ng-controller="ExampleController"> 24371 <select ng-model="template" ng-options="t.name for t in templates"> 24372 <option value="">(blank)</option> 24373 </select> 24374 url of the template: <code>{{template.url}}</code> 24375 <hr/> 24376 <div class="slide-animate-container"> 24377 <div class="slide-animate" ng-include="template.url"></div> 24378 </div> 24379 </div> 24380 </file> 24381 <file name="script.js"> 24382 angular.module('includeExample', ['ngAnimate']) 24383 .controller('ExampleController', ['$scope', function($scope) { 24384 $scope.templates = 24385 [ { name: 'template1.html', url: 'template1.html'}, 24386 { name: 'template2.html', url: 'template2.html'} ]; 24387 $scope.template = $scope.templates[0]; 24388 }]); 24389 </file> 24390 <file name="template1.html"> 24391 Content of template1.html 24392 </file> 24393 <file name="template2.html"> 24394 Content of template2.html 24395 </file> 24396 <file name="animations.css"> 24397 .slide-animate-container { 24398 position:relative; 24399 background:white; 24400 border:1px solid black; 24401 height:40px; 24402 overflow:hidden; 24403 } 24404 24405 .slide-animate { 24406 padding:10px; 24407 } 24408 24409 .slide-animate.ng-enter, .slide-animate.ng-leave { 24410 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 24411 24412 position:absolute; 24413 top:0; 24414 left:0; 24415 right:0; 24416 bottom:0; 24417 display:block; 24418 padding:10px; 24419 } 24420 24421 .slide-animate.ng-enter { 24422 top:-50px; 24423 } 24424 .slide-animate.ng-enter.ng-enter-active { 24425 top:0; 24426 } 24427 24428 .slide-animate.ng-leave { 24429 top:0; 24430 } 24431 .slide-animate.ng-leave.ng-leave-active { 24432 top:50px; 24433 } 24434 </file> 24435 <file name="protractor.js" type="protractor"> 24436 var templateSelect = element(by.model('template')); 24437 var includeElem = element(by.css('[ng-include]')); 24438 24439 it('should load template1.html', function() { 24440 expect(includeElem.getText()).toMatch(/Content of template1.html/); 24441 }); 24442 24443 it('should load template2.html', function() { 24444 if (browser.params.browser == 'firefox') { 24445 // Firefox can't handle using selects 24446 // See https://github.com/angular/protractor/issues/480 24447 return; 24448 } 24449 templateSelect.click(); 24450 templateSelect.all(by.css('option')).get(2).click(); 24451 expect(includeElem.getText()).toMatch(/Content of template2.html/); 24452 }); 24453 24454 it('should change to blank', function() { 24455 if (browser.params.browser == 'firefox') { 24456 // Firefox can't handle using selects 24457 return; 24458 } 24459 templateSelect.click(); 24460 templateSelect.all(by.css('option')).get(0).click(); 24461 expect(includeElem.isPresent()).toBe(false); 24462 }); 24463 </file> 24464 </example> 24465 */ 24466 24467 24468/** 24469 * @ngdoc event 24470 * @name ngInclude#$includeContentRequested 24471 * @eventType emit on the scope ngInclude was declared in 24472 * @description 24473 * Emitted every time the ngInclude content is requested. 24474 * 24475 * @param {Object} angularEvent Synthetic event object. 24476 * @param {String} src URL of content to load. 24477 */ 24478 24479 24480/** 24481 * @ngdoc event 24482 * @name ngInclude#$includeContentLoaded 24483 * @eventType emit on the current ngInclude scope 24484 * @description 24485 * Emitted every time the ngInclude content is reloaded. 24486 * 24487 * @param {Object} angularEvent Synthetic event object. 24488 * @param {String} src URL of content to load. 24489 */ 24490 24491 24492/** 24493 * @ngdoc event 24494 * @name ngInclude#$includeContentError 24495 * @eventType emit on the scope ngInclude was declared in 24496 * @description 24497 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 24498 * 24499 * @param {Object} angularEvent Synthetic event object. 24500 * @param {String} src URL of content to load. 24501 */ 24502var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 24503 function($templateRequest, $anchorScroll, $animate) { 24504 return { 24505 restrict: 'ECA', 24506 priority: 400, 24507 terminal: true, 24508 transclude: 'element', 24509 controller: angular.noop, 24510 compile: function(element, attr) { 24511 var srcExp = attr.ngInclude || attr.src, 24512 onloadExp = attr.onload || '', 24513 autoScrollExp = attr.autoscroll; 24514 24515 return function(scope, $element, $attr, ctrl, $transclude) { 24516 var changeCounter = 0, 24517 currentScope, 24518 previousElement, 24519 currentElement; 24520 24521 var cleanupLastIncludeContent = function() { 24522 if (previousElement) { 24523 previousElement.remove(); 24524 previousElement = null; 24525 } 24526 if (currentScope) { 24527 currentScope.$destroy(); 24528 currentScope = null; 24529 } 24530 if (currentElement) { 24531 $animate.leave(currentElement).then(function() { 24532 previousElement = null; 24533 }); 24534 previousElement = currentElement; 24535 currentElement = null; 24536 } 24537 }; 24538 24539 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 24540 var afterAnimation = function() { 24541 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 24542 $anchorScroll(); 24543 } 24544 }; 24545 var thisChangeId = ++changeCounter; 24546 24547 if (src) { 24548 //set the 2nd param to true to ignore the template request error so that the inner 24549 //contents and scope can be cleaned up. 24550 $templateRequest(src, true).then(function(response) { 24551 if (thisChangeId !== changeCounter) return; 24552 var newScope = scope.$new(); 24553 ctrl.template = response; 24554 24555 // Note: This will also link all children of ng-include that were contained in the original 24556 // html. If that content contains controllers, ... they could pollute/change the scope. 24557 // However, using ng-include on an element with additional content does not make sense... 24558 // Note: We can't remove them in the cloneAttchFn of $transclude as that 24559 // function is called before linking the content, which would apply child 24560 // directives to non existing elements. 24561 var clone = $transclude(newScope, function(clone) { 24562 cleanupLastIncludeContent(); 24563 $animate.enter(clone, null, $element).then(afterAnimation); 24564 }); 24565 24566 currentScope = newScope; 24567 currentElement = clone; 24568 24569 currentScope.$emit('$includeContentLoaded', src); 24570 scope.$eval(onloadExp); 24571 }, function() { 24572 if (thisChangeId === changeCounter) { 24573 cleanupLastIncludeContent();
vendor: 2,830 bytes, lines 24574-24656
24574 scope.$emit('$includeContentError', src); 24575 } 24576 }); 24577 scope.$emit('$includeContentRequested', src); 24578 } else { 24579 cleanupLastIncludeContent(); 24580 ctrl.template = null; 24581 } 24582 }); 24583 }; 24584 } 24585 }; 24586}]; 24587 24588// This directive is called during the $transclude call of the first `ngInclude` directive. 24589// It will replace and compile the content of the element with the loaded template. 24590// We need this directive so that the element content is already filled when 24591// the link function of another directive on the same element as ngInclude 24592// is called. 24593var ngIncludeFillContentDirective = ['$compile', 24594 function($compile) { 24595 return { 24596 restrict: 'ECA', 24597 priority: -400, 24598 require: 'ngInclude', 24599 link: function(scope, $element, $attr, ctrl) { 24600 if (/SVG/.test($element[0].toString())) { 24601 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 24602 // support innerHTML, so detect this here and try to generate the contents 24603 // specially. 24604 $element.empty(); 24605 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, 24606 function namespaceAdaptedClone(clone) { 24607 $element.append(clone); 24608 }, {futureParentElement: $element}); 24609 return; 24610 } 24611 24612 $element.html(ctrl.template); 24613 $compile($element.contents())(scope); 24614 } 24615 }; 24616 }]; 24617 24618/** 24619 * @ngdoc directive 24620 * @name ngInit 24621 * @restrict AC 24622 * 24623 * @description 24624 * The `ngInit` directive allows you to evaluate an expression in the 24625 * current scope. 24626 * 24627 * <div class="alert alert-danger"> 24628 * This directive can be abused to add unnecessary amounts of logic into your templates. 24629 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of 24630 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via 24631 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} 24632 * rather than `ngInit` to initialize values on a scope. 24633 * </div> 24634 * 24635 * <div class="alert alert-warning"> 24636 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 24637 * sure you have parentheses to ensure correct operator precedence: 24638 * <pre class="prettyprint"> 24639 * `<div ng-init="test1 = ($index | toString)"></div>` 24640 * </pre> 24641 * </div> 24642 * 24643 * @priority 450 24644 * 24645 * @element ANY 24646 * @param {expression} ngInit {@link guide/expression Expression} to eval. 24647 * 24648 * @example 24649 <example module="initExample"> 24650 <file name="index.html"> 24651 <script> 24652 angular.module('initExample', []) 24653 .controller('ExampleController', ['$scope', function($scope) { 24654 $scope.list = [['a', 'b'], ['c', 'd']]; 24655 }]); 24656 </script>
24657 <div ng-controller="ExampleController"> 24658 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 24659 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 24660 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 24661 </div> 24662 </div> 24663 </div> 24664 </file> 24665 <file name="protractor.js" type="protractor"> 24666 it('should alias index positions', function() { 24667 var elements = element.all(by.css('.example-init')); 24668 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 24669 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 24670 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 24671 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 24672 }); 24673 </file> 24674 </example> 24675 */ 24676var ngInitDirective = ngDirective({ 24677 priority: 450, 24678 compile: function() { 24679 return { 24680 pre: function(scope, element, attrs) { 24681 scope.$eval(attrs.ngInit); 24682 } 24683 }; 24684 } 24685}); 24686 24687/** 24688 * @ngdoc directive 24689 * @name ngList 24690 * 24691 * @description 24692 * Text input that converts between a delimited string and an array of strings. The default 24693 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 24694 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 24695 * 24696 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 24697 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 24698 * list item is respected. This implies that the user of the directive is responsible for 24699 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 24700 * tab or newline character. 24701 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 24702 * when joining the list items back together) and whitespace around each list item is stripped 24703 * before it is added to the model. 24704 * 24705 * ### Example with Validation 24706 * 24707 * <example name="ngList-directive" module="listExample"> 24708 * <file name="app.js"> 24709 * angular.module('listExample', []) 24710 * .controller('ExampleController', ['$scope', function($scope) { 24711 * $scope.names = ['morpheus', 'neo', 'trinity']; 24712 * }]); 24713 * </file> 24714 * <file name="index.html">
24715 * <form name="myForm" ng-controller="ExampleController"> 24716 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 24717 * <span role="alert"> 24718 * <span class="error" ng-show="myForm.namesInput.$error.required"> 24719 * Required!</span> 24720 * </span> 24721 * <br> 24722 * <tt>names = {{names}}</tt><br/> 24723 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 24724 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 24725 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24726 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24727 * </form> 24728 * </file> 24729 * <file name="protractor.js" type="protractor"> 24730 * var listInput = element(by.model('names')); 24731 * var names = element(by.exactBinding('names')); 24732 * var valid = element(by.binding('myForm.namesInput.$valid')); 24733 * var error = element(by.css('span.error')); 24734 * 24735 * it('should initialize to model', function() { 24736 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 24737 * expect(valid.getText()).toContain('true'); 24738 * expect(error.getCssValue('display')).toBe('none'); 24739 * }); 24740 * 24741 * it('should be invalid if empty', function() { 24742 * listInput.clear(); 24743 * listInput.sendKeys(''); 24744 * 24745 * expect(names.getText()).toContain(''); 24746 * expect(valid.getText()).toContain('false'); 24747 * expect(error.getCssValue('display')).not.toBe('none'); 24748 * }); 24749 * </file> 24750 * </example> 24751 * 24752 * ### Example - splitting on newline 24753 * <example name="ngList-directive-newlines"> 24754 * <file name="index.html"> 24755 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 24756 * <pre>{{ list | json }}</pre> 24757 * </file> 24758 * <file name="protractor.js" type="protractor"> 24759 * it("should split the text by newlines", function() { 24760 * var listInput = element(by.model('list')); 24761 * var output = element(by.binding('list | json')); 24762 * listInput.sendKeys('abc\ndef\nghi'); 24763 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 24764 * }); 24765 * </file> 24766 * </example> 24767 * 24768 * @element input 24769 * @param {string=} ngList optional delimiter that should be used to split the value. 24770 */ 24771var ngListDirective = function() { 24772 return { 24773 restrict: 'A', 24774 priority: 100, 24775 require: 'ngModel', 24776 link: function(scope, element, attr, ctrl) { 24777 // We want to control whitespace trimming so we use this convoluted approach 24778 // to access the ngList attribute, which doesn't pre-trim the attribute 24779 var ngList = element.attr(attr.$attr.ngList) || ', '; 24780 var trimValues = attr.ngTrim !== 'false'; 24781 var separator = trimValues ? trim(ngList) : ngList; 24782 24783 var parse = function(viewValue) { 24784 // If the viewValue is invalid (say required but empty) it will be `undefined` 24785 if (isUndefined(viewValue)) return; 24786 24787 var list = []; 24788 24789 if (viewValue) { 24790 forEach(viewValue.split(separator), function(value) { 24791 if (value) list.push(trimValues ? trim(value) : value); 24792 }); 24793 } 24794 24795 return list; 24796 }; 24797 24798 ctrl.$parsers.push(parse); 24799 ctrl.$formatters.push(function(value) { 24800 if (isArray(value)) { 24801 return value.join(ngList); 24802 } 24803 24804 return undefined; 24805 }); 24806 24807 // Override the standard $isEmpty because an empty array means the input is empty. 24808 ctrl.$isEmpty = function(value) { 24809 return !value || !value.length; 24810 }; 24811 } 24812 }; 24813}; 24814 24815/* global VALID_CLASS: true, 24816 INVALID_CLASS: true, 24817 PRISTINE_CLASS: true, 24818 DIRTY_CLASS: true, 24819 UNTOUCHED_CLASS: true, 24820 TOUCHED_CLASS: true, 24821*/ 24822 24823var VALID_CLASS = 'ng-valid', 24824 INVALID_CLASS = 'ng-invalid', 24825 PRISTINE_CLASS = 'ng-pristine', 24826 DIRTY_CLASS = 'ng-dirty', 24827 UNTOUCHED_CLASS = 'ng-untouched', 24828 TOUCHED_CLASS = 'ng-touched', 24829 PENDING_CLASS = 'ng-pending'; 24830 24831var ngModelMinErr = minErr('ngModel'); 24832 24833/** 24834 * @ngdoc type 24835 * @name ngModel.NgModelController 24836 * 24837 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 24838 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 24839 * is set. 24840 * @property {*} $modelValue The value in the model that the control is bound to. 24841 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 24842 the control reads value from the DOM. The functions are called in array order, each passing 24843 its return value through to the next. The last return value is forwarded to the 24844 {@link ngModel.NgModelController#$validators `$validators`} collection. 24845 24846Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 24847`$viewValue`}. 24848 24849Returning `undefined` from a parser means a parse error occurred. In that case, 24850no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 24851will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 24852is set to `true`. The parse error is stored in `ngModel.$error.parse`. 24853 24854 * 24855 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 24856 the model value changes. The functions are called in reverse array order, each passing the value through to the 24857 next. The last return value is used as the actual DOM value. 24858 Used to format / convert values for display in the control. 24859 * ```js 24860 * function formatter(value) { 24861 * if (value) { 24862 * return value.toUpperCase(); 24863 * } 24864 * } 24865 * ngModel.$formatters.push(formatter); 24866 * ``` 24867 * 24868 * @property {Object.<string, function>} $validators A collection of validators that are applied 24869 * whenever the model value changes. The key value within the object refers to the name of the 24870 * validator while the function refers to the validation operation. The validation operation is 24871 * provided with the model value as an argument and must return a true or false value depending 24872 * on the response of that validation. 24873 * 24874 * ```js 24875 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 24876 * var value = modelValue || viewValue; 24877 * return /[0-9]+/.test(value) && 24878 * /[a-z]+/.test(value) && 24879 * /[A-Z]+/.test(value) && 24880 * /\W+/.test(value); 24881 * }; 24882 * ``` 24883 * 24884 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 24885 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 24886 * is expected to return a promise when it is run during the model validation process. Once the promise 24887 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 24888 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 24889 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 24890 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 24891 * will only run once all synchronous validators have passed. 24892 * 24893 * Please note that if $http is used then it is important that the server returns a success HTTP response code 24894 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 24895 * 24896 * ```js 24897 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 24898 * var value = modelValue || viewValue; 24899 * 24900 * // Lookup user by username 24901 * return $http.get('/api/users/' + value). 24902 * then(function resolved() { 24903 * //username exists, this means validation fails 24904 * return $q.reject('exists'); 24905 * }, function rejected() { 24906 * //username does not exist, therefore this validation passes 24907 * return true; 24908 * }); 24909 * }; 24910 * ``` 24911 * 24912 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 24913 * view value has changed. It is called with no arguments, and its return value is ignored. 24914 * This can be used in place of additional $watches against the model value. 24915 * 24916 * @property {Object} $error An object hash with all failing validator ids as keys. 24917 * @property {Object} $pending An object hash with all pending validator ids as keys. 24918 * 24919 * @property {boolean} $untouched True if control has not lost focus yet. 24920 * @property {boolean} $touched True if control has lost focus. 24921 * @property {boolean} $pristine True if user has not interacted with the control yet. 24922 * @property {boolean} $dirty True if user has already interacted with the control. 24923 * @property {boolean} $valid True if there is no error. 24924 * @property {boolean}
24924 $invalid True if at least one error on the control. 24925 * @property {string} $name The name attribute of the control. 24926 * 24927 * @description 24928 * 24929 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 24930 * The controller contains services for data-binding, validation, CSS updates, and value formatting 24931 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 24932 * listening to DOM events. 24933 * Such DOM related logic should be provided by other directives which make use of 24934 * `NgModelController` for data-binding to control elements. 24935 * Angular provides this DOM logic for most {@link input `input`} elements. 24936 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 24937 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 24938 * 24939 * @example 24940 * ### Custom Control Example 24941 * This example shows how to use `NgModelController` with a custom control to achieve 24942 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 24943 * collaborate together to achieve the desired result. 24944 * 24945 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 24946 * contents be edited in place by the user. 24947 * 24948 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 24949 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 24950 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 24951 * that content using the `$sce` service. 24952 * 24953 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 24954 <file name="style.css"> 24955 [contenteditable] { 24956 border: 1px solid black; 24957 background-color: white; 24958 min-height: 20px; 24959 } 24960 24961 .ng-invalid { 24962 border: 1px solid red; 24963 } 24964 24965 </file> 24966 <file name="script.js"> 24967 angular.module('customControl', ['ngSanitize']). 24968 directive('contenteditable', ['$sce', function($sce) { 24969 return { 24970 restrict: 'A', // only activate on element attribute 24971 require: '?ngModel', // get a hold of NgModelController 24972 link: function(scope, element, attrs, ngModel) { 24973 if (!ngModel) return; // do nothing if no ng-model 24974 24975 // Specify how UI should be updated 24976 ngModel.$render = function() { 24977 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 24978 }; 24979 24980 // Listen for change events to enable binding 24981 element.on('blur keyup change', function() { 24982 scope.$evalAsync(read); 24983 }); 24984 read(); // initialize 24985 24986 // Write data to the model 24987 function read() { 24988 var html = element.html(); 24989 // When we clear the content editable the browser leaves a <br> behind 24990 // If strip-br attribute is provided then we strip this out 24991 if ( attrs.stripBr && html == '<br>' ) { 24992 html = ''; 24993 } 24994 ngModel.$setViewValue(html); 24995 } 24996 } 24997 }; 24998 }]); 24999 </file> 25000 <file name="index.html"> 25001 <form name="myForm"> 25002 <div contenteditable 25003 name="myWidget" ng-model="userContent" 25004 strip-br="true" 25005 required>Change me!</div> 25006 <span ng-show="myForm.myWidget.$error.required">Required!</span> 25007 <hr> 25008 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 25009 </form> 25010 </file> 25011 <file name="protractor.js" type="protractor"> 25012 it('should data-bind and become invalid', function() { 25013 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 25014 // SafariDriver can't handle contenteditable 25015 // and Firefox driver can't clear contenteditables very well 25016 return; 25017 } 25018 var contentEditable = element(by.css('[contenteditable]')); 25019 var content = 'Change me!'; 25020 25021 expect(contentEditable.getText()).toEqual(content); 25022 25023 contentEditable.clear(); 25024 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 25025 expect(contentEditable.getText()).toEqual(''); 25026 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 25027 }); 25028 </file> 25029 * </example> 25030 * 25031 * 25032 */ 25033var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 25034 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 25035 this.$viewValue = Number.NaN; 25036 this.$modelValue = Number.NaN; 25037 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 25038 this.$validators = {}; 25039 this.$asyncValidators = {}; 25040 this.$parsers = []; 25041 this.$formatters = []; 25042 this.$viewChangeListeners = []; 25043 this.$untouched = true; 25044 this.$touched = false;
25045 this.$pristine = true; 25046 this.$dirty = false; 25047 this.$valid = true; 25048 this.$invalid = false; 25049 this.$error = {}; // keep invalid keys here 25050 this.$$success = {}; // keep valid keys here 25051 this.$pending = undefined; // keep pending keys here 25052 this.$name = $interpolate($attr.name || '', false)($scope); 25053 this.$$parentForm = nullFormCtrl; 25054 25055 var parsedNgModel = $parse($attr.ngModel), 25056 parsedNgModelAssign = parsedNgModel.assign, 25057 ngModelGet = parsedNgModel, 25058 ngModelSet = parsedNgModelAssign, 25059 pendingDebounce = null, 25060 parserValid, 25061 ctrl = this; 25062 25063 this.$$setOptions = function(options) { 25064 ctrl.$options = options; 25065 if (options && options.getterSetter) { 25066 var invokeModelGetter = $parse($attr.ngModel + '()'), 25067 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 25068 25069 ngModelGet = function($scope) { 25070 var modelValue = parsedNgModel($scope); 25071 if (isFunction(modelValue)) { 25072 modelValue = invokeModelGetter($scope); 25073 } 25074 return modelValue; 25075 }; 25076 ngModelSet = function($scope, newValue) { 25077 if (isFunction(parsedNgModel($scope))) { 25078 invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); 25079 } else { 25080 parsedNgModelAssign($scope, ctrl.$modelValue); 25081 } 25082 }; 25083 } else if (!parsedNgModel.assign) { 25084 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 25085 $attr.ngModel, startingTag($element)); 25086 } 25087 }; 25088 25089 /** 25090 * @ngdoc method 25091 * @name ngModel.NgModelController#$render 25092 * 25093 * @description 25094 * Called when the view needs to be updated. It is expected that the user of the ng-model 25095 * directive will implement this method. 25096 * 25097 * The `$render()` method is invoked in the following situations: 25098 * 25099 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 25100 * committed value then `$render()` is called to update the input control. 25101 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 25102 * the `$viewValue` are different from last time. 25103 * 25104 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 25105 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue` 25106 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 25107 * invoked if you only change a property on the objects. 25108 */ 25109 this.$render = noop; 25110 25111 /** 25112 * @ngdoc method 25113 * @name ngModel.NgModelController#$isEmpty 25114 * 25115 * @description 25116 * This is called when we need to determine if the value of an input is empty. 25117 * 25118 * For instance, the required directive does this to work out if the input has data or not. 25119 * 25120 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 25121 * 25122 * You can override this for input directives whose concept of being empty is different from the 25123 * default. The `checkboxInputType` directive does this because in its case a value of `false` 25124 * implies empty. 25125 * 25126 * @param {*} value The value of the input to check for emptiness. 25127 * @returns {boolean} True if `value` is "empty". 25128 */ 25129 this.$isEmpty = function(value) { 25130 return isUndefined(value) || value === '' || value === null || value !== value; 25131 }; 25132 25133 var currentValidationRunId = 0; 25134 25135 /** 25136 * @ngdoc method 25137 * @name ngModel.NgModelController#$setValidity 25138 * 25139 * @description 25140 * Change the validity state, and notify the form. 25141 * 25142 * This method can be called within $parsers/$formatters or a custom validation implementation. 25143 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 25144 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 25145 * 25146 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 25147 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 25148 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
25149 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 25150 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 25151 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 25152 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 25153 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 25154 * Skipped is used by Angular when validators do not run because of parse errors and 25155 * when `$asyncValidators` do not run because any of the `$validators` failed. 25156 */ 25157 addSetValidityMethod({ 25158 ctrl: this, 25159 $element: $element, 25160 set: function(object, property) { 25161 object[property] = true; 25162 }, 25163 unset: function(object, property) { 25164 delete object[property]; 25165 }, 25166 $animate: $animate 25167 }); 25168 25169 /** 25170 * @ngdoc method 25171 * @name ngModel.NgModelController#$setPristine 25172 * 25173 * @description 25174 * Sets the control to its pristine state. 25175 * 25176 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 25177 * state (`ng-pristine` class). A model is considered to be pristine when the control 25178 * has not been changed from when first compiled. 25179 */ 25180 this.$setPristine = function() { 25181 ctrl.$dirty = false; 25182 ctrl.$pristine = true; 25183 $animate.removeClass($element, DIRTY_CLASS); 25184 $animate.addClass($element, PRISTINE_CLASS); 25185 }; 25186 25187 /** 25188 * @ngdoc method 25189 * @name ngModel.NgModelController#$setDirty 25190 * 25191 * @description 25192 * Sets the control to its dirty state. 25193 * 25194 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 25195 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 25196 * from when first compiled. 25197 */ 25198 this.$setDirty = function() { 25199 ctrl.$dirty = true; 25200 ctrl.$pristine = false; 25201 $animate.removeClass($element, PRISTINE_CLASS); 25202 $animate.addClass($element, DIRTY_CLASS); 25203 ctrl.$$parentForm.$setDirty(); 25204 }; 25205 25206 /** 25207 * @ngdoc method 25208 * @name ngModel.NgModelController#$setUntouched 25209 * 25210 * @description 25211 * Sets the control to its untouched state. 25212 * 25213 * This method can be called to remove the `ng-touched` class and set the control to its 25214 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 25215 * by default, however this function can be used to restore that state if the model has 25216 * already been touched by the user. 25217 */ 25218 this.$setUntouched = function() { 25219 ctrl.$touched = false; 25220 ctrl.$untouched = true; 25221 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 25222 }; 25223 25224 /** 25225 * @ngdoc method 25226 * @name ngModel.NgModelController#$setTouched 25227 * 25228 * @description 25229 * Sets the control to its touched state. 25230 * 25231 * This method can be called to remove the `ng-untouched` class and set the control to its 25232 * touched state (`ng-touched` class). A model is considered to be touched when the user has 25233 * first focused the control element and then shifted focus away from the control (blur event). 25234 */ 25235 this.$setTouched = function() { 25236 ctrl.$touched = true; 25237 ctrl.$untouched = false; 25238 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 25239 }; 25240 25241 /** 25242 * @ngdoc method 25243 * @name ngModel.NgModelController#$rollbackViewValue 25244 * 25245 * @description 25246 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 25247 * which may be caused by a pending debounced event or because the input is waiting for a some 25248 * future event. 25249 * 25250 * If you have an input that uses `ng-model-options` to set up debounced events or events such 25251 * as blur you can have a situation where there is a period when the `$viewValue` 25252 * is out of synch with the ngModel's `$modelValue`. 25253 * 25254 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` 25255 * programmatically before these debounced/future events have resolved/occurred, because Angular's 25256 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 25257 * 25258 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 25259 * input which may have such events pending. This is important in order to make sure that the 25260 * input field will be updated with the new model value and any pending operations are cancelled. 25261 * 25262 * <example name="ng-model-cancel-update" module="cancel-update-example"> 25263 * <file name="app.js">
25264 * angular.module('cancel-update-example', []) 25265 * 25266 * .controller('CancelUpdateController', ['$scope', function($scope) { 25267 * $scope.resetWithCancel = function(e) { 25268 * if (e.keyCode == 27) { 25269 * $scope.myForm.myInput1.$rollbackViewValue(); 25270 * $scope.myValue = ''; 25271 * } 25272 * }; 25273 * $scope.resetWithoutCancel = function(e) { 25274 * if (e.keyCode == 27) { 25275 * $scope.myValue = ''; 25276 * } 25277 * }; 25278 * }]); 25279 * </file> 25280 * <file name="index.html"> 25281 * <div ng-controller="CancelUpdateController"> 25282 * <p>Try typing something in each input. See that the model only updates when you 25283 * blur off the input. 25284 * </p> 25285 * <p>Now see what happens if you start typing then press the Escape key</p> 25286 * 25287 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 25288 * <p id="inputDescription1">With $rollbackViewValue()</p> 25289 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue" 25290 * ng-keydown="resetWithCancel($event)"><br/> 25291 * myValue: "{{ myValue }}" 25292 * 25293 * <p id="inputDescription2">Without $rollbackViewValue()</p> 25294 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue" 25295 * ng-keydown="resetWithoutCancel($event)"><br/> 25296 * myValue: "{{ myValue }}" 25297 * </form> 25298 * </div> 25299 * </file> 25300 * </example> 25301 */ 25302 this.$rollbackViewValue = function() { 25303 $timeout.cancel(pendingDebounce); 25304 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 25305 ctrl.$render(); 25306 }; 25307 25308 /** 25309 * @ngdoc method 25310 * @name ngModel.NgModelController#$validate 25311 * 25312 * @description 25313 * Runs each of the registered validators (first synchronous validators and then 25314 * asynchronous validators). 25315 * If the validity changes to invalid, the model will be set to `undefined`, 25316 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 25317 * If the validity changes to valid, it will set the model to the last available valid 25318 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 25319 */ 25320 this.$validate = function() { 25321 // ignore $validate before model is initialized 25322 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 25323 return; 25324 } 25325 25326 var viewValue = ctrl.$$lastCommittedViewValue; 25327 // Note: we use the $$rawModelValue as $modelValue might have been 25328 // set to undefined during a view -> model update that found validation 25329 // errors. We can't parse the view here, since that could change 25330 // the model although neither viewValue nor the model on the scope changed 25331 var modelValue = ctrl.$$rawModelValue; 25332 25333 var prevValid = ctrl.$valid; 25334 var prevModelValue = ctrl.$modelValue; 25335 25336 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 25337 25338 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 25339 // If there was no change in validity, don't update the model 25340 // This prevents changing an invalid modelValue to undefined 25341 if (!allowInvalid && prevValid !== allValid) { 25342 // Note: Don't check ctrl.$valid here, as we could have 25343 // external validators (e.g. calculated on the server), 25344 // that just call $setValidity and need the model value 25345 // to calculate their validity. 25346 ctrl.$modelValue = allValid ? modelValue : undefined; 25347 25348 if (ctrl.$modelValue !== prevModelValue) { 25349 ctrl.$$writeModelToScope(); 25350 } 25351 } 25352 }); 25353 25354 }; 25355 25356 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 25357 currentValidationRunId++; 25358 var localValidationRunId = currentValidationRunId; 25359 25360 // check parser error 25361 if (!processParseErrors()) { 25362 validationDone(false); 25363 return; 25364 } 25365 if (!processSyncValidators()) { 25366 validationDone(false); 25367 return; 25368 } 25369 processAsyncValidators(); 25370 25371 function processParseErrors() { 25372 var errorKey = ctrl.$$parserName || 'parse'; 25373 if (isUndefined(parserValid)) {
25374 setValidity(errorKey, null); 25375 } else { 25376 if (!parserValid) { 25377 forEach(ctrl.$validators, function(v, name) { 25378 setValidity(name, null); 25379 }); 25380 forEach(ctrl.$asyncValidators, function(v, name) { 25381 setValidity(name, null); 25382 }); 25383 } 25384 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 25385 setValidity(errorKey, parserValid); 25386 return parserValid; 25387 } 25388 return true; 25389 } 25390 25391 function processSyncValidators() { 25392 var syncValidatorsValid = true; 25393 forEach(ctrl.$validators, function(validator, name) { 25394 var result = validator(modelValue, viewValue); 25395 syncValidatorsValid = syncValidatorsValid && result; 25396 setValidity(name, result); 25397 }); 25398 if (!syncValidatorsValid) { 25399 forEach(ctrl.$asyncValidators, function(v, name) { 25400 setValidity(name, null); 25401 }); 25402 return false; 25403 } 25404 return true; 25405 } 25406 25407 function processAsyncValidators() { 25408 var validatorPromises = []; 25409 var allValid = true; 25410 forEach(ctrl.$asyncValidators, function(validator, name) { 25411 var promise = validator(modelValue, viewValue); 25412 if (!isPromiseLike(promise)) { 25413 throw ngModelMinErr("$asyncValidators", 25414 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 25415 } 25416 setValidity(name, undefined); 25417 validatorPromises.push(promise.then(function() { 25418 setValidity(name, true); 25419 }, function(error) { 25420 allValid = false; 25421 setValidity(name, false); 25422 })); 25423 }); 25424 if (!validatorPromises.length) { 25425 validationDone(true); 25426 } else { 25427 $q.all(validatorPromises).then(function() { 25428 validationDone(allValid); 25429 }, noop); 25430 } 25431 } 25432 25433 function setValidity(name, isValid) { 25434 if (localValidationRunId === currentValidationRunId) { 25435 ctrl.$setValidity(name, isValid); 25436 } 25437 } 25438 25439 function validationDone(allValid) { 25440 if (localValidationRunId === currentValidationRunId) { 25441 25442 doneCallback(allValid); 25443 } 25444 } 25445 }; 25446 25447 /** 25448 * @ngdoc method 25449 * @name ngModel.NgModelController#$commitViewValue 25450 * 25451 * @description 25452 * Commit a pending update to the `$modelValue`. 25453 * 25454 * Updates may be pending by a debounced event or because the input is waiting for a some future 25455 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 25456 * usually handles calling this in response to input events. 25457 */ 25458 this.$commitViewValue = function() { 25459 var viewValue = ctrl.$viewValue; 25460 25461 $timeout.cancel(pendingDebounce); 25462 25463 // If the view value has not changed then we should just exit, except in the case where there is 25464 // a native validator on the element. In this case the validation state may have changed even though 25465 // the viewValue has stayed empty. 25466 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 25467 return; 25468 } 25469 ctrl.$$lastCommittedViewValue = viewValue; 25470 25471 // change to dirty 25472 if (ctrl.$pristine) { 25473 this.$setDirty(); 25474 } 25475 this.$$parseAndValidate(); 25476 }; 25477 25478 this.$$parseAndValidate = function() { 25479 var viewValue = ctrl.$$lastCommittedViewValue; 25480 var modelValue = viewValue; 25481 parserValid = isUndefined(modelValue) ? undefined : true; 25482 25483 if (parserValid) { 25484 for (var i = 0; i < ctrl.$parsers.length; i++) { 25485 modelValue = ctrl.$parsers[i](modelValue); 25486 if (isUndefined(modelValue)) { 25487 parserValid = false; 25488 break; 25489 } 25490 } 25491 } 25492 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 25493 // ctrl.$modelValue has not been touched yet... 25494 ctrl.$modelValue = ngModelGet($scope); 25495 } 25496 var prevModelValue = ctrl.$modelValue; 25497 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 25498 ctrl.$$rawModelValue = modelValue; 25499 25500 if (allowInvalid) { 25501 ctrl.$modelValue = modelValue; 25502 writeToModelIfNeeded(); 25503 } 25504 25505 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 25506 // This can happen if e.g. $setViewValue is called from inside a parser 25507 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 25508 if (!allowInvalid) { 25509 // Note: Don't check ctrl.$valid here, as we could have 25510 // external validators (e.g. calculated on the server), 25511 // that just call $setValidity and need the model value 25512 // to calculate their validity. 25513 ctrl.$modelValue = allValid ? modelValue : undefined; 25514 writeToModelIfNeeded(); 25515 } 25516 }); 25517 25518 function writeToModelIfNeeded() { 25519 if (ctrl.$modelValue !== prevModelValue) { 25520 ctrl.$$writeModelToScope(); 25521 } 25522 } 25523 }; 25524 25525 this.$$writeModelToScope = function() { 25526 ngModelSet($scope, ctrl.$modelValue);
25527 forEach(ctrl.$viewChangeListeners, function(listener) { 25528 try { 25529 listener(); 25530 } catch (e) { 25531 $exceptionHandler(e); 25532 } 25533 }); 25534 }; 25535 25536 /** 25537 * @ngdoc method 25538 * @name ngModel.NgModelController#$setViewValue 25539 * 25540 * @description 25541 * Update the view value. 25542 * 25543 * This method should be called when a control wants to change the view value; typically, 25544 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 25545 * directive calls it when the value of the input changes and {@link ng.directive:select select} 25546 * calls it when an option is selected. 25547 * 25548 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 25549 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 25550 * value sent directly for processing, finally to be applied to `$modelValue` and then the 25551 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, 25552 * in the `$viewChangeListeners` list, are called. 25553 * 25554 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 25555 * and the `default` trigger is not listed, all those actions will remain pending until one of the 25556 * `updateOn` events is triggered on the DOM element. 25557 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 25558 * directive is used with a custom debounce for this particular event. 25559 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 25560 * is specified, once the timer runs out. 25561 * 25562 * When used with standard inputs, the view value will always be a string (which is in some cases 25563 * parsed into another type, such as a `Date` object for `input[date]`.) 25564 * However, custom controls might also pass objects to this method. In this case, we should make 25565 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 25566 * perform a deep watch of objects, it only looks for a change of identity. If you only change 25567 * the property of the object then ngModel will not realise that the object has changed and 25568 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 25569 * not change properties of the copy once it has been passed to `$setViewValue`. 25570 * Otherwise you may cause the model value on the scope to change incorrectly. 25571 * 25572 * <div class="alert alert-info"> 25573 * In any case, the value passed to the method should always reflect the current value 25574 * of the control. For example, if you are calling `$setViewValue` for an input element, 25575 * you should pass the input DOM value. Otherwise, the control and the scope model become 25576 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 25577 * the control's DOM value in any way. If we want to change the control's DOM value 25578 * programmatically, we should update the `ngModel` scope expression. Its new value will be 25579 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 25580 * to update the DOM, and finally call `$validate` on it. 25581 * </div> 25582 * 25583 * @param {*} value value from the view. 25584 * @param {string} trigger Event that triggered the update. 25585 */ 25586 this.$setViewValue = function(value, trigger) { 25587 ctrl.$viewValue = value; 25588 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 25589 ctrl.$$debounceViewValueCommit(trigger); 25590 } 25591 }; 25592 25593 this.$$debounceViewValueCommit = function(trigger) { 25594 var debounceDelay = 0, 25595 options = ctrl.$options, 25596 debounce; 25597 25598 if (options && isDefined(options.debounce)) { 25599 debounce = options.debounce; 25600 if (isNumber(debounce)) { 25601 debounceDelay = debounce; 25602 } else if (isNumber(debounce[trigger])) { 25603 debounceDelay = debounce[trigger]; 25604 } else if (isNumber(debounce['default'])) { 25605 debounceDelay = debounce['default']; 25606 } 25607 } 25608 25609 $timeout.cancel(pendingDebounce); 25610 if (debounceDelay) { 25611 pendingDebounce = $timeout(function() { 25612 ctrl.$commitViewValue(); 25613 }, debounceDelay); 25614 } else if ($rootScope.$$phase) { 25615 ctrl.$commitViewValue(); 25616 } else { 25617 $scope.$apply(function() { 25618 ctrl.$commitViewValue(); 25619 }); 25620 } 25621 }; 25622 25623 // model -> value 25624 // Note: we cannot use a normal scope.$watch as we want to detect the following: 25625 // 1. scope value is 'a' 25626 // 2. user enters 'b' 25627 // 3. ng-change kicks in and reverts scope value to 'a' 25628 // ->
25628 scope value did not change since the last digest as 25629 // ng-change executes in apply phase 25630 // 4. view should be changed back to 'a' 25631 $scope.$watch(function ngModelWatch() { 25632 var modelValue = ngModelGet($scope); 25633 25634 // if scope model value and ngModel value are out of sync 25635 // TODO(perf): why not move this to the action fn? 25636 if (modelValue !== ctrl.$modelValue && 25637 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 25638 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 25639 ) { 25640 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 25641 parserValid = undefined; 25642 25643 var formatters = ctrl.$formatters, 25644 idx = formatters.length; 25645 25646 var viewValue = modelValue; 25647 while (idx--) { 25648 viewValue = formatters[idx](viewValue); 25649 } 25650 if (ctrl.$viewValue !== viewValue) { 25651 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 25652 ctrl.$render(); 25653 25654 ctrl.$$runValidators(modelValue, viewValue, noop); 25655 } 25656 } 25657 25658 return modelValue; 25659 }); 25660}]; 25661 25662 25663/** 25664 * @ngdoc directive 25665 * @name ngModel 25666 * 25667 * @element input 25668 * @priority 1 25669 * 25670 * @description 25671 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 25672 * property on the scope using {@link ngModel.NgModelController NgModelController}, 25673 * which is created and exposed by this directive. 25674 * 25675 * `ngModel` is responsible for: 25676 * 25677 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 25678 * require. 25679 * - Providing validation behavior (i.e. required, number, email, url). 25680 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 25681 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. 25682 * - Registering the control with its parent {@link ng.directive:form form}. 25683 * 25684 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 25685 * current scope. If the property doesn't already exist on this scope, it will be created 25686 * implicitly and added to the scope. 25687 * 25688 * For best practices on using `ngModel`, see: 25689 * 25690 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 25691 * 25692 * For basic examples, how to use `ngModel`, see: 25693 * 25694 * - {@link ng.directive:input input} 25695 * - {@link input[text] text} 25696 * - {@link input[checkbox] checkbox} 25697 * - {@link input[radio] radio} 25698 * - {@link input[number] number} 25699 * - {@link input[email] email} 25700 * - {@link input[url] url} 25701 * - {@link input[date] date} 25702 * - {@link input[datetime-local] datetime-local} 25703 * - {@link input[time] time} 25704 * - {@link input[month] month} 25705 * - {@link input[week] week} 25706 * - {@link ng.directive:select select} 25707 * - {@link ng.directive:textarea textarea} 25708 * 25709 * # CSS classes 25710 * The following CSS classes are added and removed on the associated input/select/textarea element 25711 * depending on the validity of the model. 25712 * 25713 * - `ng-valid`: the model is valid 25714 * - `ng-invalid`: the model is invalid 25715 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 25716 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 25717 * - `ng-pristine`: the control hasn't been interacted with yet 25718 * - `ng-dirty`: the control has been interacted with 25719 * - `ng-touched`: the control has been blurred 25720 * - `ng-untouched`: the control hasn't been blurred 25721 * - `ng-pending`: any `$asyncValidators` are unfulfilled 25722 * 25723 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 25724 * 25725 * ## Animation Hooks 25726 * 25727 * Animations within models are triggered when any of the associated CSS classes are added and removed 25728 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 25729 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 25730 * The animations that are triggered within ngModel are similar to how they work in ngClass and 25731 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 25732 * 25733 * The following example shows a simple way to utilize CSS transitions to style an input element
25734 * that has been rendered as invalid after it has been validated: 25735 * 25736 * <pre> 25737 * //be sure to include ngAnimate as a module to hook into more 25738 * //advanced animations 25739 * .my-input { 25740 * transition:0.5s linear all; 25741 * background: white; 25742 * } 25743 * .my-input.ng-invalid { 25744 * background: red; 25745 * color:white; 25746 * } 25747 * </pre> 25748 * 25749 * @example 25750 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 25751 <file name="index.html"> 25752 <script> 25753 angular.module('inputExample', []) 25754 .controller('ExampleController', ['$scope', function($scope) { 25755 $scope.val = '1'; 25756 }]); 25757 </script> 25758 <style> 25759 .my-input { 25760 transition:all linear 0.5s; 25761 background: transparent; 25762 } 25763 .my-input.ng-invalid { 25764 color:white; 25765 background: red; 25766 } 25767 </style> 25768 <p id="inputDescription"> 25769 Update input to see transitions when valid/invalid. 25770 Integer is a valid value. 25771 </p>
25772 <form name="testForm" ng-controller="ExampleController"> 25773 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 25774 aria-describedby="inputDescription" /> 25775 </form> 25776 </file> 25777 * </example> 25778 * 25779 * ## Binding to a getter/setter 25780 * 25781 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 25782 * function that returns a representation of the model when called with zero arguments, and sets 25783 * the internal state of a model when called with an argument. It's sometimes useful to use this 25784 * for models that have an internal representation that's different from what the model exposes 25785 * to the view. 25786 * 25787 * <div class="alert alert-success"> 25788 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 25789 * frequently than other parts of your code. 25790 * </div> 25791 * 25792 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 25793 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 25794 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 25795 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 25796 * 25797 * The following example shows how to use `ngModel` with a getter/setter: 25798 * 25799 * @example 25800 * <example name="ngModel-getter-setter" module="getterSetterExample"> 25801 <file name="index.html">
25802 <div ng-controller="ExampleController"> 25803 <form name="userForm"> 25804 <label>Name: 25805 <input type="text" name="userName" 25806 ng-model="user.name" 25807 ng-model-options="{ getterSetter: true }" /> 25808 </label> 25809 </form> 25810 <pre>user.name = <span ng-bind="user.name()"></span></pre> 25811 </div> 25812 </file> 25813 <file name="app.js"> 25814 angular.module('getterSetterExample', []) 25815 .controller('ExampleController', ['$scope', function($scope) { 25816 var _name = 'Brian'; 25817 $scope.user = { 25818 name: function(newName) { 25819 // Note that newName can be undefined for two reasons: 25820 // 1. Because it is called as a getter and thus called with no arguments 25821 // 2. Because the property should actually be set to undefined. This happens e.g. if the 25822 // input is invalid 25823 return arguments.length ? (_name = newName) : _name; 25824 } 25825 }; 25826 }]); 25827 </file> 25828 * </example> 25829 */ 25830var ngModelDirective = ['$rootScope', function($rootScope) { 25831 return { 25832 restrict: 'A', 25833 require: ['ngModel', '^?form', '^?ngModelOptions'], 25834 controller: NgModelController, 25835 // Prelink needs to run before any input directive 25836 // so that we can set the NgModelOptions in NgModelController 25837 // before anyone else uses it. 25838 priority: 1, 25839 compile: function ngModelCompile(element) { 25840 // Setup initial state of the control 25841 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 25842 25843 return { 25844 pre: function ngModelPreLink(scope, element, attr, ctrls) { 25845 var modelCtrl = ctrls[0], 25846 formCtrl = ctrls[1] || modelCtrl.$$parentForm; 25847 25848 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 25849 25850 // notify others, especially parent forms 25851 formCtrl.$addControl(modelCtrl); 25852 25853 attr.$observe('name', function(newValue) { 25854 if (modelCtrl.$name !== newValue) { 25855 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 25856 } 25857 }); 25858 25859 scope.$on('$destroy', function() { 25860 modelCtrl.$$parentForm.$removeControl(modelCtrl); 25861 }); 25862 }, 25863 post: function ngModelPostLink(scope, element, attr, ctrls) { 25864 var modelCtrl = ctrls[0]; 25865 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 25866 element.on(modelCtrl.$options.updateOn, function(ev) { 25867 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 25868 }); 25869 } 25870 25871 element.on('blur', function(ev) { 25872 if (modelCtrl.$touched) return; 25873 25874 if ($rootScope.$$phase) { 25875 scope.$evalAsync(modelCtrl.$setTouched); 25876 } else { 25877 scope.$apply(modelCtrl.$setTouched); 25878 } 25879 }); 25880 } 25881 }; 25882 } 25883 }; 25884}]; 25885 25886var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 25887 25888/** 25889 * @ngdoc directive 25890 * @name ngModelOptions 25891 * 25892 * @description 25893 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 25894 * events that will trigger a model update and/or a debouncing delay so that the actual update only 25895 * takes place when a timer expires; this timer will be reset after another change takes place. 25896 * 25897 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 25898 * be different from the value in the actual model. This means that if you update the model you 25899 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 25900 * order to make sure it is synchronized with the model and that any debounced action is canceled. 25901 * 25902 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 25903 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 25904 * important because `form` controllers are published to the related scope under the name in their 25905 * `name` attribute. 25906 * 25907 * Any pending changes will take place immediately when an enclosing form is submitted via the 25908 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 25909 * to have access to the updated model. 25910 * 25911 * `ngModelOptions` has an effect on the element it's declared on and its desce
25911ndants. 25912 * 25913 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 25914 * - `updateOn`: string specifying which event should the input be bound to. You can set several 25915 * events using an space delimited list. There is a special event called `default` that 25916 * matches the default events belonging of the control. 25917 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 25918 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 25919 * custom value for each event. For example: 25920 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 25921 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 25922 * not validate correctly instead of the default behavior of setting the model to undefined. 25923 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 25924 `ngModel` as getters/setters. 25925 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 25926 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 25927 * continental US time zone abbreviations, but for general use, use a time zone offset, for 25928 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 25929 * If not specified, the timezone of the browser will be used. 25930 * 25931 * @example 25932 25933 The following example shows how to override immediate updates. Changes on the inputs within the 25934 form will update the model only when the control loses focus (blur event). If `escape` key is 25935 pressed while the input field is focused, the value is reset to the value in the current model. 25936 25937 <example name="ngModelOptions-directive-blur" module="optionsExample"> 25938 <file name="index.html">
25939 <div ng-controller="ExampleController"> 25940 <form name="userForm"> 25941 <label>Name: 25942 <input type="text" name="userName" 25943 ng-model="user.name" 25944 ng-model-options="{ updateOn: 'blur' }" 25945 ng-keyup="cancel($event)" /> 25946 </label><br /> 25947 <label>Other data: 25948 <input type="text" ng-model="user.data" /> 25949 </label><br /> 25950 </form> 25951 <pre>user.name = <span ng-bind="user.name"></span></pre> 25952 </div> 25953 </file> 25954 <file name="app.js"> 25955 angular.module('optionsExample', []) 25956 .controller('ExampleController', ['$scope', function($scope) { 25957 $scope.user = { name: 'say', data: '' }; 25958 25959 $scope.cancel = function(e) { 25960 if (e.keyCode == 27) { 25961 $scope.userForm.userName.$rollbackViewValue(); 25962 } 25963 }; 25964 }]); 25965 </file> 25966 <file name="protractor.js" type="protractor"> 25967 var model = element(by.binding('user.name')); 25968 var input = element(by.model('user.name')); 25969 var other = element(by.model('user.data')); 25970 25971 it('should allow custom events', function() { 25972 input.sendKeys(' hello'); 25973 input.click(); 25974 expect(model.getText()).toEqual('say'); 25975 other.click(); 25976 expect(model.getText()).toEqual('say hello'); 25977 }); 25978 25979 it('should $rollbackViewValue when model changes', function() { 25980 input.sendKeys(' hello'); 25981 expect(input.getAttribute('value')).toEqual('say hello'); 25982 input.sendKeys(protractor.Key.ESCAPE); 25983 expect(input.getAttribute('value')).toEqual('say'); 25984 other.click(); 25985 expect(model.getText()).toEqual('say'); 25986 }); 25987 </file> 25988 </example> 25989 25990 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 25991 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 25992 25993 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 25994 <file name="index.html">
25995 <div ng-controller="ExampleController"> 25996 <form name="userForm"> 25997 <label>Name: 25998 <input type="text" name="userName" 25999 ng-model="user.name" 26000 ng-model-options="{ debounce: 1000 }" /> 26001 </label> 26002 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 26003 <br /> 26004 </form> 26005 <pre>user.name = <span ng-bind="user.name"></span></pre> 26006 </div> 26007 </file> 26008 <file name="app.js"> 26009 angular.module('optionsExample', []) 26010 .controller('ExampleController', ['$scope', function($scope) { 26011 $scope.user = { name: 'say' }; 26012 }]); 26013 </file> 26014 </example> 26015 26016 This one shows how to bind to getter/setters: 26017 26018 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 26019 <file name="index.html"> 26020 <div ng-controller="ExampleController"> 26021 <form name="userForm"> 26022 <label>Name: 26023 <input type="text" name="userName" 26024 ng-model="user.name" 26025 ng-model-options="{ getterSetter: true }" /> 26026 </label> 26027 </form> 26028 <pre>user.name = <span ng-bind="user.name()"></span></pre> 26029 </div> 26030 </file> 26031 <file name="app.js"> 26032 angular.module('getterSetterExample', []) 26033 .controller('ExampleController', ['$scope', function($scope) { 26034 var _name = 'Brian'; 26035 $scope.user = { 26036 name: function(newName) { 26037 // Note that newName can be undefined for two reasons: 26038 // 1. Because it is called as a getter and thus called with no arguments 26039 // 2. Because the property should actually be set to undefined. This happens e.g. if the 26040 // input is invalid 26041 return arguments.length ? (_name = newName) : _name; 26042 } 26043 }; 26044 }]); 26045 </file> 26046 </example> 26047 */ 26048var ngModelOptionsDirective = function() { 26049 return { 26050 restrict: 'A', 26051 controller: ['$scope', '$attrs', function($scope, $attrs) { 26052 var that = this; 26053 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 26054 // Allow adding/overriding bound events 26055 if (isDefined(this.$options.updateOn)) { 26056 this.$options.updateOnDefault = false;
26057 // extract "default" pseudo-event from list of events that can trigger a model update 26058 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 26059 that.$options.updateOnDefault = true; 26060 return ' '; 26061 })); 26062 } else { 26063 this.$options.updateOnDefault = true; 26064 } 26065 }] 26066 }; 26067}; 26068 26069 26070 26071// helper methods 26072function addSetValidityMethod(context) { 26073 var ctrl = context.ctrl, 26074 $element = context.$element, 26075 classCache = {}, 26076 set = context.set, 26077 unset = context.unset, 26078 $animate = context.$animate; 26079 26080 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 26081 26082 ctrl.$setValidity = setValidity; 26083 26084 function setValidity(validationErrorKey, state, controller) { 26085 if (isUndefined(state)) { 26086 createAndSet('$pending', validationErrorKey, controller); 26087 } else { 26088 unsetAndCleanup('$pending', validationErrorKey, controller); 26089 } 26090 if (!isBoolean(state)) { 26091 unset(ctrl.$error, validationErrorKey, controller); 26092 unset(ctrl.$$success, validationErrorKey, controller); 26093 } else { 26094 if (state) { 26095 unset(ctrl.$error, validationErrorKey, controller); 26096 set(ctrl.$$success, validationErrorKey, controller); 26097 } else { 26098 set(ctrl.$error, validationErrorKey, controller); 26099 unset(ctrl.$$success, validationErrorKey, controller); 26100 } 26101 } 26102 if (ctrl.$pending) { 26103 cachedToggleClass(PENDING_CLASS, true); 26104 ctrl.$valid = ctrl.$invalid = undefined; 26105 toggleValidationCss('', null); 26106 } else { 26107 cachedToggleClass(PENDING_CLASS, false); 26108 ctrl.$valid = isObjectEmpty(ctrl.$error); 26109 ctrl.$invalid = !ctrl.$valid; 26110 toggleValidationCss('', ctrl.$valid); 26111 } 26112 26113 // re-read the state as the set/unset methods could have 26114 // combined state in ctrl.$error[validationError] (used for forms), 26115 // where setting/unsetting only increments/decrements the value, 26116 // and does not replace it. 26117 var combinedState; 26118 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 26119 combinedState = undefined; 26120 } else if (ctrl.$error[validationErrorKey]) { 26121 combinedState = false; 26122 } else if (ctrl.$$success[validationErrorKey]) { 26123 combinedState = true; 26124 } else { 26125 combinedState = null; 26126 } 26127
26128 toggleValidationCss(validationErrorKey, combinedState); 26129 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 26130 } 26131 26132 function createAndSet(name, value, controller) { 26133 if (!ctrl[name]) { 26134 ctrl[name] = {}; 26135 } 26136 set(ctrl[name], value, controller); 26137 } 26138 26139 function unsetAndCleanup(name, value, controller) { 26140 if (ctrl[name]) { 26141 unset(ctrl[name], value, controller); 26142 } 26143 if (isObjectEmpty(ctrl[name])) { 26144 ctrl[name] = undefined; 26145 } 26146 } 26147 26148 function cachedToggleClass(className, switchValue) { 26149 if (switchValue && !classCache[className]) { 26150 $animate.addClass($element, className); 26151 classCache[className] = true; 26152 } else if (!switchValue && classCache[className]) { 26153 $animate.removeClass($element, className); 26154 classCache[className] = false; 26155 } 26156 } 26157 26158 function toggleValidationCss(validationErrorKey, isValid) { 26159 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 26160 26161 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 26162 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 26163 } 26164} 26165 26166function isObjectEmpty(obj) { 26167 if (obj) { 26168 for (var prop in obj) { 26169 if (obj.hasOwnProperty(prop)) { 26170 return false; 26171 } 26172 } 26173 } 26174 return true; 26175} 26176 26177/** 26178 * @ngdoc directive 26179 * @name ngNonBindable 26180 * @restrict AC 26181 * @priority 1000 26182 * 26183 * @description 26184 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 26185 * DOM element. This is useful if the element contains what appears to be Angular directives and 26186 * bindings but which should be ignored by Angular. This could be the case if you have a site that 26187 * displays snippets of code, for instance. 26188 * 26189 * @element ANY 26190 * 26191 * @example 26192 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 26193 * but the one wrapped in `ngNonBindable` is left alone. 26194 * 26195 * @example 26196 <example> 26197 <file name="index.html"> 26198 <div>Normal: {{1 + 2}}</div> 26199 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 26200 </file> 26201 <file name="protractor.js" type="protractor"> 26202 it('should check ng-non-bindable', function() { 26203 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 26204 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 26205 }); 26206 </file> 26207 </example> 26208 */ 26209var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 26210 26211/* global jqLiteRemove */ 26212 26213var ngOptionsMinErr = minErr('ngOptions'); 26214 26215/** 26216 * @ngdoc directive 26217 * @name ngOptions 26218 * @restrict A 26219 * 26220 * @description 26221 * 26222 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 26223 * elements for the `<select>` element using the array or object obtained by evaluating the 26224 * `ngOptions` comprehension expression. 26225 * 26226 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 26227 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 26228 * increasing speed by not creating a new scope for each repeated instance, as well as providing 26229 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 26230 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 26231 * to a non-string value. This is because an option element can only be bound to string values at 26232 * present. 26233 * 26234 * When an item in the `<select>` menu is selected, the array element or object property 26235 * represented by the selected option will be bound to the model identified by the `ngModel` 26236 * directive. 26237 * 26238 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 26239 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 26240 * option. See example below for demonstration. 26241 * 26242 * ## Complex Models (objects or collections) 26243 * 26244 * **Note:** By default, `ngModel` watches the model by reference, not value. This is important when 26245 * binding any input directive to a model that is an object or a collection. 26246 * 26247 * Since this is a common situation for `ngOptions` the directive additionally watches the model using 26248 * `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in 26249 * the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual 26250 * object/collection has not changed identity but only a property on the object or an item in the collection 26251 * changes. 26252 *
26253 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 26254 * if the model is an array). This means that changing a property deeper inside the object/collection that the 26255 * first level will not trigger a re-rendering. 26256 * 26257 * 26258 * ## `select` **`as`** 26259 * 26260 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 26261 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 26262 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 26263 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 26264 * 26265 * 26266 * ### `select` **`as`** and **`track by`** 26267 * 26268 * <div class="alert alert-warning"> 26269 * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together. 26270 * </div> 26271 * 26272 * Consider the following example: 26273 * 26274 * ```html 26275 * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"></select> 26276 * ``` 26277 * 26278 * ```js 26279 * $scope.values = [{ 26280 * id: 1, 26281 * label: 'aLabel', 26282 * subItem: { name: 'aSubItem' } 26283 * }, { 26284 * id: 2, 26285 * label: 'bLabel', 26286 * subItem: { name: 'bSubItem' } 26287 * }]; 26288 * 26289 * $scope.selected = { name: 'aSubItem' }; 26290 * ``` 26291 * 26292 * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element 26293 * of the data source (to `item` in this example). To calculate whether an element is selected, we do the 26294 * following: 26295 * 26296 * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]` 26297 * 2. Apply **`track by`** to the already selected value in `ngModel`. 26298 * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected 26299 * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to 26300 * a wrong object, the selected element can't be found, `<select>` is always reset to the "not 26301 * selected" option. 26302 * 26303 * 26304 * @param {string} ngModel Assignable angular expression to data-bind to. 26305 * @param {string=} name Property name of the form under which the control is published. 26306 * @param {string=} required The control is considered valid only if value is entered. 26307 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26308 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26309 * `required` when you want to data-bind to the `required` attribute. 26310 * @param {comprehension_expression=} ngOptions in one of the following forms: 26311 * 26312 * * for array data sources: 26313 * * `label` **`for`** `value` **`in`** `array` 26314 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 26315 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 26316 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 26317 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 26318 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 26319 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 26320 * (for including a filter with `track by`) 26321 * * for object data sources: 26322 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 26323 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 26324 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 26325 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 26326 * * `select` **`as`** `label` **`group by`** `group` 26327 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 26328 * * `select` **`as`** `label` **`disable when`** `disable` 26329 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 26330 * 26331 * Where: 26332 * 26333 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 26334 * * `value`: local variable which will refer to each item in the `array` or each property value 26335 * of `object` during iteration. 26336 * * `key`: local variable which will refer to a property name in `object` during iteration. 26337 * * `label`: The result of this expression will be the label for `<option>` element. The 26338 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 26339 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 26340 * element. If not specified, `select` expression will default to `value`. 26341 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 26342 * DOM element. 26343 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 26344 * element. Return `true` to disable. 26345 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 26346 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 26347 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 26348 * even when the options are recreated (e.g. reloaded from the server). 26349 * 26350 * @example 26351 <example module="selectExample"> 26352 <file name="index.html"> 26353 <script> 26354 angular.module('selectExample', [])
26355 .controller('ExampleController', ['$scope', function($scope) { 26356 $scope.colors = [ 26357 {name:'black', shade:'dark'}, 26358 {name:'white', shade:'light', notAnOption: true}, 26359 {name:'red', shade:'dark'}, 26360 {name:'blue', shade:'dark', notAnOption: true}, 26361 {name:'yellow', shade:'light', notAnOption: false} 26362 ]; 26363 $scope.myColor = $scope.colors[2]; // red 26364 }]); 26365 </script> 26366 <div ng-controller="ExampleController"> 26367 <ul> 26368 <li ng-repeat="color in colors"> 26369 <label>Name: <input ng-model="color.name"></label> 26370 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 26371 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 26372 </li> 26373 <li> 26374 <button ng-click="colors.push({})">add</button> 26375 </li> 26376 </ul> 26377 <hr/> 26378 <label>Color (null not allowed): 26379 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 26380 </label><br/> 26381 <label>Color (null allowed): 26382 <span class="nullable"> 26383 <select ng-model="myColor" ng-options="color.name for color in colors"> 26384 <option value="">-- choose color --</option> 26385 </select> 26386 </span></label><br/> 26387 26388 <label>Color grouped by shade: 26389 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 26390 </select> 26391 </label><br/> 26392 26393 <label>Color grouped by shade, with some disabled: 26394 <select ng-model="myColor" 26395 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 26396 </select> 26397 </label><br/> 26398 26399 26400 26401 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 26402 <br/> 26403 <hr/> 26404 Currently selected: {{ {selected_color:myColor} }} 26405 <div style="border:solid 1px black; height:20px" 26406 ng-style="{'background-color':myColor.name}"> 26407 </div> 26408 </div> 26409 </file> 26410 <file name="protractor.js" type="protractor"> 26411 it('should check ng-options', function() { 26412 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 26413 element.all(by.model('myColor')).first().click(); 26414 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 26415 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 26416 element(by.css('.nullable select[ng-model="myColor"]')).click(); 26417 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 26418 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 26419 }); 26420 </file> 26421 </example> 26422 */ 26423 26424// jshint maxlen: false 26425// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 26426var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/; 26427 // 1: value expression (valueFn) 26428 // 2: label expression (displayFn) 26429 // 3: group by expression (groupByFn) 26430 // 4: disable when expression (disableWhenFn) 26431 // 5: array item variable name 26432 // 6: object item key variable name 26433 // 7: object item value variable name 26434 // 8: collection expression 26435 // 9: track by expression 26436// jshint maxlen: 100 26437 26438 26439var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { 26440 26441 function parseOptionsExpression(optionsExp, selectElement, scope) { 26442 26443 var match = optionsExp.match(NG_OPTIONS_REGEXP); 26444 if (!(match)) { 26445 throw ngOptionsMinErr('iexp', 26446 "Expected expression in form of " + 26447 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 26448 " but got '{0}'. Element: {1}", 26449 optionsExp, startingTag(selectElement)); 26450 } 26451 26452 // Extract the parts from the ngOptions expression 26453 26454 // The variable name for the value of the item in the collection 26455 var valueName = match[5] || match[7]; 26456 // The variable name for the key of the item in the collection 26457 var keyName = match[6]; 26458 26459 // An expression that generates the viewValue for an option if there is a label expression 26460 var selectAs = / as /.test(match[0]) && match[1]; 26461 // An expression that is used to track the id of each object in the options collection 26462 var trackBy = match[9]; 26463 // An expression that generates the viewValue for an option if there is no label expression 26464 var valueFn = $parse(match[2] ? match[1] : valueName); 26465 var selectAsFn = selectAs && $parse(selectAs); 26466 var viewValueFn = selectAsFn || valueFn; 26467 var trackByFn = trackBy && $parse(trackBy); 26468 26469 // Get the value by which we are going to track the option 26470 // if we have a trackFn then use that (passing scope and locals) 26471 // otherwise just hash the given viewValue 26472 var getTrackByValueFn = trackBy ? 26473 function(value, locals) { return trackByFn(scope, locals); } : 26474 function getHashOfValue(value) { return hashKey(value); }; 26475 var getTrackByValue = function(value, key) {
vendor: 5,716 bytes, lines 26476-26636
26476 return getTrackByValueFn(value, getLocals(value, key)); 26477 }; 26478 26479 var displayFn = $parse(match[2] || match[1]); 26480 var groupByFn = $parse(match[3] || ''); 26481 var disableWhenFn = $parse(match[4] || ''); 26482 var valuesFn = $parse(match[8]); 26483 26484 var locals = {}; 26485 var getLocals = keyName ? function(value, key) { 26486 locals[keyName] = key; 26487 locals[valueName] = value; 26488 return locals; 26489 } : function(value) { 26490 locals[valueName] = value; 26491 return locals; 26492 }; 26493 26494 26495 function Option(selectValue, viewValue, label, group, disabled) { 26496 this.selectValue = selectValue; 26497 this.viewValue = viewValue; 26498 this.label = label; 26499 this.group = group; 26500 this.disabled = disabled; 26501 } 26502 26503 function getOptionValuesKeys(optionValues) { 26504 var optionValuesKeys; 26505 26506 if (!keyName && isArrayLike(optionValues)) { 26507 optionValuesKeys = optionValues; 26508 } else { 26509 // if object, extract keys, in enumeration order, unsorted 26510 optionValuesKeys = []; 26511 for (var itemKey in optionValues) { 26512 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 26513 optionValuesKeys.push(itemKey); 26514 } 26515 } 26516 } 26517 return optionValuesKeys; 26518 } 26519 26520 return { 26521 trackBy: trackBy, 26522 getTrackByValue: getTrackByValue, 26523 getWatchables: $parse(valuesFn, function(optionValues) { 26524 // Create a collection of things that we would like to watch (watchedArray) 26525 // so that they can all be watched using a single $watchCollection 26526 // that only runs the handler once if anything changes 26527 var watchedArray = []; 26528 optionValues = optionValues || []; 26529 26530 var optionValuesKeys = getOptionValuesKeys(optionValues); 26531 var optionValuesLength = optionValuesKeys.length; 26532 for (var index = 0; index < optionValuesLength; index++) { 26533 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 26534 var value = optionValues[key]; 26535 26536 var locals = getLocals(optionValues[key], key); 26537 var selectValue = getTrackByValueFn(optionValues[key], locals); 26538 watchedArray.push(selectValue); 26539 26540 // Only need to watch the displayFn if there is a specific label expression 26541 if (match[2] || match[1]) { 26542 var label = displayFn(scope, locals); 26543 watchedArray.push(label); 26544 } 26545 26546 // Only need to watch the disableWhenFn if there is a specific disable expression 26547 if (match[4]) { 26548 var disableWhen = disableWhenFn(scope, locals); 26549 watchedArray.push(disableWhen); 26550 } 26551 } 26552 return watchedArray; 26553 }), 26554 26555 getOptions: function() { 26556 26557 var optionItems = []; 26558 var selectValueMap = {}; 26559 26560 // The option values were already computed in the `getWatchables` fn, 26561 // which must have been called to trigger `getOptions` 26562 var optionValues = valuesFn(scope) || []; 26563 var optionValuesKeys = getOptionValuesKeys(optionValues); 26564 var optionValuesLength = optionValuesKeys.length; 26565 26566 for (var index = 0; index < optionValuesLength; index++) { 26567 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 26568 var value = optionValues[key]; 26569 var locals = getLocals(value, key); 26570 var viewValue = viewValueFn(scope, locals); 26571 var selectValue = getTrackByValueFn(viewValue, locals); 26572 var label = displayFn(scope, locals); 26573 var group = groupByFn(scope, locals); 26574 var disabled = disableWhenFn(scope, locals); 26575 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 26576 26577 optionItems.push(optionItem); 26578 selectValueMap[selectValue] = optionItem; 26579 } 26580 26581 return { 26582 items: optionItems, 26583 selectValueMap: selectValueMap, 26584 getOptionFromViewValue: function(value) { 26585 return selectValueMap[getTrackByValue(value)]; 26586 }, 26587 getViewValueFromOption: function(option) { 26588 // If the viewValue could be an object that may be mutated by the application, 26589 // we need to make a copy and not return the reference to the value on the option. 26590 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 26591 } 26592 }; 26593 } 26594 }; 26595 } 26596 26597 26598 // we can't just jqLite('<option>') since jqLite is not smart enough 26599 // to create it in <select> and IE barfs otherwise. 26600 var optionTemplate = document.createElement('option'), 26601 optGroupTemplate = document.createElement('optgroup'); 26602 26603 return { 26604 restrict: 'A', 26605 terminal: true, 26606 require: ['select', '?ngModel'], 26607 link: function(scope, selectElement, attr, ctrls) { 26608 26609 // if ngModel is not defined, we don't need to do anything 26610 var ngModelCtrl = ctrls[1]; 26611 if (!ngModelCtrl) return; 26612 26613 var selectCtrl = ctrls[0]; 26614 var multiple = attr.multiple; 26615 26616 // The emptyOption allows the application developer to provide their own custom "empty" 26617 // option when the viewValue does not match any of the option values. 26618 var emptyOption; 26619 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 26620 if (children[i].value === '') { 26621 emptyOption = children.eq(i); 26622 break; 26623 } 26624 } 26625 26626 var providedEmptyOption = !!emptyOption; 26627 26628 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 26629 unknownOption.val('?'); 26630 26631 var options; 26632 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 26633 26634 26635 var renderEmptyOption = function() { 26636 if (!providedEmptyOption) {
26637 selectElement.prepend(emptyOption); 26638 } 26639 selectElement.val(''); 26640 emptyOption.prop('selected', true); // needed for IE 26641 emptyOption.attr('selected', true); 26642 }; 26643 26644 var removeEmptyOption = function() { 26645 if (!providedEmptyOption) { 26646 emptyOption.remove(); 26647 } 26648 }; 26649 26650 26651 var renderUnknownOption = function() { 26652 selectElement.prepend(unknownOption); 26653 selectElement.val('?'); 26654 unknownOption.prop('selected', true); // needed for IE 26655 unknownOption.attr('selected', true); 26656 }; 26657 26658 var removeUnknownOption = function() { 26659 unknownOption.remove(); 26660 }; 26661 26662 26663 // Update the controller methods for multiple selectable options 26664 if (!multiple) { 26665 26666 selectCtrl.writeValue = function writeNgOptionsValue(value) { 26667 var option = options.getOptionFromViewValue(value); 26668 26669 if (option && !option.disabled) { 26670 if (selectElement[0].value !== option.selectValue) { 26671 removeUnknownOption(); 26672 removeEmptyOption(); 26673 26674 selectElement[0].value = option.selectValue; 26675 option.element.selected = true; 26676 option.element.setAttribute('selected', 'selected'); 26677 } 26678 } else { 26679 if (value === null || providedEmptyOption) { 26680 removeUnknownOption(); 26681 renderEmptyOption(); 26682 } else { 26683 removeEmptyOption(); 26684 renderUnknownOption(); 26685 } 26686 } 26687 }; 26688 26689 selectCtrl.readValue = function readNgOptionsValue() { 26690 26691 var selectedOption = options.selectValueMap[selectElement.val()]; 26692 26693 if (selectedOption && !selectedOption.disabled) { 26694 removeEmptyOption(); 26695 removeUnknownOption(); 26696 return options.getViewValueFromOption(selectedOption); 26697 } 26698 return null; 26699 }; 26700 26701 // If we are using `track by` then we must watch the tracked value on the model 26702 // since ngModel only watches for object identity change 26703 if (ngOptions.trackBy) { 26704 scope.$watch( 26705 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 26706 function() { ngModelCtrl.$render(); } 26707 ); 26708 } 26709 26710 } else { 26711 26712 ngModelCtrl.$isEmpty = function(value) { 26713 return !value || value.length === 0; 26714 }; 26715 26716 26717 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 26718 options.items.forEach(function(option) { 26719 option.element.selected = false; 26720 }); 26721 26722 if (value) { 26723 value.forEach(function(item) { 26724 var option = options.getOptionFromViewValue(item); 26725 if (option && !option.disabled) option.element.selected = true; 26726 }); 26727 } 26728 }; 26729 26730 26731 selectCtrl.readValue = function readNgOptionsMultiple() { 26732 var selectedValues = selectElement.val() || [], 26733 selections = []; 26734 26735 forEach(selectedValues, function(value) { 26736 var option = options.selectValueMap[value]; 26737 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 26738 }); 26739 26740 return selections; 26741 }; 26742 26743 // If we are using `track by` then we must watch these tracked values on the model 26744 // since ngModel only watches for object identity change 26745 if (ngOptions.trackBy) { 26746 26747 scope.$watchCollection(function() { 26748 if (isArray(ngModelCtrl.$viewValue)) { 26749 return ngModelCtrl.$viewValue.map(function(value) { 26750 return ngOptions.getTrackByValue(value); 26751 }); 26752 } 26753 }, function() { 26754 ngModelCtrl.$render(); 26755 }); 26756 26757 } 26758 } 26759 26760 26761 if (providedEmptyOption) { 26762 26763 // we need to remove it before calling selectElement.empty() because otherwise IE will 26764 // remove the label from the element. wtf? 26765 emptyOption.remove(); 26766 26767 // compile the element since there might be bindings in it 26768 $compile(emptyOption)(scope); 26769 26770 // remove the class, which is added automatically because we recompile the element and it 26771 // becomes the compilation root 26772 emptyOption.removeClass('ng-scope'); 26773 } else { 26774 emptyOption = jqLite(optionTemplate.cloneNode(false)); 26775 } 26776 26777 // We need to do this here to ensure that the options object is defined 26778 // when we first hit it in writeNgOptionsValue 26779 updateOptions(); 26780 26781 // We will re-render the option elements if the option values or labels change 26782 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 26783 26784 // ------------------------------------------------------------------ // 26785 26786 26787 function updateOptionElement(option, element) { 26788 option.element = element; 26789 element.disabled = option.disabled; 26790 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive 26791 // selects in certain circumstances when multiple selects are next to each other and display 26792 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 26793 // See https://github.com/angular/angular.js/issues/11314 for more info. 26794 // This is unfortunately untestable with unit / e2e tests 26795 if (option.label !== element.label) { 26796 element.label = option.label; 26797 element.textContent = option.label; 26798 } 26799 if (option.value !== element.value) element.value = option.selectValue; 26800 } 26801 26802 function addOrReuseElement(parent, current, type, templateElement) { 26803 var element; 26804 // Check whether we can reuse the next element 26805 if (current && lowercase(current.nodeName) === type) { 26806 // The next element is the right type so reuse it 26807 element = current; 26808 } else { 26809 // The next element is not the right type so create a new one 26810 element = templateElement.cloneNode(false); 26811 if (!current) { 26812 // There are no more elements so just append it to the select 26813 parent.appendChild(element); 26814 } else { 26815 // The next element is not a group so insert the new one 26816 parent.insertBefore(element, current); 26817 } 26818 } 26819 return element; 26820 } 26821 26822 26823 function removeExcessElements(current) { 26824 var next; 26825 while (current) { 26826 next = current.nextSibling; 26827 jqLiteRemove(current); 26828 current = next; 26829 } 26830 } 26831 26832 26833 function skipEmptyAndUnknownOptions(current) { 26834 var emptyOption_ = emptyOption && emptyOption[0]; 26835 var unknownOption_ = unknownOption && unknownOption[0]; 26836 26837 if (emptyOption_ || unknownOption_) { 26838 while (current && 26839 (current === emptyOption_ || 26840 current === unknownOption_ || 26841 emptyOption_ && emptyOption_.nodeType === NODE_TYPE_COMMENT)) { 26842 // Empty options might have directives that transclude 26843 // and insert comments (e.g. ngIf) 26844 current = current.nextSibling; 26845 } 26846 } 26847 return current; 26848 } 26849 26850 26851 function updateOptions() { 26852 26853 var previousValue = options && selectCtrl.readValue(); 26854 26855 options = ngOptions.getOptions(); 26856 26857 var groupMap = {}; 26858 var currentElement = selectElement[0].firstChild; 26859 26860 // Ensure that the empty option is always there if it was explicitly provided 26861 if (providedEmptyOption) {
26862 selectElement.prepend(emptyOption); 26863 } 26864 26865 currentElement = skipEmptyAndUnknownOptions(currentElement); 26866 26867 options.items.forEach(function updateOption(option) { 26868 var group; 26869 var groupElement; 26870 var optionElement; 26871 26872 if (option.group) { 26873 26874 // This option is to live in a group 26875 // See if we have already created this group 26876 group = groupMap[option.group]; 26877 26878 if (!group) { 26879 26880 // We have not already created this group 26881 groupElement = addOrReuseElement(selectElement[0], 26882 currentElement, 26883 'optgroup', 26884 optGroupTemplate); 26885 // Move to the next element 26886 currentElement = groupElement.nextSibling; 26887 26888 // Update the label on the group element 26889 groupElement.label = option.group; 26890 26891 // Store it for use later 26892 group = groupMap[option.group] = { 26893 groupElement: groupElement, 26894 currentOptionElement: groupElement.firstChild 26895 }; 26896 26897 } 26898 26899 // So now we have a group for this option we add the option to the group 26900 optionElement = addOrReuseElement(group.groupElement, 26901 group.currentOptionElement, 26902 'option', 26903 optionTemplate); 26904 updateOptionElement(option, optionElement); 26905 // Move to the next element 26906 group.currentOptionElement = optionElement.nextSibling; 26907 26908 } else { 26909 26910 // This option is not in a group 26911 optionElement = addOrReuseElement(selectElement[0], 26912 currentElement, 26913 'option', 26914 optionTemplate); 26915 updateOptionElement(option, optionElement); 26916 // Move to the next element 26917 currentElement = optionElement.nextSibling; 26918 } 26919 }); 26920 26921 26922 // Now remove all excess options and group 26923 Object.keys(groupMap).forEach(function(key) { 26924 removeExcessElements(groupMap[key].currentOptionElement); 26925 }); 26926 removeExcessElements(currentElement); 26927 26928 ngModelCtrl.$render(); 26929 26930 // Check to see if the value has changed due to the update to the options 26931 if (!ngModelCtrl.$isEmpty(previousValue)) { 26932 var nextValue = selectCtrl.readValue(); 26933 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 26934 ngModelCtrl.$setViewValue(nextValue); 26935 ngModelCtrl.$render(); 26936 } 26937 } 26938 26939 } 26940 26941 } 26942 }; 26943}]; 26944 26945/** 26946 * @ngdoc directive 26947 * @name ngPluralize 26948 * @restrict EA 26949 * 26950 * @description 26951 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 26952 * These rules are bundled with angular.js, but can be overridden 26953 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 26954 * by specifying the mappings between 26955 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 26956 * and the strings to be displayed. 26957 * 26958 * # Plural categories and explicit number rules 26959 * There are two 26960 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 26961 * in Angular's default en-US locale: "one" and "other". 26962 * 26963 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 26964 * any number that is not 1), an explicit number rule can only match one number. For example, the 26965 * explicit number rule for "3" matches the number 3. There are examples of plural categories 26966 * and explicit number rules throughout the rest of this documentation. 26967 * 26968 * # Configuring ngPluralize 26969 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 26970 * You can also provide an optional attribute, `offset`. 26971 * 26972 * The value of the `count` attribute can be either a string or an {@link guide/expression 26973 * Angular expression}; these are evaluated on the current scope for its bound value. 26974 * 26975 * The `when` attribute specifies the mappings between plural categories and the actual 26976 * string to be displayed. The value of the attribute should be a JSON object. 26977 * 26978 * The following example shows how to configure ngPluralize: 26979 * 26980 * ```html 26981 * <ng-pluralize count="personCount"
26982 when="{'0': 'Nobody is viewing.', 26983 * 'one': '1 person is viewing.', 26984 * 'other': '{} people are viewing.'}"> 26985 * </ng-pluralize> 26986 *``` 26987 * 26988 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 26989 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 26990 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 26991 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 26992 * show "a dozen people are viewing". 26993 * 26994 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 26995 * into pluralized strings. In the previous example, Angular will replace `{}` with 26996 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 26997 * for <span ng-non-bindable>{{numberExpression}}</span>. 26998 * 26999 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 27000 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 27001 * 27002 * # Configuring ngPluralize with offset 27003 * The `offset` attribute allows further customization of pluralized text, which can result in 27004 * a better user experience. For example, instead of the message "4 people are viewing this document", 27005 * you might display "John, Kate and 2 others are viewing this document". 27006 * The offset attribute allows you to offset a number by any desired value. 27007 * Let's take a look at an example: 27008 * 27009 * ```html 27010 * <ng-pluralize count="personCount" offset=2 27011 * when="{'0': 'Nobody is viewing.', 27012 * '1': '{{person1}} is viewing.', 27013 * '2': '{{person1}} and {{person2}} are viewing.', 27014 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 27015 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 27016 * </ng-pluralize> 27017 * ``` 27018 * 27019 * Notice that we are still using two plural categories(one, other), but we added 27020 * three explicit number rules 0, 1 and 2. 27021 * When one person, perhaps John, views the document, "John is viewing" will be shown. 27022 * When three people view the document, no explicit number rule is found, so 27023 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 27024 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 27025 * is shown. 27026 * 27027 * Note that when you specify offsets, you must provide explicit number rules for 27028 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 27029 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 27030 * plural categories "one" and "other". 27031 * 27032 * @param {string|expression} count The variable to be bound to. 27033 * @param {string} when The mapping between plural category to its corresponding strings. 27034 * @param {number=} offset Offset to deduct from the total number. 27035 * 27036 * @example 27037 <example module="pluralizeExampl
vendor: 4,913 bytes, lines 27037-27151
27037e"> 27038 <file name="index.html"> 27039 <script> 27040 angular.module('pluralizeExample', []) 27041 .controller('ExampleController', ['$scope', function($scope) { 27042 $scope.person1 = 'Igor'; 27043 $scope.person2 = 'Misko'; 27044 $scope.personCount = 1; 27045 }]); 27046 </script> 27047 <div ng-controller="ExampleController"> 27048 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 27049 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 27050 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 27051 27052 <!--- Example with simple pluralization rules for en locale ---> 27053 Without Offset: 27054 <ng-pluralize count="personCount" 27055 when="{'0': 'Nobody is viewing.', 27056 'one': '1 person is viewing.', 27057 'other': '{} people are viewing.'}"> 27058 </ng-pluralize><br> 27059 27060 <!--- Example with offset ---> 27061 With Offset(2): 27062 <ng-pluralize count="personCount" offset=2 27063 when="{'0': 'Nobody is viewing.', 27064 '1': '{{person1}} is viewing.', 27065 '2': '{{person1}} and {{person2}} are viewing.', 27066 'one': '{{person1}}, {{person2}} and one other person are viewing.', 27067 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 27068 </ng-pluralize> 27069 </div> 27070 </file> 27071 <file name="protractor.js" type="protractor"> 27072 it('should show correct pluralized string', function() { 27073 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 27074 var withOffset = element.all(by.css('ng-pluralize')).get(1); 27075 var countInput = element(by.model('personCount')); 27076 27077 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 27078 expect(withOffset.getText()).toEqual('Igor is viewing.'); 27079 27080 countInput.clear(); 27081 countInput.sendKeys('0'); 27082 27083 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 27084 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 27085 27086 countInput.clear(); 27087 countInput.sendKeys('2'); 27088 27089 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 27090 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 27091 27092 countInput.clear(); 27093 countInput.sendKeys('3'); 27094 27095 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 27096 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 27097 27098 countInput.clear(); 27099 countInput.sendKeys('4'); 27100 27101 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 27102 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 27103 }); 27104 it('should show data-bound names', function() { 27105 var withOffset = element.all(by.css('ng-pluralize')).get(1); 27106 var personCount = element(by.model('personCount')); 27107 var person1 = element(by.model('person1')); 27108 var person2 = element(by.model('person2')); 27109 personCount.clear(); 27110 personCount.sendKeys('4'); 27111 person1.clear(); 27112 person1.sendKeys('Di'); 27113 person2.clear(); 27114 person2.sendKeys('Vojta'); 27115 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 27116 }); 27117 </file> 27118 </example> 27119 */ 27120var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 27121 var BRACE = /{}/g, 27122 IS_WHEN = /^when(Minus)?(.+)$/; 27123 27124 return { 27125 link: function(scope, element, attr) { 27126 var numberExp = attr.count, 27127 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 27128 offset = attr.offset || 0, 27129 whens = scope.$eval(whenExp) || {}, 27130 whensExpFns = {}, 27131 startSymbol = $interpolate.startSymbol(), 27132 endSymbol = $interpolate.endSymbol(), 27133 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 27134 watchRemover = angular.noop, 27135 lastCount; 27136 27137 forEach(attr, function(expression, attributeName) { 27138 var tmpMatch = IS_WHEN.exec(attributeName); 27139 if (tmpMatch) { 27140 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 27141 whens[whenKey] = element.attr(attr.$attr[attributeName]); 27142 } 27143 }); 27144 forEach(whens, function(expression, key) { 27145 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 27146 27147 }); 27148 27149 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 27150 var count = parseFloat(newVal); 27151 var countIsNaN = isN
vendor: 10,776 bytes, lines 27151-27390
27151aN(count); 27152 27153 if (!countIsNaN && !(count in whens)) { 27154 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 27155 // Otherwise, check it against pluralization rules in $locale service. 27156 count = $locale.pluralCat(count - offset); 27157 } 27158 27159 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 27160 // In JS `NaN !== NaN`, so we have to exlicitly check. 27161 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 27162 watchRemover(); 27163 var whenExpFn = whensExpFns[count]; 27164 if (isUndefined(whenExpFn)) { 27165 if (newVal != null) { 27166 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 27167 } 27168 watchRemover = noop; 27169 updateElementText(); 27170 } else { 27171 watchRemover = scope.$watch(whenExpFn, updateElementText); 27172 } 27173 lastCount = count; 27174 } 27175 }); 27176 27177 function updateElementText(newText) { 27178 element.text(newText || ''); 27179 } 27180 } 27181 }; 27182}]; 27183 27184/** 27185 * @ngdoc directive 27186 * @name ngRepeat 27187 * @multiElement 27188 * 27189 * @description 27190 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 27191 * instance gets its own scope, where the given loop variable is set to the current collection item, 27192 * and `$index` is set to the item index or key. 27193 * 27194 * Special properties are exposed on the local scope of each template instance, including: 27195 * 27196 * | Variable | Type | Details | 27197 * |-----------|-----------------|-----------------------------------------------------------------------------| 27198 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 27199 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 27200 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 27201 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 27202 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 27203 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 27204 * 27205 * <div class="alert alert-info"> 27206 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 27207 * This may be useful when, for instance, nesting ngRepeats. 27208 * </div> 27209 * 27210 * 27211 * # Iterating over object properties 27212 * 27213 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 27214 * syntax: 27215 * 27216 * ```js 27217 * <div ng-repeat="(key, value) in myObj"> ... </div> 27218 * ``` 27219 * 27220 * You need to be aware that the JavaScript specification does not define the order of keys 27221 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive 27222 * used to sort the keys alphabetically.) 27223 * 27224 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser 27225 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing 27226 * keys in the order in which they were defined, although there are exceptions when keys are deleted 27227 * and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues 27228 * 27229 * If this is not desired, the recommended workaround is to convert your object into an array 27230 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 27231 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 27232 * or implement a `$watch` on the object yourself. 27233 * 27234 * 27235 * # Tracking and Duplicates 27236 * 27237 * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM: 27238 * 27239 * * When an item is added, a new instance of the template is added to the DOM. 27240 * * When an item is removed, its template instance is removed from the DOM. 27241 * * When items are reordered, their respective templates are reordered in the DOM. 27242 * 27243 * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when 27244 * there are duplicates, it is not possible to maintain a one-to-one mapping between collection 27245 * items and DOM elements. 27246 * 27247 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 27248 * with your own using the `track by` expression. 27249 * 27250 * For example, you may track items by the index of each item in the collection, using the 27251 * special scope property `$index`: 27252 * ```html 27253 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 27254 * {{n}} 27255 * </div> 27256 * ``` 27257 * 27258 * You may use arbitrary expressions in `track by`, including references to custom functions 27259 * on the scope: 27260 * ```html 27261 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 27262 * {{n}} 27263 * </div> 27264 * ``` 27265 * 27266 * If you are working with objects that have an identifier property, you can track 27267 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 27268 * will not have to rebuild the DOM elements for items it has already rendered, even if the 27269 * JavaScript objects in the collection have been substituted for new ones: 27270 * ```html 27271 * <div ng-repeat="model in collection track by model.id"> 27272 * {{model.name}} 27273 * </div> 27274 * ``` 27275 * 27276 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 27277 * `$id` function, which tracks items by their identity: 27278 * ```html 27279 * <div ng-repeat="obj in collection track by $id(obj)"> 27280 * {{obj.prop}} 27281 * </div> 27282 * ``` 27283 * 27284 * <div class="alert alert-warning"> 27285 * **Note:** `track by` must always be the last expression: 27286 * </div> 27287 * ``` 27288 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 27289 * {{model.name}} 27290 * </div> 27291 * ``` 27292 * 27293 * # Special repeat start and end points 27294 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 27295 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 27296 * 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) 27297 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 27298 * 27299 * The example below makes use of this feature: 27300 * ```html 27301 * <header ng-repeat-start="item in items"> 27302 * Header {{ item }} 27303 * </header> 27304 * <div class="body"> 27305 * Body {{ item }} 27306 * </div> 27307 * <footer ng-repeat-end> 27308 * Footer {{ item }} 27309 * </footer> 27310 * ``` 27311 * 27312 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 27313 * ```html 27314 * <header> 27315 * Header A 27316 * </header> 27317 * <div class="body"> 27318 * Body A 27319 * </div> 27320 * <footer> 27321 * Footer A 27322 * </footer> 27323 * <header> 27324 * Header B 27325 * </header> 27326 * <div class="body"> 27327 * Body B 27328 * </div> 27329 * <footer> 27330 * Footer B 27331 * </footer> 27332 * ``` 27333 * 27334 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 27335 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 27336 * 27337 * @animations 27338 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 27339 * 27340 * **.leave** - when an item is removed from the list or when an item is filtered out 27341 * 27342 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 27343 * 27344 * @element ANY 27345 * @scope 27346 * @priority 1000 27347 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 27348 * formats are currently supported: 27349 * 27350 * * `variable in expression` â where variable is the user defined loop variable and `expression` 27351 * is a scope expression giving the collection to enumerate. 27352 * 27353 * For example: `album in artist.albums`. 27354 * 27355 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 27356 * and `expression` is the scope expression giving the collection to enumerate. 27357 * 27358 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 27359 * 27360 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 27361 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 27362 * is specified, ng-repeat associates elements by identity. It is an error to have 27363 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 27364 * mapped to the same DOM element, which is not possible.) 27365 * 27366 * Note that the tracking expression must come last, after any filters, and the alias expression. 27367 * 27368 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 27369 * will be associated by item identity in the array. 27370 * 27371 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 27372 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 27373 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 27374 * element in the same way in the DOM. 27375 * 27376 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 27377 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 27378 * property is same. 27379 * 27380 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 27381 * to items in conjunction with a tracking expression. 27382 * 27383 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 27384 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 27385 * when a filter is active on the repeater, but the filtered result set is empty. 27386 * 27387 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 27388 * the items have been processed through the filter. 27389 * 27390 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax s
27390o it can be used only at the end 27391 * (and not as operator, inside an expression). 27392 * 27393 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 27394 * 27395 * @example 27396 * This example initializes the scope to a list of names and 27397 * then uses `ngRepeat` to display every person: 27398 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27399 <file name="index.html"> 27400 <div ng-init="friends = [ 27401 {name:'John', age:25, gender:'boy'}, 27402 {name:'Jessie', age:30, gender:'girl'}, 27403 {name:'Johanna', age:28, gender:'girl'}, 27404 {name:'Joy', age:15, gender:'girl'}, 27405 {name:'Mary', age:28, gender:'girl'}, 27406 {name:'Peter', age:95, gender:'boy'}, 27407 {name:'Sebastian', age:50, gender:'boy'}, 27408 {name:'Erika', age:27, gender:'girl'}, 27409 {name:'Patrick', age:40, gender:'boy'}, 27410 {name:'Samantha', age:60, gender:'girl'} 27411 ]"> 27412 I have {{friends.length}} friends. They are: 27413 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 27414 <ul class="example-animate-container"> 27415 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 27416 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 27417 </li> 27418 <li class="animate-repeat" ng-if="results.length == 0"> 27419 <strong>No results found...</strong> 27420 </li> 27421 </ul> 27422 </div> 27423 </file> 27424 <file name="animations.css"> 27425 .example-animate-container { 27426 background:white; 27427 border:1px solid black; 27428 list-style:none; 27429 margin:0; 27430 padding:0 10px; 27431 } 27432 27433 .animate-repeat { 27434 line-height:40px; 27435 list-style:none; 27436 box-sizing:border-box; 27437 } 27438 27439 .animate-repeat.ng-move, 27440 .animate-repeat.ng-enter, 27441 .animate-repeat.ng-leave { 27442 transition:all linear 0.5s; 27443 } 27444 27445 .animate-repeat.ng-leave.ng-leave-active, 27446 .animate-repeat.ng-move, 27447 .animate-repeat.ng-enter { 27448 opacity:0; 27449 max-height:0; 27450 } 27451 27452 .animate-repeat.ng-leave, 27453 .animate-repeat.ng-move.ng-move-active, 27454 .animate-repeat.ng-enter.ng-enter-active { 27455 opacity:1; 27456 max-height:40px; 27457 } 27458 </file> 27459 <file name="protractor.js" type="protractor"> 27460 var friends = element.all(by.repeater('friend in friends')); 27461 27462 it('should render initial data set', function() { 27463 expect(friends.count()).toBe(10); 27464 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 27465 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 27466 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 27467 expect(element(by.binding('friends.length')).getText()) 27468 .toMatch("I have 10 friends. They are:"); 27469 }); 27470 27471 it('should update repeater when filter predicate changes', function() { 27472 expect(friends.count()).toBe(10); 27473 27474 element(by.model('q')).sendKeys('ma'); 27475 27476 expect(friends.count()).toBe(2); 27477 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 27478 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 27479 }); 27480 </file> 27481 </example> 27482 */ 27483var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 27484 var NG_REMOVED = '$$NG_REMOVED'; 27485 var ngRepeatMinErr = minErr('ngRepeat'); 27486 27487 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 27488 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 27489 scope[valueIdentifier] = value; 27490 if (keyIdentifier) scope[keyIdentifier] = key; 27491 scope.$index = index; 27492 scope.$first = (index === 0); 27493 scope.$last = (index === (arrayLength - 1)); 27494 scope.$middle = !(scope.$first || scope.$last); 27495 // jshint bitwise: false 27496 scope.$odd = !(scope.$even = (index&1) === 0); 27497 // jshint bitwise: true 27498 }; 27499 27500 var getBlockStart = function(block) { 27501 return block.clone[0]; 27502 }; 27503 27504 var getBlockEnd = function(block) { 27505 return block.clone[block.clone.length - 1]; 27506 }; 27507 27508 27509 return { 27510 restrict: 'A', 27511 multiElement: true, 27512 transclude: 'element', 27513 priority: 1000, 27514 terminal: true, 27515 $$tlb: true, 27516 compile: function ngRepeatCompile($element, $attr) { 27517 var expression = $attr.ngRepeat; 27518 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); 27519 27520 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); 27521 27522 if (!match) { 27523 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 27524 expression); 27525 } 27526 27527 var lhs = match[1]; 27528 var rhs = match[2]; 27529 var aliasAs = match[3]; 27530 var trackByExp = match[4]; 27531 27532 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 27533 27534 if (!match) { 27535 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 27536 lhs); 27537 } 27538 var valueIdentifier = match[3] || match[1]; 27539 var keyIdentifier = match[2]; 27540 27541 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 27542 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 27543 throw ngRepeatMinErr('badident', "alias '{0}
vendor: 11,933 bytes, lines 27543-27815
27543' is invalid --- must be a valid JS identifier which is not a reserved name.", 27544 aliasAs); 27545 } 27546 27547 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 27548 var hashFnLocals = {$id: hashKey}; 27549 27550 if (trackByExp) { 27551 trackByExpGetter = $parse(trackByExp); 27552 } else { 27553 trackByIdArrayFn = function(key, value) { 27554 return hashKey(value); 27555 }; 27556 trackByIdObjFn = function(key) { 27557 return key; 27558 }; 27559 } 27560 27561 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 27562 27563 if (trackByExpGetter) { 27564 trackByIdExpFn = function(key, value, index) { 27565 // assign key, value, and $index to the locals so that they can be used in hash functions 27566 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 27567 hashFnLocals[valueIdentifier] = value; 27568 hashFnLocals.$index = index; 27569 return trackByExpGetter($scope, hashFnLocals); 27570 }; 27571 } 27572 27573 // Store a list of elements from previous run. This is a hash where key is the item from the 27574 // iterator, and the value is objects with following properties. 27575 // - scope: bound scope 27576 // - element: previous element. 27577 // - index: position 27578 // 27579 // We are using no-proto object so that we don't need to guard against inherited props via 27580 // hasOwnProperty. 27581 var lastBlockMap = createMap(); 27582 27583 //watch props 27584 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 27585 var index, length, 27586 previousNode = $element[0], // node that cloned nodes should be inserted after 27587 // initialized to the comment node anchor 27588 nextNode, 27589 // Same as lastBlockMap but it has the current state. It will become the 27590 // lastBlockMap on the next iteration. 27591 nextBlockMap = createMap(), 27592 collectionLength, 27593 key, value, // key/value of iteration 27594 trackById, 27595 trackByIdFn, 27596 collectionKeys, 27597 block, // last object information {scope, element, id} 27598 nextBlockOrder, 27599 elementsToRemove; 27600 27601 if (aliasAs) { 27602 $scope[aliasAs] = collection; 27603 } 27604 27605 if (isArrayLike(collection)) { 27606 collectionKeys = collection; 27607 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 27608 } else { 27609 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 27610 // if object, extract keys, in enumeration order, unsorted 27611 collectionKeys = []; 27612 for (var itemKey in collection) { 27613 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 27614 collectionKeys.push(itemKey); 27615 } 27616 } 27617 } 27618 27619 collectionLength = collectionKeys.length; 27620 nextBlockOrder = new Array(collectionLength); 27621 27622 // locate existing items 27623 for (index = 0; index < collectionLength; index++) { 27624 key = (collection === collectionKeys) ? index : collectionKeys[index]; 27625 value = collection[key]; 27626 trackById = trackByIdFn(key, value, index); 27627 if (lastBlockMap[trackById]) { 27628 // found previously seen block 27629 block = lastBlockMap[trackById]; 27630 delete lastBlockMap[trackById]; 27631 nextBlockMap[trackById] = block; 27632 nextBlockOrder[index] = block; 27633 } else if (nextBlockMap[trackById]) { 27634 // if collision detected. restore lastBlockMap and throw an error 27635 forEach(nextBlockOrder, function(block) { 27636 if (block && block.scope) lastBlockMap[block.id] = block; 27637 }); 27638 throw ngRepeatMinErr('dupes', 27639 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 27640 expression, trackById, value); 27641 } else { 27642 // new never before seen block 27643 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 27644 nextBlockMap[trackById] = true; 27645 } 27646 } 27647 27648 // remove leftover items 27649 for (var blockKey in lastBlockMap) { 27650 block = lastBlockMap[blockKey]; 27651 elementsToRemove = getBlockNodes(block.clone); 27652 $animate.leave(elementsToRemove); 27653 if (elementsToRemove[0].parentNode) { 27654 // if the element was not removed yet because of pending animation, mark it as deleted 27655 // so that we can ignore it later 27656 for (index = 0, length = elementsToRemove.length; index < length; index++) { 27657 elementsToRemove[index][NG_REMOVED] = true; 27658 } 27659 } 27660 block.scope.$destroy(); 27661 } 27662 27663 // we are not using forEach for perf reasons (trying to avoid #call) 27664 for (index = 0; index < collectionLength; index++) { 27665 key = (collection === collectionKeys) ? index : collectionKeys[index]; 27666 value = collection[key]; 27667 block = nextBlockOrder[index]; 27668 27669 if (block.scope) { 27670 // if we have already seen this object, then we need to reuse the 27671 // associated scope/element 27672 27673 nextNode = previousNode; 27674 27675 // skip nodes that are already pending removal via leave animation 27676 do { 27677 nextNode = nextNode.nextSibling; 27678 } while (nextNode && nextNode[NG_REMOVED]); 27679 27680 if (getBlockStart(block) != nextNode) { 27681 // existing item which got moved 27682 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); 27683 } 27684 previousNode = getBlockEnd(block); 27685 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 27686 } else { 27687 // new item which we don't know about 27688 $transclude(function ngRepeatTransclude(clone, scope) { 27689 block.scope = scope; 27690 // http://jsperf.com/clone-vs-createcomment 27691 var endNode = ngRepeatEndComment.cloneNode(false); 27692 clone[clone.length++] = endNode; 27693 27694 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? 27695 $animate.enter(clone, null, jqLite(previousNode)); 27696 previousNode = endNode; 27697 // Note: We only need the first/last node of the cloned nodes. 27698 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 27699 // by a directive with templateUrl when its template arrives. 27700 block.clone = clone; 27701 nextBlockMap[block.id] = block; 27702 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 27703 }); 27704 } 27705 } 27706 lastBlockMap = nextBlockMap; 27707 }); 27708 }; 27709 } 27710 }; 27711}]; 27712 27713var NG_HIDE_CLASS = 'ng-hide'; 27714var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 27715/** 27716 * @ngdoc directive 27717 * @name ngShow 27718 * @multiElement 27719 * 27720 * @description 27721 * The `ngShow` directive shows or hides the given HTML element based on the expression 27722 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 27723 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 27724 * in AngularJS and sets the display style to none (using an !important flag). 27725 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27726 * 27727 * ```html 27728 * <!-- when $scope.myValue is truthy (element is visible) --> 27729 * <div ng-show="myValue"></div> 27730 * 27731 * <!-- when $scope.myValue is falsy (element is hidden) --> 27732 * <div ng-show="myValue" class="ng-hide"></div> 27733 * ``` 27734 * 27735 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 27736 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 27737 * from the element causing the element not to appear hidden. 27738 * 27739 * ## Why is !important used? 27740 * 27741 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27742 * can be easily overridden by heavier selectors. For example, something as simple 27743 * as changing the display style on a HTML list item would make hidden elements appear visible. 27744 * This also becomes a bigger issue when dealing with CSS frameworks. 27745 * 27746 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27747 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27748 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27749 * 27750 * ### Overriding `.ng-hide` 27751 * 27752 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27753 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27754 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 27755 * with extra animation classes that can be added. 27756 * 27757 * ```css 27758 * .ng-hide:not(.ng-hide-animate) { 27759 * /* this is just another form of hiding an element */ 27760 * display: block!important; 27761 * position: absolute; 27762 * top: -9999px; 27763 * left: -9999px; 27764 * } 27765 * ``` 27766 * 27767 * By default you don't need to override in CSS anything and the animations will work around the display style. 27768 * 27769 * ## A note about animations with `ngShow` 27770 * 27771 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 27772 * is true and false. This system works like the animation system present with ngClass except that 27773 * you must also include the !important flag to override the display property 27774 * so that you can perform an animation when the element is hidden during the time of the animation. 27775 * 27776 * ```css 27777 * // 27778 * //a working example can be found at the bottom of this page 27779 * // 27780 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 27781 * /* this is required as of 1.3x to properly 27782 * apply all styling in a show/hide animation */ 27783 * transition: 0s linear all; 27784 * } 27785 * 27786 * .my-element.ng-hide-add-active, 27787 * .my-element.ng-hide-remove-active { 27788 * /* the transition is defined in the active class */ 27789 * transition: 1s linear all; 27790 * } 27791 * 27792 * .my-element.ng-hide-add { ... } 27793 * .my-element.ng-hide-add.ng-hide-add-active { ... } 27794 * .my-element.ng-hide-remove { ... } 27795 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 27796 * ``` 27797 * 27798 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 27799 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 27800 * 27801 * @animations 27802 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible 27803 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden 27804 * 27805 * @element ANY 27806 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 27807 * then the element is shown or hidden respectively. 27808 * 27809 * @example 27810 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27811 <file name="index.html"> 27812 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 27813 <div> 27814 Show: 27815 <div class="check-element animate-show" ng-show="checked">
27816 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 27817 </div> 27818 </div> 27819 <div> 27820 Hide: 27821 <div class="check-element animate-show" ng-hide="checked"> 27822 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 27823 </div> 27824 </div> 27825 </file> 27826 <file name="glyphicons.css"> 27827 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 27828 </file> 27829 <file name="animations.css"> 27830 .animate-show { 27831 line-height: 20px; 27832 opacity: 1; 27833 padding: 10px; 27834 border: 1px solid black; 27835 background: white; 27836 } 27837 27838 .animate-show.ng-hide-add, .animate-show.ng-hide-remove { 27839 transition: all linear 0.5s; 27840 } 27841 27842 .animate-show.ng-hide { 27843 line-height: 0; 27844 opacity: 0; 27845 padding: 0 10px; 27846 } 27847 27848 .check-element { 27849 padding: 10px; 27850 border: 1px solid black; 27851 background: white; 27852 } 27853 </file> 27854 <file name="protractor.js" type="protractor"> 27855 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 27856 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 27857 27858 it('should check ng-show / ng-hide', function() { 27859 expect(thumbsUp.isDisplayed()).toBeFalsy(); 27860 expect(thumbsDown.isDisplayed()).toBeTruthy(); 27861 27862 element(by.model('checked')).click(); 27863 27864 expect(thumbsUp.isDisplayed()).toBeTruthy(); 27865 expect(thumbsDown.isDisplayed()).toBeFalsy(); 27866 }); 27867 </file> 27868 </example> 27869 */ 27870var ngShowDirective = ['$animate', function($animate) { 27871 return { 27872 restrict: 'A', 27873 multiElement: true, 27874 link: function(scope, element, attr) { 27875 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 27876 // we're adding a temporary, animation-specific class for ng-hide since this way 27877 // we can control when the element is actually displayed on screen without having 27878 // to have a global/greedy CSS selector that breaks when other animations are run. 27879 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 27880 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 27881 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 27882 }); 27883 }); 27884 } 27885 }; 27886}]; 27887 27888 27889/** 27890 * @ngdoc directive 27891 * @name ngHide 27892 * @multiElement 27893 * 27894 * @description 27895 * The `ngHide` directive shows or hides the given HTML element based on the expression 27896 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 27897 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 27898 * in AngularJS and sets the display style to none (using an !important flag). 27899 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27900 * 27901 * ```html 27902 * <!-- when $scope.myValue is truthy (element is hidden) --> 27903 * <div ng-hide="myValue" class="ng-hide"></div> 27904 * 27905 * <!-- when $scope.myValue is falsy (element is visible) --> 27906 * <div ng-hide="myValue"></div> 27907 * ``` 27908 * 27909 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 27910 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 27911 * from the element causing the element not to appear hidden. 27912 * 27913 * ## Why is !important used? 27914 * 27915 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27916 * can be easily overridden by heavier selectors. For example, something as simple 27917 * as changing the display style on a HTML list item would make hidden elements appear visible. 27918 * This also becomes a bigger issue when dealing with CSS frameworks. 27919 * 27920 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27921 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27922 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27923 * 27924 * ### Overriding `.ng-hide` 27925 * 27926 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27927 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27928 * class in CSS: 27929 * 27930 * ```css 27931 * .ng-hide { 27932 * /* this is just another form of hiding an element */ 27933 * display: block!important; 27934 * position: absolute; 27935 * top: -9999px; 27936 * left: -9999px; 27937 * } 27938 * ``` 27939 * 27940 * By default you don't need to override in CSS anything and the animations will work around the display style. 27941 * 27942 * ## A note about animations with `ngHide` 27943 * 27944 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 27945 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 27946 * CSS class is added and removed for you instead of your own CSS class. 27947 * 27948 * ```css 27949 * // 27950 * //a working example can be found at the bottom of this page 27951 * // 27952 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 27953 * transition: 0.5s linear all; 27954 * } 27955 * 27956 * .my-element.ng-hide-add { ... } 27957 * .my-element.ng-hide-add.ng-hide-add-active { ... } 27958 * .my-element.ng-hide-remove { ... } 27959 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 27960 * ``` 27961 * 27962 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 27963 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 27964 * 27965 * @animations 27966 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden 27967 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible 27968 * 27969 * @element ANY 27970 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 27971 * the element is shown or hidden respectively. 27972 * 27973 * @example 27974 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27975 <file name="index.html"> 27976 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 27977 <div> 27978 Show: 27979 <div class="check-element animate-hide" ng-show="checked">
27980 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 27981 </div> 27982 </div> 27983 <div> 27984 Hide: 27985 <div class="check-element animate-hide" ng-hide="checked"> 27986 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 27987 </div> 27988 </div> 27989 </file> 27990 <file name="glyphicons.css"> 27991 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 27992 </file> 27993 <file name="animations.css"> 27994 .animate-hide { 27995 transition: all linear 0.5s; 27996 line-height: 20px; 27997 opacity: 1; 27998 padding: 10px; 27999 border: 1px solid black; 28000 background: white; 28001 } 28002 28003 .animate-hide.ng-hide { 28004 line-height: 0; 28005 opacity: 0; 28006 padding: 0 10px; 28007 } 28008 28009 .check-element { 28010 padding: 10px; 28011 border: 1px solid black; 28012 background: white; 28013 } 28014 </file> 28015 <file name="protractor.js" type="protractor"> 28016 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 28017 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 28018 28019 it('should check ng-show / ng-hide', function() { 28020 expect(thumbsUp.isDisplayed()).toBeFalsy(); 28021 expect(thumbsDown.isDisplayed()).toBeTruthy(); 28022 28023 element(by.model('checked')).click(); 28024 28025 expect(thumbsUp.isDisplayed()).toBeTruthy(); 28026 expect(thumbsDown.isDisplayed()).toBeFalsy(); 28027 }); 28028 </file> 28029 </example> 28030 */ 28031var ngHideDirective = ['$animate', function($animate) { 28032 return { 28033 restrict: 'A', 28034 multiElement: true, 28035 link: function(scope, element, attr) { 28036 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 28037 // The comment inside of the ngShowDirective explains why we add and 28038 // remove a temporary class for the show/hide animation 28039 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 28040 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 28041 }); 28042 }); 28043 } 28044 }; 28045}]; 28046 28047/** 28048 * @ngdoc directive 28049 * @name ngStyle 28050 * @restrict AC 28051 * 28052 * @description 28053 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 28054 * 28055 * @element ANY 28056 * @param {expression} ngStyle 28057 * 28058 * {@link guide/expression Expression} which evals to an 28059 * object whose keys are CSS style names and values are corresponding values for those CSS 28060 * keys. 28061 * 28062 * Since some CSS style names are not valid keys for an object, they must be quoted. 28063 * See the 'background-color' style in the example below. 28064 * 28065 * @example 28066 <example> 28067 <file name="index.html"> 28068 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 28069 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 28070 <input type="button" value="clear" ng-click="myStyle={}"> 28071 <br/> 28072 <span ng-style="myStyle">Sample Text</span> 28073 <pre>myStyle={{myStyle}}</pre> 28074 </file> 28075 <file name="style.css"> 28076 span { 28077 color: black; 28078 } 28079 </file> 28080 <file name="protractor.js" type="protractor"> 28081 var colorSpan = element(by.css('span')); 28082 28083 it('should check ng-style', function() { 28084 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 28085 element(by.css('input[value=\'set color\']')).click(); 28086 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 28087 element(by.css('input[value=clear]')).click(); 28088 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 28089 }); 28090 </file> 28091 </example> 28092 */ 28093var ngStyleDirective = ngDirective(function(scope, element, attr) { 28094 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 28095 if (oldStyles && (newStyles !== oldStyles)) { 28096 forEach(oldStyles, function(val, style) { element.css(style, '');}); 28097 } 28098 if (newStyles) element.css(newStyles); 28099 }, true); 28100}); 28101 28102/** 28103 * @ngdoc directive 28104 * @name ngSwitch 28105 * @restrict EA 28106 * 28107 * @description 28108 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 28109 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 28110 * as specified in the template. 28111 * 28112 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 28113 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 28114 * matches the value obtained from the evaluated expression. In other words, you define a container element 28115 * (where you place the directive), place an expression on the **`on="..."` attribute** 28116 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 28117 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 28118 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 28119 * attribute is displayed. 28120 * 28121 * <div class="alert alert-info"> 28122 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 28123 * as literal string values to match against. 28124 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
28124omeVal"` not against the 28125 * value of the expression `$scope.someVal`. 28126 * </div> 28127 28128 * @animations 28129 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 28130 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 28131 * 28132 * @usage 28133 * 28134 * ``` 28135 * <ANY ng-switch="expression"> 28136 * <ANY ng-switch-when="matchValue1">...</ANY> 28137 * <ANY ng-switch-when="matchValue2">...</ANY> 28138 * <ANY ng-switch-default>...</ANY> 28139 * </ANY> 28140 * ``` 28141 * 28142 * 28143 * @scope 28144 * @priority 1200 28145 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 28146 * On child elements add: 28147 * 28148 * * `ngSwitchWhen`: the case statement to match against. If match then this 28149 * case will be displayed. If the same match appears multiple times, all the 28150 * elements will be displayed. 28151 * * `ngSwitchDefault`: the default case when no other case match. If there 28152 * are multiple default cases, all of them will be displayed when no other 28153 * case match. 28154 * 28155 * 28156 * @example 28157 <example module="switchExample" deps="angular-animate.js" animations="true"> 28158 <file name="index.html">
28159 <div ng-controller="ExampleController"> 28160 <select ng-model="selection" ng-options="item for item in items"> 28161 </select> 28162 <code>selection={{selection}}</code> 28163 <hr/> 28164 <div class="animate-switch-container" 28165 ng-switch on="selection"> 28166 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 28167 <div class="animate-switch" ng-switch-when="home">Home Span</div> 28168 <div class="animate-switch" ng-switch-default>default</div> 28169 </div> 28170 </div> 28171 </file> 28172 <file name="script.js"> 28173 angular.module('switchExample', ['ngAnimate']) 28174 .controller('ExampleController', ['$scope', function($scope) { 28175 $scope.items = ['settings', 'home', 'other']; 28176 $scope.selection = $scope.items[0]; 28177 }]); 28178 </file> 28179 <file name="animations.css"> 28180 .animate-switch-container { 28181 position:relative; 28182 background:white; 28183 border:1px solid black; 28184 height:40px; 28185 overflow:hidden; 28186 } 28187 28188 .animate-switch { 28189 padding:10px; 28190 } 28191 28192 .animate-switch.ng-animate { 28193 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 28194 28195 position:absolute; 28196 top:0; 28197 left:0; 28198 right:0; 28199 bottom:0; 28200 } 28201 28202 .animate-switch.ng-leave.ng-leave-active, 28203 .animate-switch.ng-enter { 28204 top:-50px; 28205 } 28206 .animate-switch.ng-leave, 28207 .animate-switch.ng-enter.ng-enter-active { 28208 top:0; 28209 } 28210 </file> 28211 <file name="protractor.js" type="protractor"> 28212 var switchElem = element(by.css('[ng-switch]')); 28213 var select = element(by.model('selection')); 28214 28215 it('should start in settings', function() { 28216 expect(switchElem.getText()).toMatch(/Settings Div/); 28217 }); 28218 it('should change to home', function() { 28219 select.all(by.css('option')).get(1).click(); 28220 expect(switchElem.getText()).toMatch(/Home Span/); 28221 }); 28222 it('should select default', function() { 28223 select.all(by.css('option')).get(2).click(); 28224 expect(switchElem.getText()).toMatch(/default/); 28225 }); 28226 </file> 28227 </example> 28228 */ 28229var ngSwitchDirective = ['$animate', function($animate) { 28230 return { 28231 require: 'ngSwitch', 28232 28233 // asks for $scope to fool the BC controller module 28234 controller: ['$scope', function ngSwitchController() { 28235 this.cases = {}; 28236 }], 28237 link: function(scope, element, attr, ngSwitchController) { 28238 var watchExpr = attr.ngSwitch || attr.on, 28239 selectedTranscludes = [], 28240 selectedElements = [], 28241 previousLeaveAnimations = [], 28242 selectedScopes = []; 28243 28244 var spliceFactory = function(array, index) { 28245 return function() { array.splice(index, 1); }; 28246 }; 28247 28248 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 28249 var i, ii; 28250 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 28251 $animate.cancel(previousLeaveAnimations[i]); 28252 } 28253 previousLeaveAnimations.length = 0; 28254 28255 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 28256 var selected = getBlockNodes(selectedElements[i].clone); 28257 selectedScopes[i].$destroy(); 28258 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 28259 promise.then(spliceFactory(previousLeaveAnimations, i)); 28260 } 28261 28262 selectedElements.length = 0; 28263 selectedScopes.length = 0; 28264 28265 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 28266 forEach(selectedTranscludes, function(selectedTransclude) { 28267 selectedTransclude.transclude(function(caseElement, selectedScope) { 28268 selectedScopes.push(selectedScope); 28269 var anchor = selectedTransclude.element; 28270 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); 28271 var block = { clone: caseElement }; 28272 28273 selectedElements.push(block); 28274 $animate.enter(caseElement, anchor.parent(), anchor); 28275 }); 28276 }); 28277 } 28278 }); 28279 } 28280 }; 28281}]; 28282 28283var ngSwitchWhenDirective = ngDirective({ 28284 transclude: 'element', 28285 priority: 1200, 28286 require: '^ngSwitch', 28287 multiElement: true, 28288 link: function(scope, element, attrs, ctrl, $transclude) { 28289 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 28290 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 28291 } 28292}); 28293 28294var ngSwitchDefaultDirective = ngDirective({ 28295 transclude: 'element', 28296 priority: 1200, 28297 require: '^ngSwitch', 28298 multiElement: true, 28299 link: function(scope, element, attr, ctrl, $transclude) { 28300 ctrl.cases['?'] = (ctrl.cases['?'] || []); 28301 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 28302 } 28303}); 28304 28305/** 28306 * @ngdoc directive 28307 * @name ngTransclude 28308 * @restrict EAC 28309 * 28310 * @description 28311 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 28312 * 28313 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 28314 * 28315 * @element ANY 28316 * 28317 * @example 28318 <example module="transcludeExample"> 28319 <file name="index.html"> 28320 <script> 28321 angular.module('transcludeExample', []) 28322 .directive('pane', function(){ 28323 return { 28324 restrict: 'E', 28325 transclude: true, 28326 scope: { title:'@' }, 28327 template: '<div style="border: 1px solid black;">
28327' + 28328 '<div style="background-color: gray">{{title}}</div>' + 28329 '<ng-transclude></ng-transclude>' + 28330 '</div>' 28331 }; 28332 }) 28333 .controller('ExampleController', ['$scope', function($scope) { 28334 $scope.title = 'Lorem Ipsum'; 28335 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 28336 }]); 28337 </script> 28338 <div ng-controller="ExampleController"> 28339 <input ng-model="title" aria-label="title"> <br/> 28340 <textarea ng-model="text" aria-label="text"></textarea> <br/> 28341 <pane title="{{title}}">{{text}}</pane> 28342 </div> 28343 </file> 28344 <file name="protractor.js" type="protractor"> 28345 it('should have transcluded', function() { 28346 var titleElement = element(by.model('title')); 28347 titleElement.clear(); 28348 titleElement.sendKeys('TITLE'); 28349 var textElement = element(by.model('text')); 28350 textElement.clear(); 28351 textElement.sendKeys('TEXT'); 28352 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 28353 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 28354 }); 28355 </file> 28356 </example> 28357 * 28358 */ 28359var ngTranscludeDirective = ngDirective({ 28360 restrict: 'EAC', 28361 link: function($scope, $element, $attrs, controller, $transclude) { 28362 if (!$transclude) { 28363 throw minErr('ngTransclude')('orphan', 28364 'Illegal use of ngTransclude directive in the template! ' + 28365 'No parent directive that requires a transclusion found. ' + 28366 'Element: {0}', 28367 startingTag($element)); 28368 } 28369 28370 $transclude(function(clone) { 28371 $element.empty(); 28372 $element.append(clone); 28373 }); 28374 } 28375}); 28376 28377/** 28378 * @ngdoc directive 28379 * @name script 28380 * @restrict E 28381 * 28382 * @description 28383 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 28384 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 28385 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 28386 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 28387 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 28388 * 28389 * @param {string} type Must be set to `'text/ng-template'`. 28390 * @param {string} id Cache name of the template. 28391 * 28392 * @example 28393 <example> 28394 <file name="index.html"> 28395 <script type="text/ng-template" id="/tpl.html"> 28396 Content of the template. 28397 </script> 28398 28399 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 28400 <div id="tpl-content" ng-include src="currentTpl"></div> 28401 </file> 28402 <file name="protractor.js" type="protractor"> 28403 it('should load template defined inside script tag', function() { 28404 element(by.css('#tpl-link')).click(); 28405 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 28406 }); 28407 </file> 28408 </example> 28409 */ 28410var scriptDirective = ['$templateCache', function($templateCache) { 28411 return { 28412 restrict: 'E', 28413 terminal: true, 28414 compile: function(element, attr) { 28415 if (attr.type == 'text/ng-template') { 28416 var templateUrl = attr.id, 28417 text = element[0].text; 28418 28419 $templateCache.put(templateUrl, text); 28420 } 28421 } 28422 }; 28423}]; 28424 28425var noopNgModelController = { $setViewValue: noop, $render: noop }; 28426 28427/** 28428 * @ngdoc type 28429 * @name select.SelectController 28430 * @description 28431 * The controller for the `<select>` directive. This provides support for reading 28432 * and writing the selected value(s) of the control and also coordinates dynamically 28433 * added `<option>` elements, perhaps by an `ngRepeat` directive. 28434 */ 28435var SelectController = 28436 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 28437 28438 var self = this, 28439 optionsMap = new HashMap(); 28440 28441 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 28442 self.ngModelCtrl = noopNgModelController; 28443 28444 // The "unknown" option is one that is prepended to the list if the viewValue 28445 // does not match any of the options. When it is rendered the value of the unknown 28446 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 28447 // 28448 // We can't just jqLite('<option>') since jqLite is not smart enough 28449 // to create it in <select> and IE barfs otherwise. 28450 self.unknownOption = jqLite(document.createElement('option')); 28451 self.renderUnknownOption = function(val) { 28452 var unknownVal = '? ' + hashKey(val) + ' ?'; 28453 self.unknownOption.val(unknownVal);
28454 $element.prepend(self.unknownOption); 28455 $element.val(unknownVal); 28456 }; 28457 28458 $scope.$on('$destroy', function() { 28459 // disable unknown option so that we don't do work when the whole select is being destroyed 28460 self.renderUnknownOption = noop; 28461 }); 28462 28463 self.removeUnknownOption = function() { 28464 if (self.unknownOption.parent()) self.unknownOption.remove(); 28465 }; 28466 28467 28468 // Read the value of the select control, the implementation of this changes depending 28469 // upon whether the select can have multiple values and whether ngOptions is at work. 28470 self.readValue = function readSingleValue() { 28471 self.removeUnknownOption(); 28472 return $element.val(); 28473 }; 28474 28475 28476 // Write the value to the select control, the implementation of this changes depending 28477 // upon whether the select can have multiple values and whether ngOptions is at work. 28478 self.writeValue = function writeSingleValue(value) { 28479 if (self.hasOption(value)) { 28480 self.removeUnknownOption(); 28481 $element.val(value); 28482 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 28483 } else { 28484 if (value == null && self.emptyOption) { 28485 self.removeUnknownOption(); 28486 $element.val(''); 28487 } else { 28488 self.renderUnknownOption(value); 28489 } 28490 } 28491 }; 28492 28493 28494 // Tell the select control that an option, with the given value, has been added 28495 self.addOption = function(value, element) { 28496 assertNotHasOwnProperty(value, '"option value"'); 28497 if (value === '') { 28498 self.emptyOption = element; 28499 } 28500 var count = optionsMap.get(value) || 0; 28501 optionsMap.put(value, count + 1); 28502 }; 28503 28504 // Tell the select control that an option, with the given value, has been removed 28505 self.removeOption = function(value) { 28506 var count = optionsMap.get(value); 28507 if (count) { 28508 if (count === 1) { 28509 optionsMap.remove(value); 28510 if (value === '') { 28511 self.emptyOption = undefined; 28512 } 28513 } else { 28514 optionsMap.put(value, count - 1); 28515 } 28516 } 28517 }; 28518 28519 // Check whether the select control has an option matching the given value 28520 self.hasOption = function(value) { 28521 return !!optionsMap.get(value); 28522 }; 28523}]; 28524 28525/** 28526 * @ngdoc directive 28527 * @name select 28528 * @restrict E 28529 * 28530 * @description 28531 * HTML `SELECT` element with angular data-binding. 28532 * 28533 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 28534 * between the scope and the `<select>` control (including setting default values). 28535 * Ãt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 28536 * {@link ngOptions `ngOptions`} directives. 28537 * 28538 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 28539 * to the model identified by the `ngModel` directive. With static or repeated options, this is 28540 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 28541 * If you want dynamic value attributes, you can use interpolation inside the value attribute. 28542 * 28543 * <div class="alert alert-warning"> 28544 * Note that the value of a `select` directive used without `ngOptions` is always a string. 28545 * When the model needs to be bound to a non-string value, you must either explictly convert it 28546 * using a directive (see example below) or use `ngOptions` to specify the set of options. 28547 * This is because an option element can only be bound to string values at present. 28548 * </div> 28549 * 28550 * If the viewValue of `ngModel` does not match any of the options, then the control 28551 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 28552 * 28553 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 28554 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 28555 * option. See example below for demonstration. 28556 * 28557 * <div class="alert alert-info"> 28558 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 28559 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benef
28559its, such as 28560 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 28561 * comprehension expression, and additionally in reducing memory and increasing speed by not creating 28562 * a new scope for each repeated instance. 28563 * </div> 28564 * 28565 * 28566 * @param {string} ngModel Assignable angular expression to data-bind to. 28567 * @param {string=} name Property name of the form under which the control is published. 28568 * @param {string=} required Sets `required` validation error key if the value is not entered. 28569 * @param {string=} ngRequired Adds required attribute and required validation constraint to 28570 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 28571 * when you want to data-bind to the required attribute. 28572 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user 28573 * interaction with the select element. 28574 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 28575 * set on the model on selection. See {@link ngOptions `ngOptions`}. 28576 * 28577 * @example 28578 * ### Simple `select` elements with static options 28579 * 28580 * <example name="static-select" module="staticSelect"> 28581 * <file name="index.html">
28582 * <div ng-controller="ExampleController"> 28583 * <form name="myForm"> 28584 * <label for="singleSelect"> Single select: </label><br> 28585 * <select name="singleSelect" ng-model="data.singleSelect"> 28586 * <option value="option-1">Option 1</option> 28587 * <option value="option-2">Option 2</option> 28588 * </select><br> 28589 * 28590 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 28591 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 28592 * <option value="">---Please select---</option> <!-- not selected / blank option --> 28593 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 28594 * <option value="option-2">Option 2</option> 28595 * </select><br> 28596 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 28597 * <tt>singleSelect = {{data.singleSelect}}</tt> 28598 * 28599 * <hr> 28600 * <label for="multipleSelect"> Multiple select: </label><br> 28601 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 28602 * <option value="option-1">Option 1</option> 28603 * <option value="option-2">Option 2</option> 28604 * <option value="option-3">Option 3</option> 28605 * </select><br> 28606 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 28607 * </form> 28608 * </div> 28609 * </file> 28610 * <file name="app.js"> 28611 * angular.module('staticSelect', []) 28612 * .controller('ExampleController', ['$scope', function($scope) { 28613 * $scope.data = { 28614 * singleSelect: null, 28615 * multipleSelect: [], 28616 * option1: 'option-1', 28617 * }; 28618 * 28619 * $scope.forceUnknownOption = function() { 28620 * $scope.data.singleSelect = 'nonsense'; 28621 * }; 28622 * }]); 28623 * </file> 28624 *</example> 28625 * 28626 * ### Using `ngRepeat` to generate `select` options 28627 * <example name="ngrepeat-select" module="ngrepeatSelect"> 28628 * <file name="index.html">
28629 * <div ng-controller="ExampleController"> 28630 * <form name="myForm"> 28631 * <label for="repeatSelect"> Repeat select: </label> 28632 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> 28633 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 28634 * </select> 28635 * </form> 28636 * <hr> 28637 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> 28638 * </div> 28639 * </file> 28640 * <file name="app.js"> 28641 * angular.module('ngrepeatSelect', []) 28642 * .controller('ExampleController', ['$scope', function($scope) { 28643 * $scope.data = { 28644 * repeatSelect: null, 28645 * availableOptions: [ 28646 * {id: '1', name: 'Option A'}, 28647 * {id: '2', name: 'Option B'}, 28648 * {id: '3', name: 'Option C'} 28649 * ], 28650 * }; 28651 * }]); 28652 * </file> 28653 *</example> 28654 * 28655 * 28656 * ### Using `select` with `ngOptions` and setting a default value 28657 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 28658 * 28659 * <example name="select-with-default-values" module="defaultValueSelect"> 28660 * <file name="index.html">
28661 * <div ng-controller="ExampleController"> 28662 * <form name="myForm"> 28663 * <label for="mySelect">Make a choice:</label> 28664 * <select name="mySelect" id="mySelect" 28665 * ng-options="option.name for option in data.availableOptions track by option.id" 28666 * ng-model="data.selectedOption"></select> 28667 * </form> 28668 * <hr> 28669 * <tt>option = {{data.selectedOption}}</tt><br/> 28670 * </div> 28671 * </file> 28672 * <file name="app.js"> 28673 * angular.module('defaultValueSelect', []) 28674 * .controller('ExampleController', ['$scope', function($scope) { 28675 * $scope.data = { 28676 * availableOptions: [ 28677 * {id: '1', name: 'Option A'}, 28678 * {id: '2', name: 'Option B'}, 28679 * {id: '3', name: 'Option C'} 28680 * ], 28681 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 28682 * }; 28683 * }]); 28684 * </file> 28685 *</example> 28686 * 28687 * 28688 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 28689 * 28690 * <example name="select-with-non-string-options" module="nonStringSelect"> 28691 * <file name="index.html"> 28692 * <select ng-model="model.id" convert-to-number> 28693 * <option value="0">Zero</option> 28694 * <option value="1">One</option> 28695 * <option value="2">Two</option> 28696 * </select> 28697 * {{ model }} 28698 * </file> 28699 * <file name="app.js"> 28700 * angular.module('nonStringSelect', []) 28701 * .run(function($rootScope) { 28702 * $rootScope.model = { id: 2 }; 28703 * }) 28704 * .directive('convertToNumber', function() { 28705 * return { 28706 * require: 'ngModel', 28707 * link: function(scope, element, attrs, ngModel) { 28708 * ngModel.$parsers.push(function(val) { 28709 * return parseInt(val, 10); 28710 * }); 28711 * ngModel.$formatters.push(function(val) { 28712 * return '' + val; 28713 * }); 28714 * } 28715 * }; 28716 * }); 28717 * </file> 28718 * <file name="protractor.js" type="protractor"> 28719 * it('should initialize to model', function() { 28720 * var select = element(by.css('select')); 28721 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 28722 * }); 28723 * </file> 28724 * </example> 28725 * 28726 */ 28727var selectDirective = function() { 28728 28729 return { 28730 restrict: 'E', 28731 require: ['select', '?ngModel'], 28732 controller: SelectController, 28733 link: function(scope, element, attr, ctrls) { 28734 28735 // if ngModel is not defined, we don't need to do anything 28736 var ngModelCtrl = ctrls[1]; 28737 if (!ngModelCtrl) return; 28738 28739 var selectCtrl = ctrls[0]; 28740 28741 selectCtrl.ngModelCtrl = ngModelCtrl; 28742 28743 // We delegate rendering to the `writeValue` method, which can be changed 28744 // if the select can have multiple selected values or if the options are being 28745 // generated by `ngOptions` 28746 ngModelCtrl.$render = function() { 28747 selectCtrl.writeValue(ngModelCtrl.$viewValue); 28748 }; 28749 28750 // When the selected item(s) changes we delegate getting the value of the select control 28751 // to the `readValue` method, which can be changed if the select can have multiple 28752 // selected values or if the options are being generated by `ngOptions` 28753 element.on('change', function() { 28754 scope.$apply(function() { 28755 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 28756 }); 28757 }); 28758 28759 // If the select allows multiple values then we need to modify how we read and write 28760 // values from and to the control; also what it means for the value to be empty and 28761 // we have to add an extra watch since ngModel doesn't work well with arrays - it 28762 // doesn't trigger rendering if only an item in the array changes. 28763 if (attr.multiple) { 28764 28765 // Read value now needs to check each option to see if it is selected 28766 selectCtrl.readValue = function readMultipleValue() { 28767 var array = []; 28768 forEach(element.find('option'), function(option) { 28769 if (option.selected) { 28770 array.push(option.value); 28771 } 28772 }); 28773 return array; 28774 }; 28775 28776 // Write value now needs to set the selected property of each matching option 28777 selectCtrl.writeValue = function writeMultipleValue(value) { 28778 var items = new HashMap(value);
28779 forEach(element.find('option'), function(option) { 28780 option.selected = isDefined(items.get(option.value)); 28781 }); 28782 }; 28783 28784 // we have to do it on each watch since ngModel watches reference, but 28785 // we need to work of an array, so we need to see if anything was inserted/removed 28786 var lastView, lastViewRef = NaN; 28787 scope.$watch(function selectMultipleWatch() { 28788 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 28789 lastView = shallowCopy(ngModelCtrl.$viewValue); 28790 ngModelCtrl.$render(); 28791 } 28792 lastViewRef = ngModelCtrl.$viewValue; 28793 }); 28794 28795 // If we are a multiple select then value is now a collection 28796 // so the meaning of $isEmpty changes 28797 ngModelCtrl.$isEmpty = function(value) { 28798 return !value || value.length === 0; 28799 }; 28800 28801 } 28802 } 28803 }; 28804}; 28805 28806 28807// The option directive is purely designed to communicate the existence (or lack of) 28808// of dynamically created (and destroyed) option elements to their containing select 28809// directive via its controller. 28810var optionDirective = ['$interpolate', function($interpolate) { 28811 28812 function chromeHack(optionElement) { 28813 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 28814 // Adding an <option selected="selected"> element to a <select required="required"> should 28815 // automatically select the new element 28816 if (optionElement[0].hasAttribute('selected')) { 28817 optionElement[0].selected = true; 28818 } 28819 } 28820 28821 return { 28822 restrict: 'E', 28823 priority: 100, 28824 compile: function(element, attr) { 28825 28826 if (isDefined(attr.value)) { 28827 // If the value attribute is defined, check if it contains an interpolation 28828 var valueInterpolated = $interpolate(attr.value, true); 28829 } else { 28830 // If the value attribute is not defined then we fall back to the 28831 // text content of the option element, which may be interpolated 28832 var interpolateFn = $interpolate(element.text(), true); 28833 if (!interpolateFn) { 28834 attr.$set('value', element.text()); 28835 } 28836 } 28837 28838 return function(scope, element, attr) { 28839 28840 // This is an optimization over using ^^ since we don't want to have to search 28841 // all the way to the root of the DOM for every single option element 28842 var selectCtrlName = '$selectController', 28843 parent = element.parent(), 28844 selectCtrl = parent.data(selectCtrlName) || 28845 parent.parent().data(selectCtrlName); // in case we are in optgroup 28846 28847 function addOption(optionValue) { 28848 selectCtrl.addOption(optionValue, element); 28849 selectCtrl.ngModelCtrl.$render(); 28850 chromeHack(element); 28851 } 28852 28853 // Only update trigger option updates if this is an option within a `select` 28854 // that also has `ngModel` attached 28855 if (selectCtrl && selectCtrl.ngModelCtrl) { 28856 28857 if (valueInterpolated) { 28858 // The value attribute is interpolated 28859 var oldVal; 28860 attr.$observe('value', function valueAttributeObserveAction(newVal) { 28861 if (isDefined(oldVal)) { 28862 selectCtrl.removeOption(oldVal); 28863 } 28864 oldVal = newVal; 28865 addOption(newVal); 28866 }); 28867 } else if (interpolateFn) { 28868 // The text content is interpolated 28869 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 28870 attr.$set('value', newVal); 28871 if (oldVal !== newVal) { 28872 selectCtrl.removeOption(oldVal); 28873 } 28874 addOption(newVal); 28875 }); 28876 } else { 28877 // The value attribute is static 28878 addOption(attr.value); 28879 } 28880 28881 element.on('$destroy', function() { 28882 selectCtrl.removeOption(attr.value); 28883 selectCtrl.ngModelCtrl.$render(); 28884 }); 28885 } 28886 }; 28887 } 28888 }; 28889}]; 28890 28891var styleDirective = valueFn({ 28892 restrict: 'E', 28893 terminal: false 28894}); 28895 28896var requiredDirective = function() { 28897 return { 28898 restrict: 'A', 28899 require: '?ngModel', 28900 link: function(scope, elm, attr, ctrl) { 28901 if (!ctrl) return; 28902 attr.required = true; // force truthy in case we are on non input element 28903 28904 ctrl.$validators.required = function(modelValue, viewValue) { 28905 return !attr.required || !ctrl.$isEmpty(viewValue); 28906 }; 28907 28908 attr.$observe('required', function() { 28909 ctrl.$validate(); 28910 }); 28911 } 28912 }; 28913}; 28914 28915 28916var patternDirective = function() { 28917 return { 28918 restrict: 'A', 28919 require: '?ngModel', 28920 link: function(scope, elm, attr, ctrl) { 28921 if (!ctrl) return; 28922 28923 var regexp, patternExp = attr.ngPattern || attr.pattern; 28924 attr.$observe('pattern', function(regex) { 28925 if (isString(regex) && regex.length > 0) { 28926 regex = new RegExp('^' + regex + '$'); 28927 } 28928 28929 if (regex && !regex.test) { 28930 throw minErr('ngPattern')('noregexp', 28931 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 28932 regex, startingTag(elm)); 28933 } 28934 28935 regexp = regex || undefined; 28936 ctrl.$validate(); 28937 }); 28938 28939 ctrl.$validators.pattern = function(modelValue, viewValue) { 28940 // HTML5 pattern constraint validates the input value, so we validate the viewValue 28941 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 28942 }; 28943 } 28944 }; 28945}; 28946 28947 28948var maxlengthDirective = function() { 28949 return { 28950 restrict: 'A', 28951 require: '?ngModel', 28952 link: function(scope, elm, attr, ctrl) { 28953 if (!ctrl) return; 28954 28955 var maxlength = -1; 28956 attr.$observe('maxlength', function(value) { 28957 var intVal = toInt(value); 28958 maxlength = isNaN(intVal) ? -1 : intVal; 28959 ctrl.$validate(); 28960 }); 28961 ctrl.$validators.maxlength = function(modelValue, viewValue) { 28962 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 28963 }; 28964 } 28965 }; 28966}; 28967 28968var minlengthDirective = function() { 28969 return { 28970 restrict: 'A', 28971 require: '?ngModel', 28972 link: function(scope, elm, attr, ctrl) { 28973 if (!ctrl) return; 28974 28975 var minlength = 0; 28976 attr.$observe('minlength', function(value) { 28977 minlength = toInt(value) || 0; 28978 ctrl.$validate(); 28979 }); 28980 ctrl.$validators.minlength = function(modelValue, viewValue) { 28981 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 28982 }; 28983 } 28984 }; 28985}; 28986 28987if (window.angular.bootstrap) { 28988 //AngularJS is already loaded, so we can return here... 28989 console.log('WARNING: Tried to load angular more than once.'); 28990 return; 28991} 28992 28993//try to bind to jquery now so that one can write jqLite(document).ready() 28994//but we will rebind on bootstrap again. 28995bindJQuery(); 28996 28997publishExternalAPI(angular); 28998 28999angular.module("ngLocale", [], ["$provide", function($provide) { 29000var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 29001function getDecimals(n) { 29002 n = n + ''; 29003 var i = n.indexOf('.'); 29004 return (i == -1) ? 0 : n.length - i - 1; 29005} 29006 29007function getVF(n, opt_precision) { 29008 var v = opt_precision; 29009 29010 if (undefined === v) { 29011 v = Math.min(getDecimals(n), 3); 29012 } 29013 29014 var base = Math.pow(10, v); 29015 var f = ((n * base) | 0) % base; 29016 return {v: v, f: f}; 29017} 29018 29019$provide.value("$locale", { 29020 "DATETIME_FORMATS": { 29021 "AMPMS": [ 29022 "AM", 29023 "PM" 29024 ], 29025 "DAY": [ 29026 "Sunday", 29027 "Monday", 29028 "Tuesday", 29029 "Wednesday", 29030 "Thursday", 29031 "Friday", 29032 "Saturday" 29033 ], 29034 "ERANAMES": [ 29035 "Before Christ", 29036 "Anno Domini" 29037 ], 29038 "ERAS": [ 29039 "BC", 29040 "AD" 29041 ], 29042 "FIRSTDAYOFWEEK": 6, 29043 "MONTH": [ 29044 "January", 29045 "February", 29046 "March", 29047 "April", 29048 "May", 29049 "June", 29050 "July", 29051 "August", 29052 "September", 29053 "October", 29054 "November", 29055 "December" 29056 ], 29057 "SHORTDAY": [ 29058 "Sun", 29059 "Mon", 29060 "Tue", 29061 "Wed", 29062 "Thu", 29063 "Fri", 29064 "Sat" 29065 ], 29066 "SHORTMONTH": [ 29067 "Jan", 29068 "Feb", 29069 "Mar", 29070 "Apr", 29071 "May", 29072 "Jun", 29073 "Jul", 29074 "Aug", 29075 "Sep", 29076 "Oct", 29077 "Nov", 29078 "Dec" 29079 ], 29080 "WEEKENDRANGE": [ 29081 5, 29082 6 29083 ], 29084 "fullDate": "EEEE, MMMM d, y", 29085 "longDate": "MMMM d, y", 29086 "medium": "MMM d, y h:mm:ss a", 29087 "mediumDate": "MMM d, y", 29088 "mediumTime": "h:mm:ss a", 29089 "short": "M/d/yy h:mm a", 29090 "shortDate": "M/d/yy", 29091 "shortTime": "h:mm a" 29092 }, 29093 "NUMBER_FORMATS": { 29094 "CURRENCY_SYM": "$", 29095 "DECIMAL_SEP": ".", 29096 "GROUP_SEP": ",", 29097 "PATTERNS": [ 29098 { 29099 "gSize": 3, 29100 "lgSize": 3, 29101 "maxFrac": 3, 29102 "minFrac": 0, 29103 "minInt": 1, 29104 "negPre": "-", 29105 "negSuf": "", 29106 "posPre": "", 29107 "posSuf": "" 29108 }, 29109 { 29110 "gSize": 3, 29111 "lgSize": 3, 29112 "maxFrac": 2, 29113 "minFrac": 2, 29114 "minInt": 1, 29115 "negPre": "-\u00a4", 29116 "negSuf": "", 29117 "posPre": "\u00a4", 29118 "posSuf": "" 29119 } 29120 ] 29121 }, 29122 "id": "en-us", 29123 "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;} 29124}); 29125}]); 29126 29127 jqLite(document).ready(function() { 29128 angularInit(document, bootstrap); 29129 }); 29130 29131})(window, document); 29132 29133!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>'); 29134;angular.element(document).find('head').prepend('<!--[if IE 8]><style>.ng-hide {display: none !important;}</style><![endif]-->');
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.