| .. | .. |
|---|
| 1 | 1 | /** |
|---|
| 2 | | - * @license AngularJS v1.3.0 |
|---|
| 2 | + * @license AngularJS v1.3.6 |
|---|
| 3 | 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org |
|---|
| 4 | 4 | * License: MIT |
|---|
| 5 | 5 | */ |
|---|
| .. | .. |
|---|
| 37 | 37 | |
|---|
| 38 | 38 | function minErr(module, ErrorConstructor) { |
|---|
| 39 | 39 | ErrorConstructor = ErrorConstructor || Error; |
|---|
| 40 | | - return function () { |
|---|
| 40 | + return function() { |
|---|
| 41 | 41 | var code = arguments[0], |
|---|
| 42 | 42 | prefix = '[' + (module ? module + ':' : '') + code + '] ', |
|---|
| 43 | 43 | template = arguments[1], |
|---|
| 44 | 44 | templateArgs = arguments, |
|---|
| 45 | | - stringify = function (obj) { |
|---|
| 46 | | - if (typeof obj === 'function') { |
|---|
| 47 | | - return obj.toString().replace(/ \{[\s\S]*$/, ''); |
|---|
| 48 | | - } else if (typeof obj === 'undefined') { |
|---|
| 49 | | - return 'undefined'; |
|---|
| 50 | | - } else if (typeof obj !== 'string') { |
|---|
| 51 | | - return JSON.stringify(obj); |
|---|
| 52 | | - } |
|---|
| 53 | | - return obj; |
|---|
| 54 | | - }, |
|---|
| 45 | + |
|---|
| 55 | 46 | message, i; |
|---|
| 56 | 47 | |
|---|
| 57 | | - message = prefix + template.replace(/\{\d+\}/g, function (match) { |
|---|
| 48 | + message = prefix + template.replace(/\{\d+\}/g, function(match) { |
|---|
| 58 | 49 | var index = +match.slice(1, -1), arg; |
|---|
| 59 | 50 | |
|---|
| 60 | 51 | if (index + 2 < templateArgs.length) { |
|---|
| 61 | | - arg = templateArgs[index + 2]; |
|---|
| 62 | | - if (typeof arg === 'function') { |
|---|
| 63 | | - return arg.toString().replace(/ ?\{[\s\S]*$/, ''); |
|---|
| 64 | | - } else if (typeof arg === 'undefined') { |
|---|
| 65 | | - return 'undefined'; |
|---|
| 66 | | - } else if (typeof arg !== 'string') { |
|---|
| 67 | | - return toJson(arg); |
|---|
| 68 | | - } |
|---|
| 69 | | - return arg; |
|---|
| 52 | + return toDebugString(templateArgs[index + 2]); |
|---|
| 70 | 53 | } |
|---|
| 71 | 54 | return match; |
|---|
| 72 | 55 | }); |
|---|
| 73 | 56 | |
|---|
| 74 | | - message = message + '\nhttp://errors.angularjs.org/1.3.0/' + |
|---|
| 57 | + message = message + '\nhttp://errors.angularjs.org/1.3.6/' + |
|---|
| 75 | 58 | (module ? module + '/' : '') + code; |
|---|
| 76 | 59 | for (i = 2; i < arguments.length; i++) { |
|---|
| 77 | | - message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + |
|---|
| 78 | | - encodeURIComponent(stringify(arguments[i])); |
|---|
| 60 | + message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' + |
|---|
| 61 | + encodeURIComponent(toDebugString(arguments[i])); |
|---|
| 79 | 62 | } |
|---|
| 80 | 63 | return new ErrorConstructor(message); |
|---|
| 81 | 64 | }; |
|---|
| .. | .. |
|---|
| 130 | 113 | isBoolean: true, |
|---|
| 131 | 114 | isPromiseLike: true, |
|---|
| 132 | 115 | trim: true, |
|---|
| 116 | + escapeForRegexp: true, |
|---|
| 133 | 117 | isElement: true, |
|---|
| 134 | 118 | makeMap: true, |
|---|
| 135 | | - size: true, |
|---|
| 136 | 119 | includes: true, |
|---|
| 137 | 120 | arrayRemove: true, |
|---|
| 138 | | - isLeafNode: true, |
|---|
| 139 | 121 | copy: true, |
|---|
| 140 | 122 | shallowCopy: true, |
|---|
| 141 | 123 | equals: true, |
|---|
| .. | .. |
|---|
| 205 | 187 | * @param {string} string String to be converted to lowercase. |
|---|
| 206 | 188 | * @returns {string} Lowercased string. |
|---|
| 207 | 189 | */ |
|---|
| 208 | | -var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; |
|---|
| 190 | +var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; |
|---|
| 209 | 191 | var hasOwnProperty = Object.prototype.hasOwnProperty; |
|---|
| 210 | 192 | |
|---|
| 211 | 193 | /** |
|---|
| .. | .. |
|---|
| 218 | 200 | * @param {string} string String to be converted to uppercase. |
|---|
| 219 | 201 | * @returns {string} Uppercased string. |
|---|
| 220 | 202 | */ |
|---|
| 221 | | -var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; |
|---|
| 203 | +var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; |
|---|
| 222 | 204 | |
|---|
| 223 | 205 | |
|---|
| 224 | 206 | var manualLowercase = function(s) { |
|---|
| .. | .. |
|---|
| 244 | 226 | } |
|---|
| 245 | 227 | |
|---|
| 246 | 228 | |
|---|
| 247 | | -var /** holds major version number for IE or NaN for real browsers */ |
|---|
| 248 | | - msie, |
|---|
| 229 | +var |
|---|
| 230 | + msie, // holds major version number for IE, or NaN if UA is not IE. |
|---|
| 249 | 231 | jqLite, // delay binding since jQuery could be loaded after us. |
|---|
| 250 | 232 | jQuery, // delay binding |
|---|
| 251 | 233 | slice = [].slice, |
|---|
| .. | .. |
|---|
| 302 | 284 | * It is worth noting that `.forEach` does not iterate over inherited properties because it filters |
|---|
| 303 | 285 | * using the `hasOwnProperty` method. |
|---|
| 304 | 286 | * |
|---|
| 287 | + * Unlike ES262's |
|---|
| 288 | + * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), |
|---|
| 289 | + * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just |
|---|
| 290 | + * return the value provided. |
|---|
| 291 | + * |
|---|
| 305 | 292 | ```js |
|---|
| 306 | 293 | var values = {name: 'misko', gender: 'male'}; |
|---|
| 307 | 294 | var log = []; |
|---|
| .. | .. |
|---|
| 349 | 336 | } |
|---|
| 350 | 337 | |
|---|
| 351 | 338 | function sortedKeys(obj) { |
|---|
| 352 | | - var keys = []; |
|---|
| 353 | | - for (var key in obj) { |
|---|
| 354 | | - if (obj.hasOwnProperty(key)) { |
|---|
| 355 | | - keys.push(key); |
|---|
| 356 | | - } |
|---|
| 357 | | - } |
|---|
| 358 | | - return keys.sort(); |
|---|
| 339 | + return Object.keys(obj).sort(); |
|---|
| 359 | 340 | } |
|---|
| 360 | 341 | |
|---|
| 361 | 342 | function forEachSorted(obj, iterator, context) { |
|---|
| 362 | 343 | var keys = sortedKeys(obj); |
|---|
| 363 | | - for ( var i = 0; i < keys.length; i++) { |
|---|
| 344 | + for (var i = 0; i < keys.length; i++) { |
|---|
| 364 | 345 | iterator.call(context, obj[keys[i]], keys[i]); |
|---|
| 365 | 346 | } |
|---|
| 366 | 347 | return keys; |
|---|
| .. | .. |
|---|
| 415 | 396 | * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) |
|---|
| 416 | 397 | * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so |
|---|
| 417 | 398 | * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. |
|---|
| 399 | + * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy). |
|---|
| 418 | 400 | * |
|---|
| 419 | 401 | * @param {Object} dst Destination object. |
|---|
| 420 | 402 | * @param {...Object} src Source object(s). |
|---|
| .. | .. |
|---|
| 444 | 426 | |
|---|
| 445 | 427 | |
|---|
| 446 | 428 | function inherit(parent, extra) { |
|---|
| 447 | | - return extend(new (extend(function() {}, {prototype:parent}))(), extra); |
|---|
| 429 | + return extend(Object.create(parent), extra); |
|---|
| 448 | 430 | } |
|---|
| 449 | 431 | |
|---|
| 450 | 432 | /** |
|---|
| .. | .. |
|---|
| 501 | 483 | * @param {*} value Reference to check. |
|---|
| 502 | 484 | * @returns {boolean} True if `value` is undefined. |
|---|
| 503 | 485 | */ |
|---|
| 504 | | -function isUndefined(value){return typeof value === 'undefined';} |
|---|
| 486 | +function isUndefined(value) {return typeof value === 'undefined';} |
|---|
| 505 | 487 | |
|---|
| 506 | 488 | |
|---|
| 507 | 489 | /** |
|---|
| .. | .. |
|---|
| 516 | 498 | * @param {*} value Reference to check. |
|---|
| 517 | 499 | * @returns {boolean} True if `value` is defined. |
|---|
| 518 | 500 | */ |
|---|
| 519 | | -function isDefined(value){return typeof value !== 'undefined';} |
|---|
| 501 | +function isDefined(value) {return typeof value !== 'undefined';} |
|---|
| 520 | 502 | |
|---|
| 521 | 503 | |
|---|
| 522 | 504 | /** |
|---|
| .. | .. |
|---|
| 532 | 514 | * @param {*} value Reference to check. |
|---|
| 533 | 515 | * @returns {boolean} True if `value` is an `Object` but not `null`. |
|---|
| 534 | 516 | */ |
|---|
| 535 | | -function isObject(value){ |
|---|
| 517 | +function isObject(value) { |
|---|
| 536 | 518 | // http://jsperf.com/isobject4 |
|---|
| 537 | 519 | return value !== null && typeof value === 'object'; |
|---|
| 538 | 520 | } |
|---|
| .. | .. |
|---|
| 550 | 532 | * @param {*} value Reference to check. |
|---|
| 551 | 533 | * @returns {boolean} True if `value` is a `String`. |
|---|
| 552 | 534 | */ |
|---|
| 553 | | -function isString(value){return typeof value === 'string';} |
|---|
| 535 | +function isString(value) {return typeof value === 'string';} |
|---|
| 554 | 536 | |
|---|
| 555 | 537 | |
|---|
| 556 | 538 | /** |
|---|
| .. | .. |
|---|
| 565 | 547 | * @param {*} value Reference to check. |
|---|
| 566 | 548 | * @returns {boolean} True if `value` is a `Number`. |
|---|
| 567 | 549 | */ |
|---|
| 568 | | -function isNumber(value){return typeof value === 'number';} |
|---|
| 550 | +function isNumber(value) {return typeof value === 'number';} |
|---|
| 569 | 551 | |
|---|
| 570 | 552 | |
|---|
| 571 | 553 | /** |
|---|
| .. | .. |
|---|
| 611 | 593 | * @param {*} value Reference to check. |
|---|
| 612 | 594 | * @returns {boolean} True if `value` is a `Function`. |
|---|
| 613 | 595 | */ |
|---|
| 614 | | -function isFunction(value){return typeof value === 'function';} |
|---|
| 596 | +function isFunction(value) {return typeof value === 'function';} |
|---|
| 615 | 597 | |
|---|
| 616 | 598 | |
|---|
| 617 | 599 | /** |
|---|
| .. | .. |
|---|
| 667 | 649 | return isString(value) ? value.trim() : value; |
|---|
| 668 | 650 | }; |
|---|
| 669 | 651 | |
|---|
| 652 | +// Copied from: |
|---|
| 653 | +// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 |
|---|
| 654 | +// Prereq: s is a string. |
|---|
| 655 | +var escapeForRegexp = function(s) { |
|---|
| 656 | + return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). |
|---|
| 657 | + replace(/\x08/g, '\\x08'); |
|---|
| 658 | +}; |
|---|
| 659 | + |
|---|
| 670 | 660 | |
|---|
| 671 | 661 | /** |
|---|
| 672 | 662 | * @ngdoc function |
|---|
| .. | .. |
|---|
| 692 | 682 | */ |
|---|
| 693 | 683 | function makeMap(str) { |
|---|
| 694 | 684 | var obj = {}, items = str.split(","), i; |
|---|
| 695 | | - for ( i = 0; i < items.length; i++ ) |
|---|
| 685 | + for (i = 0; i < items.length; i++) |
|---|
| 696 | 686 | obj[ items[i] ] = true; |
|---|
| 697 | 687 | return obj; |
|---|
| 698 | 688 | } |
|---|
| 699 | 689 | |
|---|
| 700 | 690 | |
|---|
| 701 | 691 | function nodeName_(element) { |
|---|
| 702 | | - return lowercase(element.nodeName || element[0].nodeName); |
|---|
| 692 | + return lowercase(element.nodeName || (element[0] && element[0].nodeName)); |
|---|
| 703 | 693 | } |
|---|
| 704 | | - |
|---|
| 705 | | - |
|---|
| 706 | | -/** |
|---|
| 707 | | - * @description |
|---|
| 708 | | - * Determines the number of elements in an array, the number of properties an object has, or |
|---|
| 709 | | - * the length of a string. |
|---|
| 710 | | - * |
|---|
| 711 | | - * Note: This function is used to augment the Object type in Angular expressions. See |
|---|
| 712 | | - * {@link angular.Object} for more information about Angular arrays. |
|---|
| 713 | | - * |
|---|
| 714 | | - * @param {Object|Array|string} obj Object, array, or string to inspect. |
|---|
| 715 | | - * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object |
|---|
| 716 | | - * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. |
|---|
| 717 | | - */ |
|---|
| 718 | | -function size(obj, ownPropsOnly) { |
|---|
| 719 | | - var count = 0, key; |
|---|
| 720 | | - |
|---|
| 721 | | - if (isArray(obj) || isString(obj)) { |
|---|
| 722 | | - return obj.length; |
|---|
| 723 | | - } else if (isObject(obj)) { |
|---|
| 724 | | - for (key in obj) |
|---|
| 725 | | - if (!ownPropsOnly || obj.hasOwnProperty(key)) |
|---|
| 726 | | - count++; |
|---|
| 727 | | - } |
|---|
| 728 | | - |
|---|
| 729 | | - return count; |
|---|
| 730 | | -} |
|---|
| 731 | | - |
|---|
| 732 | 694 | |
|---|
| 733 | 695 | function includes(array, obj) { |
|---|
| 734 | 696 | return Array.prototype.indexOf.call(array, obj) != -1; |
|---|
| .. | .. |
|---|
| 736 | 698 | |
|---|
| 737 | 699 | function arrayRemove(array, value) { |
|---|
| 738 | 700 | var index = array.indexOf(value); |
|---|
| 739 | | - if (index >=0) |
|---|
| 701 | + if (index >= 0) |
|---|
| 740 | 702 | array.splice(index, 1); |
|---|
| 741 | 703 | return value; |
|---|
| 742 | | -} |
|---|
| 743 | | - |
|---|
| 744 | | -function isLeafNode (node) { |
|---|
| 745 | | - if (node) { |
|---|
| 746 | | - switch (nodeName_(node)) { |
|---|
| 747 | | - case "option": |
|---|
| 748 | | - case "pre": |
|---|
| 749 | | - case "title": |
|---|
| 750 | | - return true; |
|---|
| 751 | | - } |
|---|
| 752 | | - } |
|---|
| 753 | | - return false; |
|---|
| 754 | 704 | } |
|---|
| 755 | 705 | |
|---|
| 756 | 706 | /** |
|---|
| .. | .. |
|---|
| 850 | 800 | var result; |
|---|
| 851 | 801 | if (isArray(source)) { |
|---|
| 852 | 802 | destination.length = 0; |
|---|
| 853 | | - for ( var i = 0; i < source.length; i++) { |
|---|
| 803 | + for (var i = 0; i < source.length; i++) { |
|---|
| 854 | 804 | result = copy(source[i], null, stackSource, stackDest); |
|---|
| 855 | 805 | if (isObject(source[i])) { |
|---|
| 856 | 806 | stackSource.push(source[i]); |
|---|
| .. | .. |
|---|
| 867 | 817 | delete destination[key]; |
|---|
| 868 | 818 | }); |
|---|
| 869 | 819 | } |
|---|
| 870 | | - for ( var key in source) { |
|---|
| 871 | | - if(source.hasOwnProperty(key)) { |
|---|
| 820 | + for (var key in source) { |
|---|
| 821 | + if (source.hasOwnProperty(key)) { |
|---|
| 872 | 822 | result = copy(source[key], null, stackSource, stackDest); |
|---|
| 873 | 823 | if (isObject(source[key])) { |
|---|
| 874 | 824 | stackSource.push(source[key]); |
|---|
| .. | .. |
|---|
| 949 | 899 | if (isArray(o1)) { |
|---|
| 950 | 900 | if (!isArray(o2)) return false; |
|---|
| 951 | 901 | if ((length = o1.length) == o2.length) { |
|---|
| 952 | | - for(key=0; key<length; key++) { |
|---|
| 902 | + for (key = 0; key < length; key++) { |
|---|
| 953 | 903 | if (!equals(o1[key], o2[key])) return false; |
|---|
| 954 | 904 | } |
|---|
| 955 | 905 | return true; |
|---|
| .. | .. |
|---|
| 962 | 912 | } else { |
|---|
| 963 | 913 | if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; |
|---|
| 964 | 914 | keySet = {}; |
|---|
| 965 | | - for(key in o1) { |
|---|
| 915 | + for (key in o1) { |
|---|
| 966 | 916 | if (key.charAt(0) === '$' || isFunction(o1[key])) continue; |
|---|
| 967 | 917 | if (!equals(o1[key], o2[key])) return false; |
|---|
| 968 | 918 | keySet[key] = true; |
|---|
| 969 | 919 | } |
|---|
| 970 | | - for(key in o2) { |
|---|
| 920 | + for (key in o2) { |
|---|
| 971 | 921 | if (!keySet.hasOwnProperty(key) && |
|---|
| 972 | 922 | key.charAt(0) !== '$' && |
|---|
| 973 | 923 | o2[key] !== undefined && |
|---|
| .. | .. |
|---|
| 1035 | 985 | return curryArgs.length |
|---|
| 1036 | 986 | ? function() { |
|---|
| 1037 | 987 | return arguments.length |
|---|
| 1038 | | - ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) |
|---|
| 988 | + ? fn.apply(self, concat(curryArgs, arguments, 0)) |
|---|
| 1039 | 989 | : fn.apply(self, curryArgs); |
|---|
| 1040 | 990 | } |
|---|
| 1041 | 991 | : function() { |
|---|
| .. | .. |
|---|
| 1078 | 1028 | * stripped since angular uses this notation internally. |
|---|
| 1079 | 1029 | * |
|---|
| 1080 | 1030 | * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. |
|---|
| 1081 | | - * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. |
|---|
| 1031 | + * @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace. |
|---|
| 1032 | + * If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2). |
|---|
| 1082 | 1033 | * @returns {string|undefined} JSON-ified string representing `obj`. |
|---|
| 1083 | 1034 | */ |
|---|
| 1084 | 1035 | function toJson(obj, pretty) { |
|---|
| 1085 | 1036 | if (typeof obj === 'undefined') return undefined; |
|---|
| 1086 | | - return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); |
|---|
| 1037 | + if (!isNumber(pretty)) { |
|---|
| 1038 | + pretty = pretty ? 2 : null; |
|---|
| 1039 | + } |
|---|
| 1040 | + return JSON.stringify(obj, toJsonReplacer, pretty); |
|---|
| 1087 | 1041 | } |
|---|
| 1088 | 1042 | |
|---|
| 1089 | 1043 | |
|---|
| .. | .. |
|---|
| 1115 | 1069 | // turns out IE does not let you set .html() on elements which |
|---|
| 1116 | 1070 | // are not allowed to have children. So we just ignore it. |
|---|
| 1117 | 1071 | element.empty(); |
|---|
| 1118 | | - } catch(e) {} |
|---|
| 1072 | + } catch (e) {} |
|---|
| 1119 | 1073 | var elemHtml = jqLite('<div>').append(element).html(); |
|---|
| 1120 | 1074 | try { |
|---|
| 1121 | 1075 | return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : |
|---|
| 1122 | 1076 | elemHtml. |
|---|
| 1123 | 1077 | match(/^(<[^>]+>)/)[1]. |
|---|
| 1124 | 1078 | replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); |
|---|
| 1125 | | - } catch(e) { |
|---|
| 1079 | + } catch (e) { |
|---|
| 1126 | 1080 | return lowercase(elemHtml); |
|---|
| 1127 | 1081 | } |
|---|
| 1128 | 1082 | |
|---|
| .. | .. |
|---|
| 1142 | 1096 | function tryDecodeURIComponent(value) { |
|---|
| 1143 | 1097 | try { |
|---|
| 1144 | 1098 | return decodeURIComponent(value); |
|---|
| 1145 | | - } catch(e) { |
|---|
| 1099 | + } catch (e) { |
|---|
| 1146 | 1100 | // Ignore any invalid uri component |
|---|
| 1147 | 1101 | } |
|---|
| 1148 | 1102 | } |
|---|
| .. | .. |
|---|
| 1155 | 1109 | function parseKeyValue(/**string*/keyValue) { |
|---|
| 1156 | 1110 | var obj = {}, key_value, key; |
|---|
| 1157 | 1111 | forEach((keyValue || "").split('&'), function(keyValue) { |
|---|
| 1158 | | - if ( keyValue ) { |
|---|
| 1112 | + if (keyValue) { |
|---|
| 1159 | 1113 | key_value = keyValue.replace(/\+/g,'%20').split('='); |
|---|
| 1160 | 1114 | key = tryDecodeURIComponent(key_value[0]); |
|---|
| 1161 | | - if ( isDefined(key) ) { |
|---|
| 1115 | + if (isDefined(key)) { |
|---|
| 1162 | 1116 | var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; |
|---|
| 1163 | 1117 | if (!hasOwnProperty.call(obj, key)) { |
|---|
| 1164 | 1118 | obj[key] = val; |
|---|
| 1165 | | - } else if(isArray(obj[key])) { |
|---|
| 1119 | + } else if (isArray(obj[key])) { |
|---|
| 1166 | 1120 | obj[key].push(val); |
|---|
| 1167 | 1121 | } else { |
|---|
| 1168 | 1122 | obj[key] = [obj[key],val]; |
|---|
| .. | .. |
|---|
| 1235 | 1189 | function getNgAttribute(element, ngAttr) { |
|---|
| 1236 | 1190 | var attr, i, ii = ngAttrPrefixes.length; |
|---|
| 1237 | 1191 | element = jqLite(element); |
|---|
| 1238 | | - for (i=0; i<ii; ++i) { |
|---|
| 1192 | + for (i = 0; i < ii; ++i) { |
|---|
| 1239 | 1193 | attr = ngAttrPrefixes[i] + ngAttr; |
|---|
| 1240 | 1194 | if (isString(attr = element.attr(attr))) { |
|---|
| 1241 | 1195 | return attr; |
|---|
| .. | .. |
|---|
| 1445 | 1399 | * @param {Object=} config an object for defining configuration options for the application. The |
|---|
| 1446 | 1400 | * following keys are supported: |
|---|
| 1447 | 1401 | * |
|---|
| 1448 | | - * - `strictDi`: disable automatic function annotation for the application. This is meant to |
|---|
| 1449 | | - * assist in finding bugs which break minified code. |
|---|
| 1402 | + * * `strictDi` - disable automatic function annotation for the application. This is meant to |
|---|
| 1403 | + * assist in finding bugs which break minified code. Defaults to `false`. |
|---|
| 1450 | 1404 | * |
|---|
| 1451 | 1405 | * @returns {auto.$injector} Returns the newly created injector for this app. |
|---|
| 1452 | 1406 | */ |
|---|
| .. | .. |
|---|
| 1999 | 1953 | config(configFn); |
|---|
| 2000 | 1954 | } |
|---|
| 2001 | 1955 | |
|---|
| 2002 | | - return moduleInstance; |
|---|
| 1956 | + return moduleInstance; |
|---|
| 2003 | 1957 | |
|---|
| 2004 | 1958 | /** |
|---|
| 2005 | 1959 | * @param {string} provider |
|---|
| .. | .. |
|---|
| 2018 | 1972 | }; |
|---|
| 2019 | 1973 | }); |
|---|
| 2020 | 1974 | |
|---|
| 1975 | +} |
|---|
| 1976 | + |
|---|
| 1977 | +/* global: toDebugString: true */ |
|---|
| 1978 | + |
|---|
| 1979 | +function serializeObject(obj) { |
|---|
| 1980 | + var seen = []; |
|---|
| 1981 | + |
|---|
| 1982 | + return JSON.stringify(obj, function(key, val) { |
|---|
| 1983 | + val = toJsonReplacer(key, val); |
|---|
| 1984 | + if (isObject(val)) { |
|---|
| 1985 | + |
|---|
| 1986 | + if (seen.indexOf(val) >= 0) return '<<already seen>>'; |
|---|
| 1987 | + |
|---|
| 1988 | + seen.push(val); |
|---|
| 1989 | + } |
|---|
| 1990 | + return val; |
|---|
| 1991 | + }); |
|---|
| 1992 | +} |
|---|
| 1993 | + |
|---|
| 1994 | +function toDebugString(obj) { |
|---|
| 1995 | + if (typeof obj === 'function') { |
|---|
| 1996 | + return obj.toString().replace(/ \{[\s\S]*$/, ''); |
|---|
| 1997 | + } else if (typeof obj === 'undefined') { |
|---|
| 1998 | + return 'undefined'; |
|---|
| 1999 | + } else if (typeof obj !== 'string') { |
|---|
| 2000 | + return serializeObject(obj); |
|---|
| 2001 | + } |
|---|
| 2002 | + return obj; |
|---|
| 2021 | 2003 | } |
|---|
| 2022 | 2004 | |
|---|
| 2023 | 2005 | /* global angularModule: true, |
|---|
| .. | .. |
|---|
| 2103 | 2085 | $TimeoutProvider, |
|---|
| 2104 | 2086 | $$RAFProvider, |
|---|
| 2105 | 2087 | $$AsyncCallbackProvider, |
|---|
| 2106 | | - $WindowProvider |
|---|
| 2088 | + $WindowProvider, |
|---|
| 2089 | + $$jqLiteProvider |
|---|
| 2107 | 2090 | */ |
|---|
| 2108 | 2091 | |
|---|
| 2109 | 2092 | |
|---|
| .. | .. |
|---|
| 2122 | 2105 | * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". |
|---|
| 2123 | 2106 | */ |
|---|
| 2124 | 2107 | var version = { |
|---|
| 2125 | | - full: '1.3.0', // all of these placeholder strings will be replaced by grunt's |
|---|
| 2108 | + full: '1.3.6', // all of these placeholder strings will be replaced by grunt's |
|---|
| 2126 | 2109 | major: 1, // package task |
|---|
| 2127 | 2110 | minor: 3, |
|---|
| 2128 | | - dot: 0, |
|---|
| 2129 | | - codeName: 'superluminal-nudge' |
|---|
| 2111 | + dot: 6, |
|---|
| 2112 | + codeName: 'robofunky-danceblaster' |
|---|
| 2130 | 2113 | }; |
|---|
| 2131 | 2114 | |
|---|
| 2132 | 2115 | |
|---|
| 2133 | | -function publishExternalAPI(angular){ |
|---|
| 2116 | +function publishExternalAPI(angular) { |
|---|
| 2134 | 2117 | extend(angular, { |
|---|
| 2135 | 2118 | 'bootstrap': bootstrap, |
|---|
| 2136 | 2119 | 'copy': copy, |
|---|
| .. | .. |
|---|
| 2256 | 2239 | $timeout: $TimeoutProvider, |
|---|
| 2257 | 2240 | $window: $WindowProvider, |
|---|
| 2258 | 2241 | $$rAF: $$RAFProvider, |
|---|
| 2259 | | - $$asyncCallback : $$AsyncCallbackProvider |
|---|
| 2242 | + $$asyncCallback: $$AsyncCallbackProvider, |
|---|
| 2243 | + $$jqLite: $$jqLiteProvider |
|---|
| 2260 | 2244 | }); |
|---|
| 2261 | 2245 | } |
|---|
| 2262 | 2246 | ]); |
|---|
| .. | .. |
|---|
| 2306 | 2290 | * - [`children()`](http://api.jquery.com/children/) - Does not support selectors |
|---|
| 2307 | 2291 | * - [`clone()`](http://api.jquery.com/clone/) |
|---|
| 2308 | 2292 | * - [`contents()`](http://api.jquery.com/contents/) |
|---|
| 2309 | | - * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()` |
|---|
| 2293 | + * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()` |
|---|
| 2310 | 2294 | * - [`data()`](http://api.jquery.com/data/) |
|---|
| 2311 | 2295 | * - [`detach()`](http://api.jquery.com/detach/) |
|---|
| 2312 | 2296 | * - [`empty()`](http://api.jquery.com/empty/) |
|---|
| .. | .. |
|---|
| 2349 | 2333 | * `'ngModel'`). |
|---|
| 2350 | 2334 | * - `injector()` - retrieves the injector of the current element or its parent. |
|---|
| 2351 | 2335 | * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current |
|---|
| 2352 | | - * element or its parent. |
|---|
| 2336 | + * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to |
|---|
| 2337 | + * be enabled. |
|---|
| 2353 | 2338 | * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the |
|---|
| 2354 | 2339 | * current element. This getter should be used only on elements that contain a directive which starts a new isolate |
|---|
| 2355 | 2340 | * scope. Calling `scope()` on this element always returns the original non-isolate scope. |
|---|
| 2341 | + * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. |
|---|
| 2356 | 2342 | * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top |
|---|
| 2357 | 2343 | * parent element is reached. |
|---|
| 2358 | 2344 | * |
|---|
| .. | .. |
|---|
| 2384 | 2370 | |
|---|
| 2385 | 2371 | var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; |
|---|
| 2386 | 2372 | var MOZ_HACK_REGEXP = /^moz([A-Z])/; |
|---|
| 2387 | | -var MOUSE_EVENT_MAP= { mouseleave : "mouseout", mouseenter : "mouseover"}; |
|---|
| 2373 | +var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; |
|---|
| 2388 | 2374 | var jqLiteMinErr = minErr('jqLite'); |
|---|
| 2389 | 2375 | |
|---|
| 2390 | 2376 | /** |
|---|
| .. | .. |
|---|
| 2513 | 2499 | return element.cloneNode(true); |
|---|
| 2514 | 2500 | } |
|---|
| 2515 | 2501 | |
|---|
| 2516 | | -function jqLiteDealoc(element, onlyDescendants){ |
|---|
| 2502 | +function jqLiteDealoc(element, onlyDescendants) { |
|---|
| 2517 | 2503 | if (!onlyDescendants) jqLiteRemoveData(element); |
|---|
| 2518 | 2504 | |
|---|
| 2519 | 2505 | if (element.querySelectorAll) { |
|---|
| .. | .. |
|---|
| 2620 | 2606 | function jqLiteHasClass(element, selector) { |
|---|
| 2621 | 2607 | if (!element.getAttribute) return false; |
|---|
| 2622 | 2608 | return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). |
|---|
| 2623 | | - indexOf( " " + selector + " " ) > -1); |
|---|
| 2609 | + indexOf(" " + selector + " ") > -1); |
|---|
| 2624 | 2610 | } |
|---|
| 2625 | 2611 | |
|---|
| 2626 | 2612 | function jqLiteRemoveClass(element, cssClasses) { |
|---|
| .. | .. |
|---|
| 2679 | 2665 | |
|---|
| 2680 | 2666 | |
|---|
| 2681 | 2667 | function jqLiteController(element, name) { |
|---|
| 2682 | | - return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); |
|---|
| 2668 | + return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); |
|---|
| 2683 | 2669 | } |
|---|
| 2684 | 2670 | |
|---|
| 2685 | 2671 | function jqLiteInheritedData(element, name, value) { |
|---|
| 2686 | 2672 | // if element is the document object work with the html element instead |
|---|
| 2687 | 2673 | // this makes $(document).scope() possible |
|---|
| 2688 | | - if(element.nodeType == NODE_TYPE_DOCUMENT) { |
|---|
| 2674 | + if (element.nodeType == NODE_TYPE_DOCUMENT) { |
|---|
| 2689 | 2675 | element = element.documentElement; |
|---|
| 2690 | 2676 | } |
|---|
| 2691 | 2677 | var names = isArray(name) ? name : [name]; |
|---|
| .. | .. |
|---|
| 2743 | 2729 | } |
|---|
| 2744 | 2730 | |
|---|
| 2745 | 2731 | // check if document is already loaded |
|---|
| 2746 | | - if (document.readyState === 'complete'){ |
|---|
| 2732 | + if (document.readyState === 'complete') { |
|---|
| 2747 | 2733 | setTimeout(trigger); |
|---|
| 2748 | 2734 | } else { |
|---|
| 2749 | 2735 | this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 |
|---|
| .. | .. |
|---|
| 2751 | 2737 | // jshint -W064 |
|---|
| 2752 | 2738 | JQLite(window).on('load', trigger); // fallback to window.onload for others |
|---|
| 2753 | 2739 | // jshint +W064 |
|---|
| 2754 | | - this.on('DOMContentLoaded', trigger); |
|---|
| 2755 | 2740 | } |
|---|
| 2756 | 2741 | }, |
|---|
| 2757 | 2742 | toString: function() { |
|---|
| 2758 | 2743 | var value = []; |
|---|
| 2759 | | - forEach(this, function(e){ value.push('' + e);}); |
|---|
| 2744 | + forEach(this, function(e) { value.push('' + e);}); |
|---|
| 2760 | 2745 | return '[' + value.join(', ') + ']'; |
|---|
| 2761 | 2746 | }, |
|---|
| 2762 | 2747 | |
|---|
| .. | .. |
|---|
| 2784 | 2769 | BOOLEAN_ELEMENTS[value] = true; |
|---|
| 2785 | 2770 | }); |
|---|
| 2786 | 2771 | var ALIASED_ATTR = { |
|---|
| 2787 | | - 'ngMinlength' : 'minlength', |
|---|
| 2788 | | - 'ngMaxlength' : 'maxlength', |
|---|
| 2789 | | - 'ngMin' : 'min', |
|---|
| 2790 | | - 'ngMax' : 'max', |
|---|
| 2791 | | - 'ngPattern' : 'pattern' |
|---|
| 2772 | + 'ngMinlength': 'minlength', |
|---|
| 2773 | + 'ngMaxlength': 'maxlength', |
|---|
| 2774 | + 'ngMin': 'min', |
|---|
| 2775 | + 'ngMax': 'max', |
|---|
| 2776 | + 'ngPattern': 'pattern' |
|---|
| 2792 | 2777 | }; |
|---|
| 2793 | 2778 | |
|---|
| 2794 | 2779 | function getBooleanAttrName(element, name) { |
|---|
| .. | .. |
|---|
| 2847 | 2832 | } |
|---|
| 2848 | 2833 | }, |
|---|
| 2849 | 2834 | |
|---|
| 2850 | | - attr: function(element, name, value){ |
|---|
| 2835 | + attr: function(element, name, value) { |
|---|
| 2851 | 2836 | var lowercasedName = lowercase(name); |
|---|
| 2852 | 2837 | if (BOOLEAN_ATTR[lowercasedName]) { |
|---|
| 2853 | 2838 | if (isDefined(value)) { |
|---|
| .. | .. |
|---|
| 2860 | 2845 | } |
|---|
| 2861 | 2846 | } else { |
|---|
| 2862 | 2847 | return (element[name] || |
|---|
| 2863 | | - (element.attributes.getNamedItem(name)|| noop).specified) |
|---|
| 2848 | + (element.attributes.getNamedItem(name) || noop).specified) |
|---|
| 2864 | 2849 | ? lowercasedName |
|---|
| 2865 | 2850 | : undefined; |
|---|
| 2866 | 2851 | } |
|---|
| .. | .. |
|---|
| 2900 | 2885 | if (isUndefined(value)) { |
|---|
| 2901 | 2886 | if (element.multiple && nodeName_(element) === 'select') { |
|---|
| 2902 | 2887 | var result = []; |
|---|
| 2903 | | - forEach(element.options, function (option) { |
|---|
| 2888 | + forEach(element.options, function(option) { |
|---|
| 2904 | 2889 | if (option.selected) { |
|---|
| 2905 | 2890 | result.push(option.value || option.text); |
|---|
| 2906 | 2891 | } |
|---|
| .. | .. |
|---|
| 2921 | 2906 | }, |
|---|
| 2922 | 2907 | |
|---|
| 2923 | 2908 | empty: jqLiteEmpty |
|---|
| 2924 | | -}, function(fn, name){ |
|---|
| 2909 | +}, function(fn, name) { |
|---|
| 2925 | 2910 | /** |
|---|
| 2926 | 2911 | * Properties: writes return selection, reads return first value |
|---|
| 2927 | 2912 | */ |
|---|
| .. | .. |
|---|
| 2973 | 2958 | }); |
|---|
| 2974 | 2959 | |
|---|
| 2975 | 2960 | function createEventHandler(element, events) { |
|---|
| 2976 | | - var eventHandler = function (event, type) { |
|---|
| 2961 | + var eventHandler = function(event, type) { |
|---|
| 2977 | 2962 | // jQuery specific api |
|---|
| 2978 | 2963 | event.isDefaultPrevented = function() { |
|---|
| 2979 | 2964 | return event.defaultPrevented; |
|---|
| .. | .. |
|---|
| 3029 | 3014 | forEach({ |
|---|
| 3030 | 3015 | removeData: jqLiteRemoveData, |
|---|
| 3031 | 3016 | |
|---|
| 3032 | | - on: function jqLiteOn(element, type, fn, unsupported){ |
|---|
| 3017 | + on: function jqLiteOn(element, type, fn, unsupported) { |
|---|
| 3033 | 3018 | if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); |
|---|
| 3034 | 3019 | |
|---|
| 3035 | 3020 | // Do not add event handlers to non-elements because they will not be cleaned up. |
|---|
| .. | .. |
|---|
| 3065 | 3050 | var target = this, related = event.relatedTarget; |
|---|
| 3066 | 3051 | // For mousenter/leave call the handler if related is outside the target. |
|---|
| 3067 | 3052 | // NB: No relatedTarget if the mouse left/entered the browser window |
|---|
| 3068 | | - if ( !related || (related !== target && !target.contains(related)) ){ |
|---|
| 3053 | + if (!related || (related !== target && !target.contains(related))) { |
|---|
| 3069 | 3054 | handle(event, type); |
|---|
| 3070 | 3055 | } |
|---|
| 3071 | 3056 | }); |
|---|
| .. | .. |
|---|
| 3099 | 3084 | replaceWith: function(element, replaceNode) { |
|---|
| 3100 | 3085 | var index, parent = element.parentNode; |
|---|
| 3101 | 3086 | jqLiteDealoc(element); |
|---|
| 3102 | | - forEach(new JQLite(replaceNode), function(node){ |
|---|
| 3087 | + forEach(new JQLite(replaceNode), function(node) { |
|---|
| 3103 | 3088 | if (index) { |
|---|
| 3104 | 3089 | parent.insertBefore(node, index.nextSibling); |
|---|
| 3105 | 3090 | } else { |
|---|
| .. | .. |
|---|
| 3111 | 3096 | |
|---|
| 3112 | 3097 | children: function(element) { |
|---|
| 3113 | 3098 | var children = []; |
|---|
| 3114 | | - forEach(element.childNodes, function(element){ |
|---|
| 3099 | + forEach(element.childNodes, function(element) { |
|---|
| 3115 | 3100 | if (element.nodeType === NODE_TYPE_ELEMENT) |
|---|
| 3116 | 3101 | children.push(element); |
|---|
| 3117 | 3102 | }); |
|---|
| .. | .. |
|---|
| 3137 | 3122 | prepend: function(element, node) { |
|---|
| 3138 | 3123 | if (element.nodeType === NODE_TYPE_ELEMENT) { |
|---|
| 3139 | 3124 | var index = element.firstChild; |
|---|
| 3140 | | - forEach(new JQLite(node), function(child){ |
|---|
| 3125 | + forEach(new JQLite(node), function(child) { |
|---|
| 3141 | 3126 | element.insertBefore(child, index); |
|---|
| 3142 | 3127 | }); |
|---|
| 3143 | 3128 | } |
|---|
| .. | .. |
|---|
| 3174 | 3159 | |
|---|
| 3175 | 3160 | toggleClass: function(element, selector, condition) { |
|---|
| 3176 | 3161 | if (selector) { |
|---|
| 3177 | | - forEach(selector.split(' '), function(className){ |
|---|
| 3162 | + forEach(selector.split(' '), function(className) { |
|---|
| 3178 | 3163 | var classCondition = condition; |
|---|
| 3179 | 3164 | if (isUndefined(classCondition)) { |
|---|
| 3180 | 3165 | classCondition = !jqLiteHasClass(element, className); |
|---|
| .. | .. |
|---|
| 3239 | 3224 | }); |
|---|
| 3240 | 3225 | } |
|---|
| 3241 | 3226 | } |
|---|
| 3242 | | -}, function(fn, name){ |
|---|
| 3227 | +}, function(fn, name) { |
|---|
| 3243 | 3228 | /** |
|---|
| 3244 | 3229 | * chaining functions |
|---|
| 3245 | 3230 | */ |
|---|
| 3246 | 3231 | JQLite.prototype[name] = function(arg1, arg2, arg3) { |
|---|
| 3247 | 3232 | var value; |
|---|
| 3248 | 3233 | |
|---|
| 3249 | | - for(var i = 0, ii = this.length; i < ii; i++) { |
|---|
| 3234 | + for (var i = 0, ii = this.length; i < ii; i++) { |
|---|
| 3250 | 3235 | if (isUndefined(value)) { |
|---|
| 3251 | 3236 | value = fn(this[i], arg1, arg2, arg3); |
|---|
| 3252 | 3237 | if (isDefined(value)) { |
|---|
| .. | .. |
|---|
| 3264 | 3249 | JQLite.prototype.bind = JQLite.prototype.on; |
|---|
| 3265 | 3250 | JQLite.prototype.unbind = JQLite.prototype.off; |
|---|
| 3266 | 3251 | }); |
|---|
| 3252 | + |
|---|
| 3253 | + |
|---|
| 3254 | +// Provider for private $$jqLite service |
|---|
| 3255 | +function $$jqLiteProvider() { |
|---|
| 3256 | + this.$get = function $$jqLite() { |
|---|
| 3257 | + return extend(JQLite, { |
|---|
| 3258 | + hasClass: function(node, classes) { |
|---|
| 3259 | + if (node.attr) node = node[0]; |
|---|
| 3260 | + return jqLiteHasClass(node, classes); |
|---|
| 3261 | + }, |
|---|
| 3262 | + addClass: function(node, classes) { |
|---|
| 3263 | + if (node.attr) node = node[0]; |
|---|
| 3264 | + return jqLiteAddClass(node, classes); |
|---|
| 3265 | + }, |
|---|
| 3266 | + removeClass: function(node, classes) { |
|---|
| 3267 | + if (node.attr) node = node[0]; |
|---|
| 3268 | + return jqLiteRemoveClass(node, classes); |
|---|
| 3269 | + } |
|---|
| 3270 | + }); |
|---|
| 3271 | + }; |
|---|
| 3272 | +} |
|---|
| 3267 | 3273 | |
|---|
| 3268 | 3274 | /** |
|---|
| 3269 | 3275 | * Computes a hash of an 'obj'. |
|---|
| .. | .. |
|---|
| 3348 | 3354 | * Creates an injector object that can be used for retrieving services as well as for |
|---|
| 3349 | 3355 | * dependency injection (see {@link guide/di dependency injection}). |
|---|
| 3350 | 3356 | * |
|---|
| 3351 | | - |
|---|
| 3352 | 3357 | * @param {Array.<string|Function>} modules A list of module functions or their aliases. See |
|---|
| 3353 | | - * {@link angular.module}. The `ng` module must be explicitly added. |
|---|
| 3358 | + * {@link angular.module}. The `ng` module must be explicitly added. |
|---|
| 3359 | + * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which |
|---|
| 3360 | + * disallows argument name annotation inference. |
|---|
| 3354 | 3361 | * @returns {injector} Injector object. See {@link auto.$injector $injector}. |
|---|
| 3355 | 3362 | * |
|---|
| 3356 | 3363 | * @example |
|---|
| .. | .. |
|---|
| 3496 | 3503 | * ## Inference |
|---|
| 3497 | 3504 | * |
|---|
| 3498 | 3505 | * In JavaScript calling `toString()` on a function returns the function definition. The definition |
|---|
| 3499 | | - * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with |
|---|
| 3500 | | - * minification, and obfuscation tools since these tools change the argument names. |
|---|
| 3506 | + * can then be parsed and the function arguments can be extracted. This method of discovering |
|---|
| 3507 | + * annotations is disallowed when the injector is in strict mode. |
|---|
| 3508 | + * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the |
|---|
| 3509 | + * argument names. |
|---|
| 3501 | 3510 | * |
|---|
| 3502 | 3511 | * ## `$inject` Annotation |
|---|
| 3503 | 3512 | * By adding an `$inject` property onto a function the injection parameters can be specified. |
|---|
| .. | .. |
|---|
| 3514 | 3523 | * Return an instance of the service. |
|---|
| 3515 | 3524 | * |
|---|
| 3516 | 3525 | * @param {string} name The name of the instance to retrieve. |
|---|
| 3526 | + * @param {string} caller An optional string to provide the origin of the function call for error messages. |
|---|
| 3517 | 3527 | * @return {*} The instance. |
|---|
| 3518 | 3528 | */ |
|---|
| 3519 | 3529 | |
|---|
| .. | .. |
|---|
| 3582 | 3592 | * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); |
|---|
| 3583 | 3593 | * ``` |
|---|
| 3584 | 3594 | * |
|---|
| 3595 | + * You can disallow this method by using strict injection mode. |
|---|
| 3596 | + * |
|---|
| 3585 | 3597 | * This method does not work with code minification / obfuscation. For this reason the following |
|---|
| 3586 | 3598 | * annotation strategies are supported. |
|---|
| 3587 | 3599 | * |
|---|
| .. | .. |
|---|
| 3633 | 3645 | * |
|---|
| 3634 | 3646 | * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to |
|---|
| 3635 | 3647 | * be retrieved as described above. |
|---|
| 3648 | + * |
|---|
| 3649 | + * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. |
|---|
| 3636 | 3650 | * |
|---|
| 3637 | 3651 | * @returns {Array.<string>} The names of the services which the function requires. |
|---|
| 3638 | 3652 | */ |
|---|
| .. | .. |
|---|
| 3960 | 3974 | } |
|---|
| 3961 | 3975 | }, |
|---|
| 3962 | 3976 | providerInjector = (providerCache.$injector = |
|---|
| 3963 | | - createInternalInjector(providerCache, function() { |
|---|
| 3977 | + createInternalInjector(providerCache, function(serviceName, caller) { |
|---|
| 3978 | + if (angular.isString(caller)) { |
|---|
| 3979 | + path.push(caller); |
|---|
| 3980 | + } |
|---|
| 3964 | 3981 | throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); |
|---|
| 3965 | 3982 | })), |
|---|
| 3966 | 3983 | instanceCache = {}, |
|---|
| 3967 | 3984 | instanceInjector = (instanceCache.$injector = |
|---|
| 3968 | | - createInternalInjector(instanceCache, function(servicename) { |
|---|
| 3969 | | - var provider = providerInjector.get(servicename + providerSuffix); |
|---|
| 3970 | | - return instanceInjector.invoke(provider.$get, provider, undefined, servicename); |
|---|
| 3985 | + createInternalInjector(instanceCache, function(serviceName, caller) { |
|---|
| 3986 | + var provider = providerInjector.get(serviceName + providerSuffix, caller); |
|---|
| 3987 | + return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); |
|---|
| 3971 | 3988 | })); |
|---|
| 3972 | 3989 | |
|---|
| 3973 | 3990 | |
|---|
| .. | .. |
|---|
| 4002 | 4019 | |
|---|
| 4003 | 4020 | function enforceReturnValue(name, factory) { |
|---|
| 4004 | 4021 | return function enforcedReturnValue() { |
|---|
| 4005 | | - var result = instanceInjector.invoke(factory, this, undefined, name); |
|---|
| 4022 | + var result = instanceInjector.invoke(factory, this); |
|---|
| 4006 | 4023 | if (isUndefined(result)) { |
|---|
| 4007 | 4024 | throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); |
|---|
| 4008 | 4025 | } |
|---|
| .. | .. |
|---|
| 4043 | 4060 | //////////////////////////////////// |
|---|
| 4044 | 4061 | // Module Loading |
|---|
| 4045 | 4062 | //////////////////////////////////// |
|---|
| 4046 | | - function loadModules(modulesToLoad){ |
|---|
| 4063 | + function loadModules(modulesToLoad) { |
|---|
| 4047 | 4064 | var runBlocks = [], moduleFn; |
|---|
| 4048 | 4065 | forEach(modulesToLoad, function(module) { |
|---|
| 4049 | 4066 | if (loadedModules.get(module)) return; |
|---|
| .. | .. |
|---|
| 4051 | 4068 | |
|---|
| 4052 | 4069 | function runInvokeQueue(queue) { |
|---|
| 4053 | 4070 | var i, ii; |
|---|
| 4054 | | - for(i = 0, ii = queue.length; i < ii; i++) { |
|---|
| 4071 | + for (i = 0, ii = queue.length; i < ii; i++) { |
|---|
| 4055 | 4072 | var invokeArgs = queue[i], |
|---|
| 4056 | 4073 | provider = providerInjector.get(invokeArgs[0]); |
|---|
| 4057 | 4074 | |
|---|
| .. | .. |
|---|
| 4097 | 4114 | |
|---|
| 4098 | 4115 | function createInternalInjector(cache, factory) { |
|---|
| 4099 | 4116 | |
|---|
| 4100 | | - function getService(serviceName) { |
|---|
| 4117 | + function getService(serviceName, caller) { |
|---|
| 4101 | 4118 | if (cache.hasOwnProperty(serviceName)) { |
|---|
| 4102 | 4119 | if (cache[serviceName] === INSTANTIATING) { |
|---|
| 4103 | 4120 | throw $injectorMinErr('cdep', 'Circular dependency found: {0}', |
|---|
| .. | .. |
|---|
| 4108 | 4125 | try { |
|---|
| 4109 | 4126 | path.unshift(serviceName); |
|---|
| 4110 | 4127 | cache[serviceName] = INSTANTIATING; |
|---|
| 4111 | | - return cache[serviceName] = factory(serviceName); |
|---|
| 4128 | + return cache[serviceName] = factory(serviceName, caller); |
|---|
| 4112 | 4129 | } catch (err) { |
|---|
| 4113 | 4130 | if (cache[serviceName] === INSTANTIATING) { |
|---|
| 4114 | 4131 | delete cache[serviceName]; |
|---|
| .. | .. |
|---|
| 4131 | 4148 | length, i, |
|---|
| 4132 | 4149 | key; |
|---|
| 4133 | 4150 | |
|---|
| 4134 | | - for(i = 0, length = $inject.length; i < length; i++) { |
|---|
| 4151 | + for (i = 0, length = $inject.length; i < length; i++) { |
|---|
| 4135 | 4152 | key = $inject[i]; |
|---|
| 4136 | 4153 | if (typeof key !== 'string') { |
|---|
| 4137 | 4154 | throw $injectorMinErr('itkn', |
|---|
| .. | .. |
|---|
| 4140 | 4157 | args.push( |
|---|
| 4141 | 4158 | locals && locals.hasOwnProperty(key) |
|---|
| 4142 | 4159 | ? locals[key] |
|---|
| 4143 | | - : getService(key) |
|---|
| 4160 | + : getService(key, serviceName) |
|---|
| 4144 | 4161 | ); |
|---|
| 4145 | 4162 | } |
|---|
| 4146 | 4163 | if (isArray(fn)) { |
|---|
| .. | .. |
|---|
| 4153 | 4170 | } |
|---|
| 4154 | 4171 | |
|---|
| 4155 | 4172 | function instantiate(Type, locals, serviceName) { |
|---|
| 4156 | | - var Constructor = function() {}, |
|---|
| 4157 | | - instance, returnedValue; |
|---|
| 4158 | | - |
|---|
| 4159 | 4173 | // Check if Type is annotated and use just the given function at n-1 as parameter |
|---|
| 4160 | 4174 | // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); |
|---|
| 4161 | | - Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; |
|---|
| 4162 | | - instance = new Constructor(); |
|---|
| 4163 | | - returnedValue = invoke(Type, instance, locals, serviceName); |
|---|
| 4175 | + // Object creation: http://jsperf.com/create-constructor/2 |
|---|
| 4176 | + var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype); |
|---|
| 4177 | + var returnedValue = invoke(Type, instance, locals, serviceName); |
|---|
| 4164 | 4178 | |
|---|
| 4165 | 4179 | return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; |
|---|
| 4166 | 4180 | } |
|---|
| .. | .. |
|---|
| 4196 | 4210 | * @name $anchorScrollProvider#disableAutoScrolling |
|---|
| 4197 | 4211 | * |
|---|
| 4198 | 4212 | * @description |
|---|
| 4199 | | - * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to |
|---|
| 4213 | + * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to |
|---|
| 4200 | 4214 | * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> |
|---|
| 4201 | 4215 | * Use this method to disable automatic scrolling. |
|---|
| 4202 | 4216 | * |
|---|
| .. | .. |
|---|
| 4347 | 4361 | */ |
|---|
| 4348 | 4362 | this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { |
|---|
| 4349 | 4363 | var document = $window.document; |
|---|
| 4350 | | - var scrollScheduled = false; |
|---|
| 4351 | 4364 | |
|---|
| 4352 | 4365 | // Helper function to get first anchor from a NodeList |
|---|
| 4353 | 4366 | // (using `Array#some()` instead of `angular#forEach()` since it's more performant |
|---|
| .. | .. |
|---|
| 4521 | 4534 | * @return {RegExp} The current CSS className expression value. If null then there is no expression value |
|---|
| 4522 | 4535 | */ |
|---|
| 4523 | 4536 | this.classNameFilter = function(expression) { |
|---|
| 4524 | | - if(arguments.length === 1) { |
|---|
| 4537 | + if (arguments.length === 1) { |
|---|
| 4525 | 4538 | this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; |
|---|
| 4526 | 4539 | } |
|---|
| 4527 | 4540 | return this.$$classNameFilter; |
|---|
| .. | .. |
|---|
| 4616 | 4629 | * page}. |
|---|
| 4617 | 4630 | */ |
|---|
| 4618 | 4631 | return { |
|---|
| 4619 | | - animate : function(element, from, to) { |
|---|
| 4632 | + animate: function(element, from, to) { |
|---|
| 4620 | 4633 | applyStyles(element, { from: from, to: to }); |
|---|
| 4621 | 4634 | return asyncPromise(); |
|---|
| 4622 | 4635 | }, |
|---|
| .. | .. |
|---|
| 4637 | 4650 | * @param {object=} options an optional collection of styles that will be applied to the element. |
|---|
| 4638 | 4651 | * @return {Promise} the animation callback promise |
|---|
| 4639 | 4652 | */ |
|---|
| 4640 | | - enter : function(element, parent, after, options) { |
|---|
| 4653 | + enter: function(element, parent, after, options) { |
|---|
| 4641 | 4654 | applyStyles(element, options); |
|---|
| 4642 | 4655 | after ? after.after(element) |
|---|
| 4643 | 4656 | : parent.prepend(element); |
|---|
| .. | .. |
|---|
| 4655 | 4668 | * @param {object=} options an optional collection of options that will be applied to the element. |
|---|
| 4656 | 4669 | * @return {Promise} the animation callback promise |
|---|
| 4657 | 4670 | */ |
|---|
| 4658 | | - leave : function(element, options) { |
|---|
| 4671 | + leave: function(element, options) { |
|---|
| 4659 | 4672 | element.remove(); |
|---|
| 4660 | 4673 | return asyncPromise(); |
|---|
| 4661 | 4674 | }, |
|---|
| .. | .. |
|---|
| 4678 | 4691 | * @param {object=} options an optional collection of options that will be applied to the element. |
|---|
| 4679 | 4692 | * @return {Promise} the animation callback promise |
|---|
| 4680 | 4693 | */ |
|---|
| 4681 | | - move : function(element, parent, after, options) { |
|---|
| 4694 | + move: function(element, parent, after, options) { |
|---|
| 4682 | 4695 | // Do not remove element before insert. Removing will cause data associated with the |
|---|
| 4683 | 4696 | // element to be dropped. Insert will implicitly do the remove. |
|---|
| 4684 | 4697 | return this.enter(element, parent, after, options); |
|---|
| .. | .. |
|---|
| 4697 | 4710 | * @param {object=} options an optional collection of options that will be applied to the element. |
|---|
| 4698 | 4711 | * @return {Promise} the animation callback promise |
|---|
| 4699 | 4712 | */ |
|---|
| 4700 | | - addClass : function(element, className, options) { |
|---|
| 4713 | + addClass: function(element, className, options) { |
|---|
| 4701 | 4714 | return this.setClass(element, className, [], options); |
|---|
| 4702 | 4715 | }, |
|---|
| 4703 | 4716 | |
|---|
| 4704 | | - $$addClassImmediately : function(element, className, options) { |
|---|
| 4717 | + $$addClassImmediately: function(element, className, options) { |
|---|
| 4705 | 4718 | element = jqLite(element); |
|---|
| 4706 | 4719 | className = !isString(className) |
|---|
| 4707 | 4720 | ? (isArray(className) ? className.join(' ') : '') |
|---|
| 4708 | 4721 | : className; |
|---|
| 4709 | | - forEach(element, function (element) { |
|---|
| 4722 | + forEach(element, function(element) { |
|---|
| 4710 | 4723 | jqLiteAddClass(element, className); |
|---|
| 4711 | 4724 | }); |
|---|
| 4712 | 4725 | applyStyles(element, options); |
|---|
| .. | .. |
|---|
| 4726 | 4739 | * @param {object=} options an optional collection of options that will be applied to the element. |
|---|
| 4727 | 4740 | * @return {Promise} the animation callback promise |
|---|
| 4728 | 4741 | */ |
|---|
| 4729 | | - removeClass : function(element, className, options) { |
|---|
| 4742 | + removeClass: function(element, className, options) { |
|---|
| 4730 | 4743 | return this.setClass(element, [], className, options); |
|---|
| 4731 | 4744 | }, |
|---|
| 4732 | 4745 | |
|---|
| 4733 | | - $$removeClassImmediately : function(element, className, options) { |
|---|
| 4746 | + $$removeClassImmediately: function(element, className, options) { |
|---|
| 4734 | 4747 | element = jqLite(element); |
|---|
| 4735 | 4748 | className = !isString(className) |
|---|
| 4736 | 4749 | ? (isArray(className) ? className.join(' ') : '') |
|---|
| 4737 | 4750 | : className; |
|---|
| 4738 | | - forEach(element, function (element) { |
|---|
| 4751 | + forEach(element, function(element) { |
|---|
| 4739 | 4752 | jqLiteRemoveClass(element, className); |
|---|
| 4740 | 4753 | }); |
|---|
| 4741 | 4754 | applyStyles(element, options); |
|---|
| .. | .. |
|---|
| 4756 | 4769 | * @param {object=} options an optional collection of options that will be applied to the element. |
|---|
| 4757 | 4770 | * @return {Promise} the animation callback promise |
|---|
| 4758 | 4771 | */ |
|---|
| 4759 | | - setClass : function(element, add, remove, options) { |
|---|
| 4772 | + setClass: function(element, add, remove, options) { |
|---|
| 4760 | 4773 | var self = this; |
|---|
| 4761 | 4774 | var STORAGE_KEY = '$$animateClasses'; |
|---|
| 4762 | 4775 | var createdCache = false; |
|---|
| .. | .. |
|---|
| 4766 | 4779 | if (!cache) { |
|---|
| 4767 | 4780 | cache = { |
|---|
| 4768 | 4781 | classes: {}, |
|---|
| 4769 | | - options : options |
|---|
| 4782 | + options: options |
|---|
| 4770 | 4783 | }; |
|---|
| 4771 | 4784 | createdCache = true; |
|---|
| 4772 | 4785 | } else if (options && cache.options) { |
|---|
| .. | .. |
|---|
| 4803 | 4816 | return cache.promise; |
|---|
| 4804 | 4817 | }, |
|---|
| 4805 | 4818 | |
|---|
| 4806 | | - $$setClassImmediately : function(element, add, remove, options) { |
|---|
| 4819 | + $$setClassImmediately: function(element, add, remove, options) { |
|---|
| 4807 | 4820 | add && this.$$addClassImmediately(element, add); |
|---|
| 4808 | 4821 | remove && this.$$removeClassImmediately(element, remove); |
|---|
| 4809 | 4822 | applyStyles(element, options); |
|---|
| 4810 | 4823 | return asyncPromise(); |
|---|
| 4811 | 4824 | }, |
|---|
| 4812 | 4825 | |
|---|
| 4813 | | - enabled : noop, |
|---|
| 4814 | | - cancel : noop |
|---|
| 4826 | + enabled: noop, |
|---|
| 4827 | + cancel: noop |
|---|
| 4815 | 4828 | }; |
|---|
| 4816 | 4829 | }]; |
|---|
| 4817 | 4830 | }]; |
|---|
| 4818 | 4831 | |
|---|
| 4819 | | -function $$AsyncCallbackProvider(){ |
|---|
| 4832 | +function $$AsyncCallbackProvider() { |
|---|
| 4820 | 4833 | this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { |
|---|
| 4821 | 4834 | return $$rAF.supported |
|---|
| 4822 | 4835 | ? function(fn) { return $$rAF(fn); } |
|---|
| .. | .. |
|---|
| 4846 | 4859 | /** |
|---|
| 4847 | 4860 | * @param {object} window The global window object. |
|---|
| 4848 | 4861 | * @param {object} document jQuery wrapped document. |
|---|
| 4849 | | - * @param {function()} XHR XMLHttpRequest constructor. |
|---|
| 4850 | | - * @param {object} $log console.log or an object with the same interface. |
|---|
| 4862 | + * @param {object} $log window.console or an object with the same interface. |
|---|
| 4851 | 4863 | * @param {object} $sniffer $sniffer service |
|---|
| 4852 | 4864 | */ |
|---|
| 4853 | 4865 | function Browser(window, document, $log, $sniffer) { |
|---|
| .. | .. |
|---|
| 4878 | 4890 | } finally { |
|---|
| 4879 | 4891 | outstandingRequestCount--; |
|---|
| 4880 | 4892 | if (outstandingRequestCount === 0) { |
|---|
| 4881 | | - while(outstandingRequestCallbacks.length) { |
|---|
| 4893 | + while (outstandingRequestCallbacks.length) { |
|---|
| 4882 | 4894 | try { |
|---|
| 4883 | 4895 | outstandingRequestCallbacks.pop()(); |
|---|
| 4884 | 4896 | } catch (e) { |
|---|
| .. | .. |
|---|
| 4887 | 4899 | } |
|---|
| 4888 | 4900 | } |
|---|
| 4889 | 4901 | } |
|---|
| 4902 | + } |
|---|
| 4903 | + |
|---|
| 4904 | + function getHash(url) { |
|---|
| 4905 | + var index = url.indexOf('#'); |
|---|
| 4906 | + return index === -1 ? '' : url.substr(index + 1); |
|---|
| 4890 | 4907 | } |
|---|
| 4891 | 4908 | |
|---|
| 4892 | 4909 | /** |
|---|
| .. | .. |
|---|
| 4899 | 4916 | // force browser to execute all pollFns - this is needed so that cookies and other pollers fire |
|---|
| 4900 | 4917 | // at some deterministic time in respect to the test runner's actions. Leaving things up to the |
|---|
| 4901 | 4918 | // regular poller would result in flaky tests. |
|---|
| 4902 | | - forEach(pollFns, function(pollFn){ pollFn(); }); |
|---|
| 4919 | + forEach(pollFns, function(pollFn) { pollFn(); }); |
|---|
| 4903 | 4920 | |
|---|
| 4904 | 4921 | if (outstandingRequestCount === 0) { |
|---|
| 4905 | 4922 | callback(); |
|---|
| .. | .. |
|---|
| 4941 | 4958 | */ |
|---|
| 4942 | 4959 | function startPoller(interval, setTimeout) { |
|---|
| 4943 | 4960 | (function check() { |
|---|
| 4944 | | - forEach(pollFns, function(pollFn){ pollFn(); }); |
|---|
| 4961 | + forEach(pollFns, function(pollFn) { pollFn(); }); |
|---|
| 4945 | 4962 | pollTimeout = setTimeout(check, interval); |
|---|
| 4946 | 4963 | })(); |
|---|
| 4947 | 4964 | } |
|---|
| .. | .. |
|---|
| 4998 | 5015 | // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. |
|---|
| 4999 | 5016 | // See https://github.com/angular/angular.js/commit/ffb2701 |
|---|
| 5000 | 5017 | if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { |
|---|
| 5001 | | - return; |
|---|
| 5018 | + return self; |
|---|
| 5002 | 5019 | } |
|---|
| 5003 | 5020 | var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); |
|---|
| 5004 | 5021 | lastBrowserUrl = url; |
|---|
| .. | .. |
|---|
| 5018 | 5035 | } |
|---|
| 5019 | 5036 | if (replace) { |
|---|
| 5020 | 5037 | location.replace(url); |
|---|
| 5021 | | - } else { |
|---|
| 5038 | + } else if (!sameBase) { |
|---|
| 5022 | 5039 | location.href = url; |
|---|
| 5040 | + } else { |
|---|
| 5041 | + location.hash = getHash(url); |
|---|
| 5023 | 5042 | } |
|---|
| 5024 | 5043 | } |
|---|
| 5025 | 5044 | return self; |
|---|
| .. | .. |
|---|
| 5197 | 5216 | // - 20 cookies per unique domain |
|---|
| 5198 | 5217 | // - 4096 bytes per cookie |
|---|
| 5199 | 5218 | if (cookieLength > 4096) { |
|---|
| 5200 | | - $log.warn("Cookie '"+ name + |
|---|
| 5201 | | - "' possibly not set or overflowed because it was too large ("+ |
|---|
| 5219 | + $log.warn("Cookie '" + name + |
|---|
| 5220 | + "' possibly not set or overflowed because it was too large (" + |
|---|
| 5202 | 5221 | cookieLength + " > 4096 bytes)!"); |
|---|
| 5203 | 5222 | } |
|---|
| 5204 | 5223 | } |
|---|
| .. | .. |
|---|
| 5276 | 5295 | |
|---|
| 5277 | 5296 | } |
|---|
| 5278 | 5297 | |
|---|
| 5279 | | -function $BrowserProvider(){ |
|---|
| 5298 | +function $BrowserProvider() { |
|---|
| 5280 | 5299 | this.$get = ['$window', '$log', '$sniffer', '$document', |
|---|
| 5281 | | - function( $window, $log, $sniffer, $document){ |
|---|
| 5300 | + function($window, $log, $sniffer, $document) { |
|---|
| 5282 | 5301 | return new Browser($window, $document, $log, $sniffer); |
|---|
| 5283 | 5302 | }]; |
|---|
| 5284 | 5303 | } |
|---|
| .. | .. |
|---|
| 5652 | 5671 | * ``` |
|---|
| 5653 | 5672 | * |
|---|
| 5654 | 5673 | * **Note:** the `script` tag containing the template does not need to be included in the `head` of |
|---|
| 5655 | | - * the document, but it must be below the `ng-app` definition. |
|---|
| 5674 | + * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, |
|---|
| 5675 | + * element with ng-app attribute), otherwise the template will be ignored. |
|---|
| 5656 | 5676 | * |
|---|
| 5657 | 5677 | * Adding via the $templateCache service: |
|---|
| 5658 | 5678 | * |
|---|
| .. | .. |
|---|
| 5797 | 5817 | * #### `multiElement` |
|---|
| 5798 | 5818 | * When this property is set to true, the HTML compiler will collect DOM nodes between |
|---|
| 5799 | 5819 | * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them |
|---|
| 5800 | | - * together as the directive elements. It is recomended that this feature be used on directives |
|---|
| 5820 | + * together as the directive elements. It is recommended that this feature be used on directives |
|---|
| 5801 | 5821 | * which are not strictly behavioural (such as {@link ngClick}), and which |
|---|
| 5802 | 5822 | * do not manipulate or replace child nodes (such as {@link ngInclude}). |
|---|
| 5803 | 5823 | * |
|---|
| .. | .. |
|---|
| 5846 | 5866 | * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected |
|---|
| 5847 | 5867 | * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent |
|---|
| 5848 | 5868 | * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You |
|---|
| 5849 | | - * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. |
|---|
| 5869 | + * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If |
|---|
| 5870 | + * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use |
|---|
| 5871 | + * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). |
|---|
| 5850 | 5872 | * |
|---|
| 5851 | 5873 | * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. |
|---|
| 5852 | 5874 | * If no `attr` name is specified then the attribute name is assumed to be the same as the |
|---|
| .. | .. |
|---|
| 5860 | 5882 | * |
|---|
| 5861 | 5883 | * |
|---|
| 5862 | 5884 | * #### `bindToController` |
|---|
| 5863 | | - * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will |
|---|
| 5885 | + * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will |
|---|
| 5864 | 5886 | * allow a component to have its properties bound to the controller, rather than to scope. When the controller |
|---|
| 5865 | 5887 | * is instantiated, the initial values of the isolate scope bindings are already available. |
|---|
| 5866 | 5888 | * |
|---|
| .. | .. |
|---|
| 6302 | 6324 | * |
|---|
| 6303 | 6325 | * |
|---|
| 6304 | 6326 | * @param {string|DOMElement} element Element or HTML string to compile into a template function. |
|---|
| 6305 | | - * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. |
|---|
| 6327 | + * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. |
|---|
| 6328 | + * |
|---|
| 6329 | + * <div class="alert alert-error"> |
|---|
| 6330 | + * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it |
|---|
| 6331 | + * e.g. will not use the right outer scope. Please pass the transclude function as a |
|---|
| 6332 | + * `parentBoundTranscludeFn` to the link function instead. |
|---|
| 6333 | + * </div> |
|---|
| 6334 | + * |
|---|
| 6306 | 6335 | * @param {number} maxPriority only apply directives lower than given priority (Only effects the |
|---|
| 6307 | 6336 | * root element(s), not their children) |
|---|
| 6308 | | - * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template |
|---|
| 6337 | + * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template |
|---|
| 6309 | 6338 | * (a DOM element/tree) to a scope. Where: |
|---|
| 6310 | 6339 | * |
|---|
| 6311 | 6340 | * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. |
|---|
| .. | .. |
|---|
| 6316 | 6345 | * |
|---|
| 6317 | 6346 | * * `clonedElement` - is a clone of the original `element` passed into the compiler. |
|---|
| 6318 | 6347 | * * `scope` - is the current scope with which the linking function is working with. |
|---|
| 6348 | + * |
|---|
| 6349 | + * * `options` - An optional object hash with linking options. If `options` is provided, then the following |
|---|
| 6350 | + * keys may be used to control linking behavior: |
|---|
| 6351 | + * |
|---|
| 6352 | + * * `parentBoundTranscludeFn` - the transclude function made available to |
|---|
| 6353 | + * directives; if given, it will be passed through to the link functions of |
|---|
| 6354 | + * directives found in `element` during compilation. |
|---|
| 6355 | + * * `transcludeControllers` - an object hash with keys that map controller names |
|---|
| 6356 | + * to controller instances; if given, it will make the controllers |
|---|
| 6357 | + * available to directives. |
|---|
| 6358 | + * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add |
|---|
| 6359 | + * the cloned elements; only needed for transcludes that are allowed to contain non html |
|---|
| 6360 | + * elements (e.g. SVG elements). See also the directive.controller property. |
|---|
| 6319 | 6361 | * |
|---|
| 6320 | 6362 | * Calling the linking function returns the element of the template. It is either the original |
|---|
| 6321 | 6363 | * element passed in, or the clone of the element if the `cloneAttachFn` is provided. |
|---|
| .. | .. |
|---|
| 6362 | 6404 | function $CompileProvider($provide, $$sanitizeUriProvider) { |
|---|
| 6363 | 6405 | var hasDirectives = {}, |
|---|
| 6364 | 6406 | Suffix = 'Directive', |
|---|
| 6365 | | - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, |
|---|
| 6366 | | - CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/, |
|---|
| 6407 | + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, |
|---|
| 6408 | + CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, |
|---|
| 6367 | 6409 | ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), |
|---|
| 6368 | 6410 | REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; |
|---|
| 6369 | 6411 | |
|---|
| .. | .. |
|---|
| 6373 | 6415 | var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; |
|---|
| 6374 | 6416 | |
|---|
| 6375 | 6417 | function parseIsolateBindings(scope, directiveName) { |
|---|
| 6376 | | - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; |
|---|
| 6418 | + var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; |
|---|
| 6377 | 6419 | |
|---|
| 6378 | 6420 | var bindings = {}; |
|---|
| 6379 | 6421 | |
|---|
| .. | .. |
|---|
| 6388 | 6430 | } |
|---|
| 6389 | 6431 | |
|---|
| 6390 | 6432 | bindings[scopeName] = { |
|---|
| 6391 | | - attrName: match[3] || scopeName, |
|---|
| 6392 | | - mode: match[1], |
|---|
| 6393 | | - optional: match[2] === '?' |
|---|
| 6433 | + mode: match[1][0], |
|---|
| 6434 | + collection: match[2] === '*', |
|---|
| 6435 | + optional: match[3] === '?', |
|---|
| 6436 | + attrName: match[4] || scopeName |
|---|
| 6394 | 6437 | }; |
|---|
| 6395 | 6438 | }); |
|---|
| 6396 | 6439 | |
|---|
| .. | .. |
|---|
| 6462 | 6505 | * Retrieves or overrides the default regular expression that is used for whitelisting of safe |
|---|
| 6463 | 6506 | * urls during a[href] sanitization. |
|---|
| 6464 | 6507 | * |
|---|
| 6465 | | - * The sanitization is a security measure aimed at prevent XSS attacks via html links. |
|---|
| 6508 | + * The sanitization is a security measure aimed at preventing XSS attacks via html links. |
|---|
| 6466 | 6509 | * |
|---|
| 6467 | 6510 | * Any url about to be assigned to a[href] via data-binding is first normalized and turned into |
|---|
| 6468 | 6511 | * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` |
|---|
| .. | .. |
|---|
| 6529 | 6572 | * * `ng-binding` CSS class |
|---|
| 6530 | 6573 | * * `$binding` data property containing an array of the binding expressions |
|---|
| 6531 | 6574 | * |
|---|
| 6532 | | - * You may want to use this in production for a significant performance boost. See |
|---|
| 6575 | + * You may want to disable this in production for a significant performance boost. See |
|---|
| 6533 | 6576 | * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. |
|---|
| 6534 | 6577 | * |
|---|
| 6535 | 6578 | * The default value is true. |
|---|
| 6536 | 6579 | */ |
|---|
| 6537 | 6580 | var debugInfoEnabled = true; |
|---|
| 6538 | 6581 | this.debugInfoEnabled = function(enabled) { |
|---|
| 6539 | | - if(isDefined(enabled)) { |
|---|
| 6582 | + if (isDefined(enabled)) { |
|---|
| 6540 | 6583 | debugInfoEnabled = enabled; |
|---|
| 6541 | 6584 | return this; |
|---|
| 6542 | 6585 | } |
|---|
| .. | .. |
|---|
| 6566 | 6609 | }; |
|---|
| 6567 | 6610 | |
|---|
| 6568 | 6611 | Attributes.prototype = { |
|---|
| 6612 | + /** |
|---|
| 6613 | + * @ngdoc method |
|---|
| 6614 | + * @name $compile.directive.Attributes#$normalize |
|---|
| 6615 | + * @kind function |
|---|
| 6616 | + * |
|---|
| 6617 | + * @description |
|---|
| 6618 | + * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or |
|---|
| 6619 | + * `data-`) to its normalized, camelCase form. |
|---|
| 6620 | + * |
|---|
| 6621 | + * Also there is special case for Moz prefix starting with upper case letter. |
|---|
| 6622 | + * |
|---|
| 6623 | + * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} |
|---|
| 6624 | + * |
|---|
| 6625 | + * @param {string} name Name to normalize |
|---|
| 6626 | + */ |
|---|
| 6569 | 6627 | $normalize: directiveNormalize, |
|---|
| 6570 | 6628 | |
|---|
| 6571 | 6629 | |
|---|
| .. | .. |
|---|
| 6580 | 6638 | * |
|---|
| 6581 | 6639 | * @param {string} classVal The className value that will be added to the element |
|---|
| 6582 | 6640 | */ |
|---|
| 6583 | | - $addClass : function(classVal) { |
|---|
| 6584 | | - if(classVal && classVal.length > 0) { |
|---|
| 6641 | + $addClass: function(classVal) { |
|---|
| 6642 | + if (classVal && classVal.length > 0) { |
|---|
| 6585 | 6643 | $animate.addClass(this.$$element, classVal); |
|---|
| 6586 | 6644 | } |
|---|
| 6587 | 6645 | }, |
|---|
| .. | .. |
|---|
| 6597 | 6655 | * |
|---|
| 6598 | 6656 | * @param {string} classVal The className value that will be removed from the element |
|---|
| 6599 | 6657 | */ |
|---|
| 6600 | | - $removeClass : function(classVal) { |
|---|
| 6601 | | - if(classVal && classVal.length > 0) { |
|---|
| 6658 | + $removeClass: function(classVal) { |
|---|
| 6659 | + if (classVal && classVal.length > 0) { |
|---|
| 6602 | 6660 | $animate.removeClass(this.$$element, classVal); |
|---|
| 6603 | 6661 | } |
|---|
| 6604 | 6662 | }, |
|---|
| .. | .. |
|---|
| 6615 | 6673 | * @param {string} newClasses The current CSS className value |
|---|
| 6616 | 6674 | * @param {string} oldClasses The former CSS className value |
|---|
| 6617 | 6675 | */ |
|---|
| 6618 | | - $updateClass : function(newClasses, oldClasses) { |
|---|
| 6676 | + $updateClass: function(newClasses, oldClasses) { |
|---|
| 6619 | 6677 | var toAdd = tokenDifference(newClasses, oldClasses); |
|---|
| 6620 | 6678 | if (toAdd && toAdd.length) { |
|---|
| 6621 | 6679 | $animate.addClass(this.$$element, toAdd); |
|---|
| .. | .. |
|---|
| 6645 | 6703 | booleanKey = getBooleanAttrName(node, key), |
|---|
| 6646 | 6704 | aliasedKey = getAliasedAttrName(node, key), |
|---|
| 6647 | 6705 | observer = key, |
|---|
| 6648 | | - normalizedVal, |
|---|
| 6649 | 6706 | nodeName; |
|---|
| 6650 | 6707 | |
|---|
| 6651 | 6708 | if (booleanKey) { |
|---|
| 6652 | 6709 | this.$$element.prop(key, value); |
|---|
| 6653 | 6710 | attrName = booleanKey; |
|---|
| 6654 | | - } else if(aliasedKey) { |
|---|
| 6711 | + } else if (aliasedKey) { |
|---|
| 6655 | 6712 | this[aliasedKey] = value; |
|---|
| 6656 | 6713 | observer = aliasedKey; |
|---|
| 6657 | 6714 | } |
|---|
| .. | .. |
|---|
| 6689 | 6746 | |
|---|
| 6690 | 6747 | // for each tuples |
|---|
| 6691 | 6748 | var nbrUrisWith2parts = Math.floor(rawUris.length / 2); |
|---|
| 6692 | | - for (var i=0; i<nbrUrisWith2parts; i++) { |
|---|
| 6693 | | - var innerIdx = i*2; |
|---|
| 6749 | + for (var i = 0; i < nbrUrisWith2parts; i++) { |
|---|
| 6750 | + var innerIdx = i * 2; |
|---|
| 6694 | 6751 | // sanitize the uri |
|---|
| 6695 | | - result += $$sanitizeUri(trim( rawUris[innerIdx]), true); |
|---|
| 6752 | + result += $$sanitizeUri(trim(rawUris[innerIdx]), true); |
|---|
| 6696 | 6753 | // add the descriptor |
|---|
| 6697 | | - result += ( " " + trim(rawUris[innerIdx+1])); |
|---|
| 6754 | + result += (" " + trim(rawUris[innerIdx + 1])); |
|---|
| 6698 | 6755 | } |
|---|
| 6699 | 6756 | |
|---|
| 6700 | 6757 | // split the last item into uri and descriptor |
|---|
| 6701 | | - var lastTuple = trim(rawUris[i*2]).split(/\s/); |
|---|
| 6758 | + var lastTuple = trim(rawUris[i * 2]).split(/\s/); |
|---|
| 6702 | 6759 | |
|---|
| 6703 | 6760 | // sanitize the last uri |
|---|
| 6704 | 6761 | result += $$sanitizeUri(trim(lastTuple[0]), true); |
|---|
| 6705 | 6762 | |
|---|
| 6706 | 6763 | // and add the last descriptor if any |
|---|
| 6707 | | - if( lastTuple.length === 2) { |
|---|
| 6764 | + if (lastTuple.length === 2) { |
|---|
| 6708 | 6765 | result += (" " + trim(lastTuple[1])); |
|---|
| 6709 | 6766 | } |
|---|
| 6710 | 6767 | this[key] = value = result; |
|---|
| .. | .. |
|---|
| 6755 | 6812 | |
|---|
| 6756 | 6813 | listeners.push(fn); |
|---|
| 6757 | 6814 | $rootScope.$evalAsync(function() { |
|---|
| 6758 | | - if (!listeners.$$inter) { |
|---|
| 6815 | + if (!listeners.$$inter && attrs.hasOwnProperty(key)) { |
|---|
| 6759 | 6816 | // no one registered attribute interpolation function, so lets call it manually |
|---|
| 6760 | 6817 | fn(attrs[key]); |
|---|
| 6761 | 6818 | } |
|---|
| .. | .. |
|---|
| 6771 | 6828 | function safeAddClass($element, className) { |
|---|
| 6772 | 6829 | try { |
|---|
| 6773 | 6830 | $element.addClass(className); |
|---|
| 6774 | | - } catch(e) { |
|---|
| 6831 | + } catch (e) { |
|---|
| 6775 | 6832 | // ignore, since it means that we are trying to set class on |
|---|
| 6776 | 6833 | // SVG element, where class name is read-only. |
|---|
| 6777 | 6834 | } |
|---|
| .. | .. |
|---|
| 6825 | 6882 | } |
|---|
| 6826 | 6883 | // We can not compile top level text elements since text nodes can be merged and we will |
|---|
| 6827 | 6884 | // not be able to attach scope data to them, so we will wrap them in <span> |
|---|
| 6828 | | - forEach($compileNodes, function(node, index){ |
|---|
| 6885 | + forEach($compileNodes, function(node, index) { |
|---|
| 6829 | 6886 | if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { |
|---|
| 6830 | 6887 | $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; |
|---|
| 6831 | 6888 | } |
|---|
| .. | .. |
|---|
| 6835 | 6892 | maxPriority, ignoreDirective, previousCompileContext); |
|---|
| 6836 | 6893 | compile.$$addScopeClass($compileNodes); |
|---|
| 6837 | 6894 | var namespace = null; |
|---|
| 6838 | | - return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){ |
|---|
| 6895 | + return function publicLinkFn(scope, cloneConnectFn, options) { |
|---|
| 6839 | 6896 | assertArg(scope, 'scope'); |
|---|
| 6897 | + |
|---|
| 6898 | + options = options || {}; |
|---|
| 6899 | + var parentBoundTranscludeFn = options.parentBoundTranscludeFn, |
|---|
| 6900 | + transcludeControllers = options.transcludeControllers, |
|---|
| 6901 | + futureParentElement = options.futureParentElement; |
|---|
| 6902 | + |
|---|
| 6903 | + // When `parentBoundTranscludeFn` is passed, it is a |
|---|
| 6904 | + // `controllersBoundTransclude` function (it was previously passed |
|---|
| 6905 | + // as `transclude` to directive.link) so we must unwrap it to get |
|---|
| 6906 | + // its `boundTranscludeFn` |
|---|
| 6907 | + if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { |
|---|
| 6908 | + parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; |
|---|
| 6909 | + } |
|---|
| 6910 | + |
|---|
| 6840 | 6911 | if (!namespace) { |
|---|
| 6841 | 6912 | namespace = detectNamespaceForChildElements(futureParentElement); |
|---|
| 6842 | 6913 | } |
|---|
| .. | .. |
|---|
| 6878 | 6949 | if (!node) { |
|---|
| 6879 | 6950 | return 'html'; |
|---|
| 6880 | 6951 | } else { |
|---|
| 6881 | | - return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html'; |
|---|
| 6952 | + return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; |
|---|
| 6882 | 6953 | } |
|---|
| 6883 | 6954 | } |
|---|
| 6884 | 6955 | |
|---|
| .. | .. |
|---|
| 6960 | 7031 | stableNodeList = nodeList; |
|---|
| 6961 | 7032 | } |
|---|
| 6962 | 7033 | |
|---|
| 6963 | | - for(i = 0, ii = linkFns.length; i < ii;) { |
|---|
| 7034 | + for (i = 0, ii = linkFns.length; i < ii;) { |
|---|
| 6964 | 7035 | node = stableNodeList[linkFns[i++]]; |
|---|
| 6965 | 7036 | nodeLinkFn = linkFns[i++]; |
|---|
| 6966 | 7037 | childLinkFn = linkFns[i++]; |
|---|
| .. | .. |
|---|
| 6973 | 7044 | childScope = scope; |
|---|
| 6974 | 7045 | } |
|---|
| 6975 | 7046 | |
|---|
| 6976 | | - if ( nodeLinkFn.transcludeOnThisElement ) { |
|---|
| 7047 | + if (nodeLinkFn.transcludeOnThisElement) { |
|---|
| 6977 | 7048 | childBoundTranscludeFn = createBoundTranscludeFn( |
|---|
| 6978 | 7049 | scope, nodeLinkFn.transclude, parentBoundTranscludeFn, |
|---|
| 6979 | 7050 | nodeLinkFn.elementTranscludeOnThisElement); |
|---|
| .. | .. |
|---|
| 7006 | 7077 | transcludedScope.$$transcluded = true; |
|---|
| 7007 | 7078 | } |
|---|
| 7008 | 7079 | |
|---|
| 7009 | | - return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement); |
|---|
| 7080 | + return transcludeFn(transcludedScope, cloneFn, { |
|---|
| 7081 | + parentBoundTranscludeFn: previousBoundTranscludeFn, |
|---|
| 7082 | + transcludeControllers: controllers, |
|---|
| 7083 | + futureParentElement: futureParentElement |
|---|
| 7084 | + }); |
|---|
| 7010 | 7085 | }; |
|---|
| 7011 | 7086 | |
|---|
| 7012 | 7087 | return boundTranscludeFn; |
|---|
| .. | .. |
|---|
| 7028 | 7103 | match, |
|---|
| 7029 | 7104 | className; |
|---|
| 7030 | 7105 | |
|---|
| 7031 | | - switch(nodeType) { |
|---|
| 7106 | + switch (nodeType) { |
|---|
| 7032 | 7107 | case NODE_TYPE_ELEMENT: /* Element */ |
|---|
| 7033 | 7108 | // use the node name: <directive> |
|---|
| 7034 | 7109 | addDirective(directives, |
|---|
| .. | .. |
|---|
| 7120 | 7195 | var nodes = []; |
|---|
| 7121 | 7196 | var depth = 0; |
|---|
| 7122 | 7197 | if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { |
|---|
| 7123 | | - var startNode = node; |
|---|
| 7124 | 7198 | do { |
|---|
| 7125 | 7199 | if (!node) { |
|---|
| 7126 | 7200 | throw $compileMinErr('uterdir', |
|---|
| .. | .. |
|---|
| 7204 | 7278 | directiveValue; |
|---|
| 7205 | 7279 | |
|---|
| 7206 | 7280 | // executes all directives on the current element |
|---|
| 7207 | | - for(var i = 0, ii = directives.length; i < ii; i++) { |
|---|
| 7281 | + for (var i = 0, ii = directives.length; i < ii; i++) { |
|---|
| 7208 | 7282 | directive = directives[i]; |
|---|
| 7209 | 7283 | var attrStart = directive.$$start; |
|---|
| 7210 | 7284 | var attrEnd = directive.$$end; |
|---|
| .. | .. |
|---|
| 7448 | 7522 | "Controller '{0}', required by directive '{1}', can't be found!", |
|---|
| 7449 | 7523 | require, directiveName); |
|---|
| 7450 | 7524 | } |
|---|
| 7451 | | - return value; |
|---|
| 7525 | + return value || null; |
|---|
| 7452 | 7526 | } else if (isArray(require)) { |
|---|
| 7453 | 7527 | value = []; |
|---|
| 7454 | 7528 | forEach(require, function(require) { |
|---|
| .. | .. |
|---|
| 7475 | 7549 | isolateScope = scope.$new(true); |
|---|
| 7476 | 7550 | } |
|---|
| 7477 | 7551 | |
|---|
| 7478 | | - transcludeFn = boundTranscludeFn && controllersBoundTransclude; |
|---|
| 7552 | + if (boundTranscludeFn) { |
|---|
| 7553 | + // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` |
|---|
| 7554 | + // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` |
|---|
| 7555 | + transcludeFn = controllersBoundTransclude; |
|---|
| 7556 | + transcludeFn.$$boundTransclude = boundTranscludeFn; |
|---|
| 7557 | + } |
|---|
| 7558 | + |
|---|
| 7479 | 7559 | if (controllerDirectives) { |
|---|
| 7480 | 7560 | // TODO: merge `controllers` and `elementControllers` into single object. |
|---|
| 7481 | 7561 | controllers = {}; |
|---|
| .. | .. |
|---|
| 7510 | 7590 | } |
|---|
| 7511 | 7591 | |
|---|
| 7512 | 7592 | if (newIsolateScopeDirective) { |
|---|
| 7513 | | - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; |
|---|
| 7514 | | - |
|---|
| 7515 | 7593 | compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || |
|---|
| 7516 | 7594 | templateDirective === newIsolateScopeDirective.$$originalDirective))); |
|---|
| 7517 | 7595 | compile.$$addScopeClass($element, true); |
|---|
| .. | .. |
|---|
| 7537 | 7615 | isolateBindingContext[scopeName] = value; |
|---|
| 7538 | 7616 | }); |
|---|
| 7539 | 7617 | attrs.$$observers[attrName].$$scope = scope; |
|---|
| 7540 | | - if( attrs[attrName] ) { |
|---|
| 7618 | + if (attrs[attrName]) { |
|---|
| 7541 | 7619 | // If the attribute has been provided then we trigger an interpolation to ensure |
|---|
| 7542 | 7620 | // the value is there for use in the link fn |
|---|
| 7543 | 7621 | isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope); |
|---|
| .. | .. |
|---|
| 7552 | 7630 | if (parentGet.literal) { |
|---|
| 7553 | 7631 | compare = equals; |
|---|
| 7554 | 7632 | } else { |
|---|
| 7555 | | - compare = function(a,b) { return a === b || (a !== a && b !== b); }; |
|---|
| 7633 | + compare = function(a, b) { return a === b || (a !== a && b !== b); }; |
|---|
| 7556 | 7634 | } |
|---|
| 7557 | 7635 | parentSet = parentGet.assign || function() { |
|---|
| 7558 | 7636 | // reset the change, or we will throw this exception on every $digest |
|---|
| .. | .. |
|---|
| 7576 | 7654 | return lastValue = parentValue; |
|---|
| 7577 | 7655 | }; |
|---|
| 7578 | 7656 | parentValueWatch.$stateful = true; |
|---|
| 7579 | | - var unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); |
|---|
| 7657 | + var unwatch; |
|---|
| 7658 | + if (definition.collection) { |
|---|
| 7659 | + unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); |
|---|
| 7660 | + } else { |
|---|
| 7661 | + unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); |
|---|
| 7662 | + } |
|---|
| 7580 | 7663 | isolateScope.$on('$destroy', unwatch); |
|---|
| 7581 | 7664 | break; |
|---|
| 7582 | 7665 | |
|---|
| .. | .. |
|---|
| 7597 | 7680 | } |
|---|
| 7598 | 7681 | |
|---|
| 7599 | 7682 | // PRELINKING |
|---|
| 7600 | | - for(i = 0, ii = preLinkFns.length; i < ii; i++) { |
|---|
| 7683 | + for (i = 0, ii = preLinkFns.length; i < ii; i++) { |
|---|
| 7601 | 7684 | linkFn = preLinkFns[i]; |
|---|
| 7602 | 7685 | invokeLinkFn(linkFn, |
|---|
| 7603 | 7686 | linkFn.isolateScope ? isolateScope : scope, |
|---|
| .. | .. |
|---|
| 7618 | 7701 | childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); |
|---|
| 7619 | 7702 | |
|---|
| 7620 | 7703 | // POSTLINKING |
|---|
| 7621 | | - for(i = postLinkFns.length - 1; i >= 0; i--) { |
|---|
| 7704 | + for (i = postLinkFns.length - 1; i >= 0; i--) { |
|---|
| 7622 | 7705 | linkFn = postLinkFns[i]; |
|---|
| 7623 | 7706 | invokeLinkFn(linkFn, |
|---|
| 7624 | 7707 | linkFn.isolateScope ? isolateScope : scope, |
|---|
| .. | .. |
|---|
| 7678 | 7761 | if (name === ignoreDirective) return null; |
|---|
| 7679 | 7762 | var match = null; |
|---|
| 7680 | 7763 | if (hasDirectives.hasOwnProperty(name)) { |
|---|
| 7681 | | - for(var directive, directives = $injector.get(name + Suffix), |
|---|
| 7682 | | - i = 0, ii = directives.length; i<ii; i++) { |
|---|
| 7764 | + for (var directive, directives = $injector.get(name + Suffix), |
|---|
| 7765 | + i = 0, ii = directives.length; i < ii; i++) { |
|---|
| 7683 | 7766 | try { |
|---|
| 7684 | 7767 | directive = directives[i]; |
|---|
| 7685 | | - if ( (maxPriority === undefined || maxPriority > directive.priority) && |
|---|
| 7768 | + if ((maxPriority === undefined || maxPriority > directive.priority) && |
|---|
| 7686 | 7769 | directive.restrict.indexOf(location) != -1) { |
|---|
| 7687 | 7770 | if (startAttrName) { |
|---|
| 7688 | 7771 | directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); |
|---|
| .. | .. |
|---|
| 7690 | 7773 | tDirectives.push(directive); |
|---|
| 7691 | 7774 | match = directive; |
|---|
| 7692 | 7775 | } |
|---|
| 7693 | | - } catch(e) { $exceptionHandler(e); } |
|---|
| 7776 | + } catch (e) { $exceptionHandler(e); } |
|---|
| 7694 | 7777 | } |
|---|
| 7695 | 7778 | } |
|---|
| 7696 | 7779 | return match; |
|---|
| .. | .. |
|---|
| 7707 | 7790 | */ |
|---|
| 7708 | 7791 | function directiveIsMultiElement(name) { |
|---|
| 7709 | 7792 | if (hasDirectives.hasOwnProperty(name)) { |
|---|
| 7710 | | - for(var directive, directives = $injector.get(name + Suffix), |
|---|
| 7711 | | - i = 0, ii = directives.length; i<ii; i++) { |
|---|
| 7793 | + for (var directive, directives = $injector.get(name + Suffix), |
|---|
| 7794 | + i = 0, ii = directives.length; i < ii; i++) { |
|---|
| 7712 | 7795 | directive = directives[i]; |
|---|
| 7713 | 7796 | if (directive.multiElement) { |
|---|
| 7714 | 7797 | return true; |
|---|
| .. | .. |
|---|
| 7824 | 7907 | }); |
|---|
| 7825 | 7908 | afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); |
|---|
| 7826 | 7909 | |
|---|
| 7827 | | - while(linkQueue.length) { |
|---|
| 7910 | + while (linkQueue.length) { |
|---|
| 7828 | 7911 | var scope = linkQueue.shift(), |
|---|
| 7829 | 7912 | beforeTemplateLinkNode = linkQueue.shift(), |
|---|
| 7830 | 7913 | linkRootElement = linkQueue.shift(), |
|---|
| .. | .. |
|---|
| 7861 | 7944 | var childBoundTranscludeFn = boundTranscludeFn; |
|---|
| 7862 | 7945 | if (scope.$$destroyed) return; |
|---|
| 7863 | 7946 | if (linkQueue) { |
|---|
| 7864 | | - linkQueue.push(scope); |
|---|
| 7865 | | - linkQueue.push(node); |
|---|
| 7866 | | - linkQueue.push(rootElement); |
|---|
| 7867 | | - linkQueue.push(childBoundTranscludeFn); |
|---|
| 7947 | + linkQueue.push(scope, |
|---|
| 7948 | + node, |
|---|
| 7949 | + rootElement, |
|---|
| 7950 | + childBoundTranscludeFn); |
|---|
| 7868 | 7951 | } else { |
|---|
| 7869 | 7952 | if (afterTemplateNodeLinkFn.transcludeOnThisElement) { |
|---|
| 7870 | 7953 | childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); |
|---|
| .. | .. |
|---|
| 7923 | 8006 | |
|---|
| 7924 | 8007 | function wrapTemplate(type, template) { |
|---|
| 7925 | 8008 | type = lowercase(type || 'html'); |
|---|
| 7926 | | - switch(type) { |
|---|
| 8009 | + switch (type) { |
|---|
| 7927 | 8010 | case 'svg': |
|---|
| 7928 | 8011 | case 'math': |
|---|
| 7929 | 8012 | var wrapper = document.createElement('div'); |
|---|
| 7930 | | - wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>'; |
|---|
| 8013 | + wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; |
|---|
| 7931 | 8014 | return wrapper.childNodes[0].childNodes; |
|---|
| 7932 | 8015 | default: |
|---|
| 7933 | 8016 | return template; |
|---|
| .. | .. |
|---|
| 8004 | 8087 | //skip animations when the first digest occurs (when |
|---|
| 8005 | 8088 | //both the new and the old values are the same) since |
|---|
| 8006 | 8089 | //the CSS classes are the non-interpolated values |
|---|
| 8007 | | - if(name === 'class' && newValue != oldValue) { |
|---|
| 8090 | + if (name === 'class' && newValue != oldValue) { |
|---|
| 8008 | 8091 | attr.$updateClass(newValue, oldValue); |
|---|
| 8009 | 8092 | } else { |
|---|
| 8010 | 8093 | attr.$set(name, newValue); |
|---|
| .. | .. |
|---|
| 8034 | 8117 | i, ii; |
|---|
| 8035 | 8118 | |
|---|
| 8036 | 8119 | if ($rootElement) { |
|---|
| 8037 | | - for(i = 0, ii = $rootElement.length; i < ii; i++) { |
|---|
| 8120 | + for (i = 0, ii = $rootElement.length; i < ii; i++) { |
|---|
| 8038 | 8121 | if ($rootElement[i] == firstElementToRemove) { |
|---|
| 8039 | 8122 | $rootElement[i++] = newNode; |
|---|
| 8040 | 8123 | for (var j = i, j2 = j + removeCount - 1, |
|---|
| .. | .. |
|---|
| 8109 | 8192 | function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { |
|---|
| 8110 | 8193 | try { |
|---|
| 8111 | 8194 | linkFn(scope, $element, attrs, controllers, transcludeFn); |
|---|
| 8112 | | - } catch(e) { |
|---|
| 8195 | + } catch (e) { |
|---|
| 8113 | 8196 | $exceptionHandler(e, startingTag($element)); |
|---|
| 8114 | 8197 | } |
|---|
| 8115 | 8198 | } |
|---|
| 8116 | 8199 | }]; |
|---|
| 8117 | 8200 | } |
|---|
| 8118 | 8201 | |
|---|
| 8119 | | -var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; |
|---|
| 8202 | +var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; |
|---|
| 8120 | 8203 | /** |
|---|
| 8121 | 8204 | * Converts all accepted directives format into proper directive name. |
|---|
| 8122 | | - * All of these will become 'myDirective': |
|---|
| 8123 | | - * my:Directive |
|---|
| 8124 | | - * my-directive |
|---|
| 8125 | | - * x-my-directive |
|---|
| 8126 | | - * data-my:directive |
|---|
| 8127 | | - * |
|---|
| 8128 | | - * Also there is special case for Moz prefix starting with upper case letter. |
|---|
| 8129 | 8205 | * @param name Name to normalize |
|---|
| 8130 | 8206 | */ |
|---|
| 8131 | 8207 | function directiveNormalize(name) { |
|---|
| .. | .. |
|---|
| 8182 | 8258 | /* NodeList */ nodeList, |
|---|
| 8183 | 8259 | /* Element */ rootElement, |
|---|
| 8184 | 8260 | /* function(Function) */ boundTranscludeFn |
|---|
| 8185 | | -){} |
|---|
| 8261 | +) {} |
|---|
| 8186 | 8262 | |
|---|
| 8187 | 8263 | function directiveLinkingFn( |
|---|
| 8188 | 8264 | /* nodesetLinkingFn */ nodesetLinkingFn, |
|---|
| .. | .. |
|---|
| 8190 | 8266 | /* Node */ node, |
|---|
| 8191 | 8267 | /* Element */ rootElement, |
|---|
| 8192 | 8268 | /* function(Function) */ boundTranscludeFn |
|---|
| 8193 | | -){} |
|---|
| 8269 | +) {} |
|---|
| 8194 | 8270 | |
|---|
| 8195 | 8271 | function tokenDifference(str1, str2) { |
|---|
| 8196 | 8272 | var values = '', |
|---|
| .. | .. |
|---|
| 8198 | 8274 | tokens2 = str2.split(/\s+/); |
|---|
| 8199 | 8275 | |
|---|
| 8200 | 8276 | outer: |
|---|
| 8201 | | - for(var i = 0; i < tokens1.length; i++) { |
|---|
| 8277 | + for (var i = 0; i < tokens1.length; i++) { |
|---|
| 8202 | 8278 | var token = tokens1[i]; |
|---|
| 8203 | | - for(var j = 0; j < tokens2.length; j++) { |
|---|
| 8204 | | - if(token == tokens2[j]) continue outer; |
|---|
| 8279 | + for (var j = 0; j < tokens2.length; j++) { |
|---|
| 8280 | + if (token == tokens2[j]) continue outer; |
|---|
| 8205 | 8281 | } |
|---|
| 8206 | 8282 | values += (values.length > 0 ? ' ' : '') + token; |
|---|
| 8207 | 8283 | } |
|---|
| .. | .. |
|---|
| 8284 | 8360 | * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global |
|---|
| 8285 | 8361 | * `window` object (not recommended) |
|---|
| 8286 | 8362 | * |
|---|
| 8363 | + * The string can use the `controller as property` syntax, where the controller instance is published |
|---|
| 8364 | + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this |
|---|
| 8365 | + * to work correctly. |
|---|
| 8366 | + * |
|---|
| 8287 | 8367 | * @param {Object} locals Injection locals for Controller. |
|---|
| 8288 | 8368 | * @return {Object} Instance of given controller. |
|---|
| 8289 | 8369 | * |
|---|
| .. | .. |
|---|
| 8307 | 8387 | identifier = ident; |
|---|
| 8308 | 8388 | } |
|---|
| 8309 | 8389 | |
|---|
| 8310 | | - if(isString(expression)) { |
|---|
| 8390 | + if (isString(expression)) { |
|---|
| 8311 | 8391 | match = expression.match(CNTRL_REG), |
|---|
| 8312 | 8392 | constructor = match[1], |
|---|
| 8313 | 8393 | identifier = identifier || match[3]; |
|---|
| .. | .. |
|---|
| 8329 | 8409 | // |
|---|
| 8330 | 8410 | // This feature is not intended for use by applications, and is thus not documented |
|---|
| 8331 | 8411 | // publicly. |
|---|
| 8332 | | - var Constructor = function() {}; |
|---|
| 8333 | | - Constructor.prototype = (isArray(expression) ? |
|---|
| 8412 | + // Object creation: http://jsperf.com/create-constructor/2 |
|---|
| 8413 | + var controllerPrototype = (isArray(expression) ? |
|---|
| 8334 | 8414 | expression[expression.length - 1] : expression).prototype; |
|---|
| 8335 | | - instance = new Constructor(); |
|---|
| 8415 | + instance = Object.create(controllerPrototype); |
|---|
| 8336 | 8416 | |
|---|
| 8337 | 8417 | if (identifier) { |
|---|
| 8338 | 8418 | addIdentifier(locals, identifier, instance, constructor || expression.name); |
|---|
| .. | .. |
|---|
| 8393 | 8473 | </file> |
|---|
| 8394 | 8474 | </example> |
|---|
| 8395 | 8475 | */ |
|---|
| 8396 | | -function $DocumentProvider(){ |
|---|
| 8397 | | - this.$get = ['$window', function(window){ |
|---|
| 8476 | +function $DocumentProvider() { |
|---|
| 8477 | + this.$get = ['$window', function(window) { |
|---|
| 8398 | 8478 | return jqLite(window.document); |
|---|
| 8399 | 8479 | }]; |
|---|
| 8400 | 8480 | } |
|---|
| .. | .. |
|---|
| 8415 | 8495 | * ## Example: |
|---|
| 8416 | 8496 | * |
|---|
| 8417 | 8497 | * ```js |
|---|
| 8418 | | - * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { |
|---|
| 8419 | | - * return function (exception, cause) { |
|---|
| 8498 | + * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { |
|---|
| 8499 | + * return function(exception, cause) { |
|---|
| 8420 | 8500 | * exception.message += ' (caused by "' + cause + '")'; |
|---|
| 8421 | 8501 | * throw exception; |
|---|
| 8422 | 8502 | * }; |
|---|
| .. | .. |
|---|
| 8447 | 8527 | }]; |
|---|
| 8448 | 8528 | } |
|---|
| 8449 | 8529 | |
|---|
| 8530 | +var APPLICATION_JSON = 'application/json'; |
|---|
| 8531 | +var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; |
|---|
| 8532 | +var JSON_START = /^\s*(\[|\{[^\{])/; |
|---|
| 8533 | +var JSON_END = /[\}\]]\s*$/; |
|---|
| 8534 | +var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; |
|---|
| 8535 | + |
|---|
| 8536 | +function defaultHttpResponseTransform(data, headers) { |
|---|
| 8537 | + if (isString(data)) { |
|---|
| 8538 | + // strip json vulnerability protection prefix |
|---|
| 8539 | + data = data.replace(JSON_PROTECTION_PREFIX, ''); |
|---|
| 8540 | + var contentType = headers('Content-Type'); |
|---|
| 8541 | + if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0 && data.trim()) || |
|---|
| 8542 | + (JSON_START.test(data) && JSON_END.test(data))) { |
|---|
| 8543 | + data = fromJson(data); |
|---|
| 8544 | + } |
|---|
| 8545 | + } |
|---|
| 8546 | + return data; |
|---|
| 8547 | +} |
|---|
| 8548 | + |
|---|
| 8450 | 8549 | /** |
|---|
| 8451 | 8550 | * Parse headers into key value object |
|---|
| 8452 | 8551 | * |
|---|
| .. | .. |
|---|
| 8454 | 8553 | * @returns {Object} Parsed headers as key value object |
|---|
| 8455 | 8554 | */ |
|---|
| 8456 | 8555 | function parseHeaders(headers) { |
|---|
| 8457 | | - var parsed = {}, key, val, i; |
|---|
| 8556 | + var parsed = createMap(), key, val, i; |
|---|
| 8458 | 8557 | |
|---|
| 8459 | 8558 | if (!headers) return parsed; |
|---|
| 8460 | 8559 | |
|---|
| .. | .. |
|---|
| 8491 | 8590 | if (!headersObj) headersObj = parseHeaders(headers); |
|---|
| 8492 | 8591 | |
|---|
| 8493 | 8592 | if (name) { |
|---|
| 8494 | | - return headersObj[lowercase(name)] || null; |
|---|
| 8593 | + var value = headersObj[lowercase(name)]; |
|---|
| 8594 | + if (value === void 0) { |
|---|
| 8595 | + value = null; |
|---|
| 8596 | + } |
|---|
| 8597 | + return value; |
|---|
| 8495 | 8598 | } |
|---|
| 8496 | 8599 | |
|---|
| 8497 | 8600 | return headersObj; |
|---|
| .. | .. |
|---|
| 8533 | 8636 | * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. |
|---|
| 8534 | 8637 | * */ |
|---|
| 8535 | 8638 | function $HttpProvider() { |
|---|
| 8536 | | - var JSON_START = /^\s*(\[|\{[^\{])/, |
|---|
| 8537 | | - JSON_END = /[\}\]]\s*$/, |
|---|
| 8538 | | - PROTECTION_PREFIX = /^\)\]\}',?\n/, |
|---|
| 8539 | | - APPLICATION_JSON = 'application/json', |
|---|
| 8540 | | - CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; |
|---|
| 8541 | | - |
|---|
| 8542 | 8639 | /** |
|---|
| 8543 | 8640 | * @ngdoc property |
|---|
| 8544 | 8641 | * @name $httpProvider#defaults |
|---|
| 8545 | 8642 | * @description |
|---|
| 8546 | 8643 | * |
|---|
| 8547 | 8644 | * Object containing default values for all {@link ng.$http $http} requests. |
|---|
| 8645 | + * |
|---|
| 8646 | + * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} |
|---|
| 8647 | + * that will provide the cache for all requests who set their `cache` property to `true`. |
|---|
| 8648 | + * If you set the `default.cache = false` then only requests that specify their own custom |
|---|
| 8649 | + * cache object will be cached. See {@link $http#caching $http Caching} for more information. |
|---|
| 8548 | 8650 | * |
|---|
| 8549 | 8651 | * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. |
|---|
| 8550 | 8652 | * Defaults value is `'XSRF-TOKEN'`. |
|---|
| .. | .. |
|---|
| 8559 | 8661 | * - **`defaults.headers.post`** |
|---|
| 8560 | 8662 | * - **`defaults.headers.put`** |
|---|
| 8561 | 8663 | * - **`defaults.headers.patch`** |
|---|
| 8664 | + * |
|---|
| 8562 | 8665 | **/ |
|---|
| 8563 | 8666 | var defaults = this.defaults = { |
|---|
| 8564 | 8667 | // transform incoming response data |
|---|
| 8565 | | - transformResponse: [function defaultHttpResponseTransform(data, headers) { |
|---|
| 8566 | | - if (isString(data)) { |
|---|
| 8567 | | - // strip json vulnerability protection prefix |
|---|
| 8568 | | - data = data.replace(PROTECTION_PREFIX, ''); |
|---|
| 8569 | | - var contentType = headers('Content-Type'); |
|---|
| 8570 | | - if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) || |
|---|
| 8571 | | - (JSON_START.test(data) && JSON_END.test(data))) { |
|---|
| 8572 | | - data = fromJson(data); |
|---|
| 8573 | | - } |
|---|
| 8574 | | - } |
|---|
| 8575 | | - return data; |
|---|
| 8576 | | - }], |
|---|
| 8668 | + transformResponse: [defaultHttpResponseTransform], |
|---|
| 8577 | 8669 | |
|---|
| 8578 | 8670 | // transform outgoing request data |
|---|
| 8579 | 8671 | transformRequest: [function(d) { |
|---|
| .. | .. |
|---|
| 8623 | 8715 | }; |
|---|
| 8624 | 8716 | |
|---|
| 8625 | 8717 | /** |
|---|
| 8626 | | - * Are ordered by request, i.e. they are applied in the same order as the |
|---|
| 8718 | + * @ngdoc property |
|---|
| 8719 | + * @name $httpProvider#interceptors |
|---|
| 8720 | + * @description |
|---|
| 8721 | + * |
|---|
| 8722 | + * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} |
|---|
| 8723 | + * pre-processing of request or postprocessing of responses. |
|---|
| 8724 | + * |
|---|
| 8725 | + * These service factories are ordered by request, i.e. they are applied in the same order as the |
|---|
| 8627 | 8726 | * array, on request, but reverse order, on response. |
|---|
| 8628 | | - */ |
|---|
| 8727 | + * |
|---|
| 8728 | + * {@link ng.$http#interceptors Interceptors detailed info} |
|---|
| 8729 | + **/ |
|---|
| 8629 | 8730 | var interceptorFactories = this.interceptors = []; |
|---|
| 8630 | 8731 | |
|---|
| 8631 | 8732 | this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', |
|---|
| .. | .. |
|---|
| 8775 | 8876 | * In addition, you can supply a `headers` property in the config object passed when |
|---|
| 8776 | 8877 | * calling `$http(config)`, which overrides the defaults without changing them globally. |
|---|
| 8777 | 8878 | * |
|---|
| 8879 | + * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, |
|---|
| 8880 | + * Use the `headers` property, setting the desired header to `undefined`. For example: |
|---|
| 8881 | + * |
|---|
| 8882 | + * ```js |
|---|
| 8883 | + * var req = { |
|---|
| 8884 | + * method: 'POST', |
|---|
| 8885 | + * url: 'http://example.com', |
|---|
| 8886 | + * headers: { |
|---|
| 8887 | + * 'Content-Type': undefined |
|---|
| 8888 | + * }, |
|---|
| 8889 | + * data: { test: 'test' }, |
|---|
| 8890 | + * } |
|---|
| 8891 | + * |
|---|
| 8892 | + * $http(req).success(function(){...}).error(function(){...}); |
|---|
| 8893 | + * ``` |
|---|
| 8778 | 8894 | * |
|---|
| 8779 | 8895 | * ## Transforming Requests and Responses |
|---|
| 8780 | 8896 | * |
|---|
| .. | .. |
|---|
| 9021 | 9137 | * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – |
|---|
| 9022 | 9138 | * transform function or an array of such functions. The transform function takes the http |
|---|
| 9023 | 9139 | * request body and headers and returns its transformed (typically serialized) version. |
|---|
| 9024 | | - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} |
|---|
| 9140 | + * See {@link ng.$http#overriding-the-default-transformations-per-request |
|---|
| 9141 | + * Overriding the Default Transformations} |
|---|
| 9025 | 9142 | * - **transformResponse** – |
|---|
| 9026 | 9143 | * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – |
|---|
| 9027 | 9144 | * transform function or an array of such functions. The transform function takes the http |
|---|
| 9028 | 9145 | * response body and headers and returns its transformed (typically deserialized) version. |
|---|
| 9029 | | - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} |
|---|
| 9146 | + * See {@link ng.$http#overriding-the-default-transformations-per-request |
|---|
| 9147 | + * Overriding the Default Transformations} |
|---|
| 9030 | 9148 | * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the |
|---|
| 9031 | 9149 | * GET request, otherwise if a cache instance built with |
|---|
| 9032 | 9150 | * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for |
|---|
| .. | .. |
|---|
| 9154 | 9272 | }; |
|---|
| 9155 | 9273 | var headers = mergeHeaders(requestConfig); |
|---|
| 9156 | 9274 | |
|---|
| 9275 | + if (!angular.isObject(requestConfig)) { |
|---|
| 9276 | + throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); |
|---|
| 9277 | + } |
|---|
| 9278 | + |
|---|
| 9157 | 9279 | extend(config, requestConfig); |
|---|
| 9158 | 9280 | config.headers = headers; |
|---|
| 9159 | 9281 | config.method = uppercase(config.method); |
|---|
| .. | .. |
|---|
| 9192 | 9314 | } |
|---|
| 9193 | 9315 | }); |
|---|
| 9194 | 9316 | |
|---|
| 9195 | | - while(chain.length) { |
|---|
| 9317 | + while (chain.length) { |
|---|
| 9196 | 9318 | var thenFn = chain.shift(); |
|---|
| 9197 | 9319 | var rejectFn = chain.shift(); |
|---|
| 9198 | 9320 | |
|---|
| .. | .. |
|---|
| 9432 | 9554 | if (isDefined(cachedResp)) { |
|---|
| 9433 | 9555 | if (isPromiseLike(cachedResp)) { |
|---|
| 9434 | 9556 | // cached request has already been sent, but there is no response yet |
|---|
| 9435 | | - cachedResp.then(removePendingReq, removePendingReq); |
|---|
| 9436 | | - return cachedResp; |
|---|
| 9557 | + cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); |
|---|
| 9437 | 9558 | } else { |
|---|
| 9438 | 9559 | // serving from cache |
|---|
| 9439 | 9560 | if (isArray(cachedResp)) { |
|---|
| .. | .. |
|---|
| 9507 | 9628 | status: status, |
|---|
| 9508 | 9629 | headers: headersGetter(headers), |
|---|
| 9509 | 9630 | config: config, |
|---|
| 9510 | | - statusText : statusText |
|---|
| 9631 | + statusText: statusText |
|---|
| 9511 | 9632 | }); |
|---|
| 9512 | 9633 | } |
|---|
| 9513 | 9634 | |
|---|
| 9635 | + function resolvePromiseWithResult(result) { |
|---|
| 9636 | + resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); |
|---|
| 9637 | + } |
|---|
| 9514 | 9638 | |
|---|
| 9515 | 9639 | function removePendingReq() { |
|---|
| 9516 | 9640 | var idx = $http.pendingRequests.indexOf(config); |
|---|
| .. | .. |
|---|
| 9528 | 9652 | |
|---|
| 9529 | 9653 | forEach(value, function(v) { |
|---|
| 9530 | 9654 | if (isObject(v)) { |
|---|
| 9531 | | - if (isDate(v)){ |
|---|
| 9655 | + if (isDate(v)) { |
|---|
| 9532 | 9656 | v = v.toISOString(); |
|---|
| 9533 | 9657 | } else { |
|---|
| 9534 | 9658 | v = toJson(v); |
|---|
| .. | .. |
|---|
| 9538 | 9662 | encodeUriQuery(v)); |
|---|
| 9539 | 9663 | }); |
|---|
| 9540 | 9664 | }); |
|---|
| 9541 | | - if(parts.length > 0) { |
|---|
| 9665 | + if (parts.length > 0) { |
|---|
| 9542 | 9666 | url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); |
|---|
| 9543 | 9667 | } |
|---|
| 9544 | 9668 | return url; |
|---|
| .. | .. |
|---|
| 9625 | 9749 | statusText); |
|---|
| 9626 | 9750 | }; |
|---|
| 9627 | 9751 | |
|---|
| 9628 | | - var requestError = function () { |
|---|
| 9752 | + var requestError = function() { |
|---|
| 9629 | 9753 | // The response is always empty |
|---|
| 9630 | 9754 | // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error |
|---|
| 9631 | 9755 | completeRequest(callback, -1, null, null, ''); |
|---|
| .. | .. |
|---|
| 9672 | 9796 | |
|---|
| 9673 | 9797 | function completeRequest(callback, status, response, headersString, statusText) { |
|---|
| 9674 | 9798 | // cancel timeout and subsequent timeout promise resolution |
|---|
| 9675 | | - timeoutId && $browserDefer.cancel(timeoutId); |
|---|
| 9799 | + if (timeoutId !== undefined) { |
|---|
| 9800 | + $browserDefer.cancel(timeoutId); |
|---|
| 9801 | + } |
|---|
| 9676 | 9802 | jsonpDone = xhr = null; |
|---|
| 9677 | 9803 | |
|---|
| 9678 | 9804 | callback(status, response, headersString, statusText); |
|---|
| .. | .. |
|---|
| 9767 | 9893 | * @param {string=} value new value to set the starting symbol to. |
|---|
| 9768 | 9894 | * @returns {string|self} Returns the symbol when used as getter and self if used as setter. |
|---|
| 9769 | 9895 | */ |
|---|
| 9770 | | - this.startSymbol = function(value){ |
|---|
| 9896 | + this.startSymbol = function(value) { |
|---|
| 9771 | 9897 | if (value) { |
|---|
| 9772 | 9898 | startSymbol = value; |
|---|
| 9773 | 9899 | return this; |
|---|
| .. | .. |
|---|
| 9785 | 9911 | * @param {string=} value new value to set the ending symbol to. |
|---|
| 9786 | 9912 | * @returns {string|self} Returns the symbol when used as getter and self if used as setter. |
|---|
| 9787 | 9913 | */ |
|---|
| 9788 | | - this.endSymbol = function(value){ |
|---|
| 9914 | + this.endSymbol = function(value) { |
|---|
| 9789 | 9915 | if (value) { |
|---|
| 9790 | 9916 | endSymbol = value; |
|---|
| 9791 | 9917 | return this; |
|---|
| .. | .. |
|---|
| 9911 | 10037 | concat = [], |
|---|
| 9912 | 10038 | expressionPositions = []; |
|---|
| 9913 | 10039 | |
|---|
| 9914 | | - while(index < textLength) { |
|---|
| 9915 | | - if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && |
|---|
| 9916 | | - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { |
|---|
| 10040 | + while (index < textLength) { |
|---|
| 10041 | + if (((startIndex = text.indexOf(startSymbol, index)) != -1) && |
|---|
| 10042 | + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { |
|---|
| 9917 | 10043 | if (index !== startIndex) { |
|---|
| 9918 | 10044 | concat.push(unescapeText(text.substring(index, startIndex))); |
|---|
| 9919 | 10045 | } |
|---|
| .. | .. |
|---|
| 9947 | 10073 | |
|---|
| 9948 | 10074 | if (!mustHaveExpression || expressions.length) { |
|---|
| 9949 | 10075 | var compute = function(values) { |
|---|
| 9950 | | - for(var i = 0, ii = expressions.length; i < ii; i++) { |
|---|
| 10076 | + for (var i = 0, ii = expressions.length; i < ii; i++) { |
|---|
| 9951 | 10077 | if (allOrNothing && isUndefined(values[i])) return; |
|---|
| 9952 | 10078 | concat[expressionPositions[i]] = values[i]; |
|---|
| 9953 | 10079 | } |
|---|
| 9954 | 10080 | return concat.join(''); |
|---|
| 9955 | 10081 | }; |
|---|
| 9956 | 10082 | |
|---|
| 9957 | | - var getValue = function (value) { |
|---|
| 10083 | + var getValue = function(value) { |
|---|
| 9958 | 10084 | return trustedContext ? |
|---|
| 9959 | 10085 | $sce.getTrusted(trustedContext, value) : |
|---|
| 9960 | 10086 | $sce.valueOf(value); |
|---|
| 9961 | 10087 | }; |
|---|
| 9962 | 10088 | |
|---|
| 9963 | | - var stringify = function (value) { |
|---|
| 10089 | + var stringify = function(value) { |
|---|
| 9964 | 10090 | if (value == null) { // null || undefined |
|---|
| 9965 | 10091 | return ''; |
|---|
| 9966 | 10092 | } |
|---|
| .. | .. |
|---|
| 9988 | 10114 | } |
|---|
| 9989 | 10115 | |
|---|
| 9990 | 10116 | return compute(values); |
|---|
| 9991 | | - } catch(err) { |
|---|
| 10117 | + } catch (err) { |
|---|
| 9992 | 10118 | var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, |
|---|
| 9993 | 10119 | err.toString()); |
|---|
| 9994 | 10120 | $exceptionHandler(newErr); |
|---|
| .. | .. |
|---|
| 9998 | 10124 | // all of these properties are undocumented for now |
|---|
| 9999 | 10125 | exp: text, //just for compatibility with regular watchers created via $watch |
|---|
| 10000 | 10126 | expressions: expressions, |
|---|
| 10001 | | - $$watchDelegate: function (scope, listener, objectEquality) { |
|---|
| 10127 | + $$watchDelegate: function(scope, listener, objectEquality) { |
|---|
| 10002 | 10128 | var lastValue; |
|---|
| 10003 | 10129 | return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { |
|---|
| 10004 | 10130 | var currValue = compute(values); |
|---|
| .. | .. |
|---|
| 10018 | 10144 | |
|---|
| 10019 | 10145 | function parseStringifyInterceptor(value) { |
|---|
| 10020 | 10146 | try { |
|---|
| 10021 | | - return stringify(getValue(value)); |
|---|
| 10022 | | - } catch(err) { |
|---|
| 10147 | + value = getValue(value); |
|---|
| 10148 | + return allOrNothing && !isDefined(value) ? value : stringify(value); |
|---|
| 10149 | + } catch (err) { |
|---|
| 10023 | 10150 | var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, |
|---|
| 10024 | 10151 | err.toString()); |
|---|
| 10025 | 10152 | $exceptionHandler(newErr); |
|---|
| .. | .. |
|---|
| 10119 | 10246 | * // Don't start a new fight if we are already fighting |
|---|
| 10120 | 10247 | * if ( angular.isDefined(stop) ) return; |
|---|
| 10121 | 10248 | * |
|---|
| 10122 | | - * stop = $interval(function() { |
|---|
| 10123 | | - * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { |
|---|
| 10124 | | - * $scope.blood_1 = $scope.blood_1 - 3; |
|---|
| 10125 | | - * $scope.blood_2 = $scope.blood_2 - 4; |
|---|
| 10126 | | - * } else { |
|---|
| 10127 | | - * $scope.stopFight(); |
|---|
| 10249 | + * stop = $interval(function() { |
|---|
| 10250 | + * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { |
|---|
| 10251 | + * $scope.blood_1 = $scope.blood_1 - 3; |
|---|
| 10252 | + * $scope.blood_2 = $scope.blood_2 - 4; |
|---|
| 10253 | + * } else { |
|---|
| 10254 | + * $scope.stopFight(); |
|---|
| 10255 | + * } |
|---|
| 10256 | + * }, 100); |
|---|
| 10257 | + * }; |
|---|
| 10258 | + * |
|---|
| 10259 | + * $scope.stopFight = function() { |
|---|
| 10260 | + * if (angular.isDefined(stop)) { |
|---|
| 10261 | + * $interval.cancel(stop); |
|---|
| 10262 | + * stop = undefined; |
|---|
| 10128 | 10263 | * } |
|---|
| 10129 | | - * }, 100); |
|---|
| 10130 | | - * }; |
|---|
| 10264 | + * }; |
|---|
| 10131 | 10265 | * |
|---|
| 10132 | | - * $scope.stopFight = function() { |
|---|
| 10133 | | - * if (angular.isDefined(stop)) { |
|---|
| 10134 | | - * $interval.cancel(stop); |
|---|
| 10135 | | - * stop = undefined; |
|---|
| 10136 | | - * } |
|---|
| 10137 | | - * }; |
|---|
| 10266 | + * $scope.resetFight = function() { |
|---|
| 10267 | + * $scope.blood_1 = 100; |
|---|
| 10268 | + * $scope.blood_2 = 120; |
|---|
| 10269 | + * }; |
|---|
| 10138 | 10270 | * |
|---|
| 10139 | | - * $scope.resetFight = function() { |
|---|
| 10140 | | - * $scope.blood_1 = 100; |
|---|
| 10141 | | - * $scope.blood_2 = 120; |
|---|
| 10142 | | - * }; |
|---|
| 10143 | | - * |
|---|
| 10144 | | - * $scope.$on('$destroy', function() { |
|---|
| 10145 | | - * // Make sure that the interval is destroyed too |
|---|
| 10146 | | - * $scope.stopFight(); |
|---|
| 10147 | | - * }); |
|---|
| 10148 | | - * }]) |
|---|
| 10271 | + * $scope.$on('$destroy', function() { |
|---|
| 10272 | + * // Make sure that the interval is destroyed too |
|---|
| 10273 | + * $scope.stopFight(); |
|---|
| 10274 | + * }); |
|---|
| 10275 | + * }]) |
|---|
| 10149 | 10276 | * // Register the 'myCurrentTime' directive factory method. |
|---|
| 10150 | 10277 | * // We inject $interval and dateFilter service since the factory method is DI. |
|---|
| 10151 | 10278 | * .directive('myCurrentTime', ['$interval', 'dateFilter', |
|---|
| .. | .. |
|---|
| 10258 | 10385 | * |
|---|
| 10259 | 10386 | * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) |
|---|
| 10260 | 10387 | */ |
|---|
| 10261 | | -function $LocaleProvider(){ |
|---|
| 10388 | +function $LocaleProvider() { |
|---|
| 10262 | 10389 | this.$get = function() { |
|---|
| 10263 | 10390 | return { |
|---|
| 10264 | 10391 | id: 'en-us', |
|---|
| .. | .. |
|---|
| 10301 | 10428 | SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), |
|---|
| 10302 | 10429 | AMPMS: ['AM','PM'], |
|---|
| 10303 | 10430 | medium: 'MMM d, y h:mm:ss a', |
|---|
| 10304 | | - short: 'M/d/yy h:mm a', |
|---|
| 10431 | + 'short': 'M/d/yy h:mm a', |
|---|
| 10305 | 10432 | fullDate: 'EEEE, MMMM d, y', |
|---|
| 10306 | 10433 | longDate: 'MMMM d, y', |
|---|
| 10307 | 10434 | mediumDate: 'MMM d, y', |
|---|
| .. | .. |
|---|
| 10342 | 10469 | return segments.join('/'); |
|---|
| 10343 | 10470 | } |
|---|
| 10344 | 10471 | |
|---|
| 10345 | | -function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { |
|---|
| 10346 | | - var parsedUrl = urlResolve(absoluteUrl, appBase); |
|---|
| 10472 | +function parseAbsoluteUrl(absoluteUrl, locationObj) { |
|---|
| 10473 | + var parsedUrl = urlResolve(absoluteUrl); |
|---|
| 10347 | 10474 | |
|---|
| 10348 | 10475 | locationObj.$$protocol = parsedUrl.protocol; |
|---|
| 10349 | 10476 | locationObj.$$host = parsedUrl.hostname; |
|---|
| .. | .. |
|---|
| 10351 | 10478 | } |
|---|
| 10352 | 10479 | |
|---|
| 10353 | 10480 | |
|---|
| 10354 | | -function parseAppUrl(relativeUrl, locationObj, appBase) { |
|---|
| 10481 | +function parseAppUrl(relativeUrl, locationObj) { |
|---|
| 10355 | 10482 | var prefixed = (relativeUrl.charAt(0) !== '/'); |
|---|
| 10356 | 10483 | if (prefixed) { |
|---|
| 10357 | 10484 | relativeUrl = '/' + relativeUrl; |
|---|
| 10358 | 10485 | } |
|---|
| 10359 | | - var match = urlResolve(relativeUrl, appBase); |
|---|
| 10486 | + var match = urlResolve(relativeUrl); |
|---|
| 10360 | 10487 | locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? |
|---|
| 10361 | 10488 | match.pathname.substring(1) : match.pathname); |
|---|
| 10362 | 10489 | locationObj.$$search = parseKeyValue(match.search); |
|---|
| .. | .. |
|---|
| 10388 | 10515 | return index == -1 ? url : url.substr(0, index); |
|---|
| 10389 | 10516 | } |
|---|
| 10390 | 10517 | |
|---|
| 10518 | +function trimEmptyHash(url) { |
|---|
| 10519 | + return url.replace(/(#.+)|#$/, '$1'); |
|---|
| 10520 | +} |
|---|
| 10521 | + |
|---|
| 10391 | 10522 | |
|---|
| 10392 | 10523 | function stripFile(url) { |
|---|
| 10393 | 10524 | return url.substr(0, stripHash(url).lastIndexOf('/') + 1); |
|---|
| .. | .. |
|---|
| 10411 | 10542 | this.$$html5 = true; |
|---|
| 10412 | 10543 | basePrefix = basePrefix || ''; |
|---|
| 10413 | 10544 | var appBaseNoFile = stripFile(appBase); |
|---|
| 10414 | | - parseAbsoluteUrl(appBase, this, appBase); |
|---|
| 10545 | + parseAbsoluteUrl(appBase, this); |
|---|
| 10415 | 10546 | |
|---|
| 10416 | 10547 | |
|---|
| 10417 | 10548 | /** |
|---|
| 10418 | 10549 | * Parse given html5 (regular) url string into properties |
|---|
| 10419 | | - * @param {string} newAbsoluteUrl HTML5 url |
|---|
| 10550 | + * @param {string} url HTML5 url |
|---|
| 10420 | 10551 | * @private |
|---|
| 10421 | 10552 | */ |
|---|
| 10422 | 10553 | this.$$parse = function(url) { |
|---|
| .. | .. |
|---|
| 10426 | 10557 | appBaseNoFile); |
|---|
| 10427 | 10558 | } |
|---|
| 10428 | 10559 | |
|---|
| 10429 | | - parseAppUrl(pathUrl, this, appBase); |
|---|
| 10560 | + parseAppUrl(pathUrl, this); |
|---|
| 10430 | 10561 | |
|---|
| 10431 | 10562 | if (!this.$$path) { |
|---|
| 10432 | 10563 | this.$$path = '/'; |
|---|
| .. | .. |
|---|
| 10457 | 10588 | var appUrl, prevAppUrl; |
|---|
| 10458 | 10589 | var rewrittenUrl; |
|---|
| 10459 | 10590 | |
|---|
| 10460 | | - if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { |
|---|
| 10591 | + if ((appUrl = beginsWith(appBase, url)) !== undefined) { |
|---|
| 10461 | 10592 | prevAppUrl = appUrl; |
|---|
| 10462 | | - if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { |
|---|
| 10593 | + if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) { |
|---|
| 10463 | 10594 | rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); |
|---|
| 10464 | 10595 | } else { |
|---|
| 10465 | 10596 | rewrittenUrl = appBase + prevAppUrl; |
|---|
| 10466 | 10597 | } |
|---|
| 10467 | | - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { |
|---|
| 10598 | + } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) { |
|---|
| 10468 | 10599 | rewrittenUrl = appBaseNoFile + appUrl; |
|---|
| 10469 | 10600 | } else if (appBaseNoFile == url + '/') { |
|---|
| 10470 | 10601 | rewrittenUrl = appBaseNoFile; |
|---|
| .. | .. |
|---|
| 10489 | 10620 | function LocationHashbangUrl(appBase, hashPrefix) { |
|---|
| 10490 | 10621 | var appBaseNoFile = stripFile(appBase); |
|---|
| 10491 | 10622 | |
|---|
| 10492 | | - parseAbsoluteUrl(appBase, this, appBase); |
|---|
| 10623 | + parseAbsoluteUrl(appBase, this); |
|---|
| 10493 | 10624 | |
|---|
| 10494 | 10625 | |
|---|
| 10495 | 10626 | /** |
|---|
| .. | .. |
|---|
| 10499 | 10630 | */ |
|---|
| 10500 | 10631 | this.$$parse = function(url) { |
|---|
| 10501 | 10632 | var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); |
|---|
| 10502 | | - var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' |
|---|
| 10503 | | - ? beginsWith(hashPrefix, withoutBaseUrl) |
|---|
| 10504 | | - : (this.$$html5) |
|---|
| 10505 | | - ? withoutBaseUrl |
|---|
| 10506 | | - : ''; |
|---|
| 10633 | + var withoutHashUrl; |
|---|
| 10507 | 10634 | |
|---|
| 10508 | | - if (!isString(withoutHashUrl)) { |
|---|
| 10509 | | - throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, |
|---|
| 10510 | | - hashPrefix); |
|---|
| 10635 | + if (withoutBaseUrl.charAt(0) === '#') { |
|---|
| 10636 | + |
|---|
| 10637 | + // The rest of the url starts with a hash so we have |
|---|
| 10638 | + // got either a hashbang path or a plain hash fragment |
|---|
| 10639 | + withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); |
|---|
| 10640 | + if (isUndefined(withoutHashUrl)) { |
|---|
| 10641 | + // There was no hashbang prefix so we just have a hash fragment |
|---|
| 10642 | + withoutHashUrl = withoutBaseUrl; |
|---|
| 10643 | + } |
|---|
| 10644 | + |
|---|
| 10645 | + } else { |
|---|
| 10646 | + // There was no hashbang path nor hash fragment: |
|---|
| 10647 | + // If we are in HTML5 mode we use what is left as the path; |
|---|
| 10648 | + // Otherwise we ignore what is left |
|---|
| 10649 | + withoutHashUrl = this.$$html5 ? withoutBaseUrl : ''; |
|---|
| 10511 | 10650 | } |
|---|
| 10512 | | - parseAppUrl(withoutHashUrl, this, appBase); |
|---|
| 10651 | + |
|---|
| 10652 | + parseAppUrl(withoutHashUrl, this); |
|---|
| 10513 | 10653 | |
|---|
| 10514 | 10654 | this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); |
|---|
| 10515 | 10655 | |
|---|
| .. | .. |
|---|
| 10526 | 10666 | * Inside of Angular, we're always using pathnames that |
|---|
| 10527 | 10667 | * do not include drive names for routing. |
|---|
| 10528 | 10668 | */ |
|---|
| 10529 | | - function removeWindowsDriveName (path, url, base) { |
|---|
| 10669 | + function removeWindowsDriveName(path, url, base) { |
|---|
| 10530 | 10670 | /* |
|---|
| 10531 | 10671 | Matches paths for file protocol on windows, |
|---|
| 10532 | 10672 | such as /C:/foo/bar, and captures only /foo/bar. |
|---|
| .. | .. |
|---|
| 10563 | 10703 | }; |
|---|
| 10564 | 10704 | |
|---|
| 10565 | 10705 | this.$$parseLinkUrl = function(url, relHref) { |
|---|
| 10566 | | - if(stripHash(appBase) == stripHash(url)) { |
|---|
| 10706 | + if (stripHash(appBase) == stripHash(url)) { |
|---|
| 10567 | 10707 | this.$$parse(url); |
|---|
| 10568 | 10708 | return true; |
|---|
| 10569 | 10709 | } |
|---|
| .. | .. |
|---|
| 10598 | 10738 | var rewrittenUrl; |
|---|
| 10599 | 10739 | var appUrl; |
|---|
| 10600 | 10740 | |
|---|
| 10601 | | - if ( appBase == stripHash(url) ) { |
|---|
| 10741 | + if (appBase == stripHash(url)) { |
|---|
| 10602 | 10742 | rewrittenUrl = url; |
|---|
| 10603 | | - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { |
|---|
| 10743 | + } else if ((appUrl = beginsWith(appBaseNoFile, url))) { |
|---|
| 10604 | 10744 | rewrittenUrl = appBase + hashPrefix + appUrl; |
|---|
| 10605 | | - } else if ( appBaseNoFile === url + '/') { |
|---|
| 10745 | + } else if (appBaseNoFile === url + '/') { |
|---|
| 10606 | 10746 | rewrittenUrl = appBaseNoFile; |
|---|
| 10607 | 10747 | } |
|---|
| 10608 | 10748 | if (rewrittenUrl) { |
|---|
| .. | .. |
|---|
| 10647 | 10787 | * Return full url representation with all segments encoded according to rules specified in |
|---|
| 10648 | 10788 | * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). |
|---|
| 10649 | 10789 | * |
|---|
| 10790 | + * |
|---|
| 10791 | + * ```js |
|---|
| 10792 | + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo |
|---|
| 10793 | + * var absUrl = $location.absUrl(); |
|---|
| 10794 | + * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" |
|---|
| 10795 | + * ``` |
|---|
| 10796 | + * |
|---|
| 10650 | 10797 | * @return {string} full url |
|---|
| 10651 | 10798 | */ |
|---|
| 10652 | 10799 | absUrl: locationGetter('$$absUrl'), |
|---|
| .. | .. |
|---|
| 10662 | 10809 | * |
|---|
| 10663 | 10810 | * Change path, search and hash, when called with parameter and return `$location`. |
|---|
| 10664 | 10811 | * |
|---|
| 10812 | + * |
|---|
| 10813 | + * ```js |
|---|
| 10814 | + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo |
|---|
| 10815 | + * var url = $location.url(); |
|---|
| 10816 | + * // => "/some/path?foo=bar&baz=xoxo" |
|---|
| 10817 | + * ``` |
|---|
| 10818 | + * |
|---|
| 10665 | 10819 | * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) |
|---|
| 10666 | 10820 | * @return {string} url |
|---|
| 10667 | 10821 | */ |
|---|
| .. | .. |
|---|
| 10670 | 10824 | return this.$$url; |
|---|
| 10671 | 10825 | |
|---|
| 10672 | 10826 | var match = PATH_MATCH.exec(url); |
|---|
| 10673 | | - if (match[1]) this.path(decodeURIComponent(match[1])); |
|---|
| 10674 | | - if (match[2] || match[1]) this.search(match[3] || ''); |
|---|
| 10827 | + if (match[1] || url === '') this.path(decodeURIComponent(match[1])); |
|---|
| 10828 | + if (match[2] || match[1] || url === '') this.search(match[3] || ''); |
|---|
| 10675 | 10829 | this.hash(match[5] || ''); |
|---|
| 10676 | 10830 | |
|---|
| 10677 | 10831 | return this; |
|---|
| .. | .. |
|---|
| 10686 | 10840 | * |
|---|
| 10687 | 10841 | * Return protocol of current url. |
|---|
| 10688 | 10842 | * |
|---|
| 10843 | + * |
|---|
| 10844 | + * ```js |
|---|
| 10845 | + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo |
|---|
| 10846 | + * var protocol = $location.protocol(); |
|---|
| 10847 | + * // => "http" |
|---|
| 10848 | + * ``` |
|---|
| 10849 | + * |
|---|
| 10689 | 10850 | * @return {string} protocol of current url |
|---|
| 10690 | 10851 | */ |
|---|
| 10691 | 10852 | protocol: locationGetter('$$protocol'), |
|---|
| .. | .. |
|---|
| 10699 | 10860 | * |
|---|
| 10700 | 10861 | * Return host of current url. |
|---|
| 10701 | 10862 | * |
|---|
| 10863 | + * |
|---|
| 10864 | + * ```js |
|---|
| 10865 | + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo |
|---|
| 10866 | + * var host = $location.host(); |
|---|
| 10867 | + * // => "example.com" |
|---|
| 10868 | + * ``` |
|---|
| 10869 | + * |
|---|
| 10702 | 10870 | * @return {string} host of current url. |
|---|
| 10703 | 10871 | */ |
|---|
| 10704 | 10872 | host: locationGetter('$$host'), |
|---|
| .. | .. |
|---|
| 10711 | 10879 | * This method is getter only. |
|---|
| 10712 | 10880 | * |
|---|
| 10713 | 10881 | * Return port of current url. |
|---|
| 10882 | + * |
|---|
| 10883 | + * |
|---|
| 10884 | + * ```js |
|---|
| 10885 | + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo |
|---|
| 10886 | + * var port = $location.port(); |
|---|
| 10887 | + * // => 80 |
|---|
| 10888 | + * ``` |
|---|
| 10714 | 10889 | * |
|---|
| 10715 | 10890 | * @return {Number} port |
|---|
| 10716 | 10891 | */ |
|---|
| .. | .. |
|---|
| 10729 | 10904 | * |
|---|
| 10730 | 10905 | * Note: Path should always begin with forward slash (/), this method will add the forward slash |
|---|
| 10731 | 10906 | * if it is missing. |
|---|
| 10907 | + * |
|---|
| 10908 | + * |
|---|
| 10909 | + * ```js |
|---|
| 10910 | + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo |
|---|
| 10911 | + * var path = $location.path(); |
|---|
| 10912 | + * // => "/some/path" |
|---|
| 10913 | + * ``` |
|---|
| 10732 | 10914 | * |
|---|
| 10733 | 10915 | * @param {(string|number)=} path New path |
|---|
| 10734 | 10916 | * @return {string} path |
|---|
| .. | .. |
|---|
| 10755 | 10937 | * var searchObject = $location.search(); |
|---|
| 10756 | 10938 | * // => {foo: 'bar', baz: 'xoxo'} |
|---|
| 10757 | 10939 | * |
|---|
| 10758 | | - * |
|---|
| 10759 | 10940 | * // set foo to 'yipee' |
|---|
| 10760 | 10941 | * $location.search('foo', 'yipee'); |
|---|
| 10761 | | - * // => $location |
|---|
| 10942 | + * // $location.search() => {foo: 'yipee', baz: 'xoxo'} |
|---|
| 10762 | 10943 | * ``` |
|---|
| 10763 | 10944 | * |
|---|
| 10764 | 10945 | * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or |
|---|
| .. | .. |
|---|
| 10828 | 11009 | * |
|---|
| 10829 | 11010 | * Change hash fragment when called with parameter and return `$location`. |
|---|
| 10830 | 11011 | * |
|---|
| 11012 | + * |
|---|
| 11013 | + * ```js |
|---|
| 11014 | + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue |
|---|
| 11015 | + * var hash = $location.hash(); |
|---|
| 11016 | + * // => "hashValue" |
|---|
| 11017 | + * ``` |
|---|
| 11018 | + * |
|---|
| 10831 | 11019 | * @param {(string|number)=} hash New hash fragment |
|---|
| 10832 | 11020 | * @return {string} hash |
|---|
| 10833 | 11021 | */ |
|---|
| .. | .. |
|---|
| 10849 | 11037 | } |
|---|
| 10850 | 11038 | }; |
|---|
| 10851 | 11039 | |
|---|
| 10852 | | -forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) { |
|---|
| 11040 | +forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { |
|---|
| 10853 | 11041 | Location.prototype = Object.create(locationPrototype); |
|---|
| 10854 | 11042 | |
|---|
| 10855 | 11043 | /** |
|---|
| .. | .. |
|---|
| 10941 | 11129 | * @description |
|---|
| 10942 | 11130 | * Use the `$locationProvider` to configure how the application deep linking paths are stored. |
|---|
| 10943 | 11131 | */ |
|---|
| 10944 | | -function $LocationProvider(){ |
|---|
| 11132 | +function $LocationProvider() { |
|---|
| 10945 | 11133 | var hashPrefix = '', |
|---|
| 10946 | 11134 | html5Mode = { |
|---|
| 10947 | 11135 | enabled: false, |
|---|
| .. | .. |
|---|
| 11048 | 11236 | */ |
|---|
| 11049 | 11237 | |
|---|
| 11050 | 11238 | this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', |
|---|
| 11051 | | - function( $rootScope, $browser, $sniffer, $rootElement) { |
|---|
| 11239 | + function($rootScope, $browser, $sniffer, $rootElement) { |
|---|
| 11052 | 11240 | var $location, |
|---|
| 11053 | 11241 | LocationMode, |
|---|
| 11054 | 11242 | baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' |
|---|
| .. | .. |
|---|
| 11149 | 11337 | $rootScope.$evalAsync(function() { |
|---|
| 11150 | 11338 | var oldUrl = $location.absUrl(); |
|---|
| 11151 | 11339 | var oldState = $location.$$state; |
|---|
| 11340 | + var defaultPrevented; |
|---|
| 11152 | 11341 | |
|---|
| 11153 | 11342 | $location.$$parse(newUrl); |
|---|
| 11154 | 11343 | $location.$$state = newState; |
|---|
| 11155 | | - if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, |
|---|
| 11156 | | - newState, oldState).defaultPrevented) { |
|---|
| 11344 | + |
|---|
| 11345 | + defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, |
|---|
| 11346 | + newState, oldState).defaultPrevented; |
|---|
| 11347 | + |
|---|
| 11348 | + // if the location was changed by a `$locationChangeStart` handler then stop |
|---|
| 11349 | + // processing this location change |
|---|
| 11350 | + if ($location.absUrl() !== newUrl) return; |
|---|
| 11351 | + |
|---|
| 11352 | + if (defaultPrevented) { |
|---|
| 11157 | 11353 | $location.$$parse(oldUrl); |
|---|
| 11158 | 11354 | $location.$$state = oldState; |
|---|
| 11159 | 11355 | setBrowserUrlWithFallback(oldUrl, false, oldState); |
|---|
| .. | .. |
|---|
| 11167 | 11363 | |
|---|
| 11168 | 11364 | // update browser |
|---|
| 11169 | 11365 | $rootScope.$watch(function $locationWatch() { |
|---|
| 11170 | | - var oldUrl = $browser.url(); |
|---|
| 11366 | + var oldUrl = trimEmptyHash($browser.url()); |
|---|
| 11367 | + var newUrl = trimEmptyHash($location.absUrl()); |
|---|
| 11171 | 11368 | var oldState = $browser.state(); |
|---|
| 11172 | 11369 | var currentReplace = $location.$$replace; |
|---|
| 11173 | | - var urlOrStateChanged = oldUrl !== $location.absUrl() || |
|---|
| 11370 | + var urlOrStateChanged = oldUrl !== newUrl || |
|---|
| 11174 | 11371 | ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); |
|---|
| 11175 | 11372 | |
|---|
| 11176 | 11373 | if (initializing || urlOrStateChanged) { |
|---|
| 11177 | 11374 | initializing = false; |
|---|
| 11178 | 11375 | |
|---|
| 11179 | 11376 | $rootScope.$evalAsync(function() { |
|---|
| 11180 | | - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl, |
|---|
| 11181 | | - $location.$$state, oldState).defaultPrevented) { |
|---|
| 11377 | + var newUrl = $location.absUrl(); |
|---|
| 11378 | + var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, |
|---|
| 11379 | + $location.$$state, oldState).defaultPrevented; |
|---|
| 11380 | + |
|---|
| 11381 | + // if the location was changed by a `$locationChangeStart` handler then stop |
|---|
| 11382 | + // processing this location change |
|---|
| 11383 | + if ($location.absUrl() !== newUrl) return; |
|---|
| 11384 | + |
|---|
| 11385 | + if (defaultPrevented) { |
|---|
| 11182 | 11386 | $location.$$parse(oldUrl); |
|---|
| 11183 | 11387 | $location.$$state = oldState; |
|---|
| 11184 | 11388 | } else { |
|---|
| 11185 | 11389 | if (urlOrStateChanged) { |
|---|
| 11186 | | - setBrowserUrlWithFallback($location.absUrl(), currentReplace, |
|---|
| 11390 | + setBrowserUrlWithFallback(newUrl, currentReplace, |
|---|
| 11187 | 11391 | oldState === $location.$$state ? null : $location.$$state); |
|---|
| 11188 | 11392 | } |
|---|
| 11189 | 11393 | afterLocationChange(oldUrl, oldState); |
|---|
| .. | .. |
|---|
| 11249 | 11453 | * @description |
|---|
| 11250 | 11454 | * Use the `$logProvider` to configure how the application logs messages |
|---|
| 11251 | 11455 | */ |
|---|
| 11252 | | -function $LogProvider(){ |
|---|
| 11456 | +function $LogProvider() { |
|---|
| 11253 | 11457 | var debug = true, |
|---|
| 11254 | 11458 | self = this; |
|---|
| 11255 | 11459 | |
|---|
| .. | .. |
|---|
| 11269 | 11473 | } |
|---|
| 11270 | 11474 | }; |
|---|
| 11271 | 11475 | |
|---|
| 11272 | | - this.$get = ['$window', function($window){ |
|---|
| 11476 | + this.$get = ['$window', function($window) { |
|---|
| 11273 | 11477 | return { |
|---|
| 11274 | 11478 | /** |
|---|
| 11275 | 11479 | * @ngdoc method |
|---|
| .. | .. |
|---|
| 11314 | 11518 | * @description |
|---|
| 11315 | 11519 | * Write a debug message |
|---|
| 11316 | 11520 | */ |
|---|
| 11317 | | - debug: (function () { |
|---|
| 11521 | + debug: (function() { |
|---|
| 11318 | 11522 | var fn = consoleLog('debug'); |
|---|
| 11319 | 11523 | |
|---|
| 11320 | 11524 | return function() { |
|---|
| .. | .. |
|---|
| 11373 | 11577 | // Sandboxing Angular Expressions |
|---|
| 11374 | 11578 | // ------------------------------ |
|---|
| 11375 | 11579 | // Angular expressions are generally considered safe because these expressions only have direct |
|---|
| 11376 | | -// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by |
|---|
| 11580 | +// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by |
|---|
| 11377 | 11581 | // obtaining a reference to native JS functions such as the Function constructor. |
|---|
| 11378 | 11582 | // |
|---|
| 11379 | 11583 | // As an example, consider the following Angular expression: |
|---|
| .. | .. |
|---|
| 11382 | 11586 | // |
|---|
| 11383 | 11587 | // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits |
|---|
| 11384 | 11588 | // against the expression language, but not to prevent exploits that were enabled by exposing |
|---|
| 11385 | | -// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good |
|---|
| 11589 | +// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good |
|---|
| 11386 | 11590 | // practice and therefore we are not even trying to protect against interaction with an object |
|---|
| 11387 | 11591 | // explicitly exposed in this way. |
|---|
| 11388 | 11592 | // |
|---|
| .. | .. |
|---|
| 11390 | 11594 | // window or some DOM object that has a reference to window is published onto a Scope. |
|---|
| 11391 | 11595 | // Similarly we prevent invocations of function known to be dangerous, as well as assignments to |
|---|
| 11392 | 11596 | // native objects. |
|---|
| 11597 | +// |
|---|
| 11598 | +// See https://docs.angularjs.org/guide/security |
|---|
| 11393 | 11599 | |
|---|
| 11394 | 11600 | |
|---|
| 11395 | 11601 | function ensureSafeMemberName(name, fullExpression) { |
|---|
| .. | .. |
|---|
| 11398 | 11604 | || name === "__proto__") { |
|---|
| 11399 | 11605 | throw $parseMinErr('isecfld', |
|---|
| 11400 | 11606 | 'Attempting to access a disallowed field in Angular expressions! ' |
|---|
| 11401 | | - +'Expression: {0}', fullExpression); |
|---|
| 11607 | + + 'Expression: {0}', fullExpression); |
|---|
| 11402 | 11608 | } |
|---|
| 11403 | 11609 | return name; |
|---|
| 11404 | 11610 | } |
|---|
| .. | .. |
|---|
| 11467 | 11673 | |
|---|
| 11468 | 11674 | //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter |
|---|
| 11469 | 11675 | var OPERATORS = extend(createMap(), { |
|---|
| 11470 | | - '+':function(self, locals, a,b){ |
|---|
| 11676 | + '+':function(self, locals, a, b) { |
|---|
| 11471 | 11677 | a=a(self, locals); b=b(self, locals); |
|---|
| 11472 | 11678 | if (isDefined(a)) { |
|---|
| 11473 | 11679 | if (isDefined(b)) { |
|---|
| .. | .. |
|---|
| 11475 | 11681 | } |
|---|
| 11476 | 11682 | return a; |
|---|
| 11477 | 11683 | } |
|---|
| 11478 | | - return isDefined(b)?b:undefined;}, |
|---|
| 11479 | | - '-':function(self, locals, a,b){ |
|---|
| 11684 | + return isDefined(b) ? b : undefined;}, |
|---|
| 11685 | + '-':function(self, locals, a, b) { |
|---|
| 11480 | 11686 | a=a(self, locals); b=b(self, locals); |
|---|
| 11481 | | - return (isDefined(a)?a:0)-(isDefined(b)?b:0); |
|---|
| 11687 | + return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0); |
|---|
| 11482 | 11688 | }, |
|---|
| 11483 | | - '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, |
|---|
| 11484 | | - '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, |
|---|
| 11485 | | - '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, |
|---|
| 11486 | | - '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, |
|---|
| 11487 | | - '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, |
|---|
| 11488 | | - '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, |
|---|
| 11489 | | - '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, |
|---|
| 11490 | | - '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, |
|---|
| 11491 | | - '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, |
|---|
| 11492 | | - '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, |
|---|
| 11493 | | - '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, |
|---|
| 11494 | | - '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, |
|---|
| 11495 | | - '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, |
|---|
| 11496 | | - '!':function(self, locals, a){return !a(self, locals);}, |
|---|
| 11689 | + '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);}, |
|---|
| 11690 | + '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);}, |
|---|
| 11691 | + '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);}, |
|---|
| 11692 | + '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);}, |
|---|
| 11693 | + '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);}, |
|---|
| 11694 | + '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);}, |
|---|
| 11695 | + '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);}, |
|---|
| 11696 | + '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);}, |
|---|
| 11697 | + '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);}, |
|---|
| 11698 | + '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);}, |
|---|
| 11699 | + '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);}, |
|---|
| 11700 | + '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);}, |
|---|
| 11701 | + '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);}, |
|---|
| 11702 | + '!':function(self, locals, a) {return !a(self, locals);}, |
|---|
| 11497 | 11703 | |
|---|
| 11498 | 11704 | //Tokenized as operators but parsed as assignment/filters |
|---|
| 11499 | 11705 | '=':true, |
|---|
| .. | .. |
|---|
| 11508 | 11714 | /** |
|---|
| 11509 | 11715 | * @constructor |
|---|
| 11510 | 11716 | */ |
|---|
| 11511 | | -var Lexer = function (options) { |
|---|
| 11717 | +var Lexer = function(options) { |
|---|
| 11512 | 11718 | this.options = options; |
|---|
| 11513 | 11719 | }; |
|---|
| 11514 | 11720 | |
|---|
| 11515 | 11721 | Lexer.prototype = { |
|---|
| 11516 | 11722 | constructor: Lexer, |
|---|
| 11517 | 11723 | |
|---|
| 11518 | | - lex: function (text) { |
|---|
| 11724 | + lex: function(text) { |
|---|
| 11519 | 11725 | this.text = text; |
|---|
| 11520 | 11726 | this.index = 0; |
|---|
| 11521 | | - this.ch = undefined; |
|---|
| 11522 | 11727 | this.tokens = []; |
|---|
| 11523 | 11728 | |
|---|
| 11524 | 11729 | while (this.index < this.text.length) { |
|---|
| 11525 | | - this.ch = this.text.charAt(this.index); |
|---|
| 11526 | | - if (this.is('"\'')) { |
|---|
| 11527 | | - this.readString(this.ch); |
|---|
| 11528 | | - } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { |
|---|
| 11730 | + var ch = this.text.charAt(this.index); |
|---|
| 11731 | + if (ch === '"' || ch === "'") { |
|---|
| 11732 | + this.readString(ch); |
|---|
| 11733 | + } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { |
|---|
| 11529 | 11734 | this.readNumber(); |
|---|
| 11530 | | - } else if (this.isIdent(this.ch)) { |
|---|
| 11735 | + } else if (this.isIdent(ch)) { |
|---|
| 11531 | 11736 | this.readIdent(); |
|---|
| 11532 | | - } else if (this.is('(){}[].,;:?')) { |
|---|
| 11533 | | - this.tokens.push({ |
|---|
| 11534 | | - index: this.index, |
|---|
| 11535 | | - text: this.ch |
|---|
| 11536 | | - }); |
|---|
| 11737 | + } else if (this.is(ch, '(){}[].,;:?')) { |
|---|
| 11738 | + this.tokens.push({index: this.index, text: ch}); |
|---|
| 11537 | 11739 | this.index++; |
|---|
| 11538 | | - } else if (this.isWhitespace(this.ch)) { |
|---|
| 11740 | + } else if (this.isWhitespace(ch)) { |
|---|
| 11539 | 11741 | this.index++; |
|---|
| 11540 | 11742 | } else { |
|---|
| 11541 | | - var ch2 = this.ch + this.peek(); |
|---|
| 11743 | + var ch2 = ch + this.peek(); |
|---|
| 11542 | 11744 | var ch3 = ch2 + this.peek(2); |
|---|
| 11543 | | - var fn = OPERATORS[this.ch]; |
|---|
| 11544 | | - var fn2 = OPERATORS[ch2]; |
|---|
| 11545 | | - var fn3 = OPERATORS[ch3]; |
|---|
| 11546 | | - if (fn3) { |
|---|
| 11547 | | - this.tokens.push({index: this.index, text: ch3, fn: fn3}); |
|---|
| 11548 | | - this.index += 3; |
|---|
| 11549 | | - } else if (fn2) { |
|---|
| 11550 | | - this.tokens.push({index: this.index, text: ch2, fn: fn2}); |
|---|
| 11551 | | - this.index += 2; |
|---|
| 11552 | | - } else if (fn) { |
|---|
| 11553 | | - this.tokens.push({ |
|---|
| 11554 | | - index: this.index, |
|---|
| 11555 | | - text: this.ch, |
|---|
| 11556 | | - fn: fn |
|---|
| 11557 | | - }); |
|---|
| 11558 | | - this.index += 1; |
|---|
| 11745 | + var op1 = OPERATORS[ch]; |
|---|
| 11746 | + var op2 = OPERATORS[ch2]; |
|---|
| 11747 | + var op3 = OPERATORS[ch3]; |
|---|
| 11748 | + if (op1 || op2 || op3) { |
|---|
| 11749 | + var token = op3 ? ch3 : (op2 ? ch2 : ch); |
|---|
| 11750 | + this.tokens.push({index: this.index, text: token, operator: true}); |
|---|
| 11751 | + this.index += token.length; |
|---|
| 11559 | 11752 | } else { |
|---|
| 11560 | 11753 | this.throwError('Unexpected next character ', this.index, this.index + 1); |
|---|
| 11561 | 11754 | } |
|---|
| .. | .. |
|---|
| 11564 | 11757 | return this.tokens; |
|---|
| 11565 | 11758 | }, |
|---|
| 11566 | 11759 | |
|---|
| 11567 | | - is: function(chars) { |
|---|
| 11568 | | - return chars.indexOf(this.ch) !== -1; |
|---|
| 11760 | + is: function(ch, chars) { |
|---|
| 11761 | + return chars.indexOf(ch) !== -1; |
|---|
| 11569 | 11762 | }, |
|---|
| 11570 | 11763 | |
|---|
| 11571 | 11764 | peek: function(i) { |
|---|
| .. | .. |
|---|
| 11574 | 11767 | }, |
|---|
| 11575 | 11768 | |
|---|
| 11576 | 11769 | isNumber: function(ch) { |
|---|
| 11577 | | - return ('0' <= ch && ch <= '9'); |
|---|
| 11770 | + return ('0' <= ch && ch <= '9') && typeof ch === "string"; |
|---|
| 11578 | 11771 | }, |
|---|
| 11579 | 11772 | |
|---|
| 11580 | 11773 | isWhitespace: function(ch) { |
|---|
| .. | .. |
|---|
| 11627 | 11820 | } |
|---|
| 11628 | 11821 | this.index++; |
|---|
| 11629 | 11822 | } |
|---|
| 11630 | | - number = 1 * number; |
|---|
| 11631 | 11823 | this.tokens.push({ |
|---|
| 11632 | 11824 | index: start, |
|---|
| 11633 | 11825 | text: number, |
|---|
| 11634 | 11826 | constant: true, |
|---|
| 11635 | | - fn: function() { return number; } |
|---|
| 11827 | + value: Number(number) |
|---|
| 11636 | 11828 | }); |
|---|
| 11637 | 11829 | }, |
|---|
| 11638 | 11830 | |
|---|
| 11639 | 11831 | readIdent: function() { |
|---|
| 11640 | | - var expression = this.text; |
|---|
| 11641 | | - |
|---|
| 11642 | | - var ident = ''; |
|---|
| 11643 | 11832 | var start = this.index; |
|---|
| 11644 | | - |
|---|
| 11645 | | - var lastDot, peekIndex, methodName, ch; |
|---|
| 11646 | | - |
|---|
| 11647 | 11833 | while (this.index < this.text.length) { |
|---|
| 11648 | | - ch = this.text.charAt(this.index); |
|---|
| 11649 | | - if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { |
|---|
| 11650 | | - if (ch === '.') lastDot = this.index; |
|---|
| 11651 | | - ident += ch; |
|---|
| 11652 | | - } else { |
|---|
| 11834 | + var ch = this.text.charAt(this.index); |
|---|
| 11835 | + if (!(this.isIdent(ch) || this.isNumber(ch))) { |
|---|
| 11653 | 11836 | break; |
|---|
| 11654 | 11837 | } |
|---|
| 11655 | 11838 | this.index++; |
|---|
| 11656 | 11839 | } |
|---|
| 11657 | | - |
|---|
| 11658 | | - //check if the identifier ends with . and if so move back one char |
|---|
| 11659 | | - if (lastDot && ident[ident.length - 1] === '.') { |
|---|
| 11660 | | - this.index--; |
|---|
| 11661 | | - ident = ident.slice(0, -1); |
|---|
| 11662 | | - lastDot = ident.lastIndexOf('.'); |
|---|
| 11663 | | - if (lastDot === -1) { |
|---|
| 11664 | | - lastDot = undefined; |
|---|
| 11665 | | - } |
|---|
| 11666 | | - } |
|---|
| 11667 | | - |
|---|
| 11668 | | - //check if this is not a method invocation and if it is back out to last dot |
|---|
| 11669 | | - if (lastDot) { |
|---|
| 11670 | | - peekIndex = this.index; |
|---|
| 11671 | | - while (peekIndex < this.text.length) { |
|---|
| 11672 | | - ch = this.text.charAt(peekIndex); |
|---|
| 11673 | | - if (ch === '(') { |
|---|
| 11674 | | - methodName = ident.substr(lastDot - start + 1); |
|---|
| 11675 | | - ident = ident.substr(0, lastDot - start); |
|---|
| 11676 | | - this.index = peekIndex; |
|---|
| 11677 | | - break; |
|---|
| 11678 | | - } |
|---|
| 11679 | | - if (this.isWhitespace(ch)) { |
|---|
| 11680 | | - peekIndex++; |
|---|
| 11681 | | - } else { |
|---|
| 11682 | | - break; |
|---|
| 11683 | | - } |
|---|
| 11684 | | - } |
|---|
| 11685 | | - } |
|---|
| 11686 | | - |
|---|
| 11687 | 11840 | this.tokens.push({ |
|---|
| 11688 | 11841 | index: start, |
|---|
| 11689 | | - text: ident, |
|---|
| 11690 | | - fn: CONSTANTS[ident] || getterFn(ident, this.options, expression) |
|---|
| 11842 | + text: this.text.slice(start, this.index), |
|---|
| 11843 | + identifier: true |
|---|
| 11691 | 11844 | }); |
|---|
| 11692 | | - |
|---|
| 11693 | | - if (methodName) { |
|---|
| 11694 | | - this.tokens.push({ |
|---|
| 11695 | | - index: lastDot, |
|---|
| 11696 | | - text: '.' |
|---|
| 11697 | | - }); |
|---|
| 11698 | | - this.tokens.push({ |
|---|
| 11699 | | - index: lastDot + 1, |
|---|
| 11700 | | - text: methodName |
|---|
| 11701 | | - }); |
|---|
| 11702 | | - } |
|---|
| 11703 | 11845 | }, |
|---|
| 11704 | 11846 | |
|---|
| 11705 | 11847 | readString: function(quote) { |
|---|
| .. | .. |
|---|
| 11730 | 11872 | this.tokens.push({ |
|---|
| 11731 | 11873 | index: start, |
|---|
| 11732 | 11874 | text: rawString, |
|---|
| 11733 | | - string: string, |
|---|
| 11734 | 11875 | constant: true, |
|---|
| 11735 | | - fn: function() { return string; } |
|---|
| 11876 | + value: string |
|---|
| 11736 | 11877 | }); |
|---|
| 11737 | 11878 | return; |
|---|
| 11738 | 11879 | } else { |
|---|
| .. | .. |
|---|
| 11752 | 11893 | /** |
|---|
| 11753 | 11894 | * @constructor |
|---|
| 11754 | 11895 | */ |
|---|
| 11755 | | -var Parser = function (lexer, $filter, options) { |
|---|
| 11896 | +var Parser = function(lexer, $filter, options) { |
|---|
| 11756 | 11897 | this.lexer = lexer; |
|---|
| 11757 | 11898 | this.$filter = $filter; |
|---|
| 11758 | 11899 | this.options = options; |
|---|
| 11759 | 11900 | }; |
|---|
| 11760 | 11901 | |
|---|
| 11761 | | -Parser.ZERO = extend(function () { |
|---|
| 11902 | +Parser.ZERO = extend(function() { |
|---|
| 11762 | 11903 | return 0; |
|---|
| 11763 | 11904 | }, { |
|---|
| 11764 | 11905 | sharedGetter: true, |
|---|
| .. | .. |
|---|
| 11768 | 11909 | Parser.prototype = { |
|---|
| 11769 | 11910 | constructor: Parser, |
|---|
| 11770 | 11911 | |
|---|
| 11771 | | - parse: function (text) { |
|---|
| 11912 | + parse: function(text) { |
|---|
| 11772 | 11913 | this.text = text; |
|---|
| 11773 | 11914 | this.tokens = this.lexer.lex(text); |
|---|
| 11774 | 11915 | |
|---|
| .. | .. |
|---|
| 11784 | 11925 | return value; |
|---|
| 11785 | 11926 | }, |
|---|
| 11786 | 11927 | |
|---|
| 11787 | | - primary: function () { |
|---|
| 11928 | + primary: function() { |
|---|
| 11788 | 11929 | var primary; |
|---|
| 11789 | 11930 | if (this.expect('(')) { |
|---|
| 11790 | 11931 | primary = this.filterChain(); |
|---|
| .. | .. |
|---|
| 11793 | 11934 | primary = this.arrayDeclaration(); |
|---|
| 11794 | 11935 | } else if (this.expect('{')) { |
|---|
| 11795 | 11936 | primary = this.object(); |
|---|
| 11937 | + } else if (this.peek().identifier) { |
|---|
| 11938 | + primary = this.identifier(); |
|---|
| 11939 | + } else if (this.peek().constant) { |
|---|
| 11940 | + primary = this.constant(); |
|---|
| 11796 | 11941 | } else { |
|---|
| 11797 | | - var token = this.expect(); |
|---|
| 11798 | | - primary = token.fn; |
|---|
| 11799 | | - if (!primary) { |
|---|
| 11800 | | - this.throwError('not a primary expression', token); |
|---|
| 11801 | | - } |
|---|
| 11802 | | - if (token.constant) { |
|---|
| 11803 | | - primary.constant = true; |
|---|
| 11804 | | - primary.literal = true; |
|---|
| 11805 | | - } |
|---|
| 11942 | + this.throwError('not a primary expression', this.peek()); |
|---|
| 11806 | 11943 | } |
|---|
| 11807 | 11944 | |
|---|
| 11808 | 11945 | var next, context; |
|---|
| .. | .. |
|---|
| 11836 | 11973 | }, |
|---|
| 11837 | 11974 | |
|---|
| 11838 | 11975 | peek: function(e1, e2, e3, e4) { |
|---|
| 11839 | | - if (this.tokens.length > 0) { |
|---|
| 11840 | | - var token = this.tokens[0]; |
|---|
| 11976 | + return this.peekAhead(0, e1, e2, e3, e4); |
|---|
| 11977 | + }, |
|---|
| 11978 | + peekAhead: function(i, e1, e2, e3, e4) { |
|---|
| 11979 | + if (this.tokens.length > i) { |
|---|
| 11980 | + var token = this.tokens[i]; |
|---|
| 11841 | 11981 | var t = token.text; |
|---|
| 11842 | 11982 | if (t === e1 || t === e2 || t === e3 || t === e4 || |
|---|
| 11843 | 11983 | (!e1 && !e2 && !e3 && !e4)) { |
|---|
| .. | .. |
|---|
| 11847 | 11987 | return false; |
|---|
| 11848 | 11988 | }, |
|---|
| 11849 | 11989 | |
|---|
| 11850 | | - expect: function(e1, e2, e3, e4){ |
|---|
| 11990 | + expect: function(e1, e2, e3, e4) { |
|---|
| 11851 | 11991 | var token = this.peek(e1, e2, e3, e4); |
|---|
| 11852 | 11992 | if (token) { |
|---|
| 11853 | 11993 | this.tokens.shift(); |
|---|
| .. | .. |
|---|
| 11856 | 11996 | return false; |
|---|
| 11857 | 11997 | }, |
|---|
| 11858 | 11998 | |
|---|
| 11859 | | - consume: function(e1){ |
|---|
| 11860 | | - if (!this.expect(e1)) { |
|---|
| 11999 | + consume: function(e1) { |
|---|
| 12000 | + if (this.tokens.length === 0) { |
|---|
| 12001 | + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); |
|---|
| 12002 | + } |
|---|
| 12003 | + |
|---|
| 12004 | + var token = this.expect(e1); |
|---|
| 12005 | + if (!token) { |
|---|
| 11861 | 12006 | this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); |
|---|
| 11862 | 12007 | } |
|---|
| 12008 | + return token; |
|---|
| 11863 | 12009 | }, |
|---|
| 11864 | 12010 | |
|---|
| 11865 | | - unaryFn: function(fn, right) { |
|---|
| 12011 | + unaryFn: function(op, right) { |
|---|
| 12012 | + var fn = OPERATORS[op]; |
|---|
| 11866 | 12013 | return extend(function $parseUnaryFn(self, locals) { |
|---|
| 11867 | 12014 | return fn(self, locals, right); |
|---|
| 11868 | 12015 | }, { |
|---|
| .. | .. |
|---|
| 11871 | 12018 | }); |
|---|
| 11872 | 12019 | }, |
|---|
| 11873 | 12020 | |
|---|
| 11874 | | - binaryFn: function(left, fn, right, isBranching) { |
|---|
| 12021 | + binaryFn: function(left, op, right, isBranching) { |
|---|
| 12022 | + var fn = OPERATORS[op]; |
|---|
| 11875 | 12023 | return extend(function $parseBinaryFn(self, locals) { |
|---|
| 11876 | 12024 | return fn(self, locals, left, right); |
|---|
| 11877 | 12025 | }, { |
|---|
| 11878 | 12026 | constant: left.constant && right.constant, |
|---|
| 11879 | 12027 | inputs: !isBranching && [left, right] |
|---|
| 12028 | + }); |
|---|
| 12029 | + }, |
|---|
| 12030 | + |
|---|
| 12031 | + identifier: function() { |
|---|
| 12032 | + var id = this.consume().text; |
|---|
| 12033 | + |
|---|
| 12034 | + //Continue reading each `.identifier` unless it is a method invocation |
|---|
| 12035 | + while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) { |
|---|
| 12036 | + id += this.consume().text + this.consume().text; |
|---|
| 12037 | + } |
|---|
| 12038 | + |
|---|
| 12039 | + return CONSTANTS[id] || getterFn(id, this.options, this.text); |
|---|
| 12040 | + }, |
|---|
| 12041 | + |
|---|
| 12042 | + constant: function() { |
|---|
| 12043 | + var value = this.consume().value; |
|---|
| 12044 | + |
|---|
| 12045 | + return extend(function $parseConstant() { |
|---|
| 12046 | + return value; |
|---|
| 12047 | + }, { |
|---|
| 12048 | + constant: true, |
|---|
| 12049 | + literal: true |
|---|
| 11880 | 12050 | }); |
|---|
| 11881 | 12051 | }, |
|---|
| 11882 | 12052 | |
|---|
| .. | .. |
|---|
| 11911 | 12081 | }, |
|---|
| 11912 | 12082 | |
|---|
| 11913 | 12083 | filter: function(inputFn) { |
|---|
| 11914 | | - var token = this.expect(); |
|---|
| 11915 | | - var fn = this.$filter(token.text); |
|---|
| 12084 | + var fn = this.$filter(this.consume().text); |
|---|
| 11916 | 12085 | var argsFn; |
|---|
| 11917 | 12086 | var args; |
|---|
| 11918 | 12087 | |
|---|
| .. | .. |
|---|
| 11975 | 12144 | var token; |
|---|
| 11976 | 12145 | if ((token = this.expect('?'))) { |
|---|
| 11977 | 12146 | middle = this.assignment(); |
|---|
| 11978 | | - if ((token = this.expect(':'))) { |
|---|
| 12147 | + if (this.consume(':')) { |
|---|
| 11979 | 12148 | var right = this.assignment(); |
|---|
| 11980 | 12149 | |
|---|
| 11981 | | - return extend(function $parseTernary(self, locals){ |
|---|
| 12150 | + return extend(function $parseTernary(self, locals) { |
|---|
| 11982 | 12151 | return left(self, locals) ? middle(self, locals) : right(self, locals); |
|---|
| 11983 | 12152 | }, { |
|---|
| 11984 | 12153 | constant: left.constant && middle.constant && right.constant |
|---|
| 11985 | 12154 | }); |
|---|
| 11986 | | - |
|---|
| 11987 | | - } else { |
|---|
| 11988 | | - this.throwError('expected :', token); |
|---|
| 11989 | 12155 | } |
|---|
| 11990 | 12156 | } |
|---|
| 11991 | 12157 | |
|---|
| .. | .. |
|---|
| 11996 | 12162 | var left = this.logicalAND(); |
|---|
| 11997 | 12163 | var token; |
|---|
| 11998 | 12164 | while ((token = this.expect('||'))) { |
|---|
| 11999 | | - left = this.binaryFn(left, token.fn, this.logicalAND(), true); |
|---|
| 12165 | + left = this.binaryFn(left, token.text, this.logicalAND(), true); |
|---|
| 12000 | 12166 | } |
|---|
| 12001 | 12167 | return left; |
|---|
| 12002 | 12168 | }, |
|---|
| .. | .. |
|---|
| 12004 | 12170 | logicalAND: function() { |
|---|
| 12005 | 12171 | var left = this.equality(); |
|---|
| 12006 | 12172 | var token; |
|---|
| 12007 | | - if ((token = this.expect('&&'))) { |
|---|
| 12008 | | - left = this.binaryFn(left, token.fn, this.logicalAND(), true); |
|---|
| 12173 | + while ((token = this.expect('&&'))) { |
|---|
| 12174 | + left = this.binaryFn(left, token.text, this.equality(), true); |
|---|
| 12009 | 12175 | } |
|---|
| 12010 | 12176 | return left; |
|---|
| 12011 | 12177 | }, |
|---|
| .. | .. |
|---|
| 12013 | 12179 | equality: function() { |
|---|
| 12014 | 12180 | var left = this.relational(); |
|---|
| 12015 | 12181 | var token; |
|---|
| 12016 | | - if ((token = this.expect('==','!=','===','!=='))) { |
|---|
| 12017 | | - left = this.binaryFn(left, token.fn, this.equality()); |
|---|
| 12182 | + while ((token = this.expect('==','!=','===','!=='))) { |
|---|
| 12183 | + left = this.binaryFn(left, token.text, this.relational()); |
|---|
| 12018 | 12184 | } |
|---|
| 12019 | 12185 | return left; |
|---|
| 12020 | 12186 | }, |
|---|
| .. | .. |
|---|
| 12022 | 12188 | relational: function() { |
|---|
| 12023 | 12189 | var left = this.additive(); |
|---|
| 12024 | 12190 | var token; |
|---|
| 12025 | | - if ((token = this.expect('<', '>', '<=', '>='))) { |
|---|
| 12026 | | - left = this.binaryFn(left, token.fn, this.relational()); |
|---|
| 12191 | + while ((token = this.expect('<', '>', '<=', '>='))) { |
|---|
| 12192 | + left = this.binaryFn(left, token.text, this.additive()); |
|---|
| 12027 | 12193 | } |
|---|
| 12028 | 12194 | return left; |
|---|
| 12029 | 12195 | }, |
|---|
| .. | .. |
|---|
| 12032 | 12198 | var left = this.multiplicative(); |
|---|
| 12033 | 12199 | var token; |
|---|
| 12034 | 12200 | while ((token = this.expect('+','-'))) { |
|---|
| 12035 | | - left = this.binaryFn(left, token.fn, this.multiplicative()); |
|---|
| 12201 | + left = this.binaryFn(left, token.text, this.multiplicative()); |
|---|
| 12036 | 12202 | } |
|---|
| 12037 | 12203 | return left; |
|---|
| 12038 | 12204 | }, |
|---|
| .. | .. |
|---|
| 12041 | 12207 | var left = this.unary(); |
|---|
| 12042 | 12208 | var token; |
|---|
| 12043 | 12209 | while ((token = this.expect('*','/','%'))) { |
|---|
| 12044 | | - left = this.binaryFn(left, token.fn, this.unary()); |
|---|
| 12210 | + left = this.binaryFn(left, token.text, this.unary()); |
|---|
| 12045 | 12211 | } |
|---|
| 12046 | 12212 | return left; |
|---|
| 12047 | 12213 | }, |
|---|
| .. | .. |
|---|
| 12051 | 12217 | if (this.expect('+')) { |
|---|
| 12052 | 12218 | return this.primary(); |
|---|
| 12053 | 12219 | } else if ((token = this.expect('-'))) { |
|---|
| 12054 | | - return this.binaryFn(Parser.ZERO, token.fn, this.unary()); |
|---|
| 12220 | + return this.binaryFn(Parser.ZERO, token.text, this.unary()); |
|---|
| 12055 | 12221 | } else if ((token = this.expect('!'))) { |
|---|
| 12056 | | - return this.unaryFn(token.fn, this.unary()); |
|---|
| 12222 | + return this.unaryFn(token.text, this.unary()); |
|---|
| 12057 | 12223 | } else { |
|---|
| 12058 | 12224 | return this.primary(); |
|---|
| 12059 | 12225 | } |
|---|
| .. | .. |
|---|
| 12061 | 12227 | |
|---|
| 12062 | 12228 | fieldAccess: function(object) { |
|---|
| 12063 | 12229 | var expression = this.text; |
|---|
| 12064 | | - var field = this.expect().text; |
|---|
| 12230 | + var field = this.consume().text; |
|---|
| 12065 | 12231 | var getter = getterFn(field, this.options, expression); |
|---|
| 12066 | 12232 | |
|---|
| 12067 | 12233 | return extend(function $parseFieldAccess(scope, locals, self) { |
|---|
| .. | .. |
|---|
| 12115 | 12281 | var args = argsFn.length ? [] : null; |
|---|
| 12116 | 12282 | |
|---|
| 12117 | 12283 | return function $parseFunctionCall(scope, locals) { |
|---|
| 12118 | | - var context = contextGetter ? contextGetter(scope, locals) : scope; |
|---|
| 12284 | + var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope; |
|---|
| 12119 | 12285 | var fn = fnGetter(scope, locals, context) || noop; |
|---|
| 12120 | 12286 | |
|---|
| 12121 | 12287 | if (args) { |
|---|
| .. | .. |
|---|
| 12128 | 12294 | ensureSafeObject(context, expressionText); |
|---|
| 12129 | 12295 | ensureSafeFunction(fn, expressionText); |
|---|
| 12130 | 12296 | |
|---|
| 12131 | | - // IE stupidity! (IE doesn't have apply for some native functions) |
|---|
| 12297 | + // IE doesn't have apply for some native functions |
|---|
| 12132 | 12298 | var v = fn.apply |
|---|
| 12133 | 12299 | ? fn.apply(context, args) |
|---|
| 12134 | 12300 | : fn(args[0], args[1], args[2], args[3], args[4]); |
|---|
| 12135 | 12301 | |
|---|
| 12136 | 12302 | return ensureSafeObject(v, expressionText); |
|---|
| 12137 | | - }; |
|---|
| 12303 | + }; |
|---|
| 12138 | 12304 | }, |
|---|
| 12139 | 12305 | |
|---|
| 12140 | 12306 | // This is used with json array declaration |
|---|
| 12141 | | - arrayDeclaration: function () { |
|---|
| 12307 | + arrayDeclaration: function() { |
|---|
| 12142 | 12308 | var elementFns = []; |
|---|
| 12143 | 12309 | if (this.peekToken().text !== ']') { |
|---|
| 12144 | 12310 | do { |
|---|
| .. | .. |
|---|
| 12146 | 12312 | // Support trailing commas per ES5.1. |
|---|
| 12147 | 12313 | break; |
|---|
| 12148 | 12314 | } |
|---|
| 12149 | | - var elementFn = this.expression(); |
|---|
| 12150 | | - elementFns.push(elementFn); |
|---|
| 12315 | + elementFns.push(this.expression()); |
|---|
| 12151 | 12316 | } while (this.expect(',')); |
|---|
| 12152 | 12317 | } |
|---|
| 12153 | 12318 | this.consume(']'); |
|---|
| .. | .. |
|---|
| 12165 | 12330 | }); |
|---|
| 12166 | 12331 | }, |
|---|
| 12167 | 12332 | |
|---|
| 12168 | | - object: function () { |
|---|
| 12333 | + object: function() { |
|---|
| 12169 | 12334 | var keys = [], valueFns = []; |
|---|
| 12170 | 12335 | if (this.peekToken().text !== '}') { |
|---|
| 12171 | 12336 | do { |
|---|
| .. | .. |
|---|
| 12173 | 12338 | // Support trailing commas per ES5.1. |
|---|
| 12174 | 12339 | break; |
|---|
| 12175 | 12340 | } |
|---|
| 12176 | | - var token = this.expect(); |
|---|
| 12177 | | - keys.push(token.string || token.text); |
|---|
| 12341 | + var token = this.consume(); |
|---|
| 12342 | + if (token.constant) { |
|---|
| 12343 | + keys.push(token.value); |
|---|
| 12344 | + } else if (token.identifier) { |
|---|
| 12345 | + keys.push(token.text); |
|---|
| 12346 | + } else { |
|---|
| 12347 | + this.throwError("invalid key", token); |
|---|
| 12348 | + } |
|---|
| 12178 | 12349 | this.consume(':'); |
|---|
| 12179 | | - var value = this.expression(); |
|---|
| 12180 | | - valueFns.push(value); |
|---|
| 12350 | + valueFns.push(this.expression()); |
|---|
| 12181 | 12351 | } while (this.expect(',')); |
|---|
| 12182 | 12352 | } |
|---|
| 12183 | 12353 | this.consume('}'); |
|---|
| .. | .. |
|---|
| 12220 | 12390 | return setValue; |
|---|
| 12221 | 12391 | } |
|---|
| 12222 | 12392 | |
|---|
| 12223 | | -var getterFnCache = createMap(); |
|---|
| 12393 | +var getterFnCacheDefault = createMap(); |
|---|
| 12394 | +var getterFnCacheExpensive = createMap(); |
|---|
| 12395 | + |
|---|
| 12396 | +function isPossiblyDangerousMemberName(name) { |
|---|
| 12397 | + return name == 'constructor'; |
|---|
| 12398 | +} |
|---|
| 12224 | 12399 | |
|---|
| 12225 | 12400 | /** |
|---|
| 12226 | 12401 | * Implementation of the "Black Hole" variant from: |
|---|
| 12227 | 12402 | * - http://jsperf.com/angularjs-parse-getter/4 |
|---|
| 12228 | 12403 | * - http://jsperf.com/path-evaluation-simplified/7 |
|---|
| 12229 | 12404 | */ |
|---|
| 12230 | | -function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) { |
|---|
| 12405 | +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) { |
|---|
| 12231 | 12406 | ensureSafeMemberName(key0, fullExp); |
|---|
| 12232 | 12407 | ensureSafeMemberName(key1, fullExp); |
|---|
| 12233 | 12408 | ensureSafeMemberName(key2, fullExp); |
|---|
| 12234 | 12409 | ensureSafeMemberName(key3, fullExp); |
|---|
| 12235 | 12410 | ensureSafeMemberName(key4, fullExp); |
|---|
| 12411 | + var eso = function(o) { |
|---|
| 12412 | + return ensureSafeObject(o, fullExp); |
|---|
| 12413 | + }; |
|---|
| 12414 | + var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity; |
|---|
| 12415 | + var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity; |
|---|
| 12416 | + var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity; |
|---|
| 12417 | + var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity; |
|---|
| 12418 | + var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity; |
|---|
| 12236 | 12419 | |
|---|
| 12237 | 12420 | return function cspSafeGetter(scope, locals) { |
|---|
| 12238 | 12421 | var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; |
|---|
| 12239 | 12422 | |
|---|
| 12240 | 12423 | if (pathVal == null) return pathVal; |
|---|
| 12241 | | - pathVal = pathVal[key0]; |
|---|
| 12424 | + pathVal = eso0(pathVal[key0]); |
|---|
| 12242 | 12425 | |
|---|
| 12243 | 12426 | if (!key1) return pathVal; |
|---|
| 12244 | 12427 | if (pathVal == null) return undefined; |
|---|
| 12245 | | - pathVal = pathVal[key1]; |
|---|
| 12428 | + pathVal = eso1(pathVal[key1]); |
|---|
| 12246 | 12429 | |
|---|
| 12247 | 12430 | if (!key2) return pathVal; |
|---|
| 12248 | 12431 | if (pathVal == null) return undefined; |
|---|
| 12249 | | - pathVal = pathVal[key2]; |
|---|
| 12432 | + pathVal = eso2(pathVal[key2]); |
|---|
| 12250 | 12433 | |
|---|
| 12251 | 12434 | if (!key3) return pathVal; |
|---|
| 12252 | 12435 | if (pathVal == null) return undefined; |
|---|
| 12253 | | - pathVal = pathVal[key3]; |
|---|
| 12436 | + pathVal = eso3(pathVal[key3]); |
|---|
| 12254 | 12437 | |
|---|
| 12255 | 12438 | if (!key4) return pathVal; |
|---|
| 12256 | 12439 | if (pathVal == null) return undefined; |
|---|
| 12257 | | - pathVal = pathVal[key4]; |
|---|
| 12440 | + pathVal = eso4(pathVal[key4]); |
|---|
| 12258 | 12441 | |
|---|
| 12259 | 12442 | return pathVal; |
|---|
| 12260 | 12443 | }; |
|---|
| 12261 | 12444 | } |
|---|
| 12262 | 12445 | |
|---|
| 12263 | | -function getterFn(path, options, fullExp) { |
|---|
| 12264 | | - var fn = getterFnCache[path]; |
|---|
| 12446 | +function getterFnWithEnsureSafeObject(fn, fullExpression) { |
|---|
| 12447 | + return function(s, l) { |
|---|
| 12448 | + return fn(s, l, ensureSafeObject, fullExpression); |
|---|
| 12449 | + }; |
|---|
| 12450 | +} |
|---|
| 12265 | 12451 | |
|---|
| 12452 | +function getterFn(path, options, fullExp) { |
|---|
| 12453 | + var expensiveChecks = options.expensiveChecks; |
|---|
| 12454 | + var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault); |
|---|
| 12455 | + var fn = getterFnCache[path]; |
|---|
| 12266 | 12456 | if (fn) return fn; |
|---|
| 12457 | + |
|---|
| 12267 | 12458 | |
|---|
| 12268 | 12459 | var pathKeys = path.split('.'), |
|---|
| 12269 | 12460 | pathKeysLength = pathKeys.length; |
|---|
| .. | .. |
|---|
| 12271 | 12462 | // http://jsperf.com/angularjs-parse-getter/6 |
|---|
| 12272 | 12463 | if (options.csp) { |
|---|
| 12273 | 12464 | if (pathKeysLength < 6) { |
|---|
| 12274 | | - fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp); |
|---|
| 12465 | + fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks); |
|---|
| 12275 | 12466 | } else { |
|---|
| 12276 | 12467 | fn = function cspSafeGetter(scope, locals) { |
|---|
| 12277 | 12468 | var i = 0, val; |
|---|
| 12278 | 12469 | do { |
|---|
| 12279 | 12470 | val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], |
|---|
| 12280 | | - pathKeys[i++], fullExp)(scope, locals); |
|---|
| 12471 | + pathKeys[i++], fullExp, expensiveChecks)(scope, locals); |
|---|
| 12281 | 12472 | |
|---|
| 12282 | 12473 | locals = undefined; // clear after first iteration |
|---|
| 12283 | 12474 | scope = val; |
|---|
| .. | .. |
|---|
| 12287 | 12478 | } |
|---|
| 12288 | 12479 | } else { |
|---|
| 12289 | 12480 | var code = ''; |
|---|
| 12481 | + if (expensiveChecks) { |
|---|
| 12482 | + code += 's = eso(s, fe);\nl = eso(l, fe);\n'; |
|---|
| 12483 | + } |
|---|
| 12484 | + var needsEnsureSafeObject = expensiveChecks; |
|---|
| 12290 | 12485 | forEach(pathKeys, function(key, index) { |
|---|
| 12291 | 12486 | ensureSafeMemberName(key, fullExp); |
|---|
| 12292 | | - code += 'if(s == null) return undefined;\n' + |
|---|
| 12293 | | - 's='+ (index |
|---|
| 12487 | + var lookupJs = (index |
|---|
| 12294 | 12488 | // we simply dereference 's' on any .dot notation |
|---|
| 12295 | 12489 | ? 's' |
|---|
| 12296 | 12490 | // but if we are first then we check locals first, and if so read it first |
|---|
| 12297 | | - : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n'; |
|---|
| 12491 | + : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key; |
|---|
| 12492 | + if (expensiveChecks || isPossiblyDangerousMemberName(key)) { |
|---|
| 12493 | + lookupJs = 'eso(' + lookupJs + ', fe)'; |
|---|
| 12494 | + needsEnsureSafeObject = true; |
|---|
| 12495 | + } |
|---|
| 12496 | + code += 'if(s == null) return undefined;\n' + |
|---|
| 12497 | + 's=' + lookupJs + ';\n'; |
|---|
| 12298 | 12498 | }); |
|---|
| 12299 | 12499 | code += 'return s;'; |
|---|
| 12300 | 12500 | |
|---|
| 12301 | 12501 | /* jshint -W054 */ |
|---|
| 12302 | | - var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals |
|---|
| 12502 | + var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject |
|---|
| 12303 | 12503 | /* jshint +W054 */ |
|---|
| 12304 | 12504 | evaledFnGetter.toString = valueFn(code); |
|---|
| 12305 | | - |
|---|
| 12505 | + if (needsEnsureSafeObject) { |
|---|
| 12506 | + evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp); |
|---|
| 12507 | + } |
|---|
| 12306 | 12508 | fn = evaledFnGetter; |
|---|
| 12307 | 12509 | } |
|---|
| 12308 | 12510 | |
|---|
| .. | .. |
|---|
| 12312 | 12514 | }; |
|---|
| 12313 | 12515 | getterFnCache[path] = fn; |
|---|
| 12314 | 12516 | return fn; |
|---|
| 12517 | +} |
|---|
| 12518 | + |
|---|
| 12519 | +var objectValueOf = Object.prototype.valueOf; |
|---|
| 12520 | + |
|---|
| 12521 | +function getValueOf(value) { |
|---|
| 12522 | + return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); |
|---|
| 12315 | 12523 | } |
|---|
| 12316 | 12524 | |
|---|
| 12317 | 12525 | /////////////////////////////////// |
|---|
| .. | .. |
|---|
| 12366 | 12574 | * service. |
|---|
| 12367 | 12575 | */ |
|---|
| 12368 | 12576 | function $ParseProvider() { |
|---|
| 12369 | | - var cache = createMap(); |
|---|
| 12577 | + var cacheDefault = createMap(); |
|---|
| 12578 | + var cacheExpensive = createMap(); |
|---|
| 12370 | 12579 | |
|---|
| 12371 | | - var $parseOptions = { |
|---|
| 12372 | | - csp: false |
|---|
| 12373 | | - }; |
|---|
| 12374 | 12580 | |
|---|
| 12375 | 12581 | |
|---|
| 12376 | 12582 | this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { |
|---|
| 12377 | | - $parseOptions.csp = $sniffer.csp; |
|---|
| 12583 | + var $parseOptions = { |
|---|
| 12584 | + csp: $sniffer.csp, |
|---|
| 12585 | + expensiveChecks: false |
|---|
| 12586 | + }, |
|---|
| 12587 | + $parseOptionsExpensive = { |
|---|
| 12588 | + csp: $sniffer.csp, |
|---|
| 12589 | + expensiveChecks: true |
|---|
| 12590 | + }; |
|---|
| 12378 | 12591 | |
|---|
| 12379 | 12592 | function wrapSharedExpression(exp) { |
|---|
| 12380 | 12593 | var wrapped = exp; |
|---|
| .. | .. |
|---|
| 12391 | 12604 | return wrapped; |
|---|
| 12392 | 12605 | } |
|---|
| 12393 | 12606 | |
|---|
| 12394 | | - return function $parse(exp, interceptorFn) { |
|---|
| 12607 | + return function $parse(exp, interceptorFn, expensiveChecks) { |
|---|
| 12395 | 12608 | var parsedExpression, oneTime, cacheKey; |
|---|
| 12396 | 12609 | |
|---|
| 12397 | 12610 | switch (typeof exp) { |
|---|
| 12398 | 12611 | case 'string': |
|---|
| 12399 | 12612 | cacheKey = exp = exp.trim(); |
|---|
| 12400 | 12613 | |
|---|
| 12614 | + var cache = (expensiveChecks ? cacheExpensive : cacheDefault); |
|---|
| 12401 | 12615 | parsedExpression = cache[cacheKey]; |
|---|
| 12402 | 12616 | |
|---|
| 12403 | 12617 | if (!parsedExpression) { |
|---|
| .. | .. |
|---|
| 12406 | 12620 | exp = exp.substring(2); |
|---|
| 12407 | 12621 | } |
|---|
| 12408 | 12622 | |
|---|
| 12409 | | - var lexer = new Lexer($parseOptions); |
|---|
| 12410 | | - var parser = new Parser(lexer, $filter, $parseOptions); |
|---|
| 12623 | + var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; |
|---|
| 12624 | + var lexer = new Lexer(parseOptions); |
|---|
| 12625 | + var parser = new Parser(lexer, $filter, parseOptions); |
|---|
| 12411 | 12626 | parsedExpression = parser.parse(exp); |
|---|
| 12412 | 12627 | |
|---|
| 12413 | 12628 | if (parsedExpression.constant) { |
|---|
| .. | .. |
|---|
| 12460 | 12675 | // attempt to convert the value to a primitive type |
|---|
| 12461 | 12676 | // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can |
|---|
| 12462 | 12677 | // be cheaply dirty-checked |
|---|
| 12463 | | - newValue = newValue.valueOf(); |
|---|
| 12678 | + newValue = getValueOf(newValue); |
|---|
| 12464 | 12679 | |
|---|
| 12465 | 12680 | if (typeof newValue === 'object') { |
|---|
| 12466 | 12681 | // objects/arrays are not supported - deep-watching them would be too expensive |
|---|
| .. | .. |
|---|
| 12487 | 12702 | var newInputValue = inputExpressions(scope); |
|---|
| 12488 | 12703 | if (!expressionInputDirtyCheck(newInputValue, oldInputValue)) { |
|---|
| 12489 | 12704 | lastResult = parsedExpression(scope); |
|---|
| 12490 | | - oldInputValue = newInputValue && newInputValue.valueOf(); |
|---|
| 12705 | + oldInputValue = newInputValue && getValueOf(newInputValue); |
|---|
| 12491 | 12706 | } |
|---|
| 12492 | 12707 | return lastResult; |
|---|
| 12493 | 12708 | }, listener, objectEquality); |
|---|
| .. | .. |
|---|
| 12504 | 12719 | for (var i = 0, ii = inputExpressions.length; i < ii; i++) { |
|---|
| 12505 | 12720 | var newInputValue = inputExpressions[i](scope); |
|---|
| 12506 | 12721 | if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { |
|---|
| 12507 | | - oldInputValueOfValues[i] = newInputValue && newInputValue.valueOf(); |
|---|
| 12722 | + oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); |
|---|
| 12508 | 12723 | } |
|---|
| 12509 | 12724 | } |
|---|
| 12510 | 12725 | |
|---|
| .. | .. |
|---|
| 12526 | 12741 | listener.apply(this, arguments); |
|---|
| 12527 | 12742 | } |
|---|
| 12528 | 12743 | if (isDefined(value)) { |
|---|
| 12529 | | - scope.$$postDigest(function () { |
|---|
| 12744 | + scope.$$postDigest(function() { |
|---|
| 12530 | 12745 | if (isDefined(lastValue)) { |
|---|
| 12531 | 12746 | unwatch(); |
|---|
| 12532 | 12747 | } |
|---|
| .. | .. |
|---|
| 12545 | 12760 | listener.call(this, value, old, scope); |
|---|
| 12546 | 12761 | } |
|---|
| 12547 | 12762 | if (isAllDefined(value)) { |
|---|
| 12548 | | - scope.$$postDigest(function () { |
|---|
| 12549 | | - if(isAllDefined(lastValue)) unwatch(); |
|---|
| 12763 | + scope.$$postDigest(function() { |
|---|
| 12764 | + if (isAllDefined(lastValue)) unwatch(); |
|---|
| 12550 | 12765 | }); |
|---|
| 12551 | 12766 | } |
|---|
| 12552 | 12767 | }, objectEquality); |
|---|
| 12553 | 12768 | |
|---|
| 12554 | 12769 | function isAllDefined(value) { |
|---|
| 12555 | 12770 | var allDefined = true; |
|---|
| 12556 | | - forEach(value, function (val) { |
|---|
| 12771 | + forEach(value, function(val) { |
|---|
| 12557 | 12772 | if (!isDefined(val)) allDefined = false; |
|---|
| 12558 | 12773 | }); |
|---|
| 12559 | 12774 | return allDefined; |
|---|
| .. | .. |
|---|
| 12574 | 12789 | |
|---|
| 12575 | 12790 | function addInterceptor(parsedExpression, interceptorFn) { |
|---|
| 12576 | 12791 | if (!interceptorFn) return parsedExpression; |
|---|
| 12792 | + var watchDelegate = parsedExpression.$$watchDelegate; |
|---|
| 12577 | 12793 | |
|---|
| 12578 | | - var fn = function interceptedExpression(scope, locals) { |
|---|
| 12794 | + var regularWatch = |
|---|
| 12795 | + watchDelegate !== oneTimeLiteralWatchDelegate && |
|---|
| 12796 | + watchDelegate !== oneTimeWatchDelegate; |
|---|
| 12797 | + |
|---|
| 12798 | + var fn = regularWatch ? function regularInterceptedExpression(scope, locals) { |
|---|
| 12799 | + var value = parsedExpression(scope, locals); |
|---|
| 12800 | + return interceptorFn(value, scope, locals); |
|---|
| 12801 | + } : function oneTimeInterceptedExpression(scope, locals) { |
|---|
| 12579 | 12802 | var value = parsedExpression(scope, locals); |
|---|
| 12580 | 12803 | var result = interceptorFn(value, scope, locals); |
|---|
| 12581 | 12804 | // we only return the interceptor's result if the |
|---|
| .. | .. |
|---|
| 12605 | 12828 | * @requires $rootScope |
|---|
| 12606 | 12829 | * |
|---|
| 12607 | 12830 | * @description |
|---|
| 12608 | | - * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). |
|---|
| 12831 | + * A service that helps you run functions asynchronously, and use their return values (or exceptions) |
|---|
| 12832 | + * when they are done processing. |
|---|
| 12833 | + * |
|---|
| 12834 | + * This is an implementation of promises/deferred objects inspired by |
|---|
| 12835 | + * [Kris Kowal's Q](https://github.com/kriskowal/q). |
|---|
| 12609 | 12836 | * |
|---|
| 12610 | 12837 | * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred |
|---|
| 12611 | 12838 | * implementations, and the other which resembles ES6 promises to some degree. |
|---|
| .. | .. |
|---|
| 12741 | 12968 | * |
|---|
| 12742 | 12969 | * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` |
|---|
| 12743 | 12970 | * |
|---|
| 12744 | | - * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, |
|---|
| 12971 | + * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, |
|---|
| 12745 | 12972 | * but to do so without modifying the final value. This is useful to release resources or do some |
|---|
| 12746 | 12973 | * clean-up that needs to be done whether the promise was rejected or resolved. See the [full |
|---|
| 12747 | 12974 | * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for |
|---|
| 12748 | 12975 | * more information. |
|---|
| 12749 | | - * |
|---|
| 12750 | | - * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as |
|---|
| 12751 | | - * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to |
|---|
| 12752 | | - * make your code IE8 and Android 2.x compatible. |
|---|
| 12753 | 12976 | * |
|---|
| 12754 | 12977 | * # Chaining promises |
|---|
| 12755 | 12978 | * |
|---|
| .. | .. |
|---|
| 12917 | 13140 | } else { |
|---|
| 12918 | 13141 | promise.reject(state.value); |
|---|
| 12919 | 13142 | } |
|---|
| 12920 | | - } catch(e) { |
|---|
| 13143 | + } catch (e) { |
|---|
| 12921 | 13144 | promise.reject(e); |
|---|
| 12922 | 13145 | exceptionHandler(e); |
|---|
| 12923 | 13146 | } |
|---|
| .. | .. |
|---|
| 12967 | 13190 | this.promise.$$state.status = 1; |
|---|
| 12968 | 13191 | scheduleProcessQueue(this.promise.$$state); |
|---|
| 12969 | 13192 | } |
|---|
| 12970 | | - } catch(e) { |
|---|
| 13193 | + } catch (e) { |
|---|
| 12971 | 13194 | fns[1](e); |
|---|
| 12972 | 13195 | exceptionHandler(e); |
|---|
| 12973 | 13196 | } |
|---|
| .. | .. |
|---|
| 12995 | 13218 | callback = callbacks[i][3]; |
|---|
| 12996 | 13219 | try { |
|---|
| 12997 | 13220 | result.notify(isFunction(callback) ? callback(progress) : progress); |
|---|
| 12998 | | - } catch(e) { |
|---|
| 13221 | + } catch (e) { |
|---|
| 12999 | 13222 | exceptionHandler(e); |
|---|
| 13000 | 13223 | } |
|---|
| 13001 | 13224 | } |
|---|
| .. | .. |
|---|
| 13060 | 13283 | var callbackOutput = null; |
|---|
| 13061 | 13284 | try { |
|---|
| 13062 | 13285 | if (isFunction(callback)) callbackOutput = callback(); |
|---|
| 13063 | | - } catch(e) { |
|---|
| 13286 | + } catch (e) { |
|---|
| 13064 | 13287 | return makePromise(e, false); |
|---|
| 13065 | 13288 | } |
|---|
| 13066 | 13289 | if (isPromiseLike(callbackOutput)) { |
|---|
| .. | .. |
|---|
| 13168 | 13391 | return $Q; |
|---|
| 13169 | 13392 | } |
|---|
| 13170 | 13393 | |
|---|
| 13171 | | -function $$RAFProvider(){ //rAF |
|---|
| 13394 | +function $$RAFProvider() { //rAF |
|---|
| 13172 | 13395 | this.$get = ['$window', '$timeout', function($window, $timeout) { |
|---|
| 13173 | 13396 | var requestAnimationFrame = $window.requestAnimationFrame || |
|---|
| 13174 | 13397 | $window.webkitRequestAnimationFrame || |
|---|
| .. | .. |
|---|
| 13267 | 13490 | * They also provide an event emission/broadcast and subscription facility. See the |
|---|
| 13268 | 13491 | * {@link guide/scope developer guide on scopes}. |
|---|
| 13269 | 13492 | */ |
|---|
| 13270 | | -function $RootScopeProvider(){ |
|---|
| 13493 | +function $RootScopeProvider() { |
|---|
| 13271 | 13494 | var TTL = 10; |
|---|
| 13272 | 13495 | var $rootScopeMinErr = minErr('$rootScope'); |
|---|
| 13273 | 13496 | var lastDirtyWatch = null; |
|---|
| .. | .. |
|---|
| 13281 | 13504 | }; |
|---|
| 13282 | 13505 | |
|---|
| 13283 | 13506 | this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', |
|---|
| 13284 | | - function( $injector, $exceptionHandler, $parse, $browser) { |
|---|
| 13507 | + function($injector, $exceptionHandler, $parse, $browser) { |
|---|
| 13285 | 13508 | |
|---|
| 13286 | 13509 | /** |
|---|
| 13287 | 13510 | * @ngdoc type |
|---|
| .. | .. |
|---|
| 13312 | 13535 | expect(child.salutation).toEqual('Welcome'); |
|---|
| 13313 | 13536 | expect(parent.salutation).toEqual('Hello'); |
|---|
| 13314 | 13537 | * ``` |
|---|
| 13538 | + * |
|---|
| 13539 | + * When interacting with `Scope` in tests, additional helper methods are available on the |
|---|
| 13540 | + * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional |
|---|
| 13541 | + * details. |
|---|
| 13315 | 13542 | * |
|---|
| 13316 | 13543 | * |
|---|
| 13317 | 13544 | * @param {Object.<string, function()>=} providers Map of service factory which need to be |
|---|
| .. | .. |
|---|
| 13624 | 13851 | if (!watchExpressions.length) { |
|---|
| 13625 | 13852 | // No expressions means we call the listener ASAP |
|---|
| 13626 | 13853 | var shouldCall = true; |
|---|
| 13627 | | - self.$evalAsync(function () { |
|---|
| 13854 | + self.$evalAsync(function() { |
|---|
| 13628 | 13855 | if (shouldCall) listener(newValues, newValues, self); |
|---|
| 13629 | 13856 | }); |
|---|
| 13630 | 13857 | return function deregisterWatchGroup() { |
|---|
| .. | .. |
|---|
| 13641 | 13868 | }); |
|---|
| 13642 | 13869 | } |
|---|
| 13643 | 13870 | |
|---|
| 13644 | | - forEach(watchExpressions, function (expr, i) { |
|---|
| 13871 | + forEach(watchExpressions, function(expr, i) { |
|---|
| 13645 | 13872 | var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { |
|---|
| 13646 | 13873 | newValues[i] = value; |
|---|
| 13647 | 13874 | oldValues[i] = oldValue; |
|---|
| .. | .. |
|---|
| 13751 | 13978 | newValue = _value; |
|---|
| 13752 | 13979 | var newLength, key, bothNaN, newItem, oldItem; |
|---|
| 13753 | 13980 | |
|---|
| 13981 | + // If the new value is undefined, then return undefined as the watch may be a one-time watch |
|---|
| 13982 | + if (isUndefined(newValue)) return; |
|---|
| 13983 | + |
|---|
| 13754 | 13984 | if (!isObject(newValue)) { // if primitive |
|---|
| 13755 | 13985 | if (oldValue !== newValue) { |
|---|
| 13756 | 13986 | oldValue = newValue; |
|---|
| .. | .. |
|---|
| 13813 | 14043 | if (oldLength > newLength) { |
|---|
| 13814 | 14044 | // we used to have more keys, need to find them and destroy them. |
|---|
| 13815 | 14045 | changeDetected++; |
|---|
| 13816 | | - for(key in oldValue) { |
|---|
| 14046 | + for (key in oldValue) { |
|---|
| 13817 | 14047 | if (!newValue.hasOwnProperty(key)) { |
|---|
| 13818 | 14048 | oldLength--; |
|---|
| 13819 | 14049 | delete oldValue[key]; |
|---|
| .. | .. |
|---|
| 13933 | 14163 | dirty = false; |
|---|
| 13934 | 14164 | current = target; |
|---|
| 13935 | 14165 | |
|---|
| 13936 | | - while(asyncQueue.length) { |
|---|
| 14166 | + while (asyncQueue.length) { |
|---|
| 13937 | 14167 | try { |
|---|
| 13938 | 14168 | asyncTask = asyncQueue.shift(); |
|---|
| 13939 | 14169 | asyncTask.scope.$eval(asyncTask.expression); |
|---|
| .. | .. |
|---|
| 13966 | 14196 | if (ttl < 5) { |
|---|
| 13967 | 14197 | logIdx = 4 - ttl; |
|---|
| 13968 | 14198 | if (!watchLog[logIdx]) watchLog[logIdx] = []; |
|---|
| 13969 | | - logMsg = (isFunction(watch.exp)) |
|---|
| 13970 | | - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) |
|---|
| 13971 | | - : watch.exp; |
|---|
| 13972 | | - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); |
|---|
| 13973 | | - watchLog[logIdx].push(logMsg); |
|---|
| 14199 | + watchLog[logIdx].push({ |
|---|
| 14200 | + msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, |
|---|
| 14201 | + newVal: value, |
|---|
| 14202 | + oldVal: last |
|---|
| 14203 | + }); |
|---|
| 13974 | 14204 | } |
|---|
| 13975 | 14205 | } else if (watch === lastDirtyWatch) { |
|---|
| 13976 | 14206 | // If the most recently dirty watcher is now clean, short circuit since the remaining watchers |
|---|
| .. | .. |
|---|
| 13990 | 14220 | // this piece should be kept in sync with the traversal in $broadcast |
|---|
| 13991 | 14221 | if (!(next = (current.$$childHead || |
|---|
| 13992 | 14222 | (current !== target && current.$$nextSibling)))) { |
|---|
| 13993 | | - while(current !== target && !(next = current.$$nextSibling)) { |
|---|
| 14223 | + while (current !== target && !(next = current.$$nextSibling)) { |
|---|
| 13994 | 14224 | current = current.$parent; |
|---|
| 13995 | 14225 | } |
|---|
| 13996 | 14226 | } |
|---|
| .. | .. |
|---|
| 13998 | 14228 | |
|---|
| 13999 | 14229 | // `break traverseScopesLoop;` takes us to here |
|---|
| 14000 | 14230 | |
|---|
| 14001 | | - if((dirty || asyncQueue.length) && !(ttl--)) { |
|---|
| 14231 | + if ((dirty || asyncQueue.length) && !(ttl--)) { |
|---|
| 14002 | 14232 | clearPhase(); |
|---|
| 14003 | 14233 | throw $rootScopeMinErr('infdig', |
|---|
| 14004 | 14234 | '{0} $digest() iterations reached. Aborting!\n' + |
|---|
| 14005 | 14235 | 'Watchers fired in the last 5 iterations: {1}', |
|---|
| 14006 | | - TTL, toJson(watchLog)); |
|---|
| 14236 | + TTL, watchLog); |
|---|
| 14007 | 14237 | } |
|---|
| 14008 | 14238 | |
|---|
| 14009 | 14239 | } while (dirty || asyncQueue.length); |
|---|
| 14010 | 14240 | |
|---|
| 14011 | 14241 | clearPhase(); |
|---|
| 14012 | 14242 | |
|---|
| 14013 | | - while(postDigestQueue.length) { |
|---|
| 14243 | + while (postDigestQueue.length) { |
|---|
| 14014 | 14244 | try { |
|---|
| 14015 | 14245 | postDigestQueue.shift()(); |
|---|
| 14016 | 14246 | } catch (e) { |
|---|
| .. | .. |
|---|
| 14166 | 14396 | asyncQueue.push({scope: this, expression: expr}); |
|---|
| 14167 | 14397 | }, |
|---|
| 14168 | 14398 | |
|---|
| 14169 | | - $$postDigest : function(fn) { |
|---|
| 14399 | + $$postDigest: function(fn) { |
|---|
| 14170 | 14400 | postDigestQueue.push(fn); |
|---|
| 14171 | 14401 | }, |
|---|
| 14172 | 14402 | |
|---|
| .. | .. |
|---|
| 14303 | 14533 | |
|---|
| 14304 | 14534 | var self = this; |
|---|
| 14305 | 14535 | return function() { |
|---|
| 14306 | | - namedListeners[namedListeners.indexOf(listener)] = null; |
|---|
| 14307 | | - decrementListenerCount(self, 1, name); |
|---|
| 14536 | + var indexOfListener = namedListeners.indexOf(listener); |
|---|
| 14537 | + if (indexOfListener !== -1) { |
|---|
| 14538 | + namedListeners[indexOfListener] = null; |
|---|
| 14539 | + decrementListenerCount(self, 1, name); |
|---|
| 14540 | + } |
|---|
| 14308 | 14541 | }; |
|---|
| 14309 | 14542 | }, |
|---|
| 14310 | 14543 | |
|---|
| .. | .. |
|---|
| 14351 | 14584 | do { |
|---|
| 14352 | 14585 | namedListeners = scope.$$listeners[name] || empty; |
|---|
| 14353 | 14586 | event.currentScope = scope; |
|---|
| 14354 | | - for (i=0, length=namedListeners.length; i<length; i++) { |
|---|
| 14587 | + for (i = 0, length = namedListeners.length; i < length; i++) { |
|---|
| 14355 | 14588 | |
|---|
| 14356 | 14589 | // if listeners were deregistered, defragment the array |
|---|
| 14357 | 14590 | if (!namedListeners[i]) { |
|---|
| .. | .. |
|---|
| 14425 | 14658 | while ((current = next)) { |
|---|
| 14426 | 14659 | event.currentScope = current; |
|---|
| 14427 | 14660 | listeners = current.$$listeners[name] || []; |
|---|
| 14428 | | - for (i=0, length = listeners.length; i<length; i++) { |
|---|
| 14661 | + for (i = 0, length = listeners.length; i < length; i++) { |
|---|
| 14429 | 14662 | // if listeners were deregistered, defragment the array |
|---|
| 14430 | 14663 | if (!listeners[i]) { |
|---|
| 14431 | 14664 | listeners.splice(i, 1); |
|---|
| .. | .. |
|---|
| 14436 | 14669 | |
|---|
| 14437 | 14670 | try { |
|---|
| 14438 | 14671 | listeners[i].apply(null, listenerArgs); |
|---|
| 14439 | | - } catch(e) { |
|---|
| 14672 | + } catch (e) { |
|---|
| 14440 | 14673 | $exceptionHandler(e); |
|---|
| 14441 | 14674 | } |
|---|
| 14442 | 14675 | } |
|---|
| .. | .. |
|---|
| 14447 | 14680 | // (though it differs due to having the extra check for $$listenerCount) |
|---|
| 14448 | 14681 | if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || |
|---|
| 14449 | 14682 | (current !== target && current.$$nextSibling)))) { |
|---|
| 14450 | | - while(current !== target && !(next = current.$$nextSibling)) { |
|---|
| 14683 | + while (current !== target && !(next = current.$$nextSibling)) { |
|---|
| 14451 | 14684 | current = current.$parent; |
|---|
| 14452 | 14685 | } |
|---|
| 14453 | 14686 | } |
|---|
| .. | .. |
|---|
| 14501 | 14734 | while (applyAsyncQueue.length) { |
|---|
| 14502 | 14735 | try { |
|---|
| 14503 | 14736 | applyAsyncQueue.shift()(); |
|---|
| 14504 | | - } catch(e) { |
|---|
| 14737 | + } catch (e) { |
|---|
| 14505 | 14738 | $exceptionHandler(e); |
|---|
| 14506 | 14739 | } |
|---|
| 14507 | 14740 | } |
|---|
| .. | .. |
|---|
| 14581 | 14814 | var normalizedVal; |
|---|
| 14582 | 14815 | normalizedVal = urlResolve(uri).href; |
|---|
| 14583 | 14816 | if (normalizedVal !== '' && !normalizedVal.match(regex)) { |
|---|
| 14584 | | - return 'unsafe:'+normalizedVal; |
|---|
| 14817 | + return 'unsafe:' + normalizedVal; |
|---|
| 14585 | 14818 | } |
|---|
| 14586 | 14819 | return uri; |
|---|
| 14587 | 14820 | }; |
|---|
| .. | .. |
|---|
| 14601 | 14834 | }; |
|---|
| 14602 | 14835 | |
|---|
| 14603 | 14836 | // Helper functions follow. |
|---|
| 14604 | | - |
|---|
| 14605 | | -// Copied from: |
|---|
| 14606 | | -// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 |
|---|
| 14607 | | -// Prereq: s is a string. |
|---|
| 14608 | | -function escapeForRegexp(s) { |
|---|
| 14609 | | - return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). |
|---|
| 14610 | | - replace(/\x08/g, '\\x08'); |
|---|
| 14611 | | -} |
|---|
| 14612 | | - |
|---|
| 14613 | 14837 | |
|---|
| 14614 | 14838 | function adjustMatcher(matcher) { |
|---|
| 14615 | 14839 | if (matcher === 'self') { |
|---|
| .. | .. |
|---|
| 14746 | 14970 | * @description |
|---|
| 14747 | 14971 | * Sets/Gets the whitelist of trusted resource URLs. |
|---|
| 14748 | 14972 | */ |
|---|
| 14749 | | - this.resourceUrlWhitelist = function (value) { |
|---|
| 14973 | + this.resourceUrlWhitelist = function(value) { |
|---|
| 14750 | 14974 | if (arguments.length) { |
|---|
| 14751 | 14975 | resourceUrlWhitelist = adjustMatchers(value); |
|---|
| 14752 | 14976 | } |
|---|
| .. | .. |
|---|
| 14780 | 15004 | * Sets/Gets the blacklist of trusted resource URLs. |
|---|
| 14781 | 15005 | */ |
|---|
| 14782 | 15006 | |
|---|
| 14783 | | - this.resourceUrlBlacklist = function (value) { |
|---|
| 15007 | + this.resourceUrlBlacklist = function(value) { |
|---|
| 14784 | 15008 | if (arguments.length) { |
|---|
| 14785 | 15009 | resourceUrlBlacklist = adjustMatchers(value); |
|---|
| 14786 | 15010 | } |
|---|
| .. | .. |
|---|
| 15261 | 15485 | * @description |
|---|
| 15262 | 15486 | * Enables/disables SCE and returns the current value. |
|---|
| 15263 | 15487 | */ |
|---|
| 15264 | | - this.enabled = function (value) { |
|---|
| 15488 | + this.enabled = function(value) { |
|---|
| 15265 | 15489 | if (arguments.length) { |
|---|
| 15266 | 15490 | enabled = !!value; |
|---|
| 15267 | 15491 | } |
|---|
| .. | .. |
|---|
| 15315 | 15539 | * sce.js and sceSpecs.js would need to be aware of this detail. |
|---|
| 15316 | 15540 | */ |
|---|
| 15317 | 15541 | |
|---|
| 15318 | | - this.$get = ['$document', '$parse', '$sceDelegate', function( |
|---|
| 15319 | | - $document, $parse, $sceDelegate) { |
|---|
| 15542 | + this.$get = ['$parse', '$sceDelegate', function( |
|---|
| 15543 | + $parse, $sceDelegate) { |
|---|
| 15320 | 15544 | // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow |
|---|
| 15321 | 15545 | // the "expression(javascript expression)" syntax which is insecure. |
|---|
| 15322 | | - if (enabled && $document[0].documentMode < 8) { |
|---|
| 15546 | + if (enabled && msie < 8) { |
|---|
| 15323 | 15547 | throw $sceMinErr('iequirks', |
|---|
| 15324 | 15548 | 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + |
|---|
| 15325 | 15549 | 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + |
|---|
| .. | .. |
|---|
| 15339 | 15563 | * @description |
|---|
| 15340 | 15564 | * Returns a boolean indicating if SCE is enabled. |
|---|
| 15341 | 15565 | */ |
|---|
| 15342 | | - sce.isEnabled = function () { |
|---|
| 15566 | + sce.isEnabled = function() { |
|---|
| 15343 | 15567 | return enabled; |
|---|
| 15344 | 15568 | }; |
|---|
| 15345 | 15569 | sce.trustAs = $sceDelegate.trustAs; |
|---|
| .. | .. |
|---|
| 15375 | 15599 | if (parsed.literal && parsed.constant) { |
|---|
| 15376 | 15600 | return parsed; |
|---|
| 15377 | 15601 | } else { |
|---|
| 15378 | | - return $parse(expr, function (value) { |
|---|
| 15602 | + return $parse(expr, function(value) { |
|---|
| 15379 | 15603 | return sce.getTrusted(type, value); |
|---|
| 15380 | 15604 | }); |
|---|
| 15381 | 15605 | } |
|---|
| .. | .. |
|---|
| 15628 | 15852 | getTrusted = sce.getTrusted, |
|---|
| 15629 | 15853 | trustAs = sce.trustAs; |
|---|
| 15630 | 15854 | |
|---|
| 15631 | | - forEach(SCE_CONTEXTS, function (enumValue, name) { |
|---|
| 15855 | + forEach(SCE_CONTEXTS, function(enumValue, name) { |
|---|
| 15632 | 15856 | var lName = lowercase(name); |
|---|
| 15633 | | - sce[camelCase("parse_as_" + lName)] = function (expr) { |
|---|
| 15857 | + sce[camelCase("parse_as_" + lName)] = function(expr) { |
|---|
| 15634 | 15858 | return parse(enumValue, expr); |
|---|
| 15635 | 15859 | }; |
|---|
| 15636 | | - sce[camelCase("get_trusted_" + lName)] = function (value) { |
|---|
| 15860 | + sce[camelCase("get_trusted_" + lName)] = function(value) { |
|---|
| 15637 | 15861 | return getTrusted(enumValue, value); |
|---|
| 15638 | 15862 | }; |
|---|
| 15639 | | - sce[camelCase("trust_as_" + lName)] = function (value) { |
|---|
| 15863 | + sce[camelCase("trust_as_" + lName)] = function(value) { |
|---|
| 15640 | 15864 | return trustAs(enumValue, value); |
|---|
| 15641 | 15865 | }; |
|---|
| 15642 | 15866 | }); |
|---|
| .. | .. |
|---|
| 15667 | 15891 | boxee = /Boxee/i.test(($window.navigator || {}).userAgent), |
|---|
| 15668 | 15892 | document = $document[0] || {}, |
|---|
| 15669 | 15893 | vendorPrefix, |
|---|
| 15670 | | - vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, |
|---|
| 15894 | + vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, |
|---|
| 15671 | 15895 | bodyStyle = document.body && document.body.style, |
|---|
| 15672 | 15896 | transitions = false, |
|---|
| 15673 | 15897 | animations = false, |
|---|
| 15674 | 15898 | match; |
|---|
| 15675 | 15899 | |
|---|
| 15676 | 15900 | if (bodyStyle) { |
|---|
| 15677 | | - for(var prop in bodyStyle) { |
|---|
| 15678 | | - if(match = vendorRegex.exec(prop)) { |
|---|
| 15901 | + for (var prop in bodyStyle) { |
|---|
| 15902 | + if (match = vendorRegex.exec(prop)) { |
|---|
| 15679 | 15903 | vendorPrefix = match[0]; |
|---|
| 15680 | 15904 | vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); |
|---|
| 15681 | 15905 | break; |
|---|
| 15682 | 15906 | } |
|---|
| 15683 | 15907 | } |
|---|
| 15684 | 15908 | |
|---|
| 15685 | | - if(!vendorPrefix) { |
|---|
| 15909 | + if (!vendorPrefix) { |
|---|
| 15686 | 15910 | vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; |
|---|
| 15687 | 15911 | } |
|---|
| 15688 | 15912 | |
|---|
| 15689 | 15913 | transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); |
|---|
| 15690 | 15914 | animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); |
|---|
| 15691 | 15915 | |
|---|
| 15692 | | - if (android && (!transitions||!animations)) { |
|---|
| 15916 | + if (android && (!transitions || !animations)) { |
|---|
| 15693 | 15917 | transitions = isString(document.body.style.webkitTransition); |
|---|
| 15694 | 15918 | animations = isString(document.body.style.webkitAnimation); |
|---|
| 15695 | 15919 | } |
|---|
| .. | .. |
|---|
| 15712 | 15936 | // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have |
|---|
| 15713 | 15937 | // it. In particular the event is not fired when backspace or delete key are pressed or |
|---|
| 15714 | 15938 | // when cut operation is performed. |
|---|
| 15715 | | - if (event == 'input' && msie == 9) return false; |
|---|
| 15939 | + // IE10+ implements 'input' event but it erroneously fires under various situations, |
|---|
| 15940 | + // e.g. when placeholder changes, or a form is focused. |
|---|
| 15941 | + if (event === 'input' && msie <= 11) return false; |
|---|
| 15716 | 15942 | |
|---|
| 15717 | 15943 | if (isUndefined(eventSupport[event])) { |
|---|
| 15718 | 15944 | var divElm = document.createElement('div'); |
|---|
| .. | .. |
|---|
| 15723 | 15949 | }, |
|---|
| 15724 | 15950 | csp: csp(), |
|---|
| 15725 | 15951 | vendorPrefix: vendorPrefix, |
|---|
| 15726 | | - transitions : transitions, |
|---|
| 15727 | | - animations : animations, |
|---|
| 15952 | + transitions: transitions, |
|---|
| 15953 | + animations: animations, |
|---|
| 15728 | 15954 | android: android |
|---|
| 15729 | 15955 | }; |
|---|
| 15730 | 15956 | }]; |
|---|
| .. | .. |
|---|
| 15755 | 15981 | var self = handleRequestFn; |
|---|
| 15756 | 15982 | self.totalPendingRequests++; |
|---|
| 15757 | 15983 | |
|---|
| 15758 | | - return $http.get(tpl, { cache : $templateCache }) |
|---|
| 15759 | | - .then(function(response) { |
|---|
| 15760 | | - var html = response.data; |
|---|
| 15761 | | - if(!html || html.length === 0) { |
|---|
| 15762 | | - return handleError(); |
|---|
| 15763 | | - } |
|---|
| 15984 | + var transformResponse = $http.defaults && $http.defaults.transformResponse; |
|---|
| 15764 | 15985 | |
|---|
| 15986 | + if (isArray(transformResponse)) { |
|---|
| 15987 | + transformResponse = transformResponse.filter(function(transformer) { |
|---|
| 15988 | + return transformer !== defaultHttpResponseTransform; |
|---|
| 15989 | + }); |
|---|
| 15990 | + } else if (transformResponse === defaultHttpResponseTransform) { |
|---|
| 15991 | + transformResponse = null; |
|---|
| 15992 | + } |
|---|
| 15993 | + |
|---|
| 15994 | + var httpOptions = { |
|---|
| 15995 | + cache: $templateCache, |
|---|
| 15996 | + transformResponse: transformResponse |
|---|
| 15997 | + }; |
|---|
| 15998 | + |
|---|
| 15999 | + return $http.get(tpl, httpOptions) |
|---|
| 16000 | + .then(function(response) { |
|---|
| 15765 | 16001 | self.totalPendingRequests--; |
|---|
| 15766 | | - $templateCache.put(tpl, html); |
|---|
| 15767 | | - return html; |
|---|
| 16002 | + return response.data; |
|---|
| 15768 | 16003 | }, handleError); |
|---|
| 15769 | 16004 | |
|---|
| 15770 | | - function handleError() { |
|---|
| 16005 | + function handleError(resp) { |
|---|
| 15771 | 16006 | self.totalPendingRequests--; |
|---|
| 15772 | 16007 | if (!ignoreRequestError) { |
|---|
| 15773 | 16008 | throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl); |
|---|
| 15774 | 16009 | } |
|---|
| 15775 | | - return $q.reject(); |
|---|
| 16010 | + return $q.reject(resp); |
|---|
| 15776 | 16011 | } |
|---|
| 15777 | 16012 | } |
|---|
| 15778 | 16013 | |
|---|
| .. | .. |
|---|
| 15815 | 16050 | if (dataBinding) { |
|---|
| 15816 | 16051 | forEach(dataBinding, function(bindingName) { |
|---|
| 15817 | 16052 | if (opt_exactMatch) { |
|---|
| 15818 | | - var matcher = new RegExp('(^|\\s)' + expression + '(\\s|\\||$)'); |
|---|
| 16053 | + var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); |
|---|
| 15819 | 16054 | if (matcher.test(bindingName)) { |
|---|
| 15820 | 16055 | matches.push(binding); |
|---|
| 15821 | 16056 | } |
|---|
| .. | .. |
|---|
| 15937 | 16172 | timeoutId = $browser.defer(function() { |
|---|
| 15938 | 16173 | try { |
|---|
| 15939 | 16174 | deferred.resolve(fn()); |
|---|
| 15940 | | - } catch(e) { |
|---|
| 16175 | + } catch (e) { |
|---|
| 15941 | 16176 | deferred.reject(e); |
|---|
| 15942 | 16177 | $exceptionHandler(e); |
|---|
| 15943 | 16178 | } |
|---|
| .. | .. |
|---|
| 15988 | 16223 | // exactly the behavior needed here. There is little value is mocking these out for this |
|---|
| 15989 | 16224 | // service. |
|---|
| 15990 | 16225 | var urlParsingNode = document.createElement("a"); |
|---|
| 15991 | | -var originUrl = urlResolve(window.location.href, true); |
|---|
| 16226 | +var originUrl = urlResolve(window.location.href); |
|---|
| 15992 | 16227 | |
|---|
| 15993 | 16228 | |
|---|
| 15994 | 16229 | /** |
|---|
| .. | .. |
|---|
| 16043 | 16278 | * | pathname | The pathname, beginning with "/" |
|---|
| 16044 | 16279 | * |
|---|
| 16045 | 16280 | */ |
|---|
| 16046 | | -function urlResolve(url, base) { |
|---|
| 16281 | +function urlResolve(url) { |
|---|
| 16047 | 16282 | var href = url; |
|---|
| 16048 | 16283 | |
|---|
| 16049 | 16284 | if (msie) { |
|---|
| .. | .. |
|---|
| 16103 | 16338 | <file name="index.html"> |
|---|
| 16104 | 16339 | <script> |
|---|
| 16105 | 16340 | angular.module('windowExample', []) |
|---|
| 16106 | | - .controller('ExampleController', ['$scope', '$window', function ($scope, $window) { |
|---|
| 16341 | + .controller('ExampleController', ['$scope', '$window', function($scope, $window) { |
|---|
| 16107 | 16342 | $scope.greeting = 'Hello, World!'; |
|---|
| 16108 | 16343 | $scope.doGreeting = function(greeting) { |
|---|
| 16109 | 16344 | $window.alert(greeting); |
|---|
| .. | .. |
|---|
| 16124 | 16359 | </file> |
|---|
| 16125 | 16360 | </example> |
|---|
| 16126 | 16361 | */ |
|---|
| 16127 | | -function $WindowProvider(){ |
|---|
| 16362 | +function $WindowProvider() { |
|---|
| 16128 | 16363 | this.$get = valueFn(window); |
|---|
| 16129 | 16364 | } |
|---|
| 16130 | 16365 | |
|---|
| .. | .. |
|---|
| 16233 | 16468 | * of the registered filter instances. |
|---|
| 16234 | 16469 | */ |
|---|
| 16235 | 16470 | function register(name, factory) { |
|---|
| 16236 | | - if(isObject(name)) { |
|---|
| 16471 | + if (isObject(name)) { |
|---|
| 16237 | 16472 | var filters = {}; |
|---|
| 16238 | 16473 | forEach(name, function(filter, key) { |
|---|
| 16239 | 16474 | filters[key] = register(key, filter); |
|---|
| .. | .. |
|---|
| 16395 | 16630 | return function(array, expression, comparator) { |
|---|
| 16396 | 16631 | if (!isArray(array)) return array; |
|---|
| 16397 | 16632 | |
|---|
| 16398 | | - var comparatorType = typeof(comparator), |
|---|
| 16399 | | - predicates = []; |
|---|
| 16633 | + var predicateFn; |
|---|
| 16634 | + var matchAgainstAnyProp; |
|---|
| 16400 | 16635 | |
|---|
| 16401 | | - predicates.check = function(value, index) { |
|---|
| 16402 | | - for (var j = 0; j < predicates.length; j++) { |
|---|
| 16403 | | - if(!predicates[j](value, index)) { |
|---|
| 16404 | | - return false; |
|---|
| 16405 | | - } |
|---|
| 16406 | | - } |
|---|
| 16407 | | - return true; |
|---|
| 16408 | | - }; |
|---|
| 16409 | | - |
|---|
| 16410 | | - if (comparatorType !== 'function') { |
|---|
| 16411 | | - if (comparatorType === 'boolean' && comparator) { |
|---|
| 16412 | | - comparator = function(obj, text) { |
|---|
| 16413 | | - return angular.equals(obj, text); |
|---|
| 16414 | | - }; |
|---|
| 16415 | | - } else { |
|---|
| 16416 | | - comparator = function(obj, text) { |
|---|
| 16417 | | - if (obj && text && typeof obj === 'object' && typeof text === 'object') { |
|---|
| 16418 | | - for (var objKey in obj) { |
|---|
| 16419 | | - if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && |
|---|
| 16420 | | - comparator(obj[objKey], text[objKey])) { |
|---|
| 16421 | | - return true; |
|---|
| 16422 | | - } |
|---|
| 16423 | | - } |
|---|
| 16424 | | - return false; |
|---|
| 16425 | | - } |
|---|
| 16426 | | - text = (''+text).toLowerCase(); |
|---|
| 16427 | | - return (''+obj).toLowerCase().indexOf(text) > -1; |
|---|
| 16428 | | - }; |
|---|
| 16429 | | - } |
|---|
| 16430 | | - } |
|---|
| 16431 | | - |
|---|
| 16432 | | - var search = function(obj, text){ |
|---|
| 16433 | | - if (typeof text === 'string' && text.charAt(0) === '!') { |
|---|
| 16434 | | - return !search(obj, text.substr(1)); |
|---|
| 16435 | | - } |
|---|
| 16436 | | - switch (typeof obj) { |
|---|
| 16437 | | - case 'boolean': |
|---|
| 16438 | | - case 'number': |
|---|
| 16439 | | - case 'string': |
|---|
| 16440 | | - return comparator(obj, text); |
|---|
| 16441 | | - case 'object': |
|---|
| 16442 | | - switch (typeof text) { |
|---|
| 16443 | | - case 'object': |
|---|
| 16444 | | - return comparator(obj, text); |
|---|
| 16445 | | - default: |
|---|
| 16446 | | - for ( var objKey in obj) { |
|---|
| 16447 | | - if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { |
|---|
| 16448 | | - return true; |
|---|
| 16449 | | - } |
|---|
| 16450 | | - } |
|---|
| 16451 | | - break; |
|---|
| 16452 | | - } |
|---|
| 16453 | | - return false; |
|---|
| 16454 | | - case 'array': |
|---|
| 16455 | | - for ( var i = 0; i < obj.length; i++) { |
|---|
| 16456 | | - if (search(obj[i], text)) { |
|---|
| 16457 | | - return true; |
|---|
| 16458 | | - } |
|---|
| 16459 | | - } |
|---|
| 16460 | | - return false; |
|---|
| 16461 | | - default: |
|---|
| 16462 | | - return false; |
|---|
| 16463 | | - } |
|---|
| 16464 | | - }; |
|---|
| 16465 | 16636 | switch (typeof expression) { |
|---|
| 16637 | + case 'function': |
|---|
| 16638 | + predicateFn = expression; |
|---|
| 16639 | + break; |
|---|
| 16466 | 16640 | case 'boolean': |
|---|
| 16467 | 16641 | case 'number': |
|---|
| 16468 | 16642 | case 'string': |
|---|
| 16469 | | - // Set up expression object and fall through |
|---|
| 16470 | | - expression = {$:expression}; |
|---|
| 16471 | | - // jshint -W086 |
|---|
| 16643 | + matchAgainstAnyProp = true; |
|---|
| 16644 | + //jshint -W086 |
|---|
| 16472 | 16645 | case 'object': |
|---|
| 16473 | | - // jshint +W086 |
|---|
| 16474 | | - for (var key in expression) { |
|---|
| 16475 | | - (function(path) { |
|---|
| 16476 | | - if (typeof expression[path] === 'undefined') return; |
|---|
| 16477 | | - predicates.push(function(value) { |
|---|
| 16478 | | - return search(path == '$' ? value : (value && value[path]), expression[path]); |
|---|
| 16479 | | - }); |
|---|
| 16480 | | - })(key); |
|---|
| 16481 | | - } |
|---|
| 16482 | | - break; |
|---|
| 16483 | | - case 'function': |
|---|
| 16484 | | - predicates.push(expression); |
|---|
| 16646 | + //jshint +W086 |
|---|
| 16647 | + predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); |
|---|
| 16485 | 16648 | break; |
|---|
| 16486 | 16649 | default: |
|---|
| 16487 | 16650 | return array; |
|---|
| 16488 | 16651 | } |
|---|
| 16489 | | - var filtered = []; |
|---|
| 16490 | | - for ( var j = 0; j < array.length; j++) { |
|---|
| 16491 | | - var value = array[j]; |
|---|
| 16492 | | - if (predicates.check(value, j)) { |
|---|
| 16493 | | - filtered.push(value); |
|---|
| 16494 | | - } |
|---|
| 16495 | | - } |
|---|
| 16496 | | - return filtered; |
|---|
| 16652 | + |
|---|
| 16653 | + return array.filter(predicateFn); |
|---|
| 16497 | 16654 | }; |
|---|
| 16655 | +} |
|---|
| 16656 | + |
|---|
| 16657 | +// Helper functions for `filterFilter` |
|---|
| 16658 | +function createPredicateFn(expression, comparator, matchAgainstAnyProp) { |
|---|
| 16659 | + var predicateFn; |
|---|
| 16660 | + |
|---|
| 16661 | + if (comparator === true) { |
|---|
| 16662 | + comparator = equals; |
|---|
| 16663 | + } else if (!isFunction(comparator)) { |
|---|
| 16664 | + comparator = function(actual, expected) { |
|---|
| 16665 | + if (isObject(actual) || isObject(expected)) { |
|---|
| 16666 | + // Prevent an object to be considered equal to a string like `'[object'` |
|---|
| 16667 | + return false; |
|---|
| 16668 | + } |
|---|
| 16669 | + |
|---|
| 16670 | + actual = lowercase('' + actual); |
|---|
| 16671 | + expected = lowercase('' + expected); |
|---|
| 16672 | + return actual.indexOf(expected) !== -1; |
|---|
| 16673 | + }; |
|---|
| 16674 | + } |
|---|
| 16675 | + |
|---|
| 16676 | + predicateFn = function(item) { |
|---|
| 16677 | + return deepCompare(item, expression, comparator, matchAgainstAnyProp); |
|---|
| 16678 | + }; |
|---|
| 16679 | + |
|---|
| 16680 | + return predicateFn; |
|---|
| 16681 | +} |
|---|
| 16682 | + |
|---|
| 16683 | +function deepCompare(actual, expected, comparator, matchAgainstAnyProp) { |
|---|
| 16684 | + var actualType = typeof actual; |
|---|
| 16685 | + var expectedType = typeof expected; |
|---|
| 16686 | + |
|---|
| 16687 | + if ((expectedType === 'string') && (expected.charAt(0) === '!')) { |
|---|
| 16688 | + return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); |
|---|
| 16689 | + } else if (actualType === 'array') { |
|---|
| 16690 | + // In case `actual` is an array, consider it a match |
|---|
| 16691 | + // if ANY of it's items matches `expected` |
|---|
| 16692 | + return actual.some(function(item) { |
|---|
| 16693 | + return deepCompare(item, expected, comparator, matchAgainstAnyProp); |
|---|
| 16694 | + }); |
|---|
| 16695 | + } |
|---|
| 16696 | + |
|---|
| 16697 | + switch (actualType) { |
|---|
| 16698 | + case 'object': |
|---|
| 16699 | + var key; |
|---|
| 16700 | + if (matchAgainstAnyProp) { |
|---|
| 16701 | + for (key in actual) { |
|---|
| 16702 | + if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) { |
|---|
| 16703 | + return true; |
|---|
| 16704 | + } |
|---|
| 16705 | + } |
|---|
| 16706 | + return false; |
|---|
| 16707 | + } else if (expectedType === 'object') { |
|---|
| 16708 | + for (key in expected) { |
|---|
| 16709 | + var expectedVal = expected[key]; |
|---|
| 16710 | + if (isFunction(expectedVal)) { |
|---|
| 16711 | + continue; |
|---|
| 16712 | + } |
|---|
| 16713 | + |
|---|
| 16714 | + var keyIsDollar = key === '$'; |
|---|
| 16715 | + var actualVal = keyIsDollar ? actual : actual[key]; |
|---|
| 16716 | + if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) { |
|---|
| 16717 | + return false; |
|---|
| 16718 | + } |
|---|
| 16719 | + } |
|---|
| 16720 | + return true; |
|---|
| 16721 | + } else { |
|---|
| 16722 | + return comparator(actual, expected); |
|---|
| 16723 | + } |
|---|
| 16724 | + break; |
|---|
| 16725 | + case 'function': |
|---|
| 16726 | + return false; |
|---|
| 16727 | + default: |
|---|
| 16728 | + return comparator(actual, expected); |
|---|
| 16729 | + } |
|---|
| 16498 | 16730 | } |
|---|
| 16499 | 16731 | |
|---|
| 16500 | 16732 | /** |
|---|
| .. | .. |
|---|
| 16508 | 16740 | * |
|---|
| 16509 | 16741 | * @param {number} amount Input to filter. |
|---|
| 16510 | 16742 | * @param {string=} symbol Currency symbol or identifier to be displayed. |
|---|
| 16511 | | - * @param {number=} fractionSize Number of decimal places to round the amount to. |
|---|
| 16743 | + * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale |
|---|
| 16512 | 16744 | * @returns {string} Formatted number. |
|---|
| 16513 | 16745 | * |
|---|
| 16514 | 16746 | * |
|---|
| .. | .. |
|---|
| 16552 | 16784 | currencyFilter.$inject = ['$locale']; |
|---|
| 16553 | 16785 | function currencyFilter($locale) { |
|---|
| 16554 | 16786 | var formats = $locale.NUMBER_FORMATS; |
|---|
| 16555 | | - return function(amount, currencySymbol, fractionSize){ |
|---|
| 16787 | + return function(amount, currencySymbol, fractionSize) { |
|---|
| 16556 | 16788 | if (isUndefined(currencySymbol)) { |
|---|
| 16557 | 16789 | currencySymbol = formats.CURRENCY_SYM; |
|---|
| 16558 | 16790 | } |
|---|
| 16559 | 16791 | |
|---|
| 16560 | 16792 | if (isUndefined(fractionSize)) { |
|---|
| 16561 | | - // TODO: read the default value from the locale file |
|---|
| 16562 | | - fractionSize = 2; |
|---|
| 16793 | + fractionSize = formats.PATTERNS[1].maxFrac; |
|---|
| 16563 | 16794 | } |
|---|
| 16564 | 16795 | |
|---|
| 16565 | 16796 | // if null or undefined pass it through |
|---|
| .. | .. |
|---|
| 16648 | 16879 | if (numStr.indexOf('e') !== -1) { |
|---|
| 16649 | 16880 | var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); |
|---|
| 16650 | 16881 | if (match && match[2] == '-' && match[3] > fractionSize + 1) { |
|---|
| 16651 | | - numStr = '0'; |
|---|
| 16652 | 16882 | number = 0; |
|---|
| 16653 | 16883 | } else { |
|---|
| 16654 | 16884 | formatedText = numStr; |
|---|
| .. | .. |
|---|
| 16669 | 16899 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round |
|---|
| 16670 | 16900 | number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); |
|---|
| 16671 | 16901 | |
|---|
| 16672 | | - if (number === 0) { |
|---|
| 16673 | | - isNegative = false; |
|---|
| 16674 | | - } |
|---|
| 16675 | | - |
|---|
| 16676 | 16902 | var fraction = ('' + number).split(DECIMAL_SEP); |
|---|
| 16677 | 16903 | var whole = fraction[0]; |
|---|
| 16678 | 16904 | fraction = fraction[1] || ''; |
|---|
| .. | .. |
|---|
| 16684 | 16910 | if (whole.length >= (lgroup + group)) { |
|---|
| 16685 | 16911 | pos = whole.length - lgroup; |
|---|
| 16686 | 16912 | for (i = 0; i < pos; i++) { |
|---|
| 16687 | | - if ((pos - i)%group === 0 && i !== 0) { |
|---|
| 16913 | + if ((pos - i) % group === 0 && i !== 0) { |
|---|
| 16688 | 16914 | formatedText += groupSep; |
|---|
| 16689 | 16915 | } |
|---|
| 16690 | 16916 | formatedText += whole.charAt(i); |
|---|
| .. | .. |
|---|
| 16692 | 16918 | } |
|---|
| 16693 | 16919 | |
|---|
| 16694 | 16920 | for (i = pos; i < whole.length; i++) { |
|---|
| 16695 | | - if ((whole.length - i)%lgroup === 0 && i !== 0) { |
|---|
| 16921 | + if ((whole.length - i) % lgroup === 0 && i !== 0) { |
|---|
| 16696 | 16922 | formatedText += groupSep; |
|---|
| 16697 | 16923 | } |
|---|
| 16698 | 16924 | formatedText += whole.charAt(i); |
|---|
| 16699 | 16925 | } |
|---|
| 16700 | 16926 | |
|---|
| 16701 | 16927 | // format fraction part. |
|---|
| 16702 | | - while(fraction.length < fractionSize) { |
|---|
| 16928 | + while (fraction.length < fractionSize) { |
|---|
| 16703 | 16929 | fraction += '0'; |
|---|
| 16704 | 16930 | } |
|---|
| 16705 | 16931 | |
|---|
| 16706 | 16932 | if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); |
|---|
| 16707 | 16933 | } else { |
|---|
| 16708 | | - |
|---|
| 16709 | | - if (fractionSize > 0 && number > -1 && number < 1) { |
|---|
| 16934 | + if (fractionSize > 0 && number < 1) { |
|---|
| 16710 | 16935 | formatedText = number.toFixed(fractionSize); |
|---|
| 16936 | + number = parseFloat(formatedText); |
|---|
| 16711 | 16937 | } |
|---|
| 16712 | 16938 | } |
|---|
| 16713 | 16939 | |
|---|
| 16714 | | - parts.push(isNegative ? pattern.negPre : pattern.posPre); |
|---|
| 16715 | | - parts.push(formatedText); |
|---|
| 16716 | | - parts.push(isNegative ? pattern.negSuf : pattern.posSuf); |
|---|
| 16940 | + if (number === 0) { |
|---|
| 16941 | + isNegative = false; |
|---|
| 16942 | + } |
|---|
| 16943 | + |
|---|
| 16944 | + parts.push(isNegative ? pattern.negPre : pattern.posPre, |
|---|
| 16945 | + formatedText, |
|---|
| 16946 | + isNegative ? pattern.negSuf : pattern.posSuf); |
|---|
| 16717 | 16947 | return parts.join(''); |
|---|
| 16718 | 16948 | } |
|---|
| 16719 | 16949 | |
|---|
| .. | .. |
|---|
| 16724 | 16954 | num = -num; |
|---|
| 16725 | 16955 | } |
|---|
| 16726 | 16956 | num = '' + num; |
|---|
| 16727 | | - while(num.length < digits) num = '0' + num; |
|---|
| 16957 | + while (num.length < digits) num = '0' + num; |
|---|
| 16728 | 16958 | if (trim) |
|---|
| 16729 | 16959 | num = num.substr(num.length - digits); |
|---|
| 16730 | 16960 | return neg + num; |
|---|
| .. | .. |
|---|
| 16737 | 16967 | var value = date['get' + name](); |
|---|
| 16738 | 16968 | if (offset > 0 || value > -offset) |
|---|
| 16739 | 16969 | value += offset; |
|---|
| 16740 | | - if (value === 0 && offset == -12 ) value = 12; |
|---|
| 16970 | + if (value === 0 && offset == -12) value = 12; |
|---|
| 16741 | 16971 | return padNumber(value, size, trim); |
|---|
| 16742 | 16972 | }; |
|---|
| 16743 | 16973 | } |
|---|
| .. | .. |
|---|
| 16932 | 17162 | tzMin = int(match[9] + match[11]); |
|---|
| 16933 | 17163 | } |
|---|
| 16934 | 17164 | dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); |
|---|
| 16935 | | - var h = int(match[4]||0) - tzHour; |
|---|
| 16936 | | - var m = int(match[5]||0) - tzMin; |
|---|
| 16937 | | - var s = int(match[6]||0); |
|---|
| 16938 | | - var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); |
|---|
| 17165 | + var h = int(match[4] || 0) - tzHour; |
|---|
| 17166 | + var m = int(match[5] || 0) - tzMin; |
|---|
| 17167 | + var s = int(match[6] || 0); |
|---|
| 17168 | + var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); |
|---|
| 16939 | 17169 | timeSetter.call(date, h, m, s, ms); |
|---|
| 16940 | 17170 | return date; |
|---|
| 16941 | 17171 | } |
|---|
| .. | .. |
|---|
| 16962 | 17192 | return date; |
|---|
| 16963 | 17193 | } |
|---|
| 16964 | 17194 | |
|---|
| 16965 | | - while(format) { |
|---|
| 17195 | + while (format) { |
|---|
| 16966 | 17196 | match = DATE_FORMATS_SPLIT.exec(format); |
|---|
| 16967 | 17197 | if (match) { |
|---|
| 16968 | 17198 | parts = concat(parts, match, 1); |
|---|
| .. | .. |
|---|
| 16977 | 17207 | date = new Date(date.getTime()); |
|---|
| 16978 | 17208 | date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); |
|---|
| 16979 | 17209 | } |
|---|
| 16980 | | - forEach(parts, function(value){ |
|---|
| 17210 | + forEach(parts, function(value) { |
|---|
| 16981 | 17211 | fn = DATE_FORMATS[value]; |
|---|
| 16982 | 17212 | text += fn ? fn(date, $locale.DATETIME_FORMATS) |
|---|
| 16983 | 17213 | : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); |
|---|
| .. | .. |
|---|
| 17000 | 17230 | * the binding is automatically converted to JSON. |
|---|
| 17001 | 17231 | * |
|---|
| 17002 | 17232 | * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. |
|---|
| 17233 | + * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. |
|---|
| 17003 | 17234 | * @returns {string} JSON string. |
|---|
| 17004 | 17235 | * |
|---|
| 17005 | 17236 | * |
|---|
| 17006 | 17237 | * @example |
|---|
| 17007 | 17238 | <example> |
|---|
| 17008 | 17239 | <file name="index.html"> |
|---|
| 17009 | | - <pre>{{ {'name':'value'} | json }}</pre> |
|---|
| 17240 | + <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> |
|---|
| 17241 | + <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> |
|---|
| 17010 | 17242 | </file> |
|---|
| 17011 | 17243 | <file name="protractor.js" type="protractor"> |
|---|
| 17012 | 17244 | it('should jsonify filtered objects', function() { |
|---|
| 17013 | | - expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); |
|---|
| 17245 | + expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); |
|---|
| 17246 | + expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); |
|---|
| 17014 | 17247 | }); |
|---|
| 17015 | 17248 | </file> |
|---|
| 17016 | 17249 | </example> |
|---|
| 17017 | 17250 | * |
|---|
| 17018 | 17251 | */ |
|---|
| 17019 | 17252 | function jsonFilter() { |
|---|
| 17020 | | - return function(object) { |
|---|
| 17021 | | - return toJson(object, true); |
|---|
| 17253 | + return function(object, spacing) { |
|---|
| 17254 | + if (isUndefined(spacing)) { |
|---|
| 17255 | + spacing = 2; |
|---|
| 17256 | + } |
|---|
| 17257 | + return toJson(object, spacing); |
|---|
| 17022 | 17258 | }; |
|---|
| 17023 | 17259 | } |
|---|
| 17024 | 17260 | |
|---|
| .. | .. |
|---|
| 17130 | 17366 | </file> |
|---|
| 17131 | 17367 | </example> |
|---|
| 17132 | 17368 | */ |
|---|
| 17133 | | -function limitToFilter(){ |
|---|
| 17369 | +function limitToFilter() { |
|---|
| 17134 | 17370 | return function(input, limit) { |
|---|
| 17135 | 17371 | if (isNumber(input)) input = input.toString(); |
|---|
| 17136 | 17372 | if (!isArray(input) && !isString(input)) return input; |
|---|
| .. | .. |
|---|
| 17167 | 17403 | n = input.length; |
|---|
| 17168 | 17404 | } |
|---|
| 17169 | 17405 | |
|---|
| 17170 | | - for (; i<n; i++) { |
|---|
| 17406 | + for (; i < n; i++) { |
|---|
| 17171 | 17407 | out.push(input[i]); |
|---|
| 17172 | 17408 | } |
|---|
| 17173 | 17409 | |
|---|
| .. | .. |
|---|
| 17291 | 17527 | </example> |
|---|
| 17292 | 17528 | */ |
|---|
| 17293 | 17529 | orderByFilter.$inject = ['$parse']; |
|---|
| 17294 | | -function orderByFilter($parse){ |
|---|
| 17530 | +function orderByFilter($parse) { |
|---|
| 17295 | 17531 | return function(array, sortPredicate, reverseOrder) { |
|---|
| 17296 | 17532 | if (!(isArrayLike(array))) return array; |
|---|
| 17297 | | - sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; |
|---|
| 17533 | + sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; |
|---|
| 17298 | 17534 | if (sortPredicate.length === 0) { sortPredicate = ['+']; } |
|---|
| 17299 | | - sortPredicate = sortPredicate.map(function(predicate){ |
|---|
| 17535 | + sortPredicate = sortPredicate.map(function(predicate) { |
|---|
| 17300 | 17536 | var descending = false, get = predicate || identity; |
|---|
| 17301 | 17537 | if (isString(predicate)) { |
|---|
| 17302 | 17538 | if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { |
|---|
| 17303 | 17539 | descending = predicate.charAt(0) == '-'; |
|---|
| 17304 | 17540 | predicate = predicate.substring(1); |
|---|
| 17305 | 17541 | } |
|---|
| 17306 | | - if ( predicate === '' ) { |
|---|
| 17542 | + if (predicate === '') { |
|---|
| 17307 | 17543 | // Effectively no predicate was passed so we compare identity |
|---|
| 17308 | | - return reverseComparator(function(a,b) { |
|---|
| 17544 | + return reverseComparator(function(a, b) { |
|---|
| 17309 | 17545 | return compare(a, b); |
|---|
| 17310 | 17546 | }, descending); |
|---|
| 17311 | 17547 | } |
|---|
| 17312 | 17548 | get = $parse(predicate); |
|---|
| 17313 | 17549 | if (get.constant) { |
|---|
| 17314 | 17550 | var key = get(); |
|---|
| 17315 | | - return reverseComparator(function(a,b) { |
|---|
| 17551 | + return reverseComparator(function(a, b) { |
|---|
| 17316 | 17552 | return compare(a[key], b[key]); |
|---|
| 17317 | 17553 | }, descending); |
|---|
| 17318 | 17554 | } |
|---|
| 17319 | 17555 | } |
|---|
| 17320 | | - return reverseComparator(function(a,b){ |
|---|
| 17556 | + return reverseComparator(function(a, b) { |
|---|
| 17321 | 17557 | return compare(get(a),get(b)); |
|---|
| 17322 | 17558 | }, descending); |
|---|
| 17323 | 17559 | }); |
|---|
| 17324 | | - var arrayCopy = []; |
|---|
| 17325 | | - for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } |
|---|
| 17326 | | - return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); |
|---|
| 17560 | + return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); |
|---|
| 17327 | 17561 | |
|---|
| 17328 | | - function comparator(o1, o2){ |
|---|
| 17329 | | - for ( var i = 0; i < sortPredicate.length; i++) { |
|---|
| 17562 | + function comparator(o1, o2) { |
|---|
| 17563 | + for (var i = 0; i < sortPredicate.length; i++) { |
|---|
| 17330 | 17564 | var comp = sortPredicate[i](o1, o2); |
|---|
| 17331 | 17565 | if (comp !== 0) return comp; |
|---|
| 17332 | 17566 | } |
|---|
| .. | .. |
|---|
| 17334 | 17568 | } |
|---|
| 17335 | 17569 | function reverseComparator(comp, descending) { |
|---|
| 17336 | 17570 | return descending |
|---|
| 17337 | | - ? function(a,b){return comp(b,a);} |
|---|
| 17571 | + ? function(a, b) {return comp(b,a);} |
|---|
| 17338 | 17572 | : comp; |
|---|
| 17339 | 17573 | } |
|---|
| 17340 | | - function compare(v1, v2){ |
|---|
| 17574 | + function compare(v1, v2) { |
|---|
| 17341 | 17575 | var t1 = typeof v1; |
|---|
| 17342 | 17576 | var t2 = typeof v2; |
|---|
| 17343 | | - if (t1 == t2) { |
|---|
| 17344 | | - if (isDate(v1) && isDate(v2)) { |
|---|
| 17345 | | - v1 = v1.valueOf(); |
|---|
| 17346 | | - v2 = v2.valueOf(); |
|---|
| 17577 | + // Prepare values for Abstract Relational Comparison |
|---|
| 17578 | + // (http://www.ecma-international.org/ecma-262/5.1/#sec-11.8.5): |
|---|
| 17579 | + // If the resulting values are identical, return 0 to prevent |
|---|
| 17580 | + // incorrect re-ordering. |
|---|
| 17581 | + if (t1 === t2 && t1 === "object") { |
|---|
| 17582 | + // If types are both numbers, emulate abstract ToPrimitive() operation |
|---|
| 17583 | + // in order to get primitive values suitable for comparison |
|---|
| 17584 | + t1 = typeof (v1.valueOf ? v1 = v1.valueOf() : v1); |
|---|
| 17585 | + t2 = typeof (v2.valueOf ? v2 = v2.valueOf() : v2); |
|---|
| 17586 | + if (t1 === t2 && t1 === "object") { |
|---|
| 17587 | + // Object.prototype.valueOf will return the original object, by |
|---|
| 17588 | + // default. If we do not receive a primitive value, use ToString() |
|---|
| 17589 | + // instead. |
|---|
| 17590 | + t1 = typeof (v1.toString ? v1 = v1.toString() : v1); |
|---|
| 17591 | + t2 = typeof (v2.toString ? v2 = v2.toString() : v2); |
|---|
| 17592 | + |
|---|
| 17593 | + // If the end result of toString() for each item is the same, do not |
|---|
| 17594 | + // perform relational comparison, and do not re-order objects. |
|---|
| 17595 | + if (t1 === t2 && v1 === v2 || t1 === "object") return 0; |
|---|
| 17347 | 17596 | } |
|---|
| 17348 | | - if (t1 == "string") { |
|---|
| 17597 | + } |
|---|
| 17598 | + if (t1 === t2) { |
|---|
| 17599 | + if (t1 === "string") { |
|---|
| 17349 | 17600 | v1 = v1.toLowerCase(); |
|---|
| 17350 | 17601 | v2 = v2.toLowerCase(); |
|---|
| 17351 | 17602 | } |
|---|
| .. | .. |
|---|
| 17389 | 17640 | // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. |
|---|
| 17390 | 17641 | var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? |
|---|
| 17391 | 17642 | 'xlink:href' : 'href'; |
|---|
| 17392 | | - element.on('click', function(event){ |
|---|
| 17643 | + element.on('click', function(event) { |
|---|
| 17393 | 17644 | // if we have no href url, then don't navigate anywhere. |
|---|
| 17394 | 17645 | if (!element.attr(href)) { |
|---|
| 17395 | 17646 | event.preventDefault(); |
|---|
| .. | .. |
|---|
| 17411 | 17662 | * make the link go to the wrong URL if the user clicks it before |
|---|
| 17412 | 17663 | * Angular has a chance to replace the `{{hash}}` markup with its |
|---|
| 17413 | 17664 | * value. Until Angular replaces the markup the link will be broken |
|---|
| 17414 | | - * and will most likely return a 404 error. |
|---|
| 17415 | | - * |
|---|
| 17416 | | - * The `ngHref` directive solves this problem. |
|---|
| 17665 | + * and will most likely return a 404 error. The `ngHref` directive |
|---|
| 17666 | + * solves this problem. |
|---|
| 17417 | 17667 | * |
|---|
| 17418 | 17668 | * The wrong way to write it: |
|---|
| 17419 | 17669 | * ```html |
|---|
| .. | .. |
|---|
| 17868 | 18118 | * - `pattern` |
|---|
| 17869 | 18119 | * - `required` |
|---|
| 17870 | 18120 | * - `url` |
|---|
| 18121 | + * - `date` |
|---|
| 18122 | + * - `datetimelocal` |
|---|
| 18123 | + * - `time` |
|---|
| 18124 | + * - `week` |
|---|
| 18125 | + * - `month` |
|---|
| 17871 | 18126 | * |
|---|
| 17872 | 18127 | * @description |
|---|
| 17873 | 18128 | * `FormController` keeps track of all its controls and nested forms as well as the state of them, |
|---|
| .. | .. |
|---|
| 18056 | 18311 | * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after |
|---|
| 18057 | 18312 | * saving or resetting it. |
|---|
| 18058 | 18313 | */ |
|---|
| 18059 | | - form.$setPristine = function () { |
|---|
| 18314 | + form.$setPristine = function() { |
|---|
| 18060 | 18315 | $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); |
|---|
| 18061 | 18316 | form.$dirty = false; |
|---|
| 18062 | 18317 | form.$pristine = true; |
|---|
| .. | .. |
|---|
| 18079 | 18334 | * Setting a form controls back to their untouched state is often useful when setting the form |
|---|
| 18080 | 18335 | * back to its pristine state. |
|---|
| 18081 | 18336 | */ |
|---|
| 18082 | | - form.$setUntouched = function () { |
|---|
| 18337 | + form.$setUntouched = function() { |
|---|
| 18083 | 18338 | forEach(controls, function(control) { |
|---|
| 18084 | 18339 | control.$setUntouched(); |
|---|
| 18085 | 18340 | }); |
|---|
| .. | .. |
|---|
| 18092 | 18347 | * @description |
|---|
| 18093 | 18348 | * Sets the form to its submitted state. |
|---|
| 18094 | 18349 | */ |
|---|
| 18095 | | - form.$setSubmitted = function () { |
|---|
| 18350 | + form.$setSubmitted = function() { |
|---|
| 18096 | 18351 | $animate.addClass(element, SUBMITTED_CLASS); |
|---|
| 18097 | 18352 | form.$submitted = true; |
|---|
| 18098 | 18353 | parentForm.$setSubmitted(); |
|---|
| .. | .. |
|---|
| 18290 | 18545 | controller.$setSubmitted(); |
|---|
| 18291 | 18546 | }); |
|---|
| 18292 | 18547 | |
|---|
| 18293 | | - event.preventDefault |
|---|
| 18294 | | - ? event.preventDefault() |
|---|
| 18295 | | - : event.returnValue = false; // IE |
|---|
| 18548 | + event.preventDefault(); |
|---|
| 18296 | 18549 | }; |
|---|
| 18297 | 18550 | |
|---|
| 18298 | 18551 | addEventListenerFn(formElement[0], 'submit', handleFormSubmission); |
|---|
| .. | .. |
|---|
| 18369 | 18622 | * @description |
|---|
| 18370 | 18623 | * Standard HTML text input with angular data binding, inherited by most of the `input` elements. |
|---|
| 18371 | 18624 | * |
|---|
| 18372 | | - * *NOTE* Not every feature offered is available for all input types. |
|---|
| 18373 | 18625 | * |
|---|
| 18374 | 18626 | * @param {string} ngModel Assignable angular expression to data-bind to. |
|---|
| 18375 | 18627 | * @param {string=} name Property name of the form under which the control is published. |
|---|
| .. | .. |
|---|
| 18380 | 18632 | * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than |
|---|
| 18381 | 18633 | * minlength. |
|---|
| 18382 | 18634 | * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than |
|---|
| 18383 | | - * maxlength. |
|---|
| 18384 | | - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the |
|---|
| 18385 | | - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for |
|---|
| 18386 | | - * patterns defined as scope expressions. |
|---|
| 18635 | + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of |
|---|
| 18636 | + * any length. |
|---|
| 18637 | + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string |
|---|
| 18638 | + * that contains the regular expression body that will be converted to a regular expression |
|---|
| 18639 | + * as in the ngPattern directive. |
|---|
| 18640 | + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match |
|---|
| 18641 | + * a RegExp found by evaluating the Angular expression given in the attribute value. |
|---|
| 18642 | + * If the expression evaluates to a RegExp object then this is used directly. |
|---|
| 18643 | + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` |
|---|
| 18644 | + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. |
|---|
| 18387 | 18645 | * @param {string=} ngChange Angular expression to be executed when input changes due to user |
|---|
| 18388 | 18646 | * interaction with the input element. |
|---|
| 18389 | 18647 | * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. |
|---|
| .. | .. |
|---|
| 18453 | 18711 | * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 |
|---|
| 18454 | 18712 | * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many |
|---|
| 18455 | 18713 | * modern browsers do not yet support this input type, it is important to provide cues to users on the |
|---|
| 18456 | | - * expected input format via a placeholder or label. The model must always be a Date object. |
|---|
| 18714 | + * expected input format via a placeholder or label. |
|---|
| 18715 | + * |
|---|
| 18716 | + * The model must always be a Date object, otherwise Angular will throw an error. |
|---|
| 18717 | + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. |
|---|
| 18457 | 18718 | * |
|---|
| 18458 | 18719 | * The timezone to be used to read/write the `Date` instance in the model can be defined using |
|---|
| 18459 | 18720 | * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. |
|---|
| .. | .. |
|---|
| 18536 | 18797 | |
|---|
| 18537 | 18798 | /** |
|---|
| 18538 | 18799 | * @ngdoc input |
|---|
| 18539 | | - * @name input[dateTimeLocal] |
|---|
| 18800 | + * @name input[datetime-local] |
|---|
| 18540 | 18801 | * |
|---|
| 18541 | 18802 | * @description |
|---|
| 18542 | 18803 | * Input with datetime validation and transformation. In browsers that do not yet support |
|---|
| 18543 | 18804 | * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 |
|---|
| 18544 | | - * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. The model must be a Date object. |
|---|
| 18805 | + * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. |
|---|
| 18806 | + * |
|---|
| 18807 | + * The model must always be a Date object, otherwise Angular will throw an error. |
|---|
| 18808 | + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. |
|---|
| 18545 | 18809 | * |
|---|
| 18546 | 18810 | * The timezone to be used to read/write the `Date` instance in the model can be defined using |
|---|
| 18547 | 18811 | * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. |
|---|
| .. | .. |
|---|
| 18632 | 18896 | * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a |
|---|
| 18633 | 18897 | * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. |
|---|
| 18634 | 18898 | * |
|---|
| 18899 | + * The model must always be a Date object, otherwise Angular will throw an error. |
|---|
| 18900 | + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. |
|---|
| 18901 | + * |
|---|
| 18635 | 18902 | * The timezone to be used to read/write the `Date` instance in the model can be defined using |
|---|
| 18636 | 18903 | * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. |
|---|
| 18637 | 18904 | * |
|---|
| .. | .. |
|---|
| 18718 | 18985 | * @description |
|---|
| 18719 | 18986 | * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support |
|---|
| 18720 | 18987 | * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 |
|---|
| 18721 | | - * week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object. |
|---|
| 18988 | + * week format (yyyy-W##), for example: `2013-W02`. |
|---|
| 18989 | + * |
|---|
| 18990 | + * The model must always be a Date object, otherwise Angular will throw an error. |
|---|
| 18991 | + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. |
|---|
| 18722 | 18992 | * |
|---|
| 18723 | 18993 | * The timezone to be used to read/write the `Date` instance in the model can be defined using |
|---|
| 18724 | 18994 | * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. |
|---|
| .. | .. |
|---|
| 18804 | 19074 | * @description |
|---|
| 18805 | 19075 | * Input with month validation and transformation. In browsers that do not yet support |
|---|
| 18806 | 19076 | * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 |
|---|
| 18807 | | - * month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is |
|---|
| 18808 | | - * not set to the first of the month, the first of that model's month is assumed. |
|---|
| 19077 | + * month format (yyyy-MM), for example: `2009-01`. |
|---|
| 19078 | + * |
|---|
| 19079 | + * The model must always be a Date object, otherwise Angular will throw an error. |
|---|
| 19080 | + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. |
|---|
| 19081 | + * If the model is not set to the first of the month, the next view to model update will set it |
|---|
| 19082 | + * to the first of the month. |
|---|
| 18809 | 19083 | * |
|---|
| 18810 | 19084 | * The timezone to be used to read/write the `Date` instance in the model can be defined using |
|---|
| 18811 | 19085 | * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. |
|---|
| .. | .. |
|---|
| 18894 | 19168 | * Text input with number validation and transformation. Sets the `number` validation |
|---|
| 18895 | 19169 | * error if not a valid number. |
|---|
| 18896 | 19170 | * |
|---|
| 19171 | + * The model must always be a number, otherwise Angular will throw an error. |
|---|
| 19172 | + * |
|---|
| 18897 | 19173 | * @param {string} ngModel Assignable angular expression to data-bind to. |
|---|
| 18898 | 19174 | * @param {string=} name Property name of the form under which the control is published. |
|---|
| 18899 | 19175 | * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. |
|---|
| .. | .. |
|---|
| 18905 | 19181 | * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than |
|---|
| 18906 | 19182 | * minlength. |
|---|
| 18907 | 19183 | * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than |
|---|
| 18908 | | - * maxlength. |
|---|
| 18909 | | - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the |
|---|
| 18910 | | - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for |
|---|
| 18911 | | - * patterns defined as scope expressions. |
|---|
| 19184 | + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of |
|---|
| 19185 | + * any length. |
|---|
| 19186 | + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string |
|---|
| 19187 | + * that contains the regular expression body that will be converted to a regular expression |
|---|
| 19188 | + * as in the ngPattern directive. |
|---|
| 19189 | + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match |
|---|
| 19190 | + * a RegExp found by evaluating the Angular expression given in the attribute value. |
|---|
| 19191 | + * If the expression evaluates to a RegExp object then this is used directly. |
|---|
| 19192 | + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` |
|---|
| 19193 | + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. |
|---|
| 18912 | 19194 | * @param {string=} ngChange Angular expression to be executed when input changes due to user |
|---|
| 18913 | 19195 | * interaction with the input element. |
|---|
| 18914 | 19196 | * |
|---|
| .. | .. |
|---|
| 18972 | 19254 | * Text input with URL validation. Sets the `url` validation error key if the content is not a |
|---|
| 18973 | 19255 | * valid URL. |
|---|
| 18974 | 19256 | * |
|---|
| 19257 | + * <div class="alert alert-warning"> |
|---|
| 19258 | + * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex |
|---|
| 19259 | + * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify |
|---|
| 19260 | + * the built-in validators (see the {@link guide/forms Forms guide}) |
|---|
| 19261 | + * </div> |
|---|
| 19262 | + * |
|---|
| 18975 | 19263 | * @param {string} ngModel Assignable angular expression to data-bind to. |
|---|
| 18976 | 19264 | * @param {string=} name Property name of the form under which the control is published. |
|---|
| 18977 | 19265 | * @param {string=} required Sets `required` validation error key if the value is not entered. |
|---|
| .. | .. |
|---|
| 18981 | 19269 | * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than |
|---|
| 18982 | 19270 | * minlength. |
|---|
| 18983 | 19271 | * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than |
|---|
| 18984 | | - * maxlength. |
|---|
| 18985 | | - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the |
|---|
| 18986 | | - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for |
|---|
| 18987 | | - * patterns defined as scope expressions. |
|---|
| 19272 | + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of |
|---|
| 19273 | + * any length. |
|---|
| 19274 | + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string |
|---|
| 19275 | + * that contains the regular expression body that will be converted to a regular expression |
|---|
| 19276 | + * as in the ngPattern directive. |
|---|
| 19277 | + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match |
|---|
| 19278 | + * a RegExp found by evaluating the Angular expression given in the attribute value. |
|---|
| 19279 | + * If the expression evaluates to a RegExp object then this is used directly. |
|---|
| 19280 | + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` |
|---|
| 19281 | + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. |
|---|
| 18988 | 19282 | * @param {string=} ngChange Angular expression to be executed when input changes due to user |
|---|
| 18989 | 19283 | * interaction with the input element. |
|---|
| 18990 | 19284 | * |
|---|
| .. | .. |
|---|
| 19049 | 19343 | * Text input with email validation. Sets the `email` validation error key if not a valid email |
|---|
| 19050 | 19344 | * address. |
|---|
| 19051 | 19345 | * |
|---|
| 19346 | + * <div class="alert alert-warning"> |
|---|
| 19347 | + * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex |
|---|
| 19348 | + * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can |
|---|
| 19349 | + * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) |
|---|
| 19350 | + * </div> |
|---|
| 19351 | + * |
|---|
| 19052 | 19352 | * @param {string} ngModel Assignable angular expression to data-bind to. |
|---|
| 19053 | 19353 | * @param {string=} name Property name of the form under which the control is published. |
|---|
| 19054 | 19354 | * @param {string=} required Sets `required` validation error key if the value is not entered. |
|---|
| .. | .. |
|---|
| 19058 | 19358 | * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than |
|---|
| 19059 | 19359 | * minlength. |
|---|
| 19060 | 19360 | * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than |
|---|
| 19061 | | - * maxlength. |
|---|
| 19062 | | - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the |
|---|
| 19063 | | - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for |
|---|
| 19064 | | - * patterns defined as scope expressions. |
|---|
| 19361 | + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of |
|---|
| 19362 | + * any length. |
|---|
| 19363 | + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string |
|---|
| 19364 | + * that contains the regular expression body that will be converted to a regular expression |
|---|
| 19365 | + * as in the ngPattern directive. |
|---|
| 19366 | + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match |
|---|
| 19367 | + * a RegExp found by evaluating the Angular expression given in the attribute value. |
|---|
| 19368 | + * If the expression evaluates to a RegExp object then this is used directly. |
|---|
| 19369 | + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` |
|---|
| 19370 | + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. |
|---|
| 19065 | 19371 | * @param {string=} ngChange Angular expression to be executed when input changes due to user |
|---|
| 19066 | 19372 | * interaction with the input element. |
|---|
| 19067 | 19373 | * |
|---|
| .. | .. |
|---|
| 19227 | 19533 | 'file': noop |
|---|
| 19228 | 19534 | }; |
|---|
| 19229 | 19535 | |
|---|
| 19230 | | -function testFlags(validity, flags) { |
|---|
| 19231 | | - var i, flag; |
|---|
| 19232 | | - if (flags) { |
|---|
| 19233 | | - for (i=0; i<flags.length; ++i) { |
|---|
| 19234 | | - flag = flags[i]; |
|---|
| 19235 | | - if (validity[flag]) { |
|---|
| 19236 | | - return true; |
|---|
| 19237 | | - } |
|---|
| 19238 | | - } |
|---|
| 19239 | | - } |
|---|
| 19240 | | - return false; |
|---|
| 19241 | | -} |
|---|
| 19242 | | - |
|---|
| 19243 | 19536 | function stringBasedInputType(ctrl) { |
|---|
| 19244 | 19537 | ctrl.$formatters.push(function(value) { |
|---|
| 19245 | 19538 | return ctrl.$isEmpty(value) ? value : value.toString(); |
|---|
| .. | .. |
|---|
| 19252 | 19545 | } |
|---|
| 19253 | 19546 | |
|---|
| 19254 | 19547 | function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { |
|---|
| 19255 | | - var validity = element.prop(VALIDITY_STATE_PROPERTY); |
|---|
| 19256 | | - var placeholder = element[0].placeholder, noevent = {}; |
|---|
| 19257 | 19548 | var type = lowercase(element[0].type); |
|---|
| 19258 | 19549 | |
|---|
| 19259 | 19550 | // In composition mode, users are still inputing intermediate text buffer, |
|---|
| .. | .. |
|---|
| 19273 | 19564 | } |
|---|
| 19274 | 19565 | |
|---|
| 19275 | 19566 | var listener = function(ev) { |
|---|
| 19567 | + if (timeout) { |
|---|
| 19568 | + $browser.defer.cancel(timeout); |
|---|
| 19569 | + timeout = null; |
|---|
| 19570 | + } |
|---|
| 19276 | 19571 | if (composing) return; |
|---|
| 19277 | 19572 | var value = element.val(), |
|---|
| 19278 | 19573 | event = ev && ev.type; |
|---|
| 19279 | | - |
|---|
| 19280 | | - // IE (11 and under) seem to emit an 'input' event if the placeholder value changes. |
|---|
| 19281 | | - // We don't want to dirty the value when this happens, so we abort here. Unfortunately, |
|---|
| 19282 | | - // IE also sends input events for other non-input-related things, (such as focusing on a |
|---|
| 19283 | | - // form control), so this change is not entirely enough to solve this. |
|---|
| 19284 | | - if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) { |
|---|
| 19285 | | - placeholder = element[0].placeholder; |
|---|
| 19286 | | - return; |
|---|
| 19287 | | - } |
|---|
| 19288 | 19574 | |
|---|
| 19289 | 19575 | // By default we will trim the value |
|---|
| 19290 | 19576 | // If the attribute ng-trim exists we will avoid trimming |
|---|
| .. | .. |
|---|
| 19308 | 19594 | } else { |
|---|
| 19309 | 19595 | var timeout; |
|---|
| 19310 | 19596 | |
|---|
| 19311 | | - var deferListener = function(ev) { |
|---|
| 19597 | + var deferListener = function(ev, input, origValue) { |
|---|
| 19312 | 19598 | if (!timeout) { |
|---|
| 19313 | 19599 | timeout = $browser.defer(function() { |
|---|
| 19314 | | - listener(ev); |
|---|
| 19315 | 19600 | timeout = null; |
|---|
| 19601 | + if (!input || input.value !== origValue) { |
|---|
| 19602 | + listener(ev); |
|---|
| 19603 | + } |
|---|
| 19316 | 19604 | }); |
|---|
| 19317 | 19605 | } |
|---|
| 19318 | 19606 | }; |
|---|
| .. | .. |
|---|
| 19324 | 19612 | // command modifiers arrows |
|---|
| 19325 | 19613 | if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; |
|---|
| 19326 | 19614 | |
|---|
| 19327 | | - deferListener(event); |
|---|
| 19615 | + deferListener(event, this, this.value); |
|---|
| 19328 | 19616 | }); |
|---|
| 19329 | 19617 | |
|---|
| 19330 | 19618 | // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it |
|---|
| .. | .. |
|---|
| 19338 | 19626 | element.on('change', listener); |
|---|
| 19339 | 19627 | |
|---|
| 19340 | 19628 | ctrl.$render = function() { |
|---|
| 19341 | | - element.val(ctrl.$isEmpty(ctrl.$modelValue) ? '' : ctrl.$viewValue); |
|---|
| 19629 | + element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); |
|---|
| 19342 | 19630 | }; |
|---|
| 19343 | 19631 | } |
|---|
| 19344 | 19632 | |
|---|
| .. | .. |
|---|
| 19386 | 19674 | // When a date is JSON'ified to wraps itself inside of an extra |
|---|
| 19387 | 19675 | // set of double quotes. This makes the date parsing code unable |
|---|
| 19388 | 19676 | // to match the date string and parse it as a date. |
|---|
| 19389 | | - if (iso.charAt(0) == '"' && iso.charAt(iso.length-1) == '"') { |
|---|
| 19390 | | - iso = iso.substring(1, iso.length-1); |
|---|
| 19677 | + if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { |
|---|
| 19678 | + iso = iso.substring(1, iso.length - 1); |
|---|
| 19391 | 19679 | } |
|---|
| 19392 | 19680 | if (ISO_DATE_REGEXP.test(iso)) { |
|---|
| 19393 | 19681 | return new Date(iso); |
|---|
| .. | .. |
|---|
| 19448 | 19736 | }); |
|---|
| 19449 | 19737 | |
|---|
| 19450 | 19738 | ctrl.$formatters.push(function(value) { |
|---|
| 19451 | | - if (!ctrl.$isEmpty(value)) { |
|---|
| 19452 | | - if (!isDate(value)) { |
|---|
| 19453 | | - throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); |
|---|
| 19454 | | - } |
|---|
| 19739 | + if (value && !isDate(value)) { |
|---|
| 19740 | + throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); |
|---|
| 19741 | + } |
|---|
| 19742 | + if (isValidDate(value)) { |
|---|
| 19455 | 19743 | previousDate = value; |
|---|
| 19456 | 19744 | if (previousDate && timezone === 'UTC') { |
|---|
| 19457 | 19745 | var timezoneOffset = 60000 * previousDate.getTimezoneOffset(); |
|---|
| .. | .. |
|---|
| 19460 | 19748 | return $filter('date')(value, format, timezone); |
|---|
| 19461 | 19749 | } else { |
|---|
| 19462 | 19750 | previousDate = null; |
|---|
| 19751 | + return ''; |
|---|
| 19463 | 19752 | } |
|---|
| 19464 | | - return ''; |
|---|
| 19465 | 19753 | }); |
|---|
| 19466 | 19754 | |
|---|
| 19467 | 19755 | if (isDefined(attr.min) || attr.ngMin) { |
|---|
| 19468 | 19756 | var minVal; |
|---|
| 19469 | 19757 | ctrl.$validators.min = function(value) { |
|---|
| 19470 | | - return ctrl.$isEmpty(value) || isUndefined(minVal) || parseDate(value) >= minVal; |
|---|
| 19758 | + return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; |
|---|
| 19471 | 19759 | }; |
|---|
| 19472 | 19760 | attr.$observe('min', function(val) { |
|---|
| 19473 | 19761 | minVal = parseObservedDateValue(val); |
|---|
| .. | .. |
|---|
| 19478 | 19766 | if (isDefined(attr.max) || attr.ngMax) { |
|---|
| 19479 | 19767 | var maxVal; |
|---|
| 19480 | 19768 | ctrl.$validators.max = function(value) { |
|---|
| 19481 | | - return ctrl.$isEmpty(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; |
|---|
| 19769 | + return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; |
|---|
| 19482 | 19770 | }; |
|---|
| 19483 | 19771 | attr.$observe('max', function(val) { |
|---|
| 19484 | 19772 | maxVal = parseObservedDateValue(val); |
|---|
| 19485 | 19773 | ctrl.$validate(); |
|---|
| 19486 | 19774 | }); |
|---|
| 19487 | 19775 | } |
|---|
| 19488 | | - // Override the standard $isEmpty to detect invalid dates as well |
|---|
| 19489 | | - ctrl.$isEmpty = function(value) { |
|---|
| 19776 | + |
|---|
| 19777 | + function isValidDate(value) { |
|---|
| 19490 | 19778 | // Invalid Date: getTime() returns NaN |
|---|
| 19491 | | - return !value || (value.getTime && value.getTime() !== value.getTime()); |
|---|
| 19492 | | - }; |
|---|
| 19779 | + return value && !(value.getTime && value.getTime() !== value.getTime()); |
|---|
| 19780 | + } |
|---|
| 19493 | 19781 | |
|---|
| 19494 | 19782 | function parseObservedDateValue(val) { |
|---|
| 19495 | 19783 | return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; |
|---|
| .. | .. |
|---|
| 19573 | 19861 | stringBasedInputType(ctrl); |
|---|
| 19574 | 19862 | |
|---|
| 19575 | 19863 | ctrl.$$parserName = 'url'; |
|---|
| 19576 | | - ctrl.$validators.url = function(value) { |
|---|
| 19864 | + ctrl.$validators.url = function(modelValue, viewValue) { |
|---|
| 19865 | + var value = modelValue || viewValue; |
|---|
| 19577 | 19866 | return ctrl.$isEmpty(value) || URL_REGEXP.test(value); |
|---|
| 19578 | 19867 | }; |
|---|
| 19579 | 19868 | } |
|---|
| .. | .. |
|---|
| 19585 | 19874 | stringBasedInputType(ctrl); |
|---|
| 19586 | 19875 | |
|---|
| 19587 | 19876 | ctrl.$$parserName = 'email'; |
|---|
| 19588 | | - ctrl.$validators.email = function(value) { |
|---|
| 19877 | + ctrl.$validators.email = function(modelValue, viewValue) { |
|---|
| 19878 | + var value = modelValue || viewValue; |
|---|
| 19589 | 19879 | return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); |
|---|
| 19590 | 19880 | }; |
|---|
| 19591 | 19881 | } |
|---|
| .. | .. |
|---|
| 19639 | 19929 | element[0].checked = ctrl.$viewValue; |
|---|
| 19640 | 19930 | }; |
|---|
| 19641 | 19931 | |
|---|
| 19642 | | - // Override the standard `$isEmpty` because an empty checkbox is never equal to the trueValue |
|---|
| 19932 | + // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` |
|---|
| 19933 | + // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert |
|---|
| 19934 | + // it to a boolean. |
|---|
| 19643 | 19935 | ctrl.$isEmpty = function(value) { |
|---|
| 19644 | | - return value !== trueValue; |
|---|
| 19936 | + return value === false; |
|---|
| 19645 | 19937 | }; |
|---|
| 19646 | 19938 | |
|---|
| 19647 | 19939 | ctrl.$formatters.push(function(value) { |
|---|
| .. | .. |
|---|
| 19673 | 19965 | * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than |
|---|
| 19674 | 19966 | * minlength. |
|---|
| 19675 | 19967 | * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than |
|---|
| 19676 | | - * maxlength. |
|---|
| 19968 | + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any |
|---|
| 19969 | + * length. |
|---|
| 19677 | 19970 | * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the |
|---|
| 19678 | 19971 | * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for |
|---|
| 19679 | 19972 | * patterns defined as scope expressions. |
|---|
| .. | .. |
|---|
| 19689 | 19982 | * @restrict E |
|---|
| 19690 | 19983 | * |
|---|
| 19691 | 19984 | * @description |
|---|
| 19692 | | - * HTML input element control with angular data-binding. Input control follows HTML5 input types |
|---|
| 19693 | | - * and polyfills the HTML5 validation behavior for older browsers. |
|---|
| 19985 | + * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, |
|---|
| 19986 | + * input state control, and validation. |
|---|
| 19987 | + * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. |
|---|
| 19694 | 19988 | * |
|---|
| 19695 | | - * *NOTE* Not every feature offered is available for all input types. |
|---|
| 19989 | + * <div class="alert alert-warning"> |
|---|
| 19990 | + * **Note:** Not every feature offered is available for all input types. |
|---|
| 19991 | + * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. |
|---|
| 19992 | + * </div> |
|---|
| 19696 | 19993 | * |
|---|
| 19697 | 19994 | * @param {string} ngModel Assignable angular expression to data-bind to. |
|---|
| 19698 | 19995 | * @param {string=} name Property name of the form under which the control is published. |
|---|
| .. | .. |
|---|
| 19701 | 19998 | * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than |
|---|
| 19702 | 19999 | * minlength. |
|---|
| 19703 | 20000 | * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than |
|---|
| 19704 | | - * maxlength. |
|---|
| 20001 | + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any |
|---|
| 20002 | + * length. |
|---|
| 19705 | 20003 | * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the |
|---|
| 19706 | 20004 | * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for |
|---|
| 19707 | 20005 | * patterns defined as scope expressions. |
|---|
| .. | .. |
|---|
| 19828 | 20126 | * @name ngModel.NgModelController |
|---|
| 19829 | 20127 | * |
|---|
| 19830 | 20128 | * @property {string} $viewValue Actual string value in the view. |
|---|
| 19831 | | - * @property {*} $modelValue The value in the model, that the control is bound to. |
|---|
| 20129 | + * @property {*} $modelValue The value in the model that the control is bound to. |
|---|
| 19832 | 20130 | * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever |
|---|
| 19833 | | - the control reads value from the DOM. Each function is called, in turn, passing the value |
|---|
| 19834 | | - through to the next. The last return value is used to populate the model. |
|---|
| 19835 | | - Used to sanitize / convert the value as well as validation. For validation, |
|---|
| 19836 | | - the parsers should update the validity state using |
|---|
| 19837 | | - {@link ngModel.NgModelController#$setValidity $setValidity()}, |
|---|
| 19838 | | - and return `undefined` for invalid values. |
|---|
| 20131 | + the control reads value from the DOM. The functions are called in array order, each passing |
|---|
| 20132 | + its return value through to the next. The last return value is forwarded to the |
|---|
| 20133 | + {@link ngModel.NgModelController#$validators `$validators`} collection. |
|---|
| 20134 | + |
|---|
| 20135 | +Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue |
|---|
| 20136 | +`$viewValue`}. |
|---|
| 20137 | + |
|---|
| 20138 | +Returning `undefined` from a parser means a parse error occurred. In that case, |
|---|
| 20139 | +no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` |
|---|
| 20140 | +will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} |
|---|
| 20141 | +is set to `true`. The parse error is stored in `ngModel.$error.parse`. |
|---|
| 19839 | 20142 | |
|---|
| 19840 | 20143 | * |
|---|
| 19841 | 20144 | * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever |
|---|
| 19842 | | - the model value changes. Each function is called, in turn, passing the value through to the |
|---|
| 19843 | | - next. Used to format / convert values for display in the control and validation. |
|---|
| 20145 | + the model value changes. The functions are called in reverse array order, each passing the value through to the |
|---|
| 20146 | + next. The last return value is used as the actual DOM value. |
|---|
| 20147 | + Used to format / convert values for display in the control. |
|---|
| 19844 | 20148 | * ```js |
|---|
| 19845 | 20149 | * function formatter(value) { |
|---|
| 19846 | 20150 | * if (value) { |
|---|
| .. | .. |
|---|
| 19871 | 20175 | * is expected to return a promise when it is run during the model validation process. Once the promise |
|---|
| 19872 | 20176 | * is delivered then the validation status will be set to true when fulfilled and false when rejected. |
|---|
| 19873 | 20177 | * When the asynchronous validators are triggered, each of the validators will run in parallel and the model |
|---|
| 19874 | | - * value will only be updated once all validators have been fulfilled. Also, keep in mind that all |
|---|
| 19875 | | - * asynchronous validators will only run once all synchronous validators have passed. |
|---|
| 20178 | + * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator |
|---|
| 20179 | + * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators |
|---|
| 20180 | + * will only run once all synchronous validators have passed. |
|---|
| 19876 | 20181 | * |
|---|
| 19877 | 20182 | * Please note that if $http is used then it is important that the server returns a success HTTP response code |
|---|
| 19878 | 20183 | * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. |
|---|
| .. | .. |
|---|
| 19893 | 20198 | * }; |
|---|
| 19894 | 20199 | * ``` |
|---|
| 19895 | 20200 | * |
|---|
| 19896 | | - * @param {string} name The name of the validator. |
|---|
| 19897 | | - * @param {Function} validationFn The validation function that will be run. |
|---|
| 19898 | | - * |
|---|
| 19899 | 20201 | * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the |
|---|
| 19900 | 20202 | * view value has changed. It is called with no arguments, and its return value is ignored. |
|---|
| 19901 | 20203 | * This can be used in place of additional $watches against the model value. |
|---|
| .. | .. |
|---|
| 19909 | 20211 | * @property {boolean} $dirty True if user has already interacted with the control. |
|---|
| 19910 | 20212 | * @property {boolean} $valid True if there is no error. |
|---|
| 19911 | 20213 | * @property {boolean} $invalid True if at least one error on the control. |
|---|
| 20214 | + * @property {string} $name The name attribute of the control. |
|---|
| 19912 | 20215 | * |
|---|
| 19913 | 20216 | * @description |
|---|
| 19914 | 20217 | * |
|---|
| 19915 | | - * `NgModelController` provides API for the `ng-model` directive. The controller contains |
|---|
| 19916 | | - * services for data-binding, validation, CSS updates, and value formatting and parsing. It |
|---|
| 19917 | | - * purposefully does not contain any logic which deals with DOM rendering or listening to |
|---|
| 19918 | | - * DOM events. Such DOM related logic should be provided by other directives which make use of |
|---|
| 19919 | | - * `NgModelController` for data-binding. |
|---|
| 20218 | + * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. |
|---|
| 20219 | + * The controller contains services for data-binding, validation, CSS updates, and value formatting |
|---|
| 20220 | + * and parsing. It purposefully does not contain any logic which deals with DOM rendering or |
|---|
| 20221 | + * listening to DOM events. |
|---|
| 20222 | + * Such DOM related logic should be provided by other directives which make use of |
|---|
| 20223 | + * `NgModelController` for data-binding to control elements. |
|---|
| 20224 | + * Angular provides this DOM logic for most {@link input `input`} elements. |
|---|
| 20225 | + * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example |
|---|
| 20226 | + * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. |
|---|
| 19920 | 20227 | * |
|---|
| 19921 | | - * ## Custom Control Example |
|---|
| 20228 | + * @example |
|---|
| 20229 | + * ### Custom Control Example |
|---|
| 19922 | 20230 | * This example shows how to use `NgModelController` with a custom control to achieve |
|---|
| 19923 | 20231 | * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) |
|---|
| 19924 | 20232 | * collaborate together to achieve the desired result. |
|---|
| .. | .. |
|---|
| 19928 | 20236 | * |
|---|
| 19929 | 20237 | * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} |
|---|
| 19930 | 20238 | * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). |
|---|
| 19931 | | - * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks |
|---|
| 20239 | + * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks |
|---|
| 19932 | 20240 | * that content using the `$sce` service. |
|---|
| 19933 | 20241 | * |
|---|
| 19934 | 20242 | * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> |
|---|
| .. | .. |
|---|
| 19960 | 20268 | |
|---|
| 19961 | 20269 | // Listen for change events to enable binding |
|---|
| 19962 | 20270 | element.on('blur keyup change', function() { |
|---|
| 19963 | | - scope.$apply(read); |
|---|
| 20271 | + scope.$evalAsync(read); |
|---|
| 19964 | 20272 | }); |
|---|
| 19965 | 20273 | read(); // initialize |
|---|
| 19966 | 20274 | |
|---|
| .. | .. |
|---|
| 20015 | 20323 | function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { |
|---|
| 20016 | 20324 | this.$viewValue = Number.NaN; |
|---|
| 20017 | 20325 | this.$modelValue = Number.NaN; |
|---|
| 20326 | + this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. |
|---|
| 20018 | 20327 | this.$validators = {}; |
|---|
| 20019 | 20328 | this.$asyncValidators = {}; |
|---|
| 20020 | 20329 | this.$parsers = []; |
|---|
| .. | .. |
|---|
| 20033 | 20342 | |
|---|
| 20034 | 20343 | |
|---|
| 20035 | 20344 | var parsedNgModel = $parse($attr.ngModel), |
|---|
| 20345 | + parsedNgModelAssign = parsedNgModel.assign, |
|---|
| 20346 | + ngModelGet = parsedNgModel, |
|---|
| 20347 | + ngModelSet = parsedNgModelAssign, |
|---|
| 20036 | 20348 | pendingDebounce = null, |
|---|
| 20037 | 20349 | ctrl = this; |
|---|
| 20038 | 20350 | |
|---|
| 20039 | | - var ngModelGet = function ngModelGet() { |
|---|
| 20040 | | - var modelValue = parsedNgModel($scope); |
|---|
| 20041 | | - if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) { |
|---|
| 20042 | | - modelValue = modelValue(); |
|---|
| 20043 | | - } |
|---|
| 20044 | | - return modelValue; |
|---|
| 20045 | | - }; |
|---|
| 20046 | | - |
|---|
| 20047 | | - var ngModelSet = function ngModelSet(newValue) { |
|---|
| 20048 | | - var getterSetter; |
|---|
| 20049 | | - if (ctrl.$options && ctrl.$options.getterSetter && |
|---|
| 20050 | | - isFunction(getterSetter = parsedNgModel($scope))) { |
|---|
| 20051 | | - |
|---|
| 20052 | | - getterSetter(ctrl.$modelValue); |
|---|
| 20053 | | - } else { |
|---|
| 20054 | | - parsedNgModel.assign($scope, ctrl.$modelValue); |
|---|
| 20055 | | - } |
|---|
| 20056 | | - }; |
|---|
| 20057 | | - |
|---|
| 20058 | 20351 | this.$$setOptions = function(options) { |
|---|
| 20059 | 20352 | ctrl.$options = options; |
|---|
| 20353 | + if (options && options.getterSetter) { |
|---|
| 20354 | + var invokeModelGetter = $parse($attr.ngModel + '()'), |
|---|
| 20355 | + invokeModelSetter = $parse($attr.ngModel + '($$$p)'); |
|---|
| 20060 | 20356 | |
|---|
| 20061 | | - if (!parsedNgModel.assign && (!options || !options.getterSetter)) { |
|---|
| 20357 | + ngModelGet = function($scope) { |
|---|
| 20358 | + var modelValue = parsedNgModel($scope); |
|---|
| 20359 | + if (isFunction(modelValue)) { |
|---|
| 20360 | + modelValue = invokeModelGetter($scope); |
|---|
| 20361 | + } |
|---|
| 20362 | + return modelValue; |
|---|
| 20363 | + }; |
|---|
| 20364 | + ngModelSet = function($scope, newValue) { |
|---|
| 20365 | + if (isFunction(parsedNgModel($scope))) { |
|---|
| 20366 | + invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); |
|---|
| 20367 | + } else { |
|---|
| 20368 | + parsedNgModelAssign($scope, ctrl.$modelValue); |
|---|
| 20369 | + } |
|---|
| 20370 | + }; |
|---|
| 20371 | + } else if (!parsedNgModel.assign) { |
|---|
| 20062 | 20372 | throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", |
|---|
| 20063 | 20373 | $attr.ngModel, startingTag($element)); |
|---|
| 20064 | 20374 | } |
|---|
| .. | .. |
|---|
| 20091 | 20401 | * @name ngModel.NgModelController#$isEmpty |
|---|
| 20092 | 20402 | * |
|---|
| 20093 | 20403 | * @description |
|---|
| 20094 | | - * This is called when we need to determine if the value of the input is empty. |
|---|
| 20404 | + * This is called when we need to determine if the value of an input is empty. |
|---|
| 20095 | 20405 | * |
|---|
| 20096 | 20406 | * For instance, the required directive does this to work out if the input has data or not. |
|---|
| 20407 | + * |
|---|
| 20097 | 20408 | * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. |
|---|
| 20098 | 20409 | * |
|---|
| 20099 | 20410 | * You can override this for input directives whose concept of being empty is different to the |
|---|
| 20100 | 20411 | * default. The `checkboxInputType` directive does this because in its case a value of `false` |
|---|
| 20101 | 20412 | * implies empty. |
|---|
| 20102 | 20413 | * |
|---|
| 20103 | | - * @param {*} value Model value to check. |
|---|
| 20104 | | - * @returns {boolean} True if `value` is empty. |
|---|
| 20414 | + * @param {*} value The value of the input to check for emptiness. |
|---|
| 20415 | + * @returns {boolean} True if `value` is "empty". |
|---|
| 20105 | 20416 | */ |
|---|
| 20106 | 20417 | this.$isEmpty = function(value) { |
|---|
| 20107 | 20418 | return isUndefined(value) || value === '' || value === null || value !== value; |
|---|
| .. | .. |
|---|
| 20115 | 20426 | * @name ngModel.NgModelController#$setValidity |
|---|
| 20116 | 20427 | * |
|---|
| 20117 | 20428 | * @description |
|---|
| 20118 | | - * Change the validity state, and notifies the form. |
|---|
| 20429 | + * Change the validity state, and notify the form. |
|---|
| 20119 | 20430 | * |
|---|
| 20120 | | - * This method can be called within $parsers/$formatters. However, if possible, please use the |
|---|
| 20121 | | - * `ngModel.$validators` pipeline which is designed to call this method automatically. |
|---|
| 20431 | + * This method can be called within $parsers/$formatters or a custom validation implementation. |
|---|
| 20432 | + * However, in most cases it should be sufficient to use the `ngModel.$validators` and |
|---|
| 20433 | + * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. |
|---|
| 20122 | 20434 | * |
|---|
| 20123 | | - * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign |
|---|
| 20124 | | - * to `$error[validationErrorKey]` and `$pending[validationErrorKey]` |
|---|
| 20125 | | - * so that it is available for data-binding. |
|---|
| 20435 | + * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned |
|---|
| 20436 | + * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` |
|---|
| 20437 | + * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. |
|---|
| 20126 | 20438 | * The `validationErrorKey` should be in camelCase and will get converted into dash-case |
|---|
| 20127 | 20439 | * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` |
|---|
| 20128 | 20440 | * class and can be bound to as `{{someForm.someControl.$error.myError}}` . |
|---|
| 20129 | 20441 | * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), |
|---|
| 20130 | | - * or skipped (null). |
|---|
| 20442 | + * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. |
|---|
| 20443 | + * Skipped is used by Angular when validators do not run because of parse errors and |
|---|
| 20444 | + * when `$asyncValidators` do not run because any of the `$validators` failed. |
|---|
| 20131 | 20445 | */ |
|---|
| 20132 | 20446 | addSetValidityMethod({ |
|---|
| 20133 | 20447 | ctrl: this, |
|---|
| .. | .. |
|---|
| 20149 | 20463 | * @description |
|---|
| 20150 | 20464 | * Sets the control to its pristine state. |
|---|
| 20151 | 20465 | * |
|---|
| 20152 | | - * This method can be called to remove the 'ng-dirty' class and set the control to its pristine |
|---|
| 20153 | | - * state (ng-pristine class). A model is considered to be pristine when the model has not been changed |
|---|
| 20154 | | - * from when first compiled within then form. |
|---|
| 20466 | + * This method can be called to remove the `ng-dirty` class and set the control to its pristine |
|---|
| 20467 | + * state (`ng-pristine` class). A model is considered to be pristine when the control |
|---|
| 20468 | + * has not been changed from when first compiled. |
|---|
| 20155 | 20469 | */ |
|---|
| 20156 | | - this.$setPristine = function () { |
|---|
| 20470 | + this.$setPristine = function() { |
|---|
| 20157 | 20471 | ctrl.$dirty = false; |
|---|
| 20158 | 20472 | ctrl.$pristine = true; |
|---|
| 20159 | 20473 | $animate.removeClass($element, DIRTY_CLASS); |
|---|
| 20160 | 20474 | $animate.addClass($element, PRISTINE_CLASS); |
|---|
| 20475 | + }; |
|---|
| 20476 | + |
|---|
| 20477 | + /** |
|---|
| 20478 | + * @ngdoc method |
|---|
| 20479 | + * @name ngModel.NgModelController#$setDirty |
|---|
| 20480 | + * |
|---|
| 20481 | + * @description |
|---|
| 20482 | + * Sets the control to its dirty state. |
|---|
| 20483 | + * |
|---|
| 20484 | + * This method can be called to remove the `ng-pristine` class and set the control to its dirty |
|---|
| 20485 | + * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed |
|---|
| 20486 | + * from when first compiled. |
|---|
| 20487 | + */ |
|---|
| 20488 | + this.$setDirty = function() { |
|---|
| 20489 | + ctrl.$dirty = true; |
|---|
| 20490 | + ctrl.$pristine = false; |
|---|
| 20491 | + $animate.removeClass($element, PRISTINE_CLASS); |
|---|
| 20492 | + $animate.addClass($element, DIRTY_CLASS); |
|---|
| 20493 | + parentForm.$setDirty(); |
|---|
| 20161 | 20494 | }; |
|---|
| 20162 | 20495 | |
|---|
| 20163 | 20496 | /** |
|---|
| .. | .. |
|---|
| 20167 | 20500 | * @description |
|---|
| 20168 | 20501 | * Sets the control to its untouched state. |
|---|
| 20169 | 20502 | * |
|---|
| 20170 | | - * This method can be called to remove the 'ng-touched' class and set the control to its |
|---|
| 20171 | | - * untouched state (ng-untouched class). Upon compilation, a model is set as untouched |
|---|
| 20503 | + * This method can be called to remove the `ng-touched` class and set the control to its |
|---|
| 20504 | + * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched |
|---|
| 20172 | 20505 | * by default, however this function can be used to restore that state if the model has |
|---|
| 20173 | 20506 | * already been touched by the user. |
|---|
| 20174 | 20507 | */ |
|---|
| .. | .. |
|---|
| 20185 | 20518 | * @description |
|---|
| 20186 | 20519 | * Sets the control to its touched state. |
|---|
| 20187 | 20520 | * |
|---|
| 20188 | | - * This method can be called to remove the 'ng-untouched' class and set the control to its |
|---|
| 20189 | | - * touched state (ng-touched class). A model is considered to be touched when the user has |
|---|
| 20190 | | - * first interacted (focussed) on the model input element and then shifted focus away (blurred) |
|---|
| 20191 | | - * from the input element. |
|---|
| 20521 | + * This method can be called to remove the `ng-untouched` class and set the control to its |
|---|
| 20522 | + * touched state (`ng-touched` class). A model is considered to be touched when the user has |
|---|
| 20523 | + * first focused the control element and then shifted focus away from the control (blur event). |
|---|
| 20192 | 20524 | */ |
|---|
| 20193 | 20525 | this.$setTouched = function() { |
|---|
| 20194 | 20526 | ctrl.$touched = true; |
|---|
| .. | .. |
|---|
| 20222 | 20554 | * angular.module('cancel-update-example', []) |
|---|
| 20223 | 20555 | * |
|---|
| 20224 | 20556 | * .controller('CancelUpdateController', ['$scope', function($scope) { |
|---|
| 20225 | | - * $scope.resetWithCancel = function (e) { |
|---|
| 20557 | + * $scope.resetWithCancel = function(e) { |
|---|
| 20226 | 20558 | * if (e.keyCode == 27) { |
|---|
| 20227 | 20559 | * $scope.myForm.myInput1.$rollbackViewValue(); |
|---|
| 20228 | 20560 | * $scope.myValue = ''; |
|---|
| 20229 | 20561 | * } |
|---|
| 20230 | 20562 | * }; |
|---|
| 20231 | | - * $scope.resetWithoutCancel = function (e) { |
|---|
| 20563 | + * $scope.resetWithoutCancel = function(e) { |
|---|
| 20232 | 20564 | * if (e.keyCode == 27) { |
|---|
| 20233 | 20565 | * $scope.myValue = ''; |
|---|
| 20234 | 20566 | * } |
|---|
| .. | .. |
|---|
| 20266 | 20598 | * @name ngModel.NgModelController#$validate |
|---|
| 20267 | 20599 | * |
|---|
| 20268 | 20600 | * @description |
|---|
| 20269 | | - * Runs each of the registered validators (first synchronous validators and then asynchronous validators). |
|---|
| 20601 | + * Runs each of the registered validators (first synchronous validators and then |
|---|
| 20602 | + * asynchronous validators). |
|---|
| 20603 | + * If the validity changes to invalid, the model will be set to `undefined`, |
|---|
| 20604 | + * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. |
|---|
| 20605 | + * If the validity changes to valid, it will set the model to the last available valid |
|---|
| 20606 | + * modelValue, i.e. either the last parsed value or the last value set from the scope. |
|---|
| 20270 | 20607 | */ |
|---|
| 20271 | 20608 | this.$validate = function() { |
|---|
| 20272 | 20609 | // ignore $validate before model is initialized |
|---|
| 20273 | 20610 | if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { |
|---|
| 20274 | 20611 | return; |
|---|
| 20275 | 20612 | } |
|---|
| 20276 | | - this.$$parseAndValidate(); |
|---|
| 20613 | + |
|---|
| 20614 | + var viewValue = ctrl.$$lastCommittedViewValue; |
|---|
| 20615 | + // Note: we use the $$rawModelValue as $modelValue might have been |
|---|
| 20616 | + // set to undefined during a view -> model update that found validation |
|---|
| 20617 | + // errors. We can't parse the view here, since that could change |
|---|
| 20618 | + // the model although neither viewValue nor the model on the scope changed |
|---|
| 20619 | + var modelValue = ctrl.$$rawModelValue; |
|---|
| 20620 | + |
|---|
| 20621 | + // Check if the there's a parse error, so we don't unset it accidentially |
|---|
| 20622 | + var parserName = ctrl.$$parserName || 'parse'; |
|---|
| 20623 | + var parserValid = ctrl.$error[parserName] ? false : undefined; |
|---|
| 20624 | + |
|---|
| 20625 | + var prevValid = ctrl.$valid; |
|---|
| 20626 | + var prevModelValue = ctrl.$modelValue; |
|---|
| 20627 | + |
|---|
| 20628 | + var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; |
|---|
| 20629 | + |
|---|
| 20630 | + ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) { |
|---|
| 20631 | + // If there was no change in validity, don't update the model |
|---|
| 20632 | + // This prevents changing an invalid modelValue to undefined |
|---|
| 20633 | + if (!allowInvalid && prevValid !== allValid) { |
|---|
| 20634 | + // Note: Don't check ctrl.$valid here, as we could have |
|---|
| 20635 | + // external validators (e.g. calculated on the server), |
|---|
| 20636 | + // that just call $setValidity and need the model value |
|---|
| 20637 | + // to calculate their validity. |
|---|
| 20638 | + ctrl.$modelValue = allValid ? modelValue : undefined; |
|---|
| 20639 | + |
|---|
| 20640 | + if (ctrl.$modelValue !== prevModelValue) { |
|---|
| 20641 | + ctrl.$$writeModelToScope(); |
|---|
| 20642 | + } |
|---|
| 20643 | + } |
|---|
| 20644 | + }); |
|---|
| 20645 | + |
|---|
| 20277 | 20646 | }; |
|---|
| 20278 | 20647 | |
|---|
| 20279 | 20648 | this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) { |
|---|
| .. | .. |
|---|
| 20392 | 20761 | |
|---|
| 20393 | 20762 | // change to dirty |
|---|
| 20394 | 20763 | if (ctrl.$pristine) { |
|---|
| 20395 | | - ctrl.$dirty = true; |
|---|
| 20396 | | - ctrl.$pristine = false; |
|---|
| 20397 | | - $animate.removeClass($element, PRISTINE_CLASS); |
|---|
| 20398 | | - $animate.addClass($element, DIRTY_CLASS); |
|---|
| 20399 | | - parentForm.$setDirty(); |
|---|
| 20764 | + this.$setDirty(); |
|---|
| 20400 | 20765 | } |
|---|
| 20401 | 20766 | this.$$parseAndValidate(); |
|---|
| 20402 | 20767 | }; |
|---|
| .. | .. |
|---|
| 20407 | 20772 | var parserValid = isUndefined(modelValue) ? undefined : true; |
|---|
| 20408 | 20773 | |
|---|
| 20409 | 20774 | if (parserValid) { |
|---|
| 20410 | | - for(var i = 0; i < ctrl.$parsers.length; i++) { |
|---|
| 20775 | + for (var i = 0; i < ctrl.$parsers.length; i++) { |
|---|
| 20411 | 20776 | modelValue = ctrl.$parsers[i](modelValue); |
|---|
| 20412 | 20777 | if (isUndefined(modelValue)) { |
|---|
| 20413 | 20778 | parserValid = false; |
|---|
| .. | .. |
|---|
| 20417 | 20782 | } |
|---|
| 20418 | 20783 | if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { |
|---|
| 20419 | 20784 | // ctrl.$modelValue has not been touched yet... |
|---|
| 20420 | | - ctrl.$modelValue = ngModelGet(); |
|---|
| 20785 | + ctrl.$modelValue = ngModelGet($scope); |
|---|
| 20421 | 20786 | } |
|---|
| 20422 | 20787 | var prevModelValue = ctrl.$modelValue; |
|---|
| 20423 | 20788 | var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; |
|---|
| 20789 | + ctrl.$$rawModelValue = modelValue; |
|---|
| 20790 | + |
|---|
| 20424 | 20791 | if (allowInvalid) { |
|---|
| 20425 | 20792 | ctrl.$modelValue = modelValue; |
|---|
| 20426 | 20793 | writeToModelIfNeeded(); |
|---|
| 20427 | 20794 | } |
|---|
| 20428 | | - ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) { |
|---|
| 20795 | + |
|---|
| 20796 | + // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. |
|---|
| 20797 | + // This can happen if e.g. $setViewValue is called from inside a parser |
|---|
| 20798 | + ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { |
|---|
| 20429 | 20799 | if (!allowInvalid) { |
|---|
| 20430 | 20800 | // Note: Don't check ctrl.$valid here, as we could have |
|---|
| 20431 | 20801 | // external validators (e.g. calculated on the server), |
|---|
| .. | .. |
|---|
| 20444 | 20814 | }; |
|---|
| 20445 | 20815 | |
|---|
| 20446 | 20816 | this.$$writeModelToScope = function() { |
|---|
| 20447 | | - ngModelSet(ctrl.$modelValue); |
|---|
| 20817 | + ngModelSet($scope, ctrl.$modelValue); |
|---|
| 20448 | 20818 | forEach(ctrl.$viewChangeListeners, function(listener) { |
|---|
| 20449 | 20819 | try { |
|---|
| 20450 | 20820 | listener(); |
|---|
| 20451 | | - } catch(e) { |
|---|
| 20821 | + } catch (e) { |
|---|
| 20452 | 20822 | $exceptionHandler(e); |
|---|
| 20453 | 20823 | } |
|---|
| 20454 | 20824 | }); |
|---|
| .. | .. |
|---|
| 20540 | 20910 | // ng-change executes in apply phase |
|---|
| 20541 | 20911 | // 4. view should be changed back to 'a' |
|---|
| 20542 | 20912 | $scope.$watch(function ngModelWatch() { |
|---|
| 20543 | | - var modelValue = ngModelGet(); |
|---|
| 20913 | + var modelValue = ngModelGet($scope); |
|---|
| 20544 | 20914 | |
|---|
| 20545 | 20915 | // if scope model value and ngModel value are out of sync |
|---|
| 20546 | 20916 | // TODO(perf): why not move this to the action fn? |
|---|
| 20547 | 20917 | if (modelValue !== ctrl.$modelValue) { |
|---|
| 20548 | | - ctrl.$modelValue = modelValue; |
|---|
| 20918 | + ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; |
|---|
| 20549 | 20919 | |
|---|
| 20550 | 20920 | var formatters = ctrl.$formatters, |
|---|
| 20551 | 20921 | idx = formatters.length; |
|---|
| 20552 | 20922 | |
|---|
| 20553 | 20923 | var viewValue = modelValue; |
|---|
| 20554 | | - while(idx--) { |
|---|
| 20924 | + while (idx--) { |
|---|
| 20555 | 20925 | viewValue = formatters[idx](viewValue); |
|---|
| 20556 | 20926 | } |
|---|
| 20557 | 20927 | if (ctrl.$viewValue !== viewValue) { |
|---|
| .. | .. |
|---|
| 20572 | 20942 | * @name ngModel |
|---|
| 20573 | 20943 | * |
|---|
| 20574 | 20944 | * @element input |
|---|
| 20945 | + * @priority 1 |
|---|
| 20575 | 20946 | * |
|---|
| 20576 | 20947 | * @description |
|---|
| 20577 | 20948 | * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a |
|---|
| .. | .. |
|---|
| 20593 | 20964 | * |
|---|
| 20594 | 20965 | * For best practices on using `ngModel`, see: |
|---|
| 20595 | 20966 | * |
|---|
| 20596 | | - * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] |
|---|
| 20967 | + * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) |
|---|
| 20597 | 20968 | * |
|---|
| 20598 | 20969 | * For basic examples, how to use `ngModel`, see: |
|---|
| 20599 | 20970 | * |
|---|
| .. | .. |
|---|
| 20605 | 20976 | * - {@link input[email] email} |
|---|
| 20606 | 20977 | * - {@link input[url] url} |
|---|
| 20607 | 20978 | * - {@link input[date] date} |
|---|
| 20608 | | - * - {@link input[dateTimeLocal] dateTimeLocal} |
|---|
| 20979 | + * - {@link input[datetime-local] datetime-local} |
|---|
| 20609 | 20980 | * - {@link input[time] time} |
|---|
| 20610 | 20981 | * - {@link input[month] month} |
|---|
| 20611 | 20982 | * - {@link input[week] week} |
|---|
| .. | .. |
|---|
| 20616 | 20987 | * The following CSS classes are added and removed on the associated input/select/textarea element |
|---|
| 20617 | 20988 | * depending on the validity of the model. |
|---|
| 20618 | 20989 | * |
|---|
| 20619 | | - * - `ng-valid` is set if the model is valid. |
|---|
| 20620 | | - * - `ng-invalid` is set if the model is invalid. |
|---|
| 20621 | | - * - `ng-pristine` is set if the model is pristine. |
|---|
| 20622 | | - * - `ng-dirty` is set if the model is dirty. |
|---|
| 20990 | + * - `ng-valid`: the model is valid |
|---|
| 20991 | + * - `ng-invalid`: the model is invalid |
|---|
| 20992 | + * - `ng-valid-[key]`: for each valid key added by `$setValidity` |
|---|
| 20993 | + * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` |
|---|
| 20994 | + * - `ng-pristine`: the control hasn't been interacted with yet |
|---|
| 20995 | + * - `ng-dirty`: the control has been interacted with |
|---|
| 20996 | + * - `ng-touched`: the control has been blurred |
|---|
| 20997 | + * - `ng-untouched`: the control hasn't been blurred |
|---|
| 20998 | + * - `ng-pending`: any `$asyncValidators` are unfulfilled |
|---|
| 20623 | 20999 | * |
|---|
| 20624 | 21000 | * Keep in mind that ngAnimate can detect each of these classes when added and removed. |
|---|
| 20625 | 21001 | * |
|---|
| .. | .. |
|---|
| 20713 | 21089 | .controller('ExampleController', ['$scope', function($scope) { |
|---|
| 20714 | 21090 | var _name = 'Brian'; |
|---|
| 20715 | 21091 | $scope.user = { |
|---|
| 20716 | | - name: function (newName) { |
|---|
| 21092 | + name: function(newName) { |
|---|
| 20717 | 21093 | if (angular.isDefined(newName)) { |
|---|
| 20718 | 21094 | _name = newName; |
|---|
| 20719 | 21095 | } |
|---|
| .. | .. |
|---|
| 20724 | 21100 | </file> |
|---|
| 20725 | 21101 | * </example> |
|---|
| 20726 | 21102 | */ |
|---|
| 20727 | | -var ngModelDirective = function() { |
|---|
| 21103 | +var ngModelDirective = ['$rootScope', function($rootScope) { |
|---|
| 20728 | 21104 | return { |
|---|
| 20729 | 21105 | restrict: 'A', |
|---|
| 20730 | 21106 | require: ['ngModel', '^?form', '^?ngModelOptions'], |
|---|
| .. | .. |
|---|
| 20768 | 21144 | element.on('blur', function(ev) { |
|---|
| 20769 | 21145 | if (modelCtrl.$touched) return; |
|---|
| 20770 | 21146 | |
|---|
| 20771 | | - scope.$apply(function() { |
|---|
| 20772 | | - modelCtrl.$setTouched(); |
|---|
| 20773 | | - }); |
|---|
| 21147 | + if ($rootScope.$$phase) { |
|---|
| 21148 | + scope.$evalAsync(modelCtrl.$setTouched); |
|---|
| 21149 | + } else { |
|---|
| 21150 | + scope.$apply(modelCtrl.$setTouched); |
|---|
| 21151 | + } |
|---|
| 20774 | 21152 | }); |
|---|
| 20775 | 21153 | } |
|---|
| 20776 | 21154 | }; |
|---|
| 20777 | 21155 | } |
|---|
| 20778 | 21156 | }; |
|---|
| 20779 | | -}; |
|---|
| 21157 | +}]; |
|---|
| 20780 | 21158 | |
|---|
| 20781 | 21159 | |
|---|
| 20782 | 21160 | /** |
|---|
| .. | .. |
|---|
| 20865 | 21243 | if (!ctrl) return; |
|---|
| 20866 | 21244 | attr.required = true; // force truthy in case we are on non input element |
|---|
| 20867 | 21245 | |
|---|
| 20868 | | - ctrl.$validators.required = function(value) { |
|---|
| 20869 | | - return !attr.required || !ctrl.$isEmpty(value); |
|---|
| 21246 | + ctrl.$validators.required = function(modelValue, viewValue) { |
|---|
| 21247 | + return !attr.required || !ctrl.$isEmpty(viewValue); |
|---|
| 20870 | 21248 | }; |
|---|
| 20871 | 21249 | |
|---|
| 20872 | 21250 | attr.$observe('required', function() { |
|---|
| .. | .. |
|---|
| 20887 | 21265 | var regexp, patternExp = attr.ngPattern || attr.pattern; |
|---|
| 20888 | 21266 | attr.$observe('pattern', function(regex) { |
|---|
| 20889 | 21267 | if (isString(regex) && regex.length > 0) { |
|---|
| 20890 | | - regex = new RegExp(regex); |
|---|
| 21268 | + regex = new RegExp('^' + regex + '$'); |
|---|
| 20891 | 21269 | } |
|---|
| 20892 | 21270 | |
|---|
| 20893 | 21271 | if (regex && !regex.test) { |
|---|
| .. | .. |
|---|
| 20915 | 21293 | link: function(scope, elm, attr, ctrl) { |
|---|
| 20916 | 21294 | if (!ctrl) return; |
|---|
| 20917 | 21295 | |
|---|
| 20918 | | - var maxlength = 0; |
|---|
| 21296 | + var maxlength = -1; |
|---|
| 20919 | 21297 | attr.$observe('maxlength', function(value) { |
|---|
| 20920 | | - maxlength = int(value) || 0; |
|---|
| 21298 | + var intVal = int(value); |
|---|
| 21299 | + maxlength = isNaN(intVal) ? -1 : intVal; |
|---|
| 20921 | 21300 | ctrl.$validate(); |
|---|
| 20922 | 21301 | }); |
|---|
| 20923 | 21302 | ctrl.$validators.maxlength = function(modelValue, viewValue) { |
|---|
| 20924 | | - return ctrl.$isEmpty(modelValue) || viewValue.length <= maxlength; |
|---|
| 21303 | + return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength); |
|---|
| 20925 | 21304 | }; |
|---|
| 20926 | 21305 | } |
|---|
| 20927 | 21306 | }; |
|---|
| .. | .. |
|---|
| 20940 | 21319 | ctrl.$validate(); |
|---|
| 20941 | 21320 | }); |
|---|
| 20942 | 21321 | ctrl.$validators.minlength = function(modelValue, viewValue) { |
|---|
| 20943 | | - return ctrl.$isEmpty(modelValue) || viewValue.length >= minlength; |
|---|
| 21322 | + return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; |
|---|
| 20944 | 21323 | }; |
|---|
| 20945 | 21324 | } |
|---|
| 20946 | 21325 | }; |
|---|
| .. | .. |
|---|
| 21080 | 21459 | * @name ngValue |
|---|
| 21081 | 21460 | * |
|---|
| 21082 | 21461 | * @description |
|---|
| 21083 | | - * Binds the given expression to the value of `input[select]` or `input[radio]`, so |
|---|
| 21084 | | - * that when the element is selected, the `ngModel` of that element is set to the |
|---|
| 21085 | | - * bound value. |
|---|
| 21462 | + * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, |
|---|
| 21463 | + * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to |
|---|
| 21464 | + * the bound value. |
|---|
| 21086 | 21465 | * |
|---|
| 21087 | | - * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as |
|---|
| 21088 | | - * shown below. |
|---|
| 21466 | + * `ngValue` is useful when dynamically generating lists of radio buttons using |
|---|
| 21467 | + * {@link ngRepeat `ngRepeat`}, as shown below. |
|---|
| 21468 | + * |
|---|
| 21469 | + * Likewise, `ngValue` can be used to generate `<option>` elements for |
|---|
| 21470 | + * the {@link select `select`} element. In that case however, only strings are supported |
|---|
| 21471 | + * for the `value `attribute, so the resulting `ngModel` will always be a string. |
|---|
| 21472 | + * Support for `select` models with non-string values is available via `ngOptions`. |
|---|
| 21089 | 21473 | * |
|---|
| 21090 | 21474 | * @element input |
|---|
| 21091 | 21475 | * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute |
|---|
| .. | .. |
|---|
| 21173 | 21557 | * `ngModelOptions` has an effect on the element it's declared on and its descendants. |
|---|
| 21174 | 21558 | * |
|---|
| 21175 | 21559 | * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: |
|---|
| 21176 | | - * - `updateOn`: string specifying which event should be the input bound to. You can set several |
|---|
| 21560 | + * - `updateOn`: string specifying which event should the input be bound to. You can set several |
|---|
| 21177 | 21561 | * events using an space delimited list. There is a special event called `default` that |
|---|
| 21178 | 21562 | * matches the default events belonging of the control. |
|---|
| 21179 | 21563 | * - `debounce`: integer value which contains the debounce model update value in milliseconds. A |
|---|
| .. | .. |
|---|
| 21215 | 21599 | .controller('ExampleController', ['$scope', function($scope) { |
|---|
| 21216 | 21600 | $scope.user = { name: 'say', data: '' }; |
|---|
| 21217 | 21601 | |
|---|
| 21218 | | - $scope.cancel = function (e) { |
|---|
| 21602 | + $scope.cancel = function(e) { |
|---|
| 21219 | 21603 | if (e.keyCode == 27) { |
|---|
| 21220 | 21604 | $scope.userForm.userName.$rollbackViewValue(); |
|---|
| 21221 | 21605 | } |
|---|
| .. | .. |
|---|
| 21289 | 21673 | .controller('ExampleController', ['$scope', function($scope) { |
|---|
| 21290 | 21674 | var _name = 'Brian'; |
|---|
| 21291 | 21675 | $scope.user = { |
|---|
| 21292 | | - name: function (newName) { |
|---|
| 21676 | + name: function(newName) { |
|---|
| 21293 | 21677 | return angular.isDefined(newName) ? (_name = newName) : _name; |
|---|
| 21294 | 21678 | } |
|---|
| 21295 | 21679 | }; |
|---|
| .. | .. |
|---|
| 21512 | 21896 | <file name="index.html"> |
|---|
| 21513 | 21897 | <script> |
|---|
| 21514 | 21898 | angular.module('bindExample', []) |
|---|
| 21515 | | - .controller('ExampleController', ['$scope', function ($scope) { |
|---|
| 21899 | + .controller('ExampleController', ['$scope', function($scope) { |
|---|
| 21516 | 21900 | $scope.salutation = 'Hello'; |
|---|
| 21517 | 21901 | $scope.name = 'World'; |
|---|
| 21518 | 21902 | }]); |
|---|
| .. | .. |
|---|
| 21563 | 21947 | * @name ngBindHtml |
|---|
| 21564 | 21948 | * |
|---|
| 21565 | 21949 | * @description |
|---|
| 21566 | | - * Creates a binding that will innerHTML the result of evaluating the `expression` into the current |
|---|
| 21567 | | - * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link |
|---|
| 21568 | | - * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` |
|---|
| 21569 | | - * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in |
|---|
| 21570 | | - * core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to |
|---|
| 21571 | | - * include "angular-sanitize.js" in your application. |
|---|
| 21950 | + * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, |
|---|
| 21951 | + * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. |
|---|
| 21952 | + * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link |
|---|
| 21953 | + * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} |
|---|
| 21954 | + * in your module's dependencies, you need to include "angular-sanitize.js" in your application. |
|---|
| 21572 | 21955 | * |
|---|
| 21573 | 21956 | * You may also bypass sanitization for values you know are safe. To do so, bind to |
|---|
| 21574 | 21957 | * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example |
|---|
| .. | .. |
|---|
| 21667 | 22050 | attr.$removeClass(newClasses); |
|---|
| 21668 | 22051 | } |
|---|
| 21669 | 22052 | |
|---|
| 21670 | | - function digestClassCounts (classes, count) { |
|---|
| 22053 | + function digestClassCounts(classes, count) { |
|---|
| 21671 | 22054 | var classCounts = element.data('$classCounts') || {}; |
|---|
| 21672 | 22055 | var classesToUpdate = []; |
|---|
| 21673 | | - forEach(classes, function (className) { |
|---|
| 22056 | + forEach(classes, function(className) { |
|---|
| 21674 | 22057 | if (count > 0 || classCounts[className]) { |
|---|
| 21675 | 22058 | classCounts[className] = (classCounts[className] || 0) + count; |
|---|
| 21676 | 22059 | if (classCounts[className] === +(count > 0)) { |
|---|
| .. | .. |
|---|
| 21682 | 22065 | return classesToUpdate.join(' '); |
|---|
| 21683 | 22066 | } |
|---|
| 21684 | 22067 | |
|---|
| 21685 | | - function updateClasses (oldClasses, newClasses) { |
|---|
| 22068 | + function updateClasses(oldClasses, newClasses) { |
|---|
| 21686 | 22069 | var toAdd = arrayDifference(newClasses, oldClasses); |
|---|
| 21687 | 22070 | var toRemove = arrayDifference(oldClasses, newClasses); |
|---|
| 21688 | 22071 | toAdd = digestClassCounts(toAdd, 1); |
|---|
| .. | .. |
|---|
| 21714 | 22097 | var values = []; |
|---|
| 21715 | 22098 | |
|---|
| 21716 | 22099 | outer: |
|---|
| 21717 | | - for(var i = 0; i < tokens1.length; i++) { |
|---|
| 22100 | + for (var i = 0; i < tokens1.length; i++) { |
|---|
| 21718 | 22101 | var token = tokens1[i]; |
|---|
| 21719 | | - for(var j = 0; j < tokens2.length; j++) { |
|---|
| 21720 | | - if(token == tokens2[j]) continue outer; |
|---|
| 22102 | + for (var j = 0; j < tokens2.length; j++) { |
|---|
| 22103 | + if (token == tokens2[j]) continue outer; |
|---|
| 21721 | 22104 | } |
|---|
| 21722 | 22105 | values.push(token); |
|---|
| 21723 | 22106 | } |
|---|
| 21724 | 22107 | return values; |
|---|
| 21725 | 22108 | } |
|---|
| 21726 | 22109 | |
|---|
| 21727 | | - function arrayClasses (classVal) { |
|---|
| 22110 | + function arrayClasses(classVal) { |
|---|
| 21728 | 22111 | if (isArray(classVal)) { |
|---|
| 21729 | 22112 | return classVal; |
|---|
| 21730 | 22113 | } else if (isString(classVal)) { |
|---|
| 21731 | 22114 | return classVal.split(' '); |
|---|
| 21732 | 22115 | } else if (isObject(classVal)) { |
|---|
| 21733 | | - var classes = [], i = 0; |
|---|
| 22116 | + var classes = []; |
|---|
| 21734 | 22117 | forEach(classVal, function(v, k) { |
|---|
| 21735 | 22118 | if (v) { |
|---|
| 21736 | 22119 | classes = classes.concat(k.split(' ')); |
|---|
| .. | .. |
|---|
| 22489 | 22872 | </example> |
|---|
| 22490 | 22873 | */ |
|---|
| 22491 | 22874 | /* |
|---|
| 22492 | | - * A directive that allows creation of custom onclick handlers that are defined as angular |
|---|
| 22493 | | - * expressions and are compiled and executed within the current scope. |
|---|
| 22494 | | - * |
|---|
| 22495 | | - * Events that are handled via these handler are always configured not to propagate further. |
|---|
| 22875 | + * A collection of directives that allows creation of custom event handlers that are defined as |
|---|
| 22876 | + * angular expressions and are compiled and executed within the current scope. |
|---|
| 22496 | 22877 | */ |
|---|
| 22497 | 22878 | var ngEventDirectives = {}; |
|---|
| 22498 | 22879 | |
|---|
| .. | .. |
|---|
| 22511 | 22892 | return { |
|---|
| 22512 | 22893 | restrict: 'A', |
|---|
| 22513 | 22894 | compile: function($element, attr) { |
|---|
| 22514 | | - var fn = $parse(attr[directiveName]); |
|---|
| 22895 | + // We expose the powerful $event object on the scope that provides access to the Window, |
|---|
| 22896 | + // etc. that isn't protected by the fast paths in $parse. We explicitly request better |
|---|
| 22897 | + // checks at the cost of speed since event handler expressions are not executed as |
|---|
| 22898 | + // frequently as regular change detection. |
|---|
| 22899 | + var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); |
|---|
| 22515 | 22900 | return function ngEventHandler(scope, element) { |
|---|
| 22516 | 22901 | element.on(eventName, function(event) { |
|---|
| 22517 | 22902 | var callback = function() { |
|---|
| .. | .. |
|---|
| 23022 | 23407 | terminal: true, |
|---|
| 23023 | 23408 | restrict: 'A', |
|---|
| 23024 | 23409 | $$tlb: true, |
|---|
| 23025 | | - link: function ($scope, $element, $attr, ctrl, $transclude) { |
|---|
| 23410 | + link: function($scope, $element, $attr, ctrl, $transclude) { |
|---|
| 23026 | 23411 | var block, childScope, previousElements; |
|---|
| 23027 | 23412 | $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { |
|---|
| 23028 | 23413 | |
|---|
| 23029 | 23414 | if (value) { |
|---|
| 23030 | 23415 | if (!childScope) { |
|---|
| 23031 | | - $transclude(function (clone, newScope) { |
|---|
| 23416 | + $transclude(function(clone, newScope) { |
|---|
| 23032 | 23417 | childScope = newScope; |
|---|
| 23033 | 23418 | clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); |
|---|
| 23034 | 23419 | // Note: We only need the first/last node of the cloned nodes. |
|---|
| .. | .. |
|---|
| 23260 | 23645 | currentElement; |
|---|
| 23261 | 23646 | |
|---|
| 23262 | 23647 | var cleanupLastIncludeContent = function() { |
|---|
| 23263 | | - if(previousElement) { |
|---|
| 23648 | + if (previousElement) { |
|---|
| 23264 | 23649 | previousElement.remove(); |
|---|
| 23265 | 23650 | previousElement = null; |
|---|
| 23266 | 23651 | } |
|---|
| 23267 | | - if(currentScope) { |
|---|
| 23652 | + if (currentScope) { |
|---|
| 23268 | 23653 | currentScope.$destroy(); |
|---|
| 23269 | 23654 | currentScope = null; |
|---|
| 23270 | 23655 | } |
|---|
| 23271 | | - if(currentElement) { |
|---|
| 23656 | + if (currentElement) { |
|---|
| 23272 | 23657 | $animate.leave(currentElement).then(function() { |
|---|
| 23273 | 23658 | previousElement = null; |
|---|
| 23274 | 23659 | }); |
|---|
| .. | .. |
|---|
| 23346 | 23731 | $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, |
|---|
| 23347 | 23732 | function namespaceAdaptedClone(clone) { |
|---|
| 23348 | 23733 | $element.append(clone); |
|---|
| 23349 | | - }, undefined, undefined, $element); |
|---|
| 23734 | + }, {futureParentElement: $element}); |
|---|
| 23350 | 23735 | return; |
|---|
| 23351 | 23736 | } |
|---|
| 23352 | 23737 | |
|---|
| .. | .. |
|---|
| 23630 | 24015 | </example> |
|---|
| 23631 | 24016 | */ |
|---|
| 23632 | 24017 | var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { |
|---|
| 23633 | | - var BRACE = /{}/g; |
|---|
| 24018 | + var BRACE = /{}/g, |
|---|
| 24019 | + IS_WHEN = /^when(Minus)?(.+)$/; |
|---|
| 24020 | + |
|---|
| 23634 | 24021 | return { |
|---|
| 23635 | 24022 | restrict: 'EA', |
|---|
| 23636 | 24023 | link: function(scope, element, attr) { |
|---|
| .. | .. |
|---|
| 23641 | 24028 | whensExpFns = {}, |
|---|
| 23642 | 24029 | startSymbol = $interpolate.startSymbol(), |
|---|
| 23643 | 24030 | endSymbol = $interpolate.endSymbol(), |
|---|
| 23644 | | - isWhen = /^when(Minus)?(.+)$/; |
|---|
| 24031 | + braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, |
|---|
| 24032 | + watchRemover = angular.noop, |
|---|
| 24033 | + lastCount; |
|---|
| 23645 | 24034 | |
|---|
| 23646 | 24035 | forEach(attr, function(expression, attributeName) { |
|---|
| 23647 | | - if (isWhen.test(attributeName)) { |
|---|
| 23648 | | - whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = |
|---|
| 23649 | | - element.attr(attr.$attr[attributeName]); |
|---|
| 24036 | + var tmpMatch = IS_WHEN.exec(attributeName); |
|---|
| 24037 | + if (tmpMatch) { |
|---|
| 24038 | + var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); |
|---|
| 24039 | + whens[whenKey] = element.attr(attr.$attr[attributeName]); |
|---|
| 23650 | 24040 | } |
|---|
| 23651 | 24041 | }); |
|---|
| 23652 | 24042 | forEach(whens, function(expression, key) { |
|---|
| 23653 | | - whensExpFns[key] = |
|---|
| 23654 | | - $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + |
|---|
| 23655 | | - offset + endSymbol)); |
|---|
| 24043 | + whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); |
|---|
| 24044 | + |
|---|
| 23656 | 24045 | }); |
|---|
| 23657 | 24046 | |
|---|
| 23658 | | - scope.$watch(function ngPluralizeWatch() { |
|---|
| 23659 | | - var value = parseFloat(scope.$eval(numberExp)); |
|---|
| 24047 | + scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { |
|---|
| 24048 | + var count = parseFloat(newVal); |
|---|
| 24049 | + var countIsNaN = isNaN(count); |
|---|
| 23660 | 24050 | |
|---|
| 23661 | | - if (!isNaN(value)) { |
|---|
| 23662 | | - //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, |
|---|
| 23663 | | - //check it against pluralization rules in $locale service |
|---|
| 23664 | | - if (!(value in whens)) value = $locale.pluralCat(value - offset); |
|---|
| 23665 | | - return whensExpFns[value](scope); |
|---|
| 23666 | | - } else { |
|---|
| 23667 | | - return ''; |
|---|
| 24051 | + if (!countIsNaN && !(count in whens)) { |
|---|
| 24052 | + // If an explicit number rule such as 1, 2, 3... is defined, just use it. |
|---|
| 24053 | + // Otherwise, check it against pluralization rules in $locale service. |
|---|
| 24054 | + count = $locale.pluralCat(count - offset); |
|---|
| 23668 | 24055 | } |
|---|
| 23669 | | - }, function ngPluralizeWatchAction(newVal) { |
|---|
| 23670 | | - element.text(newVal); |
|---|
| 24056 | + |
|---|
| 24057 | + // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. |
|---|
| 24058 | + // In JS `NaN !== NaN`, so we have to exlicitly check. |
|---|
| 24059 | + if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) { |
|---|
| 24060 | + watchRemover(); |
|---|
| 24061 | + watchRemover = scope.$watch(whensExpFns[count], updateElementText); |
|---|
| 24062 | + lastCount = count; |
|---|
| 24063 | + } |
|---|
| 23671 | 24064 | }); |
|---|
| 24065 | + |
|---|
| 24066 | + function updateElementText(newText) { |
|---|
| 24067 | + element.text(newText || ''); |
|---|
| 24068 | + } |
|---|
| 23672 | 24069 | } |
|---|
| 23673 | 24070 | }; |
|---|
| 23674 | 24071 | }]; |
|---|
| .. | .. |
|---|
| 23951 | 24348 | if (trackByExp) { |
|---|
| 23952 | 24349 | trackByExpGetter = $parse(trackByExp); |
|---|
| 23953 | 24350 | } else { |
|---|
| 23954 | | - trackByIdArrayFn = function (key, value) { |
|---|
| 24351 | + trackByIdArrayFn = function(key, value) { |
|---|
| 23955 | 24352 | return hashKey(value); |
|---|
| 23956 | 24353 | }; |
|---|
| 23957 | | - trackByIdObjFn = function (key) { |
|---|
| 24354 | + trackByIdObjFn = function(key) { |
|---|
| 23958 | 24355 | return key; |
|---|
| 23959 | 24356 | }; |
|---|
| 23960 | 24357 | } |
|---|
| .. | .. |
|---|
| 24034 | 24431 | nextBlockOrder[index] = block; |
|---|
| 24035 | 24432 | } else if (nextBlockMap[trackById]) { |
|---|
| 24036 | 24433 | // if collision detected. restore lastBlockMap and throw an error |
|---|
| 24037 | | - forEach(nextBlockOrder, function (block) { |
|---|
| 24434 | + forEach(nextBlockOrder, function(block) { |
|---|
| 24038 | 24435 | if (block && block.scope) lastBlockMap[block.id] = block; |
|---|
| 24039 | 24436 | }); |
|---|
| 24040 | 24437 | throw ngRepeatMinErr('dupes', |
|---|
| 24041 | 24438 | "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", |
|---|
| 24042 | | - expression, trackById, toJson(value)); |
|---|
| 24439 | + expression, trackById, value); |
|---|
| 24043 | 24440 | } else { |
|---|
| 24044 | 24441 | // new never before seen block |
|---|
| 24045 | 24442 | nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; |
|---|
| .. | .. |
|---|
| 24150 | 24547 | * |
|---|
| 24151 | 24548 | * ### Overriding `.ng-hide` |
|---|
| 24152 | 24549 | * |
|---|
| 24153 | | - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change |
|---|
| 24550 | + * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change |
|---|
| 24154 | 24551 | * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` |
|---|
| 24155 | 24552 | * class in CSS: |
|---|
| 24156 | 24553 | * |
|---|
| 24157 | 24554 | * ```css |
|---|
| 24158 | 24555 | * .ng-hide { |
|---|
| 24159 | 24556 | * /* this is just another form of hiding an element */ |
|---|
| 24160 | | - * display:block!important; |
|---|
| 24161 | | - * position:absolute; |
|---|
| 24162 | | - * top:-9999px; |
|---|
| 24163 | | - * left:-9999px; |
|---|
| 24557 | + * display: block!important; |
|---|
| 24558 | + * position: absolute; |
|---|
| 24559 | + * top: -9999px; |
|---|
| 24560 | + * left: -9999px; |
|---|
| 24164 | 24561 | * } |
|---|
| 24165 | 24562 | * ``` |
|---|
| 24166 | 24563 | * |
|---|
| .. | .. |
|---|
| 24180 | 24577 | * .my-element.ng-hide-add, .my-element.ng-hide-remove { |
|---|
| 24181 | 24578 | * /* this is required as of 1.3x to properly |
|---|
| 24182 | 24579 | * apply all styling in a show/hide animation */ |
|---|
| 24183 | | - * transition:0s linear all; |
|---|
| 24580 | + * transition: 0s linear all; |
|---|
| 24184 | 24581 | * } |
|---|
| 24185 | 24582 | * |
|---|
| 24186 | 24583 | * .my-element.ng-hide-add-active, |
|---|
| 24187 | 24584 | * .my-element.ng-hide-remove-active { |
|---|
| 24188 | 24585 | * /* the transition is defined in the active class */ |
|---|
| 24189 | | - * transition:1s linear all; |
|---|
| 24586 | + * transition: 1s linear all; |
|---|
| 24190 | 24587 | * } |
|---|
| 24191 | 24588 | * |
|---|
| 24192 | 24589 | * .my-element.ng-hide-add { ... } |
|---|
| .. | .. |
|---|
| 24224 | 24621 | </div> |
|---|
| 24225 | 24622 | </file> |
|---|
| 24226 | 24623 | <file name="glyphicons.css"> |
|---|
| 24227 | | - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); |
|---|
| 24624 | + @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); |
|---|
| 24228 | 24625 | </file> |
|---|
| 24229 | 24626 | <file name="animations.css"> |
|---|
| 24230 | 24627 | .animate-show { |
|---|
| 24231 | | - line-height:20px; |
|---|
| 24232 | | - opacity:1; |
|---|
| 24233 | | - padding:10px; |
|---|
| 24234 | | - border:1px solid black; |
|---|
| 24235 | | - background:white; |
|---|
| 24628 | + line-height: 20px; |
|---|
| 24629 | + opacity: 1; |
|---|
| 24630 | + padding: 10px; |
|---|
| 24631 | + border: 1px solid black; |
|---|
| 24632 | + background: white; |
|---|
| 24236 | 24633 | } |
|---|
| 24237 | 24634 | |
|---|
| 24238 | 24635 | .animate-show.ng-hide-add.ng-hide-add-active, |
|---|
| 24239 | 24636 | .animate-show.ng-hide-remove.ng-hide-remove-active { |
|---|
| 24240 | | - -webkit-transition:all linear 0.5s; |
|---|
| 24241 | | - transition:all linear 0.5s; |
|---|
| 24637 | + -webkit-transition: all linear 0.5s; |
|---|
| 24638 | + transition: all linear 0.5s; |
|---|
| 24242 | 24639 | } |
|---|
| 24243 | 24640 | |
|---|
| 24244 | 24641 | .animate-show.ng-hide { |
|---|
| 24245 | | - line-height:0; |
|---|
| 24246 | | - opacity:0; |
|---|
| 24247 | | - padding:0 10px; |
|---|
| 24642 | + line-height: 0; |
|---|
| 24643 | + opacity: 0; |
|---|
| 24644 | + padding: 0 10px; |
|---|
| 24248 | 24645 | } |
|---|
| 24249 | 24646 | |
|---|
| 24250 | 24647 | .check-element { |
|---|
| 24251 | | - padding:10px; |
|---|
| 24252 | | - border:1px solid black; |
|---|
| 24253 | | - background:white; |
|---|
| 24648 | + padding: 10px; |
|---|
| 24649 | + border: 1px solid black; |
|---|
| 24650 | + background: white; |
|---|
| 24254 | 24651 | } |
|---|
| 24255 | 24652 | </file> |
|---|
| 24256 | 24653 | <file name="protractor.js" type="protractor"> |
|---|
| .. | .. |
|---|
| 24274 | 24671 | restrict: 'A', |
|---|
| 24275 | 24672 | multiElement: true, |
|---|
| 24276 | 24673 | link: function(scope, element, attr) { |
|---|
| 24277 | | - scope.$watch(attr.ngShow, function ngShowWatchAction(value){ |
|---|
| 24674 | + scope.$watch(attr.ngShow, function ngShowWatchAction(value) { |
|---|
| 24278 | 24675 | // we're adding a temporary, animation-specific class for ng-hide since this way |
|---|
| 24279 | 24676 | // we can control when the element is actually displayed on screen without having |
|---|
| 24280 | 24677 | // to have a global/greedy CSS selector that breaks when other animations are run. |
|---|
| 24281 | 24678 | // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 |
|---|
| 24282 | 24679 | $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { |
|---|
| 24283 | | - tempClasses : NG_HIDE_IN_PROGRESS_CLASS |
|---|
| 24680 | + tempClasses: NG_HIDE_IN_PROGRESS_CLASS |
|---|
| 24284 | 24681 | }); |
|---|
| 24285 | 24682 | }); |
|---|
| 24286 | 24683 | } |
|---|
| .. | .. |
|---|
| 24324 | 24721 | * |
|---|
| 24325 | 24722 | * ### Overriding `.ng-hide` |
|---|
| 24326 | 24723 | * |
|---|
| 24327 | | - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change |
|---|
| 24724 | + * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change |
|---|
| 24328 | 24725 | * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` |
|---|
| 24329 | 24726 | * class in CSS: |
|---|
| 24330 | 24727 | * |
|---|
| 24331 | 24728 | * ```css |
|---|
| 24332 | 24729 | * .ng-hide { |
|---|
| 24333 | 24730 | * /* this is just another form of hiding an element */ |
|---|
| 24334 | | - * display:block!important; |
|---|
| 24335 | | - * position:absolute; |
|---|
| 24336 | | - * top:-9999px; |
|---|
| 24337 | | - * left:-9999px; |
|---|
| 24731 | + * display: block!important; |
|---|
| 24732 | + * position: absolute; |
|---|
| 24733 | + * top: -9999px; |
|---|
| 24734 | + * left: -9999px; |
|---|
| 24338 | 24735 | * } |
|---|
| 24339 | 24736 | * ``` |
|---|
| 24340 | 24737 | * |
|---|
| .. | .. |
|---|
| 24351 | 24748 | * //a working example can be found at the bottom of this page |
|---|
| 24352 | 24749 | * // |
|---|
| 24353 | 24750 | * .my-element.ng-hide-add, .my-element.ng-hide-remove { |
|---|
| 24354 | | - * transition:0.5s linear all; |
|---|
| 24751 | + * transition: 0.5s linear all; |
|---|
| 24355 | 24752 | * } |
|---|
| 24356 | 24753 | * |
|---|
| 24357 | 24754 | * .my-element.ng-hide-add { ... } |
|---|
| .. | .. |
|---|
| 24389 | 24786 | </div> |
|---|
| 24390 | 24787 | </file> |
|---|
| 24391 | 24788 | <file name="glyphicons.css"> |
|---|
| 24392 | | - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); |
|---|
| 24789 | + @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); |
|---|
| 24393 | 24790 | </file> |
|---|
| 24394 | 24791 | <file name="animations.css"> |
|---|
| 24395 | 24792 | .animate-hide { |
|---|
| 24396 | | - -webkit-transition:all linear 0.5s; |
|---|
| 24397 | | - transition:all linear 0.5s; |
|---|
| 24398 | | - line-height:20px; |
|---|
| 24399 | | - opacity:1; |
|---|
| 24400 | | - padding:10px; |
|---|
| 24401 | | - border:1px solid black; |
|---|
| 24402 | | - background:white; |
|---|
| 24793 | + -webkit-transition: all linear 0.5s; |
|---|
| 24794 | + transition: all linear 0.5s; |
|---|
| 24795 | + line-height: 20px; |
|---|
| 24796 | + opacity: 1; |
|---|
| 24797 | + padding: 10px; |
|---|
| 24798 | + border: 1px solid black; |
|---|
| 24799 | + background: white; |
|---|
| 24403 | 24800 | } |
|---|
| 24404 | 24801 | |
|---|
| 24405 | 24802 | .animate-hide.ng-hide { |
|---|
| 24406 | | - line-height:0; |
|---|
| 24407 | | - opacity:0; |
|---|
| 24408 | | - padding:0 10px; |
|---|
| 24803 | + line-height: 0; |
|---|
| 24804 | + opacity: 0; |
|---|
| 24805 | + padding: 0 10px; |
|---|
| 24409 | 24806 | } |
|---|
| 24410 | 24807 | |
|---|
| 24411 | 24808 | .check-element { |
|---|
| 24412 | | - padding:10px; |
|---|
| 24413 | | - border:1px solid black; |
|---|
| 24414 | | - background:white; |
|---|
| 24809 | + padding: 10px; |
|---|
| 24810 | + border: 1px solid black; |
|---|
| 24811 | + background: white; |
|---|
| 24415 | 24812 | } |
|---|
| 24416 | 24813 | </file> |
|---|
| 24417 | 24814 | <file name="protractor.js" type="protractor"> |
|---|
| .. | .. |
|---|
| 24435 | 24832 | restrict: 'A', |
|---|
| 24436 | 24833 | multiElement: true, |
|---|
| 24437 | 24834 | link: function(scope, element, attr) { |
|---|
| 24438 | | - scope.$watch(attr.ngHide, function ngHideWatchAction(value){ |
|---|
| 24835 | + scope.$watch(attr.ngHide, function ngHideWatchAction(value) { |
|---|
| 24439 | 24836 | // The comment inside of the ngShowDirective explains why we add and |
|---|
| 24440 | 24837 | // remove a temporary class for the show/hide animation |
|---|
| 24441 | 24838 | $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { |
|---|
| 24442 | | - tempClasses : NG_HIDE_IN_PROGRESS_CLASS |
|---|
| 24839 | + tempClasses: NG_HIDE_IN_PROGRESS_CLASS |
|---|
| 24443 | 24840 | }); |
|---|
| 24444 | 24841 | }); |
|---|
| 24445 | 24842 | } |
|---|
| .. | .. |
|---|
| 24740 | 25137 | }]); |
|---|
| 24741 | 25138 | </script> |
|---|
| 24742 | 25139 | <div ng-controller="ExampleController"> |
|---|
| 24743 | | - <input ng-model="title"><br> |
|---|
| 25140 | + <input ng-model="title"> <br/> |
|---|
| 24744 | 25141 | <textarea ng-model="text"></textarea> <br/> |
|---|
| 24745 | 25142 | <pane title="{{title}}">{{text}}</pane> |
|---|
| 24746 | 25143 | </div> |
|---|
| .. | .. |
|---|
| 24818 | 25215 | compile: function(element, attr) { |
|---|
| 24819 | 25216 | if (attr.type == 'text/ng-template') { |
|---|
| 24820 | 25217 | var templateUrl = attr.id, |
|---|
| 24821 | | - // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent |
|---|
| 24822 | 25218 | text = element[0].text; |
|---|
| 24823 | 25219 | |
|---|
| 24824 | 25220 | $templateCache.put(templateUrl, text); |
|---|
| .. | .. |
|---|
| 24842 | 25238 | * elements for the `<select>` element using the array or object obtained by evaluating the |
|---|
| 24843 | 25239 | * `ngOptions` comprehension_expression. |
|---|
| 24844 | 25240 | * |
|---|
| 25241 | + * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a |
|---|
| 25242 | + * similar result. However, the `ngOptions` provides some benefits such as reducing memory and |
|---|
| 25243 | + * increasing speed by not creating a new scope for each repeated instance, as well as providing |
|---|
| 25244 | + * more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions` should be |
|---|
| 25245 | + * used when the `select` model needs to be bound to a non-string value. This is because an option |
|---|
| 25246 | + * element can only be bound to string values at present. |
|---|
| 25247 | + * |
|---|
| 24845 | 25248 | * When an item in the `<select>` menu is selected, the array element or object property |
|---|
| 24846 | 25249 | * represented by the selected option will be bound to the model identified by the `ngModel` |
|---|
| 24847 | 25250 | * directive. |
|---|
| 24848 | | - * |
|---|
| 24849 | | - * <div class="alert alert-warning"> |
|---|
| 24850 | | - * **Note:** `ngModel` compares by reference, not value. This is important when binding to an |
|---|
| 24851 | | - * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). |
|---|
| 24852 | | - * </div> |
|---|
| 24853 | 25251 | * |
|---|
| 24854 | 25252 | * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can |
|---|
| 24855 | 25253 | * be nested into the `<select>` element. This element will then represent the `null` or "not selected" |
|---|
| 24856 | 25254 | * option. See example below for demonstration. |
|---|
| 24857 | 25255 | * |
|---|
| 24858 | 25256 | * <div class="alert alert-warning"> |
|---|
| 24859 | | - * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead |
|---|
| 24860 | | - * of {@link ng.directive:ngRepeat ngRepeat} when you want the |
|---|
| 24861 | | - * `select` model to be bound to a non-string value. This is because an option element can only |
|---|
| 24862 | | - * be bound to string values at present. |
|---|
| 25257 | + * **Note:** `ngModel` compares by reference, not value. This is important when binding to an |
|---|
| 25258 | + * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). |
|---|
| 24863 | 25259 | * </div> |
|---|
| 24864 | 25260 | * |
|---|
| 24865 | | - * <div class="alert alert-info"> |
|---|
| 24866 | | - * **Note:** Using `select as` will bind the result of the `select as` expression to the model, but |
|---|
| 25261 | + * ## `select as` |
|---|
| 25262 | + * |
|---|
| 25263 | + * Using `select as` will bind the result of the `select as` expression to the model, but |
|---|
| 24867 | 25264 | * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) |
|---|
| 24868 | | - * or property name (for object data sources) of the value within the collection. |
|---|
| 24869 | | - * </div> |
|---|
| 25265 | + * or property name (for object data sources) of the value within the collection. If a `track by` expression |
|---|
| 25266 | + * is used, the result of that expression will be set as the value of the `option` and `select` elements. |
|---|
| 24870 | 25267 | * |
|---|
| 24871 | | - * **Note:** Using `select as` together with `trackexpr` is not recommended. |
|---|
| 24872 | | - * Reasoning: |
|---|
| 25268 | + * ### `select as` with `track by` |
|---|
| 25269 | + * |
|---|
| 25270 | + * Using `select as` together with `track by` is not recommended. Reasoning: |
|---|
| 25271 | + * |
|---|
| 24873 | 25272 | * - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"> |
|---|
| 24874 | 25273 | * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}], |
|---|
| 24875 | 25274 | * $scope.selected = {name: 'aSubItem'}; |
|---|
| .. | .. |
|---|
| 24893 | 25292 | * * for array data sources: |
|---|
| 24894 | 25293 | * * `label` **`for`** `value` **`in`** `array` |
|---|
| 24895 | 25294 | * * `select` **`as`** `label` **`for`** `value` **`in`** `array` |
|---|
| 24896 | | - * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` |
|---|
| 24897 | | - * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` |
|---|
| 25295 | + * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` |
|---|
| 25296 | + * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` |
|---|
| 25297 | + * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` |
|---|
| 25298 | + * (for including a filter with `track by`) |
|---|
| 24898 | 25299 | * * for object data sources: |
|---|
| 24899 | 25300 | * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` |
|---|
| 24900 | 25301 | * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` |
|---|
| .. | .. |
|---|
| 25036 | 25437 | self.removeOption = function(value) { |
|---|
| 25037 | 25438 | if (this.hasOption(value)) { |
|---|
| 25038 | 25439 | delete optionsMap[value]; |
|---|
| 25039 | | - if (ngModelCtrl.$viewValue == value) { |
|---|
| 25440 | + if (ngModelCtrl.$viewValue === value) { |
|---|
| 25040 | 25441 | this.renderUnknownOption(value); |
|---|
| 25041 | 25442 | } |
|---|
| 25042 | 25443 | } |
|---|
| .. | .. |
|---|
| 25080 | 25481 | unknownOption = optionTemplate.clone(); |
|---|
| 25081 | 25482 | |
|---|
| 25082 | 25483 | // find "null" option |
|---|
| 25083 | | - for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { |
|---|
| 25484 | + for (var i = 0, children = element.children(), ii = children.length; i < ii; i++) { |
|---|
| 25084 | 25485 | if (children[i].value === '') { |
|---|
| 25085 | 25486 | emptyOption = nullOption = children.eq(i); |
|---|
| 25086 | 25487 | break; |
|---|
| .. | .. |
|---|
| 25182 | 25583 | valuesFn = $parse(match[7]), |
|---|
| 25183 | 25584 | track = match[8], |
|---|
| 25184 | 25585 | trackFn = track ? $parse(match[8]) : null, |
|---|
| 25586 | + trackKeysCache = {}, |
|---|
| 25185 | 25587 | // This is an array of array of existing option groups in DOM. |
|---|
| 25186 | 25588 | // We try to reuse these if possible |
|---|
| 25187 | 25589 | // - optionGroupsCache[0] is the options with no option group |
|---|
| .. | .. |
|---|
| 25227 | 25629 | |
|---|
| 25228 | 25630 | function selectionChanged() { |
|---|
| 25229 | 25631 | scope.$apply(function() { |
|---|
| 25230 | | - var optionGroup, |
|---|
| 25231 | | - collection = valuesFn(scope) || [], |
|---|
| 25232 | | - key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; |
|---|
| 25632 | + var collection = valuesFn(scope) || []; |
|---|
| 25233 | 25633 | var viewValue; |
|---|
| 25234 | 25634 | if (multiple) { |
|---|
| 25235 | 25635 | viewValue = []; |
|---|
| 25236 | 25636 | forEach(selectElement.val(), function(selectedKey) { |
|---|
| 25637 | + selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey; |
|---|
| 25237 | 25638 | viewValue.push(getViewValue(selectedKey, collection[selectedKey])); |
|---|
| 25238 | 25639 | }); |
|---|
| 25239 | 25640 | } else { |
|---|
| 25240 | | - var selectedKey = selectElement.val(); |
|---|
| 25641 | + var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val(); |
|---|
| 25241 | 25642 | viewValue = getViewValue(selectedKey, collection[selectedKey]); |
|---|
| 25242 | 25643 | } |
|---|
| 25243 | 25644 | ctrl.$setViewValue(viewValue); |
|---|
| .. | .. |
|---|
| 25307 | 25708 | if (multiple) { |
|---|
| 25308 | 25709 | return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value))); |
|---|
| 25309 | 25710 | } else { |
|---|
| 25310 | | - return viewValue == callExpression(compareValueFn, key, value); |
|---|
| 25711 | + return viewValue === callExpression(compareValueFn, key, value); |
|---|
| 25311 | 25712 | } |
|---|
| 25312 | 25713 | }; |
|---|
| 25313 | 25714 | } |
|---|
| .. | .. |
|---|
| 25359 | 25760 | anySelected = false, |
|---|
| 25360 | 25761 | lastElement, |
|---|
| 25361 | 25762 | element, |
|---|
| 25362 | | - label; |
|---|
| 25763 | + label, |
|---|
| 25764 | + optionId; |
|---|
| 25765 | + |
|---|
| 25766 | + trackKeysCache = {}; |
|---|
| 25363 | 25767 | |
|---|
| 25364 | 25768 | // We now build up the list of options we need (we merge later) |
|---|
| 25365 | 25769 | for (index = 0; length = keys.length, index < length; index++) { |
|---|
| 25366 | 25770 | key = index; |
|---|
| 25367 | 25771 | if (keyName) { |
|---|
| 25368 | 25772 | key = keys[index]; |
|---|
| 25369 | | - if ( key.charAt(0) === '$' ) continue; |
|---|
| 25773 | + if (key.charAt(0) === '$') continue; |
|---|
| 25370 | 25774 | } |
|---|
| 25371 | 25775 | value = values[key]; |
|---|
| 25372 | 25776 | |
|---|
| .. | .. |
|---|
| 25383 | 25787 | |
|---|
| 25384 | 25788 | // doing displayFn(scope, locals) || '' overwrites zero values |
|---|
| 25385 | 25789 | label = isDefined(label) ? label : ''; |
|---|
| 25790 | + optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index); |
|---|
| 25791 | + if (trackFn) { |
|---|
| 25792 | + trackKeysCache[optionId] = key; |
|---|
| 25793 | + } |
|---|
| 25794 | + |
|---|
| 25386 | 25795 | optionGroup.push({ |
|---|
| 25387 | 25796 | // either the index into array or key from object |
|---|
| 25388 | | - id: (keyName ? keys[index] : index), |
|---|
| 25797 | + id: optionId, |
|---|
| 25389 | 25798 | label: label, |
|---|
| 25390 | 25799 | selected: selected // determine if we should be selected |
|---|
| 25391 | 25800 | }); |
|---|
| .. | .. |
|---|
| 25430 | 25839 | } |
|---|
| 25431 | 25840 | |
|---|
| 25432 | 25841 | lastElement = null; // start at the beginning |
|---|
| 25433 | | - for(index = 0, length = optionGroup.length; index < length; index++) { |
|---|
| 25842 | + for (index = 0, length = optionGroup.length; index < length; index++) { |
|---|
| 25434 | 25843 | option = optionGroup[index]; |
|---|
| 25435 | | - if ((existingOption = existingOptions[index+1])) { |
|---|
| 25844 | + if ((existingOption = existingOptions[index + 1])) { |
|---|
| 25436 | 25845 | // reuse elements |
|---|
| 25437 | 25846 | lastElement = existingOption.element; |
|---|
| 25438 | 25847 | if (existingOption.label !== option.label) { |
|---|
| 25439 | 25848 | updateLabelMap(labelMap, existingOption.label, false); |
|---|
| 25440 | 25849 | updateLabelMap(labelMap, option.label, true); |
|---|
| 25441 | 25850 | lastElement.text(existingOption.label = option.label); |
|---|
| 25851 | + lastElement.prop('label', existingOption.label); |
|---|
| 25442 | 25852 | } |
|---|
| 25443 | 25853 | if (existingOption.id !== option.id) { |
|---|
| 25444 | 25854 | lastElement.val(existingOption.id = option.id); |
|---|
| .. | .. |
|---|
| 25468 | 25878 | .val(option.id) |
|---|
| 25469 | 25879 | .prop('selected', option.selected) |
|---|
| 25470 | 25880 | .attr('selected', option.selected) |
|---|
| 25881 | + .prop('label', option.label) |
|---|
| 25471 | 25882 | .text(option.label); |
|---|
| 25472 | 25883 | } |
|---|
| 25473 | 25884 | |
|---|
| .. | .. |
|---|
| 25488 | 25899 | } |
|---|
| 25489 | 25900 | // remove any excessive OPTIONs in a group |
|---|
| 25490 | 25901 | index++; // increment since the existingOptions[0] is parent element not OPTION |
|---|
| 25491 | | - while(existingOptions.length > index) { |
|---|
| 25902 | + while (existingOptions.length > index) { |
|---|
| 25492 | 25903 | option = existingOptions.pop(); |
|---|
| 25493 | 25904 | updateLabelMap(labelMap, option.label, false); |
|---|
| 25494 | 25905 | option.element.remove(); |
|---|
| 25495 | 25906 | } |
|---|
| 25496 | | - forEach(labelMap, function (count, label) { |
|---|
| 25497 | | - if (count > 0) { |
|---|
| 25498 | | - selectCtrl.addOption(label); |
|---|
| 25499 | | - } else if (count < 0) { |
|---|
| 25500 | | - selectCtrl.removeOption(label); |
|---|
| 25501 | | - } |
|---|
| 25502 | | - }); |
|---|
| 25503 | 25907 | } |
|---|
| 25504 | 25908 | // remove any excessive OPTGROUPs from select |
|---|
| 25505 | | - while(optionGroupsCache.length > groupIndex) { |
|---|
| 25506 | | - optionGroupsCache.pop()[0].element.remove(); |
|---|
| 25909 | + while (optionGroupsCache.length > groupIndex) { |
|---|
| 25910 | + // remove all the labels in the option group |
|---|
| 25911 | + optionGroup = optionGroupsCache.pop(); |
|---|
| 25912 | + for (index = 1; index < optionGroup.length; ++index) { |
|---|
| 25913 | + updateLabelMap(labelMap, optionGroup[index].label, false); |
|---|
| 25914 | + } |
|---|
| 25915 | + optionGroup[0].element.remove(); |
|---|
| 25507 | 25916 | } |
|---|
| 25917 | + forEach(labelMap, function(count, label) { |
|---|
| 25918 | + if (count > 0) { |
|---|
| 25919 | + selectCtrl.addOption(label); |
|---|
| 25920 | + } else if (count < 0) { |
|---|
| 25921 | + selectCtrl.removeOption(label); |
|---|
| 25922 | + } |
|---|
| 25923 | + }); |
|---|
| 25508 | 25924 | } |
|---|
| 25509 | 25925 | } |
|---|
| 25510 | 25926 | } |
|---|
| .. | .. |
|---|
| 25528 | 25944 | } |
|---|
| 25529 | 25945 | } |
|---|
| 25530 | 25946 | |
|---|
| 25531 | | - return function (scope, element, attr) { |
|---|
| 25947 | + return function(scope, element, attr) { |
|---|
| 25532 | 25948 | var selectCtrlName = '$selectController', |
|---|
| 25533 | 25949 | parent = element.parent(), |
|---|
| 25534 | 25950 | selectCtrl = parent.data(selectCtrlName) || |
|---|