1(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ 2/* 3 * angular-marked 4 * (c) 2014 - 2016 J. Harshbarger 5 * Licensed MIT 6 */ 7 8/* global angular, marked */ 9 10'use strict'; 11 12var unindent = require('./strip-indent'); 13 14 /** 15 * @ngdoc overview 16 * @name index 17 * 18 * @description 19 * AngularJS Markdown using [marked](https://github.com/chjj/marked). 20 * 21 * ## Why? 22 * 23 * I wanted to use [marked](https://github.com/chjj/marked) instead of [showdown](https://github.com/coreyti/showdown) as used in [angular-markdown-directive](https://github.com/btford/angular-markdown-directive) as well as expose the option to globally set defaults. 24 * 25 * ## How? 26 * 27 * - {@link hc.marked.directive:marked As a directive} 28 * - {@link hc.marked.service:marked As a service} 29 * - {@link hc.marked.service:markedProvider Set default options} 30 * 31 * @example 32 33 Convert markdown to html at run time. For example: 34 35 <example module="app"> 36 <file name="example.html"> 37 <form ng-controller="MainController"> 38 Markdown:<br /> 39 <textarea ng-model="my_markdown" cols="60" rows="5" class="span8"></textarea><br /> 40 Output:<br /> 41 <div marked="my_markdown" /> 42 </form> 43 </file> 44 <file name="example.js"> 45 function MainController($scope) { 46 $scope.my_markdown = "*This* **is** [markdown](https://daringfireball.net/projects/markdown/)"; 47 } 48 angular.module('app', ['hc.marked']).controller('MainController', MainController); 49 </file> 50 </example> 51 52 * 53 */ 54 55 /** 56 * @ngdoc overview 57 * @name hc.marked 58 * @description # angular-marked (core module) 59 # Installation 60 First include angular-marked.js in your HTML: 61 62 ```js 63 <script src="angular-marked.js"> 64 ``` 65 66 Then load the module in your application by adding it as a dependency: 67 68 ```js 69 angular.module('yourApp', ['hc.marked']); 70 ``` 71 72 With that you're ready to get started! 73 */ 74 75 /** 76 * @ngdoc service 77 * @name hc.marked.service:marked 78 * @requires $window 79 * @description 80 * A reference to the [marked](https://github.com/chjj/marked) parser. 81 * 82 * @example 83 <example module="app"> 84 <file name="example.html"> 85 <div ng-controller="MainController"> 86 html: {{html}} 87 </div> 88 </file> 89 <file name="example.js"> 90 function MainController($scope, marked) { 91 $scope.html = marked('#TEST'); 92 } 93 angular.module('app', ['hc.marked']).controller('MainController', MainController); 94 </file> 95 </example> 96 **/ 97 98 /** 99 * @ngdoc service 100 * @name hc.marked.service:markedProvider 101 * @description 102 * Use `markedProvider` to change the default behavior of the {@link hc.marked.service:marked marked} service. 103 * 104 * @example 105 106 ## Example using [google-code-prettify syntax highlighter](https://code.google.com/p/google-code-prettify/) (must include google-code-prettify.js script). Also works with [highlight.js Javascript syntax highlighter](http://highlightjs.org/). 107 108 <example module="myAppA"> 109 <file name="exampleA.js"> 110 angular.module('myAppA', ['hc.marked']) 111 .config(['markedProvider', function(markedProvider) { 112 markedProvider.setOptions({ 113 gfm: true, 114 tables: true, 115 highlight: function (code) { 116 return prettyPrintOne(code); 117 } 118 }); 119 }]); 120 </file> 121 <file name="exampleA.html"> 122 <marked> 123 ```js 124 angular.module('myAppA', ['hc.marked']) 125 .config(['markedProvider', function(markedProvider) { 126 markedProvider.setOptions({ 127 gfm: true, 128 tables: true, 129 highlight: function (code) { 130 return prettyPrintOne(code); 131 } 132 }); 133 }]); 134 ``` 135 </marked> 136 </file> 137 </example> 138 139 ## Example overriding the way custom markdown links are displayed 140 141 <example module="myAppB"> 142 <file name="exampleB.js"> 143 angular.module('myAppB', ['hc.marked']) 144 .config(['markedProvider', function(markedProvider) { 145 markedProvider.setRenderer({ 146 link: function(href, title, text) { 147 return "<a href='" + href + "'" + (title ? " title='" + title + "'" : '') + " target='_blank'>
147" + text + "</a>"; 148 } 149 }); 150 }]); 151 </file> 152 <file name="exampleB.html"> 153 <marked> 154 This is [an example](http://example.com/ "Title") inline link. 155 [This link](http://example.net/) has no title attribute. 156 </marked> 157 </file> 158 </example> 159 **/ 160 161function markedProvider() { 162 var self = this; 163 164 /** 165 * @ngdoc method 166 * @name markedProvider#setRenderer 167 * @methodOf hc.marked.service:markedProvider 168 * 169 * @param {object} opts Default renderer options for [marked](https://github.com/chjj/marked#overriding-renderer-methods). 170 */ 171 172 self.setRenderer = function (opts) { 173 this.renderer = opts; 174 }; 175 176 /** 177 * @ngdoc method 178 * @name markedProvider#setOptions 179 * @methodOf hc.marked.service:markedProvider 180 * 181 * @param {object} opts Default options for [marked](https://github.com/chjj/marked#options-1). 182 */ 183 184 self.setOptions = function (opts) { // Store options for later 185 this.defaults = opts; 186 }; 187 188 self.$get = ['$log', '$window', function ($log, $window) { 189 var m; 190 191 try { 192 m = require('marked'); 193 } catch (err) { 194 m = $window.marked || marked; 195 } 196 197 if (angular.isUndefined(m)) { 198 $log.error('angular-marked Error: marked not loaded. See installation instructions.'); 199 return; 200 } 201 202 var r = new m.Renderer(); 203 204 // override rendered markdown html 205 // with custom definitions if defined 206 if (self.renderer) { 207 var o = Object.keys(self.renderer); 208 var l = o.length; 209 210 while (l--) { 211 r[o[l]] = self.renderer[o[l]]; 212 } 213 } 214 215 // Customize code and codespan rendering to wrap default or overriden output in a ng-non-bindable span 216 function wrapNonBindable(string) { 217 return '<span ng-non-bindable>' + string + '</span>'; 218 } 219 220 var renderCode = r.code.bind(r); 221 r.code = function (code, lang, escaped) { 222 return wrapNonBindable(renderCode(code, lang, escaped)); 223 }; 224 var renderCodespan = r.codespan.bind(r); 225 r.codespan = function (code) { 226 return wrapNonBindable(renderCodespan(code)); 227 }; 228 229 // add the new renderer to the options if need be 230 self.defaults = self.defaults || {}; 231 self.defaults.renderer = r; 232 233 m.setOptions(self.defaults); 234 235 return m; 236 }]; 237} 238 239 // xTODO: filter and tests */ 240 // app.filter('marked', ['marked', function(marked) { 241 // return marked; 242 // }]); 243 244 /** 245 * @ngdoc directive 246 * @name hc.marked.directive:marked 247 * @restrict AE 248 * @element any 249 * 250 * @description 251 * Compiles source test into HTML. 252 * 253 * @param {expression=} marked The source text to be compiled. If blank uses content as the source. 254 * @param {expression=} opts Hash of options that override defaults. 255 * @param {boolean=} compile Set to true to to support AngularJS directives inside markdown. 256 * @param {string=} src Expression evaluating to URL. If the source is a string constant, 257 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 258 * 259 * @example 260 261 ## A simple block of text 262 263 <example module="hc.marked"> 264 <file name="exampleA.html"> 265 * <marked> 266 * ### Markdown directive 267 * 268 * *It works!* 269 * 270 * *This* **is** [markdown](https://daringfireball.net/projects/markdown/) in the view. 271 * </marked> 272 </file> 273 </example> 274 275 ## Bind to a scope variable 276 277 <example module="app"> 278 <file name="exampleB.html"> 279 <form ng-controller="MainController"> 280 Markdown:<br /> 281 <textarea ng-model="my_markdown" class="span8" cols="60" rows="5"></textarea><br /> 282 Output:<br /> 283 <blockquote marked="my_markdown"></blockquote> 284 </form> 285 </file> 286 <file name="exampleB.js"> 287 * function MainController($scope) { 288 * $scope.my_markdown = '*This* **is** [markdown](https://daringfireball.net/projects/markdown/)'; 289 * $scope.my_markdown += ' in a scope variable'; 290 * } 291 * angular.module('app', ['hc.marked']).controller('MainController', MainController); 292 </file> 293 </example> 294 295 ## Include a markdown file: 296 297 <example module="hc.marked"> 298 <file name="exampleC.html"> 299 <div marked src="'include.html'" /> 300 </file> 301 * <file name="include.html"> 302 * *This* **is** [markdown](https://daringfireball.net/projects/markdown/) in a include file. 303 * </file> 304 </example> 305 */ 306markedDirective.$inject = ['marked', '$templateRequest', '$compile']; 307function markedDirective(marked, $templateRequest, $compile) { 308 return { 309 restrict: 'AE', 310 replace: true, 311 scope: { 312 opts: '=', 313 marked: '=', 314 compile: '@', 315 src: '=' 316 }, 317 link: function (scope, element, attrs) { 318 if (attrs.marked) { 319 set(scope.marked); 320 scope.$watch('marked', set); 321 } else if (attrs.src) { 322 scope.$watch('src', function (src) { 323 $templateRequest(src, true).then(function (response) { 324 set(response); 325 }, function () { 326 set(''); 327 scope.$emit('$markedIncludeError', attrs.src); 328 }); 329 }); 330 } else { 331 set(element.text()); 332 } 333 334 function set(text) { 335 text = unindent(String(text || '')); 336 element.html(marked(text, scope.opts || null)); 337 if (scope.$eval(attrs.compile)) { 338 $compile(element.contents())(scope.$parent); 339 } 340 } 341 } 342 }; 343} 344 345module.exports = 346 angular.module('hc.marked', []) 347 .directive('marked', markedDirective) 348 .provider('marked', markedProvider) 349 .name; 350 351},{"./strip-indent":2,"marked":11}],2:[function(require,module,exports){ 352module.exports = function unindent(text) { 353 if (!text) { 354 return text; 355 } 356 357 var lines = text 358 .replace(/\t/g, ' ') 359 .split(/\r?\n/); 360 361 var min = null; 362 var len = lines.length; 363 var i; 364 365 for (i = 0; i < len; i++) { 366 var line = lines[i]; 367 var l = line.match(/^(\s*)/)[0].length; 368 if (l === line.length) { 369 continue; 370 } 371 min = (l < min || min === null) ? l : min; 372 } 373 374 if (min !== null && min > 0) { 375 for (i = 0; i < len; i++) { 376 lines[i] = lines[i].substr(min); 377 } 378 } 379 return lines.join('\n'); 380}; 381 382},{}],3:[function(require,module,exports){ 383/** 384 * State-based routing for AngularJS 385 * @version v0.4.3 386 * @link http://angular-ui.github.com/ 387 * @license MIT License, http://www.opensource.org/licenses/MIT 388 */ 389 390/* commonjs package manager support (eg componentjs) */ 391if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){ 392 module.exports = 'ui.router'; 393} 394 395(function (window, angular, undefined) { 396/*jshint globalstrict:true*/ 397/*global angular:false*/ 398'use strict'; 399 400var isDefined = angular.isDefined, 401 isFunction = angular.isFunction, 402 isString = angular.isString, 403 isObject = angular.isObject, 404 isArray = angular.isArray,
405 forEach = angular.forEach, 406 extend = angular.extend, 407 copy = angular.copy, 408 toJson = angular.toJson; 409 410function inherit(parent, extra) { 411 return extend(new (extend(function() {}, { prototype: parent }))(), extra); 412} 413 414function merge(dst) { 415 forEach(arguments, function(obj) { 416 if (obj !== dst) { 417 forEach(obj, function(value, key) { 418 if (!dst.hasOwnProperty(key)) dst[key] = value; 419 }); 420 } 421 }); 422 return dst; 423} 424 425/** 426 * Finds the common ancestor path between two states. 427 * 428 * @param {Object} first The first state. 429 * @param {Object} second The second state. 430 * @return {Array} Returns an array of state names in descending order, not including the root. 431 */ 432function ancestors(first, second) { 433 var path = []; 434 435 for (var n in first.path) { 436 if (first.path[n] !== second.path[n]) break; 437 path.push(first.path[n]); 438 } 439 return path; 440} 441 442/** 443 * IE8-safe wrapper for `Object.keys()`. 444 * 445 * @param {Object} object A JavaScript object. 446 * @return {Array} Returns the keys of the object as an array. 447 */ 448function objectKeys(object) { 449 if (Object.keys) { 450 return Object.keys(object); 451 } 452 var result = []; 453 454 forEach(object, function(val, key) { 455 result.push(key); 456 }); 457 return result; 458} 459 460/** 461 * IE8-safe wrapper for `Array.prototype.indexOf()`. 462 * 463 * @param {Array} array A JavaScript array. 464 * @param {*} value A value to search the array for. 465 * @return {Number} Returns the array index value of `value`, or `-1` if not present. 466 */ 467function indexOf(array, value) { 468 if (Array.prototype.indexOf) { 469 return array.indexOf(value, Number(arguments[2]) || 0); 470 } 471 var len = array.length >>> 0, from = Number(arguments[2]) || 0; 472 from = (from < 0) ? Math.ceil(from) : Math.floor(from); 473 474 if (from < 0) from += len; 475 476 for (; from < len; from++) { 477 if (from in array && array[from] === value) return from; 478 } 479 return -1; 480} 481 482/** 483 * Merges a set of parameters with all parameters inherited between the common parents of the 484 * current state and a given destination state. 485 * 486 * @param {Object} currentParams The value of the current state parameters ($stateParams). 487 * @param {Object} newParams The set of parameters which will be composited with inherited params. 488 * @param {Object} $current Internal definition of object representing the current state. 489 * @param {Object} $to Internal definition of object representing state to transition to. 490 */ 491function inheritParams(currentParams, newParams, $current, $to) { 492 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = []; 493 494 for (var i in parents) { 495 if (!parents[i] || !parents[i].params) continue; 496 parentParams = objectKeys(parents[i].params);
497 if (!parentParams.length) continue; 498 499 for (var j in parentParams) { 500 if (indexOf(inheritList, parentParams[j]) >= 0) continue; 501 inheritList.push(parentParams[j]); 502 inherited[parentParams[j]] = currentParams[parentParams[j]]; 503 } 504 } 505 return extend({}, inherited, newParams); 506} 507 508/** 509 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys. 510 * 511 * @param {Object} a The first object. 512 * @param {Object} b The second object. 513 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified, 514 * it defaults to the list of keys in `a`. 515 * @return {Boolean} Returns `true` if the keys match, otherwise `false`. 516 */ 517function equalForKeys(a, b, keys) { 518 if (!keys) { 519 keys = []; 520 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility 521 } 522 523 for (var i=0; i<keys.length; i++) { 524 var k = keys[i]; 525 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized 526 } 527 return true; 528} 529 530/** 531 * Returns the subset of an object, based on a list of keys. 532 * 533 * @param {Array} keys 534 * @param {Object} values 535 * @return {Boolean} Returns a subset of `values`. 536 */ 537function filterByKeys(keys, values) { 538 var filtered = {}; 539 540 forEach(keys, function (name) { 541 filtered[name] = values[name]; 542 }); 543 return filtered; 544} 545 546// like _.indexBy 547// when you know that your index values will be unique, or you want last-one-in to win 548function indexBy(array, propName) { 549 var result = {}; 550 forEach(array, function(item) { 551 result[item[propName]] = item; 552 }); 553 return result; 554} 555 556// extracted from underscore.js 557// Return a copy of the object only containing the whitelisted properties. 558function pick(obj) { 559 var copy = {}; 560 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1)); 561 forEach(keys, function(key) { 562 if (key in obj) copy[key] = obj[key]; 563 }); 564 return copy; 565} 566 567// extracted from underscore.js 568// Return a copy of the object omitting the blacklisted properties. 569function omit(obj) { 570 var copy = {}; 571 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1)); 572 for (var key in obj) { 573 if (indexOf(keys, key) == -1) copy[key] = obj[key]; 574 } 575 return copy; 576} 577 578function pluck(collection, key) { 579 var result = isArray(collection) ? [] : {}; 580 581 forEach(collection, function(val, i) { 582 result[i] = isFunction(key) ? key(val) : val[key]; 583 }); 584 return result; 585} 586 587function filter(collection, callback) { 588 var array = isArray(collection); 589 var result = array ? [] : {}; 590 forEach(collection, function(val, i) { 591 if (callback(val, i)) { 592 result[array ? result.length : i] = val; 593 } 594 }); 595 return result; 596} 597 598function map(collection, callback) { 599 var result = isArray(collection) ? [] : {}; 600 601 forEach(collection, function(val, i) { 602 result[i] = callback(val, i); 603 }); 604 return result; 605} 606 607// issue #2676 #2889 608function silenceUncaughtInPromise (promise) { 609 return promise.then(undefined, function() {}) && promise; 610} 611 612/** 613 * @ngdoc overview 614 * @name ui.router.util 615 * 616 * @description 617 * # ui.router.util sub-module 618 * 619 * This module is a dependency of other sub-modules. Do not include this module as a dependency 620 * in your angular app (use {@link ui.router} module instead). 621 * 622 */ 623angular.module('ui.router.util', ['ng']); 624 625/** 626 * @ngdoc overview 627 * @name ui.router.router 628 * 629 * @requires ui.router.util 630 * 631 * @description 632 * # ui.router.router sub-module 633 * 634 * This module is a dependency of other sub-modules. Do not include this module as a dependency 635 * in your angular app (use {@link ui.router} module instead). 636 */ 637angular.module('ui.router.router', ['ui.router.util']); 638 639/** 640 * @ngdoc overview 641 * @name ui.router.state 642 * 643 * @requires ui.router.router 644 * @requires ui.router.util 645 * 646 * @description 647 * # ui.router.state sub-module 648 * 649 * This module is a dependency of the main ui.router module. Do not include this module as a dependency 650 * in your angular app (use {@link ui.router} module instead). 651 * 652 */ 653angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']); 654 655/** 656 * @ngdoc overview 657 * @name ui.router 658 * 659 * @requires ui.router.state 660 * 661 * @description 662 * # ui.router 663 * 664 * ## The main module for ui.router 665 * There are several sub-modules included with the ui.router module, however only this module is needed 666 * as a dependency within your angular app. The other modules are for organization purposes. 667 * 668 * The modules are: 669 * * ui.router - the main "umbrella" module 670 * * ui.router.router - 671 * 672 * *You'll need to include **only** this module as the dependency within your angular app.* 673 * 674 * <pre> 675 * <!doctype html> 676 * <html ng-app="myApp"> 677 * <head> 678 * <script src="js/angular.js"></script> 679 * <!-- Include the ui-router script --> 680 * <script src="js/angular-ui-router.min.js"></script> 681 * <script> 682 * // ...and add 'ui.router' as a dependency 683 * var myApp = angular.module('myApp', ['ui.router']); 684 * </script> 685 * </head> 686 * <body> 687 * </body> 688 * </html> 689 * </pre> 690 */ 691angular.module('ui.router', ['ui.router.state']); 692 693angular.module('ui.router.compat', ['ui.router']); 694 695/** 696 * @ngdoc object 697 * @name ui.router.util.$resolve 698 * 699 * @requires $q 700 * @requires $injector 701 * 702 * @description 703 * Manages resolution of (acyclic) graphs of promises. 704 */ 705$Resolve.$inject = ['$q', '$injector']; 706function $Resolve( $q, $injector) { 707 708 var VISIT_IN_PROGRESS = 1, 709 VISIT_DONE = 2, 710 NOTHING = {}, 711 NO_DEPENDENCIES = [], 712 NO_LOCALS = NOTHING, 713 NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING }); 714 715 716 /** 717 * @ngdoc function 718 * @name ui.router.util.$resolve#study 719 * @methodOf ui.router.util.$resolve 720 * 721 * @description 722 * Studies a set of invocables that are likely to be used multiple times. 723 * <pre> 724 * $resolve.study(invocables)(locals, parent, self) 725 * </pre> 726 * is equivalent to 727 * <pre> 728 * $resolve.resolve(invocables, locals, parent, self) 729 * </pre> 730 * but the former is more efficient (in fact `resolve` just calls `study` 731 * internally). 732 * 733 * @param {object} invocables Invocable objects 734 * @return {function} a function to pass in locals, parent and self 735 */ 736 this.study = function (invocables) {
737 if (!isObject(invocables)) throw new Error("'invocables' must be an object"); 738 var invocableKeys = objectKeys(invocables || {}); 739 740 // Perform a topological sort of invocables to build an ordered plan 741 var plan = [], cycle = [], visited = {}; 742 function visit(value, key) { 743 if (visited[key] === VISIT_DONE) return; 744 745 cycle.push(key); 746 if (visited[key] === VISIT_IN_PROGRESS) { 747 cycle.splice(0, indexOf(cycle, key)); 748 throw new Error("Cyclic dependency: " + cycle.join(" -> ")); 749 } 750 visited[key] = VISIT_IN_PROGRESS; 751 752 if (isString(value)) { 753 plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); 754 } else { 755 var params = $injector.annotate(value); 756 forEach(params, function (param) { 757 if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param); 758 }); 759 plan.push(key, value, params); 760 } 761 762 cycle.pop(); 763 visited[key] = VISIT_DONE; 764 } 765 forEach(invocables, visit); 766 invocables = cycle = visited = null; // plan is all that's required 767 768 function isResolve(value) { 769 return isObject(value) && value.then && value.$$promises; 770 } 771 772 return function (locals, parent, self) { 773 if (isResolve(locals) && self === undefined) { 774 self = parent; parent = locals; locals = null; 775 } 776 if (!locals) locals = NO_LOCALS; 777 else if (!isObject(locals)) { 778 throw new Error("'locals' must be an object"); 779 } 780 if (!parent) parent = NO_PARENT; 781 else if (!isResolve(parent)) { 782 throw new Error("'parent' must be a promise returned by $resolve.resolve()"); 783 } 784 785 // To complete the overall resolution, we have to wait for the parent 786 // promise and for the promise for each invokable in our plan. 787 var resolution = $q.defer(), 788 result = silenceUncaughtInPromise(resolution.promise), 789 promises = result.$$promises = {}, 790 values = extend({}, locals), 791 wait = 1 + plan.length/3, 792 merged = false; 793 794 silenceUncaughtInPromise(result); 795 796 function done() { 797 // Merge parent values we haven't got yet and publish our own $$values 798 if (!--wait) { 799 if (!merged) merge(values, parent.$$values); 800 result.$$values = values; 801 result.$$promises = result.$$promises || true; // keep for isResolve() 802 delete result.$$inheritedValues; 803 resolution.resolve(values); 804 } 805 } 806 807 function fail(reason) { 808 result.$$failure = reason; 809 resolution.reject(reason); 810 } 811 812 // Short-circuit if parent has already failed 813 if (isDefined(parent.$$failure)) { 814 fail(parent.$$failure); 815 return result; 816 } 817 818 if (parent.$$inheritedValues) { 819 merge(values, omit(parent.$$inheritedValues, invocableKeys)); 820 } 821 822 // Merge parent values if the parent has already resolved, or merge 823 // parent promises and wait if the parent resolve is still in progress. 824 extend(promises, parent.$$promises); 825 if (parent.$$values) { 826 merged = merge(values, omit(parent.$$values, invocableKeys)); 827 result.$$inheritedValues = omit(parent.$$values, invocableKeys); 828 done(); 829 } else { 830 if (parent.$$inheritedValues) { 831 result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys); 832 } 833 parent.then(done, fail); 834 } 835 836 // Process each invocable in the plan, but ignore any where a local of the same name exists. 837 for (var i=0, ii=plan.length; i<ii; i+=3) { 838 if (locals.hasOwnProperty(plan[i])) done(); 839 else invoke(plan[i], plan[i+1], plan[i+2]); 840 } 841 842 function invoke(key, invocable, params) { 843 // Create a deferred for this invocation. Failures will propagate to the resolution as well. 844 var invocation = $q.defer(), waitParams = 0; 845 function onfailure(reason) { 846 invocation.reject(reason); 847 fail(reason); 848 } 849 // Wait for any parameter that we have a promise for (either from parent or from this 850 // resolve; in that case study() will have made sure it's ordered before us in the plan).
851 forEach(params, function (dep) { 852 if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) { 853 waitParams++; 854 promises[dep].then(function (result) { 855 values[dep] = result; 856 if (!(--waitParams)) proceed(); 857 }, onfailure); 858 } 859 }); 860 if (!waitParams) proceed(); 861 function proceed() { 862 if (isDefined(result.$$failure)) return; 863 try { 864 invocation.resolve($injector.invoke(invocable, self, values)); 865 invocation.promise.then(function (result) { 866 values[key] = result; 867 done(); 868 }, onfailure); 869 } catch (e) { 870 onfailure(e); 871 } 872 } 873 // Publish promise synchronously; invocations further down in the plan may depend on it. 874 promises[key] = silenceUncaughtInPromise(invocation.promise); 875 } 876 877 return result; 878 }; 879 }; 880 881 /** 882 * @ngdoc function 883 * @name ui.router.util.$resolve#resolve 884 * @methodOf ui.router.util.$resolve 885 * 886 * @description 887 * Resolves a set of invocables. An invocable is a function to be invoked via 888 * `$injector.invoke()`, and can have an arbitrary number of dependencies. 889 * An invocable can either return a value directly, 890 * or a `$q` promise. If a promise is returned it will be resolved and the 891 * resulting value will be used instead. Dependencies of invocables are resolved 892 * (in this order of precedence) 893 * 894 * - from the specified `locals` 895 * - from another invocable that is part of this `$resolve` call 896 * - from an invocable that is inherited from a `parent` call to `$resolve` 897 * (or recursively 898 * - from any ancestor `$resolve` of that parent). 899 * 900 * The return value of `$resolve` is a promise for an object that contains 901 * (in this order of precedence) 902 * 903 * - any `locals` (if specified) 904 * - the resolved return values of all injectables 905 * - any values inherited from a `parent` call to `$resolve` (if specified) 906 * 907 * The promise will resolve after the `parent` promise (if any) and all promises 908 * returned by injectables have been resolved. If any invocable 909 * (or `$injector.invoke`) throws an exception, or if a promise returned by an 910 * invocable is rejected, the `$resolve` promise is immediately rejected with the 911 * same error. A rejection of a `parent` promise (if specified) will likewise be 912 * propagated immediately. Once the `$resolve` promise has been rejected, no 913 * further invocables will be called. 914 * 915 * Cyclic dependencies between invocables are not permitted and will cause `$resolve` 916 * to throw an error. As a special case, an injectable can depend on a parameter 917 * with the same name as the injectable, which will be fulfilled from the `parent` 918 * injectable of the same name. This allows inherited values to be decorated. 919 * Note that in this case any other injectable in the same `$resolve` with the same 920 * dependency would see the decorated value, not the inherited value. 921 * 922 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an 923 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous) 924 * exception. 925 * 926 * Invocables are invoked eagerly as soon as all dependencies are available. 927 * This is true even for dependencies inherited from a `parent` call to `$resolve`. 928 * 929 * As a special case, an invocable can be a string, in which case it is taken to 930 * be a service name to be passed to `$injector.get()`. This is supported primarily 931 * for backwards-compatibility with the `resolve` property of `$routeProvider` 932 * routes. 933 * 934 * @param {object} invocables functions to invoke or 935 * `$injector` services to fetch. 936 * @param {object} locals values to make available to the injectables 937 * @param {object} parent a promise returned by another call to `$resolve`. 938 * @param {object} self the `this` for the invoked methods 939 * @return {object} Promise for an object that contains the resolved return value 940 * of all invocables, as well as any inherited and local values. 941 */ 942 this.resolve = function (invocables, locals, parent, self) { 943 return this.study(invocables)(locals, parent, self); 944 }; 945} 946 947angular.module('ui.router.util').service('$resolve', $Resolve); 948 949 950 951/** 952 * @ngdoc object
953 * @name ui.router.util.$templateFactoryProvider 954 * 955 * @description 956 * Provider for $templateFactory. Manages which template-loading mechanism to 957 * use, and will default to the most recent one ($templateRequest on Angular 958 * versions starting from 1.3, $http otherwise). 959 */ 960function TemplateFactoryProvider() { 961 var shouldUnsafelyUseHttp = angular.version.minor < 3; 962 963 /** 964 * @ngdoc function 965 * @name ui.router.util.$templateFactoryProvider#shouldUnsafelyUseHttp 966 * @methodOf ui.router.util.$templateFactoryProvider 967 * 968 * @description 969 * Forces $templateFactory to use $http instead of $templateRequest. This 970 * might cause XSS, as $http doesn't enforce the regular security checks for 971 * templates that have been introduced in Angular 1.3. Note that setting this 972 * to false on Angular older than 1.3.x will crash, as the $templateRequest 973 * service (and the security checks) are not implemented on these versions. 974 * 975 * See the $sce documentation, section 976 * <a href="https://docs.angularjs.org/api/ng/service/$sce#impact-on-loading-templates"> 977 * Impact on loading templates</a> for more details about this mechanism. 978 * 979 * @param {boolean} value 980 */ 981 this.shouldUnsafelyUseHttp = function(value) { 982 shouldUnsafelyUseHttp = !!value; 983 }; 984 985 /** 986 * @ngdoc object 987 * @name ui.router.util.$templateFactory 988 * 989 * @requires $http 990 * @requires $templateCache 991 * @requires $injector 992 * 993 * @description 994 * Service. Manages loading of templates. 995 */ 996 this.$get = ['$http', '$templateCache', '$injector', function($http, $templateCache, $injector){ 997 return new TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp);}]; 998} 999 1000 1001/** 1002 * @ngdoc object 1003 * @name ui.router.util.$templateFactory 1004 * 1005 * @requires $http 1006 * @requires $templateCache 1007 * @requires $injector 1008 * 1009 * @description 1010 * Service. Manages loading of templates. 1011 */ 1012function TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp) { 1013 1014 /** 1015 * @ngdoc function 1016 * @name ui.router.util.$templateFactory#fromConfig 1017 * @methodOf ui.router.util.$templateFactory 1018 * 1019 * @description 1020 * Creates a template from a configuration object. 1021 * 1022 * @param {object} config Configuration object for which to load a template. 1023 * The following properties are search in the specified order, and the first one 1024 * that is defined is used to create the template: 1025 * 1026 * @param {string|object} config.template html string template or function to 1027 * load via {@link ui.router.util.$templateFactory#fromString fromString}. 1028 * @param {string|object} config.templateUrl url to load or a function returning 1029 * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}. 1030 * @param {Function} config.templateProvider function to invoke via 1031 * {@link ui.router.util.$templateFactory#fromProvider fromProvider}. 1032 * @param {object} params Parameters to pass to the template function. 1033 * @param {object} locals Locals to pass to `invoke` if the template is loaded 1034 * via a `templateProvider`. Defaults to `{ params: params }`. 1035 * 1036 * @return {string|object} The template html as a string, or a promise for 1037 * that string,or `null` if no template is configured. 1038 */ 1039 this.fromConfig = function (config, params, locals) { 1040 return ( 1041 isDefined(config.template) ? this.fromString(config.template, params) : 1042 isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) : 1043 isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) : 1044 null 1045 ); 1046 }; 1047 1048 /** 1049 * @ngdoc function 1050 * @name ui.router.util.$templateFactory#fromString 1051 * @methodOf ui.router.util.$templateFactory 1052 * 1053 * @description 1054 * Creates a template from a string or a function returning a string. 1055 * 1056 * @param {string|object} template html template as a string or function that 1057 * returns an html template as a string. 1058 * @param {object} params Parameters to pass to the template function. 1059 * 1060 * @return {string|object} The template html as a string, or a promise for that 1061 * string. 1062 */ 1063 this.fromString = function (template, params) { 1064 return isFunction(template) ? template(params) : template; 1065 }; 1066 1067 /** 1068 * @ngdoc function 1069 * @name ui.router.util.$templateFactory#fromUrl 1070 * @methodOf ui.router.util.$templateFactory 1071 * 1072 * @description 1073 * Loads a template from the a URL via `$http` and `$templateCache`. 1074 * 1075 * @param {string|Function} url url of the template to load, or a function 1076 * that returns a url. 1077 * @param {Object} params Parameters to pass to the url function. 1078 * @return {string|Promise.<string>} The template html as a string, or a promise 1079 * for that string. 1080 */ 1081 this.fromUrl = function (url, params) { 1082 if (isFunction(url)) url = url(params); 1083 if (url == null) return null; 1084 else { 1085 if(!shouldUnsafelyUseHttp) { 1086 return $injector.get('$templateRequest')(url); 1087 } else { 1088 return $http 1089 .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }}) 1090 .then(function(response) { return response.data; }); 1091 } 1092 } 1093 }; 1094 1095 /** 1096 * @ngdoc function 1097 * @name ui.router.util.$templateFactory#fromProvider 1098 * @methodOf ui.router.util.$templateFactory 1099 * 1100 * @description 1101 * Creates a template by invoking an injectable provider function. 1102 * 1103 * @param {Function} provider Function to invoke via `$injector.invoke` 1104 * @param {Object} params Parameters for the template. 1105 * @param {Object} locals Locals to pass to `invoke`. Defaults to 1106 * `{ params: params }`. 1107 * @return {string|Promise.<string>} The template html as a string, or a promise 1108 * for that string. 1109 */ 1110 this.fromProvider = function (provider, params, locals) { 1111 return $injector.invoke(provider, null, locals || { params: params }); 1112 }; 1113} 1114 1115angular.module('ui.router.util').provider('$templateFactory', TemplateFactoryProvi
1115der); 1116 1117var $$UMFP; // reference to $UrlMatcherFactoryProvider 1118 1119/** 1120 * @ngdoc object 1121 * @name ui.router.util.type:UrlMatcher 1122 * 1123 * @description 1124 * Matches URLs against patterns and extracts named parameters from the path or the search 1125 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list 1126 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters 1127 * do not influence whether or not a URL is matched, but their values are passed through into 1128 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. 1129 * 1130 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace 1131 * syntax, which optionally allows a regular expression for the parameter to be specified: 1132 * 1133 * * `':'` name - colon placeholder 1134 * * `'*'` name - catch-all placeholder 1135 * * `'{' name '}'` - curly placeholder 1136 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the 1137 * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash. 1138 * 1139 * Parameter names may contain only word characters (latin letters, digits, and underscore) and 1140 * must be unique within the pattern (across both path and search parameters). For colon 1141 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any 1142 * number of characters other than '/'. For catch-all placeholders the path parameter matches 1143 * any number of characters. 1144 * 1145 * Examples: 1146 * 1147 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for 1148 * trailing slashes, and patterns have to match the entire path, not just a prefix. 1149 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or 1150 * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. 1151 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax. 1152 * * `'/user/{id:[^/]*}'` - Same as the previous example. 1153 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id 1154 * parameter consists of 1 to 8 hex digits. 1155 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the 1156 * path into the parameter 'path'. 1157 * * `'/files/*path'` - ditto. 1158 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined 1159 * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start 1160 * 1161 * @param {string} pattern The pattern to compile into a matcher. 1162 * @param {Object} config A configuration object hash: 1163 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto 1164 * an existing UrlMatcher 1165 * 1166 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`. 1167 * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`. 1168 * 1169 * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any 1170 * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns 1171 * non-null) will start with this prefix. 1172 * 1173 * @property {string} source The pattern that was passed into the constructor 1174 * 1175 * @property {string} sourcePath The path portion of the source property 1176 * 1177 * @property {string} sourceSearch The search portion of the source property 1178 * 1179 * @property {string} regex The constructed regex that will be used to match against the url when 1180 * it is time to determine which url will match. 1181 * 1182 * @returns {Object} New `UrlMatcher` object 1183 */ 1184function UrlMatcher(pattern, config, parentMatcher) { 1185 config = extend({ params: {} }, isObject(config) ? config : {}); 1186 1187 // Find all placeholders and create a compiled pattern, using either classic or curly syntax: 1188 // '*' name 1189 // ':' name 1190 // '{' name '}' 1191 // '{' name ':' regexp '}' 1192 // The regular expression is somewhat complicated due to the need to allow curly braces 1193 // inside the regular expression. The placeholder regexp breaks down as follows: 1194 // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case) 1195 // \{([\w\[\]]+)(?:\:\s*( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case 1196 // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either 1197 // [^{}\\]+ - anything other than curly braces or backslash 1198 // \\. - a backslash escape 1199 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms 1200 var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, 1201 searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, 1202 compiled = '^', last = 0, m, 1203 segments = this.segments = [],
1204 parentParams = parentMatcher ? parentMatcher.params : {}, 1205 params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(), 1206 paramNames = []; 1207 1208 function addParameter(id, type, config, location) { 1209 paramNames.push(id); 1210 if (parentParams[id]) return parentParams[id]; 1211 if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); 1212 if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); 1213 params[id] = new $$UMFP.Param(id, type, config, location); 1214 return params[id]; 1215 } 1216 1217 function quoteRegExp(string, pattern, squash, optional) { 1218 var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); 1219 if (!pattern) return result; 1220 switch(squash) { 1221 case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break; 1222 case true: 1223 result = result.replace(/\/$/, ''); 1224 surroundPattern = ['(?:\/(', ')|\/)?']; 1225 break; 1226 default: surroundPattern = ['(' + squash + "|", ')?']; break; 1227 } 1228 return result + surroundPattern[0] + pattern + surroundPattern[1]; 1229 } 1230 1231 this.source = pattern; 1232 1233 // Split into static segments separated by path parameter placeholders. 1234 // The number of segments is always 1 more than the number of parameters. 1235 function matchDetails(m, isSearch) { 1236 var id, regexp, segment, type, cfg, arrayMode; 1237 id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null 1238 cfg = config.params[id]; 1239 segment = pattern.substring(last, m.index); 1240 regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null); 1241 1242 if (regexp) { 1243 type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) }); 1244 } 1245 1246 return { 1247 id: id, regexp: regexp, segment: segment, type: type, cfg: cfg 1248 }; 1249 } 1250 1251 var p, param, segment; 1252 while ((m = placeholder.exec(pattern))) { 1253 p = matchDetails(m, false); 1254 if (p.segment.indexOf('?') >= 0) break; // we're into the search part 1255 1256 param = addParameter(p.id, p.type, p.cfg, "path"); 1257 compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional); 1258 segments.push(p.segment); 1259 last = placeholder.lastIndex; 1260 } 1261 segment = pattern.substring(last); 1262 1263 // Find any search parameter names and remove them from the last segment 1264 var i = segment.indexOf('?'); 1265 1266 if (i >= 0) { 1267 var search = this.sourceSearch = segment.substring(i); 1268 segment = segment.substring(0, i); 1269 this.sourcePath = pattern.substring(0, last + i); 1270 1271 if (search.length > 0) { 1272 last = 0; 1273 while ((m = searchPlaceholder.exec(search))) { 1274 p = matchDetails(m, true); 1275 param = addParameter(p.id, p.type, p.cfg, "search"); 1276 last = placeholder.lastIndex; 1277 // check if ?& 1278 } 1279 } 1280 } else { 1281 this.sourcePath = pattern; 1282 this.sourceSearch = ''; 1283 } 1284 1285 compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$'; 1286 segments.push(segment); 1287 1288 this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined); 1289 this.prefix = segments[0]; 1290 this.$$paramNames = paramNames; 1291} 1292 1293/** 1294 * @ngdoc function 1295 * @name ui.router.util.type:UrlMatcher#concat 1296 * @methodOf ui.router.util.type:UrlMatcher 1297 * 1298 * @description 1299 * Returns a new matcher for a pattern constructed by appending the path part and adding the 1300 * search parameters of the specified pattern to this pattern. The current pattern is not 1301 * modified. This can be understood as creating a pattern for URLs that are relative to (or 1302 * suffixes of) the current pattern. 1303 * 1304 * @example 1305 * The following two matchers are equivalent: 1306 * <pre> 1307 * new UrlMatcher('/user/{id}?q').concat('/details?date'); 1308 * new UrlMatcher('/user/{id}/details?q&date'); 1309 * </pre> 1310 * 1311 * @param {string} pattern The pattern to append. 1312 * @param {Object} config An object hash of the configuration for the matcher. 1313 * @returns {UrlMatcher} A matcher for the concatenated pattern. 1314 */ 1315UrlMatcher.prototype.concat = function (pattern, config) { 1316 // Because order of search parameters is irrelevant, we can add our own search 1317 // parameters to the end of the new pattern. Parse the new pattern by itself 1318 // and then join the bits together, but it's much easier to do this on a string level. 1319 var defaultConfig = { 1320 caseInsensitive: $$UMFP.caseInsensitive(), 1321 strict: $$UMFP.strictMode(), 1322 squash: $$UMFP.defaultSquashPolicy() 1323 }; 1324 return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this); 1325}; 1326 1327UrlMatcher.prototype.toString = function () { 1328 return this.source; 1329}; 1330 1331/** 1332 * @ngdoc function 1333 * @name ui.router.util.type:UrlMatcher#exec 1334 * @methodOf ui.router.util.type:UrlMatcher 1335 * 1336 * @description 1337 * Tests the specified path against this matcher, and returns an object c
1337ontaining the captured 1338 * parameter values, or null if the path does not match. The returned object contains the values 1339 * of any search parameters that are mentioned in the pattern, but their value may be null if 1340 * they are not present in `searchParams`. This means that search parameters are always treated 1341 * as optional. 1342 * 1343 * @example 1344 * <pre> 1345 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { 1346 * x: '1', q: 'hello' 1347 * }); 1348 * // returns { id: 'bob', q: 'hello', r: null } 1349 * </pre> 1350 * 1351 * @param {string} path The URL path to match, e.g. `$location.path()`. 1352 * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. 1353 * @returns {Object} The captured parameter values. 1354 */ 1355UrlMatcher.prototype.exec = function (path, searchParams) { 1356 var m = this.regexp.exec(path); 1357 if (!m) return null; 1358 searchParams = searchParams || {}; 1359 1360 var paramNames = this.parameters(), nTotal = paramNames.length, 1361 nPath = this.segments.length - 1, 1362 values = {}, i, j, cfg, paramName; 1363 1364 if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); 1365 1366 function decodePathArray(string) { 1367 function reverseString(str) { return str.split("").reverse().join(""); } 1368 function unquoteDashes(str) { return str.replace(/\\-/g, "-"); } 1369 1370 var split = reverseString(string).split(/-(?!\\)/); 1371 var allReversed = map(split, reverseString); 1372 return map(allReversed, unquoteDashes).reverse(); 1373 } 1374 1375 var param, paramVal; 1376 for (i = 0; i < nPath; i++) { 1377 paramName = paramNames[i]; 1378 param = this.params[paramName]; 1379 paramVal = m[i+1]; 1380 // if the param value matches a pre-replace pair, replace the value before decoding. 1381 for (j = 0; j < param.replace.length; j++) { 1382 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; 1383 } 1384 if (paramVal && param.array === true) paramVal = decodePathArray(paramVal); 1385 if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); 1386 values[paramName] = param.value(paramVal); 1387 } 1388 for (/**/; i < nTotal; i++) { 1389 paramName = paramNames[i]; 1390 values[paramName] = this.params[paramName].value(searchParams[paramName]); 1391 param = this.params[paramName]; 1392 paramVal = searchParams[paramName]; 1393 for (j = 0; j < param.replace.length; j++) { 1394 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; 1395 } 1396 if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); 1397 values[paramName] = param.value(paramVal); 1398 } 1399 1400 return values; 1401}; 1402 1403/** 1404 * @ngdoc function 1405 * @name ui.router.util.type:UrlMatcher#parameters 1406 * @methodOf ui.router.util.type:UrlMatcher 1407 * 1408 * @description 1409 * Returns the names of all path and search parameters of this pattern in an unspecified order. 1410 * 1411 * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the 1412 * pattern has no parameters, an empty array is returned. 1413 */ 1414UrlMatcher.prototype.parameters = function (param) { 1415 if (!isDefined(param)) return this.$$paramNames; 1416 return this.params[param] || null; 1417}; 1418 1419/** 1420 * @ngdoc function 1421 * @name ui.router.util.type:UrlMatcher#validates 1422 * @methodOf ui.router.util.type:UrlMatcher 1423 * 1424 * @description 1425 * Checks an object hash of parameters to validate their correctness according to the parameter 1426 * types of this `UrlMatcher`. 1427 * 1428 * @param {Object} params The object hash of parameters to validate. 1429 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`. 1430 */ 1431UrlMatcher.prototype.validates = function (params) { 1432 return this.params.$$validates(params); 1433}; 1434 1435/** 1436 * @ngdoc function 1437 * @name ui.router.util.type:UrlMatcher#format 1438 * @methodOf ui.router.util.type:UrlMatcher 1439 * 1440 * @description 1441 * Creates a URL that matches this pattern by substituting the specified values 1442 * for the path and search parameters. Null values for path parameters are 1443 * treated as empty strings. 1444 * 1445 * @example 1446 * <pre> 1447 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' }); 1448 * // returns '/user/bob?q=yes' 1449 * </pre> 1450 * 1451 * @param {Object} values the values to substitute for the parameters in this pattern. 1452 * @returns {string} the formatted URL (path and optionally search part). 1453 */ 1454UrlMatcher.prototype.format = function (values) { 1455 values = values || {}; 1456 var segments = this.segments, params = this.parameters(), paramset = this.params; 1457 if (!this.validates(values)) return null; 1458 1459 var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0]; 1460 1461 function encodeDashes(str) { // Replace dashes with encoded "\-" 1462 return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCo
1462deAt(0).toString(16).toUpperCase(); }); 1463 } 1464 1465 for (i = 0; i < nTotal; i++) { 1466 var isPathParam = i < nPath; 1467 var name = params[i], param = paramset[name], value = param.value(values[name]); 1468 var isDefaultValue = param.isOptional && param.type.equals(param.value(), value); 1469 var squash = isDefaultValue ? param.squash : false; 1470 var encoded = param.type.encode(value); 1471 1472 if (isPathParam) { 1473 var nextSegment = segments[i + 1]; 1474 var isFinalPathParam = i + 1 === nPath; 1475 1476 if (squash === false) { 1477 if (encoded != null) { 1478 if (isArray(encoded)) { 1479 result += map(encoded, encodeDashes).join("-"); 1480 } else { 1481 result += encodeURIComponent(encoded); 1482 } 1483 } 1484 result += nextSegment; 1485 } else if (squash === true) { 1486 var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/; 1487 result += nextSegment.match(capture)[1]; 1488 } else if (isString(squash)) { 1489 result += squash + nextSegment; 1490 } 1491 1492 if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1); 1493 } else { 1494 if (encoded == null || (isDefaultValue && squash !== false)) continue; 1495 if (!isArray(encoded)) encoded = [ encoded ]; 1496 if (encoded.length === 0) continue; 1497 encoded = map(encoded, encodeURIComponent).join('&' + name + '='); 1498 result += (search ? '&' : '?') + (name + '=' + encoded); 1499 search = true; 1500 } 1501 } 1502 1503 return result; 1504}; 1505 1506/** 1507 * @ngdoc object 1508 * @name ui.router.util.type:Type 1509 * 1510 * @description 1511 * Implements an interface to define custom parameter types that can be decoded from and encoded to 1512 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`} 1513 * objects when matching or formatting URLs, or comparing or validating parameter values. 1514 * 1515 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more 1516 * information on registering custom types. 1517 * 1518 * @param {Object} config A configuration object which contains the custom type definition. The object's 1519 * properties will override the default methods and/or pattern in `Type`'s public interface. 1520 * @example 1521 * <pre> 1522 * { 1523 * decode: function(val) { return parseInt(val, 10); }, 1524 * encode: function(val) { return val && val.toString(); }, 1525 * equals: function(a, b) { return this.is(a) && a === b; }, 1526 * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; }, 1527 * pattern: /\d+/ 1528 * } 1529 * </pre> 1530 * 1531 * @property {RegExp} pattern The regular expression pattern used to match values of this type when 1532 * coming from a substring of a URL. 1533 * 1534 * @returns {Object} Returns a new `Type` object. 1535 */ 1536function Type(config) { 1537 extend(this, config); 1538} 1539 1540/** 1541 * @ngdoc function 1542 * @name ui.router.util.type:Type#is 1543 * @methodOf ui.router.util.type:Type 1544 * 1545 * @description 1546 * Detects whether a value is of a particular type. Accepts a native (decoded) value 1547 * and determines whether it matches the current `Type` object. 1548 * 1549 * @param {*} val The value to check. 1550 * @param {string} key Optional. If the type check is happening in the context of a specific 1551 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the 1552 * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects. 1553 * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`. 1554 */ 1555Type.prototype.is = function(val, key) { 1556 return true; 1557}; 1558 1559/** 1560 * @ngdoc function 1561 * @name ui.router.util.type:Type#encode 1562 * @methodOf ui.router.util.type:Type 1563 * 1564 * @description 1565 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the 1566 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it 1567 * only needs to be a representation of `val` that has been coerced to a string. 1568 * 1569 * @param {*} val The value to encode. 1570 * @param {string} key The name of the parameter in which `val` is stored. Can be used for 1571 * meta-programming of `Type` objects. 1572 * @returns {string} Returns a string representation of `val` that can be encoded in a URL. 1573 */ 1574Type.prototype.encode = function(val, key) { 1575 return val; 1576}; 1577 1578/** 1579 * @ngdoc function 1580 * @name ui.router.util.type:Type#decode 1581 * @methodOf ui.router.util.type:Type 1582 * 1583 * @description 1584 * Converts a parameter value (from URL string or transition param) to a custom/native value. 1585 * 1586 * @param {string} val The URL parameter value to decode. 1587 * @param {string} key The name of the parameter in which `val` is stored. Can be used for 1588 * meta-programming of `Type` objects. 1589 * @returns {*} Returns a custom representation of the URL parameter value. 1590 */ 1591Type.prototype.decode = function(val, key) { 1592 return val; 1593}; 1594 1595/** 1596 * @ngdoc function 1597 * @name ui.router.util.type:Type#equals 1598 * @methodOf ui.router.util.type:Type 1599 * 1600 * @description 1601 * Determines whether two decoded values are equivalent. 1602 * 1603 * @param {*} a A value to compare against. 1604 * @param {*} b A value to compare against. 1605 * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`. 1606 */ 1607Type.prototype.equals = function(a, b) { 1608 return a == b; 1609}; 1610 1611Type.prototype.$subPattern = function() { 1612 var sub = this.pattern.toString(); 1613 return sub.substr(1, sub.length - 2); 1614}; 1615 1616Type.prototype.pattern = /.*/; 1617 1618Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; 1619 1620/** Given an encoded string, or a decoded object, returns a decoded object */ 1621Type.prototype.$normalize = function(val) { 1622 return this.is(val) ? val : this.decode(val); 1623}; 1624 1625/* 1626 * Wraps an existing custom Type as an array of Type, depending on 'mode'. 1627 * e.g.: 1628 * - urlmatcher pattern "/path?{queryParam[]:int}" 1629 * - url: "/path?queryParam=1&queryParam=2 1630 * - $stateParams.queryParam will be [1, 2] 1631 * if `mode` is "auto", then 1632 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1 1633 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2] 1634 */ 1635Type.prototype.$asArray = function(mode, isSearch) { 1636 if (!mode) return this; 1637 if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only"); 1638 1639 function ArrayType(type, mode) { 1640 function bindTo(type, callbackName) { 1641 return function() { 1642 return type[callbackName].apply(type, arguments); 1643 }; 1644 } 1645 1646 // Wrap non-array value as array 1647 function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); } 1648 // Unwrap array value for "auto" mode. Return undefined for empty array. 1649 function arrayUnwrap(val) { 1650 switch(val.length) { 1651 case 0: return undefined; 1652 case 1: return mode === "auto" ? val[0] : val; 1653 default: return val; 1654 } 1655 } 1656 function falsey(val) { return !val; } 1657 1658 // Wraps type (.is/.encode/.decode) functions to operate on each value of an array 1659 function arrayHandler(callback, allTruthyMode) { 1660 return function handleArray(val) { 1661 if (isArray(val) && val.length === 0) return val; 1662 val = arrayWrap(val); 1663 var result = map(val, callback); 1664 if (allTruthyMode === true) 1665 return filter(result, falsey).length === 0; 1666 return arrayUnwrap(result); 1667 }; 1668 } 1669 1670 // Wraps type (.equals) functions to operate on each value of an array 1671 function arrayEqualsHandler(callback) { 1672 return function handleArray(val1, val2) { 1673 var left = arrayWrap(val1), right = arrayWrap(val2); 1674 if (left.length !== right.length) return false;
1675 for (var i = 0; i < left.length; i++) { 1676 if (!callback(left[i], right[i])) return false; 1677 } 1678 return true; 1679 }; 1680 } 1681 1682 this.encode = arrayHandler(bindTo(type, 'encode')); 1683 this.decode = arrayHandler(bindTo(type, 'decode')); 1684 this.is = arrayHandler(bindTo(type, 'is'), true); 1685 this.equals = arrayEqualsHandler(bindTo(type, 'equals')); 1686 this.pattern = type.pattern; 1687 this.$normalize = arrayHandler(bindTo(type, '$normalize')); 1688 this.name = type.name; 1689 this.$arrayMode = mode; 1690 } 1691 1692 return new ArrayType(this, mode); 1693}; 1694 1695 1696 1697/** 1698 * @ngdoc object 1699 * @name ui.router.util.$urlMatcherFactory 1700 * 1701 * @description 1702 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory 1703 * is also available to providers under the name `$urlMatcherFactoryProvider`. 1704 */ 1705function $UrlMatcherFactory() { 1706 $$UMFP = this; 1707 1708 var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false; 1709 1710 // Use tildes to pre-encode slashes. 1711 // If the slashes are simply URLEncoded, the browser can choose to pre-decode them, 1712 // and bidirectional encoding/decoding fails. 1713 // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character 1714 function valToString(val) { return val != null ? val.toString().replace(/(~|\/)/g, function (m) { return {'~':'~~', '/':'~2F'}[m]; }) : val; } 1715 function valFromString(val) { return val != null ? val.toString().replace(/(~~|~2F)/g, function (m) { return {'~~':'~', '~2F':'/'}[m]; }) : val; } 1716 1717 var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = { 1718 "string": { 1719 encode: valToString, 1720 decode: valFromString, 1721 // TODO: in 1.0, make string .is() return false if value is undefined/null by default. 1722 // In 0.2.x, string params are optional by default for backwards compat 1723 is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; }, 1724 pattern: /[^/]*/ 1725 }, 1726 "int": { 1727 encode: valToString, 1728 decode: function(val) { return parseInt(val, 10); }, 1729 is: function(val) { return val !== undefined && val !== null && this.decode(val.toString()) === val; }, 1730 pattern: /-?\d+/ 1731 }, 1732 "bool": { 1733 encode: function(val) { return val ? 1 : 0; }, 1734 decode: function(val) { return parseInt(val, 10) !== 0; }, 1735 is: function(val) { return val === true || val === false; }, 1736 pattern: /0|1/ 1737 }, 1738 "date": { 1739 encode: function (val) { 1740 if (!this.is(val)) 1741 return undefined; 1742 return [ val.getFullYear(), 1743 ('0' + (val.getMonth() + 1)).slice(-2), 1744 ('0' + val.getDate()).slice(-2) 1745 ].join("-"); 1746 }, 1747 decode: function (val) { 1748 if (this.is(val)) return val; 1749 var match = this.capture.exec(val); 1750 return match ? new Date(match[1], match[2] - 1, match[3]) : undefined; 1751 }, 1752 is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); }, 1753 equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); }, 1754 pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/, 1755 capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/ 1756 }, 1757 "json": { 1758 encode: angular.toJson, 1759 decode: angular.fromJson, 1760 is: angular.isObject, 1761 equals: angular.equals, 1762 pattern: /[^/]*/ 1763 }, 1764 "any": { // does not encode/decode 1765 encode: angular.identity, 1766 decode: angular.identity, 1767 equals: angular.equals, 1768 pattern: /.*/ 1769 } 1770 }; 1771 1772 function getDefaultConfig() { 1773 return { 1774 strict: isStrictMode, 1775 caseInsensitive: isCaseInsensitive 1776 }; 1777 } 1778 1779 function isInjectable(value) { 1780 return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1]))); 1781 } 1782 1783 /** 1784 * [Internal] Get the default value of a parameter, which may be an injectable function. 1785 */ 1786 $UrlMatcherFactory.$$getDefaultValue = function(config) { 1787 if (!isInjectable(config.value)) return config.value; 1788 if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); 1789 return injector.invoke(config.value); 1790 }; 1791 1792 /** 1793 * @ngdoc function 1794 * @name ui.router.util.$urlMatcherFactory#caseInsensitive 1795 * @methodOf ui.router.util.$urlMatcherFactory 1796 * 1797 * @description 1798 * Defines whether URL matching should be case sensitive (the default behavior), or not. 1799 * 1800 * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`; 1801 * @returns {boolean} the current value of caseInsensitive 1802 */ 1803 this.caseInsensitive = function(value) { 1804 if (isDefined(value)) 1805 isCaseInsensitive = value; 1806 return isCaseInsensitive; 1807 }; 1808 1809 /** 1810 * @ngdoc function 1811 * @name ui.router.util.$urlMatcherFactory#strictMode 1812 * @methodOf ui.router.util.$urlMatcherFactory 1813 * 1814 * @description 1815 * Defines whether URLs should match trailing slashes, or not (the default behavior). 1816 * 1817 * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`. 1818 * @returns {boolean} the current value of strictMode 1819 */ 1820 this.strictMode = function(value) { 1821 if (isDefined(value)) 1822 isStrictMode = value; 1823 return isStrictMode; 1824 }; 1825 1826 /** 1827 * @ngdoc function 1828 * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy 1829 * @methodOf ui.router.util.$urlMatcherFactory 1830 * 1831 * @description 1832 * Sets the default behavior when generating or matching URLs with default parameter values. 1833 * 1834 * @param {string} value A string that defines the default parameter URL squashing behavior. 1835 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL 1836 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the 1837 * parameter is surrounded by slashes, squash (remove) one slash from the URL 1838 * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove) 1839 * the parameter value from the URL and replace it with this string. 1840 */ 1841 this.defaultSquashPolicy = function(value) { 1842 if (!isDefined(value)) return defaultSquashPolicy; 1843 if (value !== true && value !== false && !isString(value)) 1844 throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string"); 1845 defaultSquashPolicy = value; 1846 return value; 1847 }; 1848 1849 /** 1850 * @ngdoc function 1851 * @name ui.router.util.$urlMatcherFactory#compile 1852 * @methodOf ui.router.util.$urlMatcherFactory 1853 * 1854 * @description 1855 * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern. 1856 * 1857 * @param {string} pattern The URL pattern. 1858 * @param {Object} config The config object hash. 1859 * @returns {UrlMatcher} The UrlMatcher. 1860 */ 1861 this.compile = function (pattern, config) { 1862 return new UrlMatcher(pattern, extend(getDefaultConfig(), config)); 1863 }; 1864 1865 /** 1866 * @ngdoc function 1867 * @name ui.router.util.$urlMatcherFactory#isMatcher 1868 * @methodOf ui.router.util.$urlMatcherFactory 1869 * 1870 * @description 1871 * Returns true if the specified object is a `UrlMatcher`, or false otherwise. 1872 * 1873 * @param {Object} object The object to perform the type check against. 1874 * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by 1875 * implementing all the same methods. 1876 */ 1877 this.isMatcher = function (o) { 1878 if (!isObject(o)) return false;
1879 var result = true; 1880 1881 forEach(UrlMatcher.prototype, function(val, name) { 1882 if (isFunction(val)) { 1883 result = result && (isDefined(o[name]) && isFunction(o[name])); 1884 } 1885 }); 1886 return result; 1887 }; 1888 1889 /** 1890 * @ngdoc function 1891 * @name ui.router.util.$urlMatcherFactory#type 1892 * @methodOf ui.router.util.$urlMatcherFactory 1893 * 1894 * @description 1895 * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to 1896 * generate URLs with typed parameters. 1897 * 1898 * @param {string} name The type name. 1899 * @param {Object|Function} definition The type definition. See 1900 * {@link ui.router.util.type:Type `Type`} for information on the values accepted. 1901 * @param {Object|Function} definitionFn (optional) A function that is injected before the app 1902 * runtime starts. The result of this function is merged into the existing `definition`. 1903 * See {@link ui.router.util.type:Type `Type`} for information on the values accepted. 1904 * 1905 * @returns {Object} Returns `$urlMatcherFactoryProvider`. 1906 * 1907 * @example 1908 * This is a simple example of a custom type that encodes and decodes items from an 1909 * array, using the array index as the URL-encoded value: 1910 * 1911 * <pre> 1912 * var list = ['John', 'Paul', 'George', 'Ringo']; 1913 * 1914 * $urlMatcherFactoryProvider.type('listItem', { 1915 * encode: function(item) { 1916 * // Represent the list item in the URL using its corresponding index 1917 * return list.indexOf(item); 1918 * }, 1919 * decode: function(item) { 1920 * // Look up the list item by index 1921 * return list[parseInt(item, 10)]; 1922 * }, 1923 * is: function(item) { 1924 * // Ensure the item is valid by checking to see that it appears 1925 * // in the list 1926 * return list.indexOf(item) > -1; 1927 * } 1928 * }); 1929 * 1930 * $stateProvider.state('list', { 1931 * url: "/list/{item:listItem}", 1932 * controller: function($scope, $stateParams) { 1933 * console.log($stateParams.item); 1934 * } 1935 * }); 1936 * 1937 * // ... 1938 * 1939 * // Changes URL to '/list/3', logs "Ringo" to the console 1940 * $state.go('list', { item: "Ringo" }); 1941 * </pre> 1942 * 1943 * This is a more complex example of a type that relies on dependency injection to 1944 * interact with services, and uses the parameter name from the URL to infer how to 1945 * handle encoding and decoding parameter values: 1946 * 1947 * <pre> 1948 * // Defines a custom type that gets a value from a service, 1949 * // where each service gets different types of values from 1950 * // a backend API: 1951 * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) { 1952 * 1953 * // Matches up services to URL parameter names 1954 * var services = { 1955 * user: Users, 1956 * post: Posts 1957 * }; 1958 * 1959 * return { 1960 * encode: function(object) { 1961 * // Represent the object in the URL using its unique ID 1962 * return object.id; 1963 * }, 1964 * decode: function(value, key) { 1965 * // Look up the object by ID, using the parameter 1966 * // name (key) to call the correct service 1967 * return services[key].findById(value); 1968 * }, 1969 * is: function(object, key) { 1970 * // Check that object is a valid dbObject 1971 * return angular.isObject(object) && object.id && services[key]; 1972 * } 1973 * equals: function(a, b) { 1974 * // Check the equality of decoded objects by comparing 1975 * // their unique IDs 1976 * return a.id === b.id; 1977 * } 1978 * }; 1979 * }); 1980 * 1981 * // In a config() block, you can then attach URLs with 1982 * // type-annotated parameters: 1983 * $stateProvider.state('users', { 1984 * url: "/users", 1985 * // ... 1986 * }).state('users.item', { 1987 * url: "/{user:dbObject}", 1988 * controller: function($scope, $stateParams) { 1989 * // $stateParams.user will now be an object returned from 1990 * // the Users service 1991 * }, 1992 * // ... 1993 * }); 1994 * </pre> 1995 */ 1996 this.type = function (name, definition, definitionFn) { 1997 if (!isDefined(definition)) return $types[name]; 1998 if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined."); 1999 2000 $types[name] = new Type(extend({ name: name }, definition)); 2001 if (definitionFn) { 2002 typeQueue.push({ name: name, def: definitionFn }); 2003 if (!enqueue) flushTypeQueue(); 2004 } 2005 return this; 2006 }; 2007 2008 // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s 2009 function flushTypeQueue() { 2010 while(typeQueue.length) { 2011 var type = typeQueue.shift(); 2012 if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime."); 2013 angular.extend($types[type.name], injector.invoke(type.def)); 2014 } 2015 } 2016 2017 // Register default types. Store them in the prototype of $types. 2018 forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); }); 2019 $types = inherit($types, {}); 2020 2021 /* No need to document $get, since it returns this */ 2022 this.$get = ['$injector', function ($injector) { 2023 injector = $injector; 2024 enqueue = false;
2025 flushTypeQueue(); 2026 2027 forEach(defaultTypes, function(type, name) { 2028 if (!$types[name]) $types[name] = new Type(type); 2029 }); 2030 return this; 2031 }]; 2032 2033 this.Param = function Param(id, type, config, location) { 2034 var self = this; 2035 config = unwrapShorthand(config); 2036 type = getType(config, type, location); 2037 var arrayMode = getArrayMode(); 2038 type = arrayMode ? type.$asArray(arrayMode, location === "search") : type; 2039 if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined) 2040 config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to "" 2041 var isOptional = config.value !== undefined; 2042 var squash = getSquashPolicy(config, isOptional); 2043 var replace = getReplace(config, arrayMode, isOptional, squash); 2044 2045 function unwrapShorthand(config) { 2046 var keys = isObject(config) ? objectKeys(config) : []; 2047 var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 && 2048 indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1; 2049 if (isShorthand) config = { value: config }; 2050 config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; }; 2051 return config; 2052 } 2053 2054 function getType(config, urlType, location) { 2055 if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations."); 2056 if (urlType) return urlType; 2057 if (!config.type) return (location === "config" ? $types.any : $types.string); 2058 2059 if (angular.isString(config.type)) 2060 return $types[config.type]; 2061 if (config.type instanceof Type) 2062 return config.type; 2063 return new Type(config.type); 2064 } 2065 2066 // array config: param name (param[]) overrides default settings. explicit config overrides param name. 2067 function getArrayMode() { 2068 var arrayDefaults = { array: (location === "search" ? "auto" : false) }; 2069 var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {}; 2070 return extend(arrayDefaults, arrayParamNomenclature, config).array; 2071 } 2072 2073 /** 2074 * returns false, true, or the squash value to indicate the "default parameter url squash policy". 2075 */ 2076 function getSquashPolicy(config, isOptional) { 2077 var squash = config.squash; 2078 if (!isOptional || squash === false) return false; 2079 if (!isDefined(squash) || squash == null) return defaultSquashPolicy; 2080 if (squash === true || isString(squash)) return squash; 2081 throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string"); 2082 } 2083 2084 function getReplace(config, arrayMode, isOptional, squash) { 2085 var replace, configuredKeys, defaultPolicy = [ 2086 { from: "", to: (isOptional || arrayMode ? undefined : "") }, 2087 { from: null, to: (isOptional || arrayMode ? undefined : "") } 2088 ]; 2089 replace = isArray(config.replace) ? config.replace : []; 2090 if (isString(squash)) 2091 replace.push({ from: squash, to: undefined }); 2092 configuredKeys = map(replace, function(item) { return item.from; } ); 2093 return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace); 2094 } 2095 2096 /** 2097 * [Internal] Get the default value of a parameter, which may be an injectable function. 2098 */ 2099 function $$getDefaultValue() { 2100 if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); 2101 var defaultValue = injector.invoke(config.$$fn); 2102 if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue)) 2103 throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")"); 2104 return defaultValue; 2105 } 2106 2107 /** 2108 * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the 2109 * default value, which may be the result of an injectable function. 2110 */ 2111 function $value(value) { 2112 function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; } 2113 function $replace(value) { 2114 var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; }); 2115 return replacement.length ? replacement[0] : value; 2116 } 2117 value = $replace(value); 2118 return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value); 2119 } 2120 2121 function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; } 2122 2123 extend(this, { 2124 id: id, 2125 type: type, 2126 location: location, 2127 array: arrayMode, 2128 squash: squash, 2129 replace: replace, 2130 isOptional: isOptional, 2131 value: $value, 2132 dynamic: undefined, 2133 config: config, 2134 toString: toString 2135 }); 2136 }; 2137 2138 function ParamSet(params) { 2139 extend(this, params || {}); 2140 } 2141 2142 ParamSet.prototype = { 2143 $$new: function() { 2144 return inherit(this, extend(new ParamSet(), { $$parent: this})); 2145 }, 2146 $$keys: function () { 2147 var keys = [], chain = [], parent = this, 2148 ignore = objectKeys(ParamSet.prototype); 2149 while (parent) { chain.push(parent); parent = parent.$$parent; } 2150 chain.reverse();
2151 forEach(chain, function(paramset) { 2152 forEach(objectKeys(paramset), function(key) { 2153 if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key); 2154 }); 2155 }); 2156 return keys; 2157 }, 2158 $$values: function(paramValues) { 2159 var values = {}, self = this; 2160 forEach(self.$$keys(), function(key) { 2161 values[key] = self[key].value(paramValues && paramValues[key]); 2162 }); 2163 return values; 2164 }, 2165 $$equals: function(paramValues1, paramValues2) { 2166 var equal = true, self = this; 2167 forEach(self.$$keys(), function(key) { 2168 var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key]; 2169 if (!self[key].type.equals(left, right)) equal = false; 2170 }); 2171 return equal; 2172 }, 2173 $$validates: function $$validate(paramValues) { 2174 var keys = this.$$keys(), i, param, rawVal, normalized, encoded; 2175 for (i = 0; i < keys.length; i++) { 2176 param = this[keys[i]]; 2177 rawVal = paramValues[keys[i]]; 2178 if ((rawVal === undefined || rawVal === null) && param.isOptional) 2179 break; // There was no parameter value, but the param is optional 2180 normalized = param.type.$normalize(rawVal); 2181 if (!param.type.is(normalized)) 2182 return false; // The value was not of the correct Type, and could not be decoded to the correct Type 2183 encoded = param.type.encode(normalized); 2184 if (angular.isString(encoded) && !param.type.pattern.exec(encoded)) 2185 return false; // The value was of the correct type, but when encoded, did not match the Type's regexp 2186 } 2187 return true; 2188 }, 2189 $$parent: undefined 2190 }; 2191 2192 this.ParamSet = ParamSet; 2193} 2194 2195// Register as a provider so it's available to other providers 2196angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory); 2197angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]); 2198 2199/** 2200 * @ngdoc object 2201 * @name ui.router.router.$urlRouterProvider 2202 * 2203 * @requires ui.router.util.$urlMatcherFactoryProvider 2204 * @requires $locationProvider 2205 * 2206 * @description 2207 * `$urlRouterProvider` has the responsibility of watching `$location`. 2208 * When `$location` changes it runs through a list of rules one by one until a 2209 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify 2210 * a url in a state configuration. All urls are compiled into a UrlMatcher object. 2211 * 2212 * There are several methods on `$urlRouterProvider` that make it useful to use directly 2213 * in your module config. 2214 */ 2215$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider']; 2216function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { 2217 var rules = [], otherwise = null, interceptDeferred = false, listener; 2218 2219 // Returns a string that is a prefix of all strings matching the RegExp 2220 function regExpPrefix(re) { 2221 var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source); 2222 return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : ''; 2223 } 2224 2225 // Interpolates matched values into a String.replace()-style pattern 2226 function interpolate(pattern, match) { 2227 return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) { 2228 return match[what === '$' ? 0 : Number(what)]; 2229 }); 2230 } 2231 2232 /** 2233 * @ngdoc function 2234 * @name ui.router.router.$urlRouterProvider#rule 2235 * @methodOf ui.router.router.$urlRouterProvider 2236 * 2237 * @description 2238 * Defines rules that are used by `$urlRouterProvider` to find matches for 2239 * specific URLs. 2240 * 2241 * @example 2242 * <pre> 2243 * var app = angular.module('app', ['ui.router.router']); 2244 * 2245 * app.config(function ($urlRouterProvider) { 2246 * // Here's an example of how you might allow case insensitive urls 2247 * $urlRouterProvider.rule(function ($injector, $location) { 2248 * var path = $location.path(), 2249 * normalized = path.toLowerCase(); 2250 * 2251 * if (path !== normalized) { 2252 * return normalized; 2253 * } 2254 * }); 2255 * }); 2256 * </pre> 2257 * 2258 * @param {function} rule Handler function that takes `$injector` and `$location` 2259 * services as arguments. You can use them to return a valid path as a string. 2260 * 2261 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance 2262 */ 2263 this.rule = function (rule) { 2264 if (!isFunction(rule)) throw new Error("'rule' must be a function"); 2265 rules.push(rule); 2266 return this; 2267 }; 2268 2269 /** 2270 * @ngdoc object 2271 * @name ui.router.router.$urlRouterProvider#otherwise 2272 * @methodOf ui.router.router.$urlRouterProvider 2273 * 2274 * @description
2275 * Defines a path that is used when an invalid route is requested. 2276 * 2277 * @example 2278 * <pre> 2279 * var app = angular.module('app', ['ui.router.router']); 2280 * 2281 * app.config(function ($urlRouterProvider) { 2282 * // if the path doesn't match any of the urls you configured 2283 * // otherwise will take care of routing the user to the 2284 * // specified url 2285 * $urlRouterProvider.otherwise('/index'); 2286 * 2287 * // Example of using function rule as param 2288 * $urlRouterProvider.otherwise(function ($injector, $location) { 2289 * return '/a/valid/url'; 2290 * }); 2291 * }); 2292 * </pre> 2293 * 2294 * @param {string|function} rule The url path you want to redirect to or a function 2295 * rule that returns the url path. The function version is passed two params: 2296 * `$injector` and `$location` services, and must return a url string. 2297 * 2298 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance 2299 */ 2300 this.otherwise = function (rule) { 2301 if (isString(rule)) { 2302 var redirect = rule; 2303 rule = function () { return redirect; }; 2304 } 2305 else if (!isFunction(rule)) throw new Error("'rule' must be a function"); 2306 otherwise = rule; 2307 return this; 2308 }; 2309 2310 2311 function handleIfMatch($injector, handler, match) { 2312 if (!match) return false; 2313 var result = $injector.invoke(handler, handler, { $match: match }); 2314 return isDefined(result) ? result : true; 2315 } 2316 2317 /** 2318 * @ngdoc function 2319 * @name ui.router.router.$urlRouterProvider#when 2320 * @methodOf ui.router.router.$urlRouterProvider 2321 * 2322 * @description 2323 * Registers a handler for a given url matching. 2324 * 2325 * If the handler is a string, it is 2326 * treated as a redirect, and is interpolated according to the syntax of match 2327 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise). 2328 * 2329 * If the handler is a function, it is injectable. It gets invoked if `$location` 2330 * matches. You have the option of inject the match object as `$match`. 2331 * 2332 * The handler can return 2333 * 2334 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` 2335 * will continue trying to find another one that matches. 2336 * - **string** which is treated as a redirect and passed to `$location.url()` 2337 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. 2338 * 2339 * @example 2340 * <pre> 2341 * var app = angular.module('app', ['ui.router.router']); 2342 * 2343 * app.config(function ($urlRouterProvider) { 2344 * $urlRouterProvider.when($state.url, function ($match, $stateParams) { 2345 * if ($state.$current.navigable !== state || 2346 * !equalForKeys($match, $stateParams) { 2347 * $state.transitionTo(state, $match, false); 2348 * } 2349 * }); 2350 * }); 2351 * </pre> 2352 * 2353 * @param {string|object} what The incoming path that you want to redirect. 2354 * @param {string|function} handler The path you want to redirect your user to. 2355 */ 2356 this.when = function (what, handler) { 2357 var redirect, handlerIsString = isString(handler); 2358 if (isString(what)) what = $urlMatcherFactory.compile(what); 2359 2360 if (!handlerIsString && !isFunction(handler) && !isArray(handler)) 2361 throw new Error("invalid 'handler' in when()"); 2362 2363 var strategies = { 2364 matcher: function (what, handler) { 2365 if (handlerIsString) { 2366 redirect = $urlMatcherFactory.compile(handler); 2367 handler = ['$match', function ($match) { return redirect.format($match); }]; 2368 } 2369 return extend(function ($injector, $location) { 2370 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); 2371 }, { 2372 prefix: isString(what.prefix) ? what.prefix : '' 2373 }); 2374 }, 2375 regex: function (what, handler) { 2376 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); 2377 2378 if (handlerIsString) { 2379 redirect = handler; 2380 handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; 2381 } 2382 return extend(function ($injector, $location) { 2383 return handleIfMatch($injector, handler, what.exec($location.path())); 2384 }, { 2385 prefix: regExpPrefix(what) 2386 }); 2387 } 2388 }; 2389 2390 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; 2391 2392 for (var n in check) { 2393 if (check[n]) return this.rule(strategies[n](what, handler)); 2394 } 2395 2396 throw new Error("invalid 'what' in when()"); 2397 }; 2398 2399 /** 2400 * @ngdoc function 2401 * @name ui.router.router.$urlRouterProvider#deferIntercept 2402 * @methodOf ui.router.router.$urlRouterProvider 2403 * 2404 * @description 2405 * Disables (or enables) deferring location change interception. 2406 * 2407 * If you wish to customize the behavior of syncing the URL (for example, if you wish to 2408 * defer a transition but maintain the current URL), call this method at configuration time. 2409 * Then, at run time, call `$urlRouter.listen()` after you have configured your own 2410 * `$locationChangeSuccess` event handler. 2411 * 2412 * @example 2413 * <pre> 2414 * var app = angular.module('app', ['ui.router.router']); 2415 * 2416 * app.config(function ($urlRouterProvider) { 2417 * 2418 * // Prevent $urlRouter from automatically intercepting URL changes; 2419 * // this allows you to configure custom behavior in between 2420 * // location changes and route synchronization: 2421 * $urlRouterProvider.deferIntercept(); 2422 * 2423 * }).run(function ($rootScope, $urlRouter, UserService) { 2424 * 2425 * $rootScope.$on('$locationChangeSuccess', function(e) { 2426 * // UserService is an example service for managing user state 2427 * if (UserService.isLoggedIn()) return; 2428 * 2429 * // Prevent $urlRouter's default handler from firing 2430 * e.preventDefault(); 2431 * 2432 * UserService.handleLogin().then(function() { 2433 * // Once the user has logged in, sync the current URL 2434 * // to the router: 2435 * $urlRouter.sync(); 2436 * }); 2437 * }); 2438 * 2439 * // Configures $urlRouter's listener *after* your custom listener 2440 * $urlRouter.listen(); 2441 * }); 2442 * </pre> 2443 * 2444 * @param {boolean} defer Indicates whether to defer location change interception. Passing 2445 no parameter is equivalent to `true`. 2446 */ 2447 this.deferIntercept = function (defer) { 2448 if (defer === undefined) defer = true; 2449 interceptDeferred = defer; 2450 }; 2451 2452 /** 2453 * @ngdoc object 2454 * @name ui.router.router.$urlRouter 2455 * 2456 * @requires $location 2457 * @requires $rootScope 2458 * @requires $injector 2459 * @requires $browser 2460 * 2461 * @description 2462 * 2463 */ 2464 this.$get = $get; 2465 $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer']; 2466 function $get( $location, $rootScope, $injector, $browser, $sniffer) { 2467 2468 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl; 2469 2470 function appendBasePath(url, isHtml5, absolute) { 2471 if (baseHref === '/') return url; 2472 if (isHtml5) return baseHref.slice(0, -1) + url; 2473 if (absolute) return baseHref.slice(1) + url; 2474 return url; 2475 } 2476 2477 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree 2478 function update(evt) { 2479 if (evt && evt.defaultPrevented) return; 2480 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; 2481 lastPushedUrl = undefined; 2482 // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573 2483 //if (ignoreUpdate) return true; 2484 2485 function check(rule) { 2486 var handled = rule($injector, $location); 2487 2488 if (!handled) return false;
2489 if (isString(handled)) $location.replace().url(handled); 2490 return true; 2491 } 2492 var n = rules.length, i; 2493 2494 for (i = 0; i < n; i++) { 2495 if (check(rules[i])) return; 2496 } 2497 // always check otherwise last to allow dynamic updates to the set of rules 2498 if (otherwise) check(otherwise); 2499 } 2500 2501 function listen() { 2502 listener = listener || $rootScope.$on('$locationChangeSuccess', update); 2503 return listener; 2504 } 2505 2506 if (!interceptDeferred) listen(); 2507 2508 return { 2509 /** 2510 * @ngdoc function 2511 * @name ui.router.router.$urlRouter#sync 2512 * @methodOf ui.router.router.$urlRouter 2513 * 2514 * @description 2515 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`. 2516 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event, 2517 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed 2518 * with the transition by calling `$urlRouter.sync()`. 2519 * 2520 * @example 2521 * <pre> 2522 * angular.module('app', ['ui.router']) 2523 * .run(function($rootScope, $urlRouter) { 2524 * $rootScope.$on('$locationChangeSuccess', function(evt) { 2525 * // Halt state change from even starting 2526 * evt.preventDefault(); 2527 * // Perform custom logic 2528 * var meetsRequirement = ... 2529 * // Continue with the update and state transition if logic allows 2530 * if (meetsRequirement) $urlRouter.sync(); 2531 * }); 2532 * }); 2533 * </pre> 2534 */ 2535 sync: function() { 2536 update(); 2537 }, 2538 2539 listen: function() { 2540 return listen(); 2541 }, 2542 2543 update: function(read) { 2544 if (read) { 2545 location = $location.url(); 2546 return; 2547 } 2548 if ($location.url() === location) return; 2549 2550 $location.url(location); 2551 $location.replace(); 2552 }, 2553 2554 push: function(urlMatcher, params, options) { 2555 var url = urlMatcher.format(params || {}); 2556 2557 // Handle the special hash param, if needed 2558 if (url !== null && params && params['#']) { 2559 url += '#' + params['#']; 2560 } 2561 2562 $location.url(url); 2563 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined; 2564 if (options && options.replace) $location.replace(); 2565 }, 2566 2567 /** 2568 * @ngdoc function 2569 * @name ui.router.router.$urlRouter#href 2570 * @methodOf ui.router.router.$urlRouter 2571 * 2572 * @description 2573 * A URL generation method that returns the compiled URL for a given 2574 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters. 2575 * 2576 * @example 2577 * <pre> 2578 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), { 2579 * person: "bob" 2580 * }); 2581 * // $bob == "/about/bob"; 2582 * </pre> 2583 * 2584 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate. 2585 * @param {object=} params An object of parameter values to fill the matcher's required parameters. 2586 * @param {object=} options Options object. The options are: 2587 * 2588 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". 2589 * 2590 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher` 2591 */ 2592 href: function(urlMatcher, params, options) { 2593 if (!urlMatcher.validates(params)) return null; 2594 2595 var isHtml5 = $locationProvider.html5Mode(); 2596 if (angular.isObject(isHtml5)) { 2597 isHtml5 = isHtml5.enabled; 2598 } 2599 2600 isHtml5 = isHtml5 && $sniffer.history; 2601 2602 var url = urlMatcher.format(params); 2603 options = options || {}; 2604 2605 if (!isHtml5 && url !== null) { 2606 url = "#" + $locationProvider.hashPrefix() + url; 2607 } 2608 2609 // Handle special hash param, if needed 2610 if (url !== null && params && params['#']) { 2611 url += '#' + params['#']; 2612 } 2613 2614 url = appendBasePath(url, isHtml5, options.absolute); 2615 2616 if (!options.absolute || !url) { 2617 return url; 2618 } 2619 2620 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port(); 2621 port = (port === 80 || port === 443 ? '' : ':' + port); 2622 2623 return [$location.protocol(), '://', $location.host(), port, slash, url].join(''); 2624 } 2625 }; 2626 } 2627} 2628 2629angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider); 2630 2631/** 2632 * @ngdoc object 2633 * @name ui.router.state.$stateProvider 2634 * 2635 * @requires ui.router.router.$urlRouterProvider
2636 * @requires ui.router.util.$urlMatcherFactoryProvider 2637 * 2638 * @description 2639 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely 2640 * on state. 2641 * 2642 * A state corresponds to a "place" in the application in terms of the overall UI and 2643 * navigation. A state describes (via the controller / template / view properties) what 2644 * the UI looks like and does at that place. 2645 * 2646 * States often have things in common, and the primary way of factoring out these 2647 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka 2648 * nested states. 2649 * 2650 * The `$stateProvider` provides interfaces to declare these states for your app. 2651 */ 2652$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider']; 2653function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { 2654 2655 var root, states = {}, $state, queue = {}, abstractKey = 'abstract'; 2656 2657 // Builds state properties from definition passed to registerState() 2658 var stateBuilder = { 2659 2660 // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined. 2661 // state.children = []; 2662 // if (parent) parent.children.push(state); 2663 parent: function(state) { 2664 if (isDefined(state.parent) && state.parent) return findState(state.parent); 2665 // regex matches any valid composite state name 2666 // would match "contact.list" but not "contacts" 2667 var compositeName = /^(.+)\.[^.]+$/.exec(state.name); 2668 return compositeName ? findState(compositeName[1]) : root; 2669 }, 2670 2671 // inherit 'data' from parent and override by own values (if any) 2672 data: function(state) { 2673 if (state.parent && state.parent.data) { 2674 state.data = state.self.data = inherit(state.parent.data, state.data); 2675 } 2676 return state.data; 2677 }, 2678 2679 // Build a URLMatcher if necessary, either via a relative or absolute URL 2680 url: function(state) { 2681 var url = state.url, config = { params: state.params || {} }; 2682 2683 if (isString(url)) { 2684 if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config); 2685 return (state.parent.navigable || root).url.concat(url, config); 2686 } 2687 2688 if (!url || $urlMatcherFactory.isMatcher(url)) return url; 2689 throw new Error("Invalid url '" + url + "' in state '" + state + "'"); 2690 }, 2691 2692 // Keep track of the closest ancestor state that has a URL (i.e. is navigable) 2693 navigable: function(state) { 2694 return state.url ? state : (state.parent ? state.parent.navigable : null); 2695 }, 2696 2697 // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params 2698 ownParams: function(state) { 2699 var params = state.url && state.url.params || new $$UMFP.ParamSet(); 2700 forEach(state.params || {}, function(config, id) { 2701 if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config"); 2702 }); 2703 return params; 2704 }, 2705 2706 // Derive parameters for this state and ensure they're a super-set of parent's parameters 2707 params: function(state) { 2708 var ownParams = pick(state.ownParams, state.ownParams.$$keys()); 2709 return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet(); 2710 }, 2711 2712 // If there is no explicit multi-view configuration, make one up so we don't have 2713 // to handle both cases in the view directive later. Note that having an explicit 2714 // 'views' property will mean the default unnamed view properties are ignored. This 2715 // is also a good time to resolve view names to absolute names, so everything is a 2716 // straight lookup at link time. 2717 views: function(state) { 2718 var views = {}; 2719 2720 forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) { 2721 if (name.indexOf('@') < 0) name += '@' + state.parent.name; 2722 view.resolveAs = view.resolveAs || state.resolveAs || '$resolve'; 2723 views[name] = view; 2724 }); 2725 return views; 2726 }, 2727 2728 // Keep a full path from the root down to this state as this is needed for state activation. 2729 path: function(state) { 2730 return state.parent ? state.parent.path.concat(state) : []; // exclude root from path 2731 }, 2732 2733 // Speed up $state.contains() as it's used a lot 2734 includes: function(state) { 2735 var includes = state.parent ? extend({}, state.parent.includes) : {}; 2736 includes[state.name] = true; 2737 return includes; 2738 }, 2739 2740 $delegates: {} 2741 }; 2742 2743 function isRelative(stateName) { 2744 return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; 2745 } 2746 2747 function findState(stateOrName, base) { 2748 if (!stateOrName) return undefined; 2749 2750 var isStr = isString(stateOrName), 2751 name = isStr ? stateOrName : stateOrName.name, 2752 path = isRelative(name); 2753 2754 if (path) { 2755 if (!base) throw new Error("No reference point given for path '" + name + "'"); 2756 base = findState(base); 2757 2758 var rel = name.split("."), i = 0, pathLength = rel.length, current = base; 2759 2760 for (; i < pathLength; i++) { 2761 if (rel[i] === "" && i === 0) { 2762 current = base; 2763 continue; 2764 } 2765 if (rel[i] === "^") { 2766 if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'"); 2767 current = current.parent; 2768 continue; 2769 } 2770 break; 2771 } 2772 rel = rel.slice(i).join("."); 2773 name = current.name + (current.name && rel ? "." : "") + rel; 2774 } 2775 var state = states[name]; 2776 2777 if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { 2778 return state; 2779 } 2780 return undefined; 2781 } 2782 2783 function queueState(parentName, state) { 2784 if (!queue[parentName]) { 2785 queue[parentName] = []; 2786 } 2787 queue[parentName].push(state); 2788 } 2789 2790 function flushQueuedChildren(parentName) { 2791 var queued = queue[parentName] || []; 2792 while(queued.length) { 2793 registerState(queued.shift()); 2794 } 2795 } 2796 2797 function registerState(state) { 2798 // Wrap a new object around the state so we can store our private details easily. 2799 state = inherit(state, { 2800 self: state, 2801 resolve: state.resolve || {}, 2802 toString: function() { return this.name; } 2803 }); 2804 2805 var name = state.name; 2806 if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name"); 2807 if (states.hasOwnProperty(name)) throw new Error("State '" + n
2807ame + "' is already defined"); 2808 2809 // Get parent name 2810 var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.')) 2811 : (isString(state.parent)) ? state.parent 2812 : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name 2813 : ''; 2814 2815 // If parent is not registered yet, add state to queue and register later 2816 if (parentName && !states[parentName]) { 2817 return queueState(parentName, state.self); 2818 } 2819 2820 for (var key in stateBuilder) { 2821 if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]); 2822 } 2823 states[name] = state; 2824 2825 // Register the state in the global state list and with $urlRouter if necessary. 2826 if (!state[abstractKey] && state.url) { 2827 $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { 2828 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { 2829 $state.transitionTo(state, $match, { inherit: true, location: false }); 2830 } 2831 }]); 2832 } 2833 2834 // Register any queued children 2835 flushQueuedChildren(name); 2836 2837 return state; 2838 } 2839 2840 // Checks text to see if it looks like a glob. 2841 function isGlob (text) { 2842 return text.indexOf('*') > -1; 2843 } 2844 2845 // Returns true if glob matches current $state name. 2846 function doesStateMatchGlob (glob) { 2847 var globSegments = glob.split('.'), 2848 segments = $state.$current.name.split('.'); 2849 2850 //match single stars 2851 for (var i = 0, l = globSegments.length; i < l; i++) { 2852 if (globSegments[i] === '*') { 2853 segments[i] = '*'; 2854 } 2855 } 2856 2857 //match greedy starts 2858 if (globSegments[0] === '**') { 2859 segments = segments.slice(indexOf(segments, globSegments[1])); 2860 segments.unshift('**'); 2861 } 2862 //match greedy ends 2863 if (globSegments[globSegments.length - 1] === '**') { 2864 segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE); 2865 segments.push('**'); 2866 } 2867 2868 if (globSegments.length != segments.length) { 2869 return false; 2870 } 2871 2872 return segments.join('') === globSegments.join(''); 2873 } 2874 2875 2876 // Implicit root state that is always active 2877 root = registerState({ 2878 name: '', 2879 url: '^', 2880 views: null, 2881 'abstract': true 2882 }); 2883 root.navigable = null; 2884 2885 2886 /** 2887 * @ngdoc function 2888 * @name ui.router.state.$stateProvider#decorator 2889 * @methodOf ui.router.state.$stateProvider 2890 * 2891 * @description 2892 * Allows you to extend (carefully) or override (at your own peril) the 2893 * `stateBuilder` object used internally by `$stateProvider`. This can be used 2894 * to add custom functionality to ui-router, for example inferring templateUrl 2895 * based on the state name. 2896 * 2897 * When passing only a name, it returns the current (original or decorated) builder 2898 * function that matches `name`. 2899 * 2900 * The builder functions that can be decorated are listed below. Though not all 2901 * necessarily have a good use case for decoration, that is up to you to decide. 2902 * 2903 * In addition, users can attach custom decorators, which will generate new 2904 * properties within the state's internal definition. There is currently no clear 2905 * use-case for this beyond accessing internal states (i.e. $state.$current), 2906 * however, expect this to become increasingly relevant as we introduce additional 2907 * meta-programming features. 2908 * 2909 * **Warning**: Decorators should not be interdependent because the order of 2910 * execution of the builder functions in non-deterministic. Builder functions 2911 * should only be dependent on the state definition object and super function. 2912 * 2913 * 2914 * Existing builder functions and current return values: 2915 * 2916 * - **parent** `{object}` - returns the parent state object. 2917 * - **data** `{object}` - returns state data, including any inherited data that is not 2918 * overridden by own values (if any). 2919 * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher} 2920 * or `null`. 2921 * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is 2922 * navigable). 2923 * - **params** `{object}` - returns an array of state params that are ensured to 2924 * be a super-set of parent's params. 2925 * - **views** `{object}` - returns a views object where each key is an absolute view 2926 * name (i.e. "viewName@stateName") and each value is the config object 2927 * (template, controller) for the view. Even when you don't use the views object 2928 * explicitly on a state config, one is still created for you internally. 2929 * So by decorating this builder function you have access to decorating template 2930 * and controller properties. 2931 * - **ownParams** `{object}` - returns an array of params that belong to the state, 2932 * not including any params defined by ancestor states. 2933 * - **path** `{string}` - returns the full path from the root down to this state. 2934 * Needed for state activation. 2935 * - **includes** `{object}` - returns an object that includes every state that 2936 * would pass a `$state.includes()` test. 2937 * 2938 * @example 2939 * <pre> 2940 * // Override the internal 'views' builder with a function that takes the state 2941 * // definition, and a reference to the internal function being overridden: 2942 * $stateProvider.decorator('views', function (state, parent) { 2943 * var result = {}, 2944 * views = parent(state); 2945 * 2946 * angular.forEach(views, function (config, name) { 2947 * var autoName = (state.name + '.' + name).replace('.', '/'); 2948 * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html'; 2949 * result[name] = config; 2950 * }); 2951 * return result; 2952 * }); 2953 * 2954 * $stateProvider.state('home', { 2955 * views: { 2956 * 'contact.list': { controller: 'ListController' },
2957 * 'contact.item': { controller: 'ItemController' } 2958 * } 2959 * }); 2960 * 2961 * // ... 2962 * 2963 * $state.go('home'); 2964 * // Auto-populates list and item views with /partials/home/contact/list.html, 2965 * // and /partials/home/contact/item.html, respectively. 2966 * </pre> 2967 * 2968 * @param {string} name The name of the builder function to decorate. 2969 * @param {object} func A function that is responsible for decorating the original 2970 * builder function. The function receives two parameters: 2971 * 2972 * - `{object}` - state - The state config object. 2973 * - `{object}` - super - The original builder function. 2974 * 2975 * @return {object} $stateProvider - $stateProvider instance 2976 */ 2977 this.decorator = decorator; 2978 function decorator(name, func) { 2979 /*jshint validthis: true */ 2980 if (isString(name) && !isDefined(func)) { 2981 return stateBuilder[name]; 2982 } 2983 if (!isFunction(func) || !isString(name)) { 2984 return this; 2985 } 2986 if (stateBuilder[name] && !stateBuilder.$delegates[name]) { 2987 stateBuilder.$delegates[name] = stateBuilder[name]; 2988 } 2989 stateBuilder[name] = func; 2990 return this; 2991 } 2992 2993 /** 2994 * @ngdoc function 2995 * @name ui.router.state.$stateProvider#state 2996 * @methodOf ui.router.state.$stateProvider 2997 * 2998 * @description 2999 * Registers a state configuration under a given state name. The stateConfig object 3000 * has the following acceptable properties. 3001 * 3002 * @param {string} name A unique state name, e.g. "home", "about", "contacts". 3003 * To create a parent/child state use a dot, e.g. "about.sales", "home.newest". 3004 * @param {object} stateConfig State configuration object. 3005 * @param {string|function=} stateConfig.template 3006 * <a id='template'></a> 3007 * html template as a string or a function that returns 3008 * an html template as a string which should be used by the uiView directives. This property 3009 * takes precedence over templateUrl. 3010 * 3011 * If `template` is a function, it will be called with the following parameters: 3012 * 3013 * - {array.<object>} - state parameters extracted from the current $location.path() by 3014 * applying the current state 3015 * 3016 * <pre>template: 3017 * "<h1>inline template definition</h1>" + 3018 * "<div ui-view></div>"</pre> 3019 * <pre>template: function(params) { 3020 * return "<h1>generated template</h1>"; }</pre> 3021 * </div> 3022 * 3023 * @param {string|function=} stateConfig.templateUrl 3024 * <a id='templateUrl'></a> 3025 * 3026 * path or function that returns a path to an html 3027 * template that should be used by uiView. 3028 * 3029 * If `templateUrl` is a function, it will be called with the following parameters: 3030 * 3031 * - {array.<object>} - state parameters extracted from the current $location.path() by 3032 * applying the current state 3033 * 3034 * <pre>templateUrl: "home.html"</pre> 3035 * <pre>templateUrl: function(params) { 3036 * return myTemplates[params.pageId]; }</pre> 3037 * 3038 * @param {function=} stateConfig.templateProvider 3039 * <a id='templateProvider'></a> 3040 * Provider function that returns HTML content string. 3041 * <pre> templateProvider: 3042 * function(MyTemplateService, params) { 3043 * return MyTemplateService.getTemplate(params.pageId); 3044 * }</pre> 3045 * 3046 * @param {string|function=} stateConfig.controller 3047 * <a id='controller'></a> 3048 * 3049 * Controller fn that should be associated with newly 3050 * related scope or the name of a registered controller if passed as a string. 3051 * Optionally, the ControllerAs may be declared here. 3052 * <pre>controller: "MyRegisteredController"</pre> 3053 * <pre>controller: 3054 * "MyRegisteredController as fooCtrl"}</pre> 3055 * <pre>controller: function($scope, MyService) { 3056 * $scope.data = MyService.getData(); }</pre> 3057 * 3058 * @param {function=} stateConfig.controllerProvider 3059 * <a id='controllerProvider'></a> 3060 * 3061 * Injectable provider function that returns the actual controller or string. 3062 * <pre>controllerProvider: 3063 * function(MyResolveData) { 3064 * if (MyResolveData.foo) 3065 * return "FooCtrl" 3066 * else if (MyResolveData.bar) 3067 * return "BarCtrl"; 3068 * else return function($scope) { 3069 * $scope.baz = "Qux"; 3070 * } 3071 * }</pre> 3072 * 3073 * @param {string=} stateConfig.controllerAs 3074 * <a id='controllerAs'></a> 3075 * 3076 * A controller alias name. If present the controller will be 3077 * published to scope under the controllerAs name. 3078 * <pre>controllerAs: "myCtrl"</pre> 3079 * 3080 * @param {string|object=} stateConfig.parent 3081 * <a id='parent'></a> 3082 * Optionally specifies the parent state of this state. 3083 * 3084 * <pre>parent: 'parentState'</pre> 3085 * <pre>parent: parentState // JS variable</pre> 3086 * 3087 * @param {object=} stateConfig.resolve 3088 * <a id='resolve'></a> 3089 * 3090 * An optional map<string, function> of dependencies which 3091 * should be injected into the controller. If any of these dependencies are promises, 3092 * the router will wait for them all to be resolved before the controller is instantiated. 3093 * If all the promises are resolved successfully, the $stateChangeSuccess event is fired 3094 * and the values of the resolved promises are injected into any controllers that reference them. 3095 * If any of the promises are rejected the $stateChangeError event is fired. 3096 * 3097 * The map object is: 3098 * 3099 * - key - {string}: name of dependency to be injected into controller 3100 * - factory - {string|function}: If string then it is alias for service. Otherwise if function, 3101 * it is injected and return value it treated as dependency. If result is a promise, it is 3102 * resolved before its value is injected into controller. 3103 * 3104 * <pre>resolve: { 3105 * myResolve1: 3106 * function($http, $stateParams) { 3107 * return $http.get("/api/foos/"+stateParams.fooID); 3108 * } 3109 * }</pre> 3110 * 3111 * @param {string=} stateConfig.url 3112 * <a id='url'></a> 3113 * 3114 * A url fragment with optional parameters. When a state is navigated or 3115 * transitioned to, the `$stateParams` service will be populated with any 3116 * parameters that were passed. 3117 * 3118 * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for 3119 * more details on acceptable patterns ) 3120 * 3121 * examples: 3122 * <pre>url: "/home" 3123 * url: "/users/:userid" 3124 * url: "/books/{bookid:[a-zA-Z_-]}" 3125 * url: "/books/{categoryid:int}" 3126 * url: "/books/{publishername:string}/{categoryid:int}" 3127 * url: "/messages?before&after" 3128 * url: "/messages?{before:date}&{after:date}" 3129 * url: "/messages/:mailboxid?{before:date}&{after:date}" 3130 * </pre> 3131 * 3132 * @param {object=} stateConfig.views 3133 * <a id='views'></a> 3134 * an optional map<string, object> which defined multiple views, or targets views 3135 * manually/explicitly. 3136 * 3137 * Examples: 3138 * 3139 * Targets three named `ui-view`s in the parent state's template 3140 * <pre>views: { 3141 * header: { 3142 * controller: "headerCtrl", 3143 * templateUrl: "header.html" 3144 * }, body: { 3145 * controller: "bodyCtrl", 3146 * templateUrl: "body.html" 3147 * }, footer: { 3148 * controller: "footCtrl", 3149 * templateUrl: "footer.html" 3150 * } 3151 * }</pre> 3152 * 3153 * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template. 3154 * <pre>views: { 3155 * 'header@top': { 3156 * controller: "msgHeaderCtrl", 3157 * templateUrl: "msgHeader.html" 3158 * }, 'body': { 3159 * controller: "messagesCtrl", 3160 * templateUrl: "messages.html" 3161 * } 3162 * }</pre> 3163 * 3164 * @param {boolean=} [stateConfig.abstract=false] 3165 * <a id='abstract'></a> 3166 * An abstract state will never be directly activated, 3167 * but can provide inherited properties to its common children states. 3168 * <pre>abstract: true</pre> 3169 * 3170 * @param {function=} stateConfig.onEnter 3171 * <a id='onEnter'></a> 3172 * 3173 * Callback function for when a state is entered. Good way 3174 * to trigger an action or dispatch an event, such as opening a dialog. 3175 * If minifying your scripts, make sure to explicitly annotate this function, 3176 * because it won't be automatically annotated by your build tools. 3177 * 3178 * <pre>onEnter: function(MyService, $stateParams) { 3179 * MyService.foo($stateParams.myParam); 3180 * }</pre> 3181 * 3182 * @param {function=} stateConfig.onExit 3183 * <a id='onExit'></a> 3184 * 3185 * Callback function for when a state is exited. Good way to 3186 * trigger an action or dispatch an event, such as opening a dialog. 3187 * If minifying your scripts, make sure to explicitly annotate this function, 3188 * because it won't be automatically annotated by your build tools. 3189 * 3190 * <pre>onExit: function(MyService, $stateParams) { 3191 * MyService.cleanup($stateParams.myParam); 3192 * }</pre> 3193 * 3194 * @param {boolean=} [stateConfig.reloadOnSearch=true] 3195 * <a id='reloadOnSearch'></a> 3196 * 3197 * If `false`, will not retrigger the same state 3198 * just because a search/query parameter has changed (via $location.search() or $location.hash()). 3199 * Useful for when you'd like to modify $location.search() without triggering a reload. 3200 * <pre>reloadOnSearch: false</pre> 3201 * 3202 * @param {object=} stateConfig.data 3203 * <a id='data'></a> 3204 * 3205 * Arbitrary data object, useful for custom configuration. The parent state's `data` is 3206 * prototypally inherited. In other words, adding a data property to a
3206state adds it to 3207 * the entire subtree via prototypal inheritance. 3208 * 3209 * <pre>data: { 3210 * requiredRole: 'foo' 3211 * } </pre> 3212 * 3213 * @param {object=} stateConfig.params 3214 * <a id='params'></a> 3215 * 3216 * A map which optionally configures parameters declared in the `url`, or 3217 * defines additional non-url parameters. For each parameter being 3218 * configured, add a configuration object keyed to the name of the parameter. 3219 * 3220 * Each parameter configuration object may contain the following properties: 3221 * 3222 * - ** value ** - {object|function=}: specifies the default value for this 3223 * parameter. This implicitly sets this parameter as optional. 3224 * 3225 * When UI-Router routes to a state and no value is 3226 * specified for this parameter in the URL or transition, the 3227 * default value will be used instead. If `value` is a function, 3228 * it will be injected and invoked, and the return value used. 3229 * 3230 * *Note*: `undefined` is treated as "no default value" while `null` 3231 * is treated as "the default value is `null`". 3232 * 3233 * *Shorthand*: If you only need to configure the default value of the 3234 * parameter, you may use a shorthand syntax. In the **`params`** 3235 * map, instead mapping the param name to a full parameter configuration 3236 * object, simply set map it to the default parameter value, e.g.: 3237 * 3238 * <pre>// define a parameter's default value 3239 * params: { 3240 * param1: { value: "defaultValue" } 3241 * } 3242 * // shorthand default values 3243 * params: { 3244 * param1: "defaultValue", 3245 * param2: "param2Default" 3246 * }</pre> 3247 * 3248 * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be 3249 * treated as an array of values. If you specified a Type, the value will be 3250 * treated as an array of the specified Type. Note: query parameter values 3251 * default to a special `"auto"` mode. 3252 * 3253 * For query parameters in `"auto"` mode, if multiple values for a single parameter 3254 * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values 3255 * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if 3256 * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single 3257 * value (e.g.: `{ foo: '1' }`). 3258 * 3259 * <pre>params: { 3260 * param1: { array: true } 3261 * }</pre> 3262 * 3263 * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when 3264 * the current parameter value is the same as the default value. If `squash` is not set, it uses the 3265 * configured default squash policy. 3266 * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`}) 3267 * 3268 * There are three squash settings: 3269 * 3270 * - false: The parameter's default value is not squashed. It is encoded and included in the URL 3271 * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed 3272 * by slashes in the state's `url` declaration, then one of those slashes are omitted. 3273 * This can allow for cleaner looking URLs. 3274 * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice. 3275 * 3276 * <pre>params: { 3277 * param1: { 3278 * value: "defaultId", 3279 * squash: true 3280 * } } 3281 * // squash "defaultValue" to "~" 3282 * params: { 3283 * param1: { 3284 * value: "defaultValue", 3285 * squash: "~" 3286 * } } 3287 * </pre> 3288 * 3289 * 3290 * @example 3291 * <pre> 3292 * // Some state name examples 3293 * 3294 * // stateName can be a single top-level name (must be unique). 3295 * $stateProvider.state("home", {}); 3296 * 3297 * // Or it can be a nested state name. This state is a child of the 3298 * // above "home" state. 3299 * $stateProvider.state("home.newest", {}); 3300 * 3301 * // Nest states as deeply as needed. 3302 * $stateProvider.state("home.newest.abc.xyz.inception", {}); 3303 * 3304 * // state() returns $stateProvider, so you can chain state declarations. 3305 * $stateProvider 3306 * .state("home", {}) 3307 * .state("about", {}) 3308 * .state("contacts", {}); 3309 * </pre> 3310 * 3311 */ 3312 this.state = state; 3313 function state(name, definition) { 3314 /*jshint validthis: true */ 3315 if (isObject(name)) definition = name; 3316 else definition.name = name; 3317 registerState(definition); 3318 return this; 3319 } 3320 3321 /** 3322 * @ngdoc object 3323 * @name ui.router.state.$state 3324 * 3325 * @requires $rootScope 3326 * @requires $q 3327 * @requires ui.router.state.$view 3328 * @requires $injector 3329 * @requires ui.router.util.$resolve 3330 * @requires ui.router.state.$stateParams 3331 * @requires ui.router.router.$urlRouter 3332 * 3333 * @property {object} params A param object, e.g. {sectionId: section.id)}, that 3334 * you'd like to test against the current active state. 3335 * @property {object} current A reference to the state's config object. However 3336 * you passed it in. Useful for accessing custom data. 3337 * @property {object} transition Currently pending transition. A promise that'll 3338 * resolve or reject. 3339 * 3340 * @description 3341 * `$state` service is responsible for representing states as well as transitioning 3342 * between them. It also provides interfaces to ask for current state or even states 3343 * you're coming from. 3344 */ 3345 this.$get = $get; 3346 $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory']; 3347 function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) { 3348 3349 var TransitionSupersededError = new Error('transition superseded'); 3350
3351 var TransitionSuperseded = silenceUncaughtInPromise($q.reject(TransitionSupersededError)); 3352 var TransitionPrevented = silenceUncaughtInPromise($q.reject(new Error('transition prevented'))); 3353 var TransitionAborted = silenceUncaughtInPromise($q.reject(new Error('transition aborted'))); 3354 var TransitionFailed = silenceUncaughtInPromise($q.reject(new Error('transition failed'))); 3355 3356 // Handles the case where a state which is the target of a transition is not found, and the user 3357 // can optionally retry or defer the transition 3358 function handleRedirect(redirect, state, params, options) { 3359 /** 3360 * @ngdoc event 3361 * @name ui.router.state.$state#$stateNotFound 3362 * @eventOf ui.router.state.$state 3363 * @eventType broadcast on root scope 3364 * @description 3365 * Fired when a requested state **cannot be found** using the provided state name during transition. 3366 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by 3367 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler, 3368 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the 3369 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value. 3370 * 3371 * @param {Object} event Event object. 3372 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties. 3373 * @param {State} fromState Current state object. 3374 * @param {Object} fromParams Current state params. 3375 * 3376 * @example 3377 * 3378 * <pre> 3379 * // somewhere, assume lazy.state has not been defined 3380 * $state.go("lazy.state", {a:1, b:2}, {inherit:false}); 3381 * 3382 * // somewhere else 3383 * $scope.$on('$stateNotFound', 3384 * function(event, unfoundState, fromState, fromParams){ 3385 * console.log(unfoundState.to); // "lazy.state" 3386 * console.log(unfoundState.toParams); // {a:1, b:2} 3387 * console.log(unfoundState.options); // {inherit:false} + default options 3388 * }) 3389 * </pre> 3390 */ 3391 var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params); 3392 3393 if (evt.defaultPrevented) { 3394 $urlRouter.update(); 3395 return TransitionAborted; 3396 } 3397 3398 if (!evt.retry) { 3399 return null; 3400 } 3401 3402 // Allow the handler to return a promise to defer state lookup retry 3403 if (options.$retry) { 3404 $urlRouter.update(); 3405 return TransitionFailed; 3406 } 3407 var retryTransition = $state.transition = $q.when(evt.retry); 3408 3409 retryTransition.then(function() { 3410 if (retryTransition !== $state.transition) { 3411 $rootScope.$broadcast('$stateChangeCancel', redirect.to, redirect.toParams, state, params); 3412 return TransitionSuperseded; 3413 } 3414 redirect.options.$retry = true; 3415 return $state.transitionTo(redirect.to, redirect.toParams, redirect.options); 3416 }, function() { 3417 return TransitionAborted; 3418 }); 3419 $urlRouter.update(); 3420 3421 return retryTransition; 3422 } 3423 3424 root.locals = { resolve: null, globals: { $stateParams: {} } }; 3425 3426 $state = { 3427 params: {}, 3428 current: root.self, 3429 $current: root, 3430 transition: null 3431 }; 3432 3433 /** 3434 * @ngdoc function 3435 * @name ui.router.state.$state#reload 3436 * @methodOf ui.router.state.$state 3437 * 3438 * @description 3439 * A method that force reloads the current state. All resolves are re-resolved, 3440 * controllers reinstantiated, and events re-fired. 3441 * 3442 * @example 3443 * <pre> 3444 * var app angular.module('app', ['ui.router']); 3445 * 3446 * app.controller('ctrl', function ($scope, $state) { 3447 * $scope.reload = function(){ 3448 * $state.reload(); 3449 * } 3450 * }); 3451 * </pre> 3452 * 3453 * `reload()` is just an alias for: 3454 * <pre> 3455 * $state.transitionTo($state.current, $stateParams, { 3456 * reload: true, inherit: false, notify: true 3457 * }); 3458 * </pre> 3459 * 3460 * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved. 3461 * @example 3462 * <pre> 3463 * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 3464 * //and current state is 'contacts.detail.item' 3465 * var app angular.module('app', ['ui.router']); 3466 * 3467 * app.controller('ctrl', function ($scope, $state) { 3468 * $scope.reload = function(){ 3469 * //will reload 'contact.detail' and 'contact.detail.item' states 3470 * $state.reload('contact.detail'); 3471 * } 3472 * }); 3473 * </pre> 3474 * 3475 * `reload()` is just an alias for: 3476 * <pre> 3477 * $state.transitionTo($state.current, $stateParams, { 3478 * reload: true, inherit: false, notify: true 3479 * }); 3480 * </pre> 3481 3482 * @returns {promise} A promise representing the state of the new transition. See 3483 * {@link ui.router.state.$state#methods_go $state.go}. 3484 */ 3485 $state.reload = function reload(state) { 3486 return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true}); 3487 }; 3488 3489 /** 3490 * @ngdoc function 3491 * @name ui.router.state.$state#go 3492 * @methodOf ui.router.state.$state 3493 *
3494 * @description 3495 * Convenience method for transitioning to a new state. `$state.go` calls 3496 * `$state.transitionTo` internally but automatically sets options to 3497 * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. 3498 * This allows you to easily use an absolute or relative to path and specify 3499 * only the parameters you'd like to update (while letting unspecified parameters 3500 * inherit from the currently active ancestor states). 3501 * 3502 * @example 3503 * <pre> 3504 * var app = angular.module('app', ['ui.router']); 3505 * 3506 * app.controller('ctrl', function ($scope, $state) { 3507 * $scope.changeState = function () { 3508 * $state.go('contact.detail'); 3509 * }; 3510 * }); 3511 * </pre> 3512 * <img src='../ngdoc_assets/StateGoExamples.png'/> 3513 * 3514 * @param {string} to Absolute state name or relative state path. Some examples: 3515 * 3516 * - `$state.go('contact.detail')` - will go to the `contact.detail` state 3517 * - `$state.go('^')` - will go to a parent state 3518 * - `$state.go('^.sibling')` - will go to a sibling state 3519 * - `$state.go('.child.grandchild')` - will go to grandchild state 3520 * 3521 * @param {object=} params A map of the parameters that will be sent to the state, 3522 * will populate $stateParams. Any parameters that are not specified will be inherited from currently 3523 * defined parameters. Only parameters specified in the state definition can be overridden, new 3524 * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters 3525 * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. 3526 * transitioning to a sibling will get you the parameters for all parents, transitioning to a child 3527 * will get you all current parameters, etc. 3528 * @param {object=} options Options object. The options are: 3529 * 3530 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` 3531 * will not. If string, must be `"replace"`, which will update url and also replace last history record. 3532 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. 3533 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), 3534 * defines which state to be relative from. 3535 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. 3536 * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params 3537 * have changed. It will reload the resolves and views of the current state and parent states. 3538 * If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \ 3539 * the transition reloads the resolves and views for that matched state, and all its children states. 3540 * 3541 * @returns {promise} A promise representing the state of the new transition. 3542 * 3543 * Possible success values: 3544 * 3545 * - $state.current 3546 * 3547 * <br/>Possible rejection values: 3548 * 3549 * - 'transition superseded' - when a newer transition has been started after this one 3550 * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener 3551 * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or 3552 * when a `$stateNotFound` `event.retry` promise errors. 3553 * - 'transition failed' - when a state has been unsuccessfully found after 2 tries. 3554 * - *resolve error* - when an error has occurred with a `resolve` 3555 * 3556 */ 3557 $state.go = function go(to, params, options) { 3558 return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); 3559 }; 3560 3561 /** 3562 * @ngdoc function 3563 * @name ui.router.state.$state#transitionTo 3564 * @methodOf ui.router.state.$state 3565 * 3566 * @description 3567 * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} 3568 * uses `transitionTo` internally. `$state.go` is recommended in most situations. 3569 * 3570 * @example 3571 * <pre> 3572 * var app = angular.module('app', ['ui.router']); 3573 * 3574 * app.controller('ctrl', function ($scope, $state) { 3575 * $scope.changeState = function () { 3576 * $state.transitionTo('contact.detail'); 3577 * }; 3578 * }); 3579 * </pre> 3580 * 3581 * @param {string} to State name. 3582 * @param {object=} toParams A map of the parameters that will be sent to the state, 3583 * will populate $stateParams. 3584 * @param {object=} options Options object. The options are: 3585 * 3586 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` 3587 * will not. If string, must be `"replace"`, which will update url and also replace last history record. 3588 * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. 3589 * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), 3590 * defines which state to be relative from. 3591 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. 3592 * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params 3593 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd 3594 * use this when you want to force a reload when *everything* is the same, including search params. 3595 * if String, then will reload the state with the name given in reload, and any children. 3596 * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children. 3597 * 3598 * @returns {promise} A promise representing the state of the new transition. See 3599 * {@link ui.router.state.$state#methods_go $state.go}. 3600 */
3601 $state.transitionTo = function transitionTo(to, toParams, options) { 3602 toParams = toParams || {}; 3603 options = extend({ 3604 location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false 3605 }, options || {}); 3606 3607 var from = $state.$current, fromParams = $state.params, fromPath = from.path; 3608 var evt, toState = findState(to, options.relative); 3609 3610 // Store the hash param for later (since it will be stripped out by various methods) 3611 var hash = toParams['#']; 3612 3613 if (!isDefined(toState)) { 3614 var redirect = { to: to, toParams: toParams, options: options }; 3615 var redirectResult = handleRedirect(redirect, from.self, fromParams, options); 3616 3617 if (redirectResult) { 3618 return redirectResult; 3619 } 3620 3621 // Always retry once if the $stateNotFound was not prevented 3622 // (handles either redirect changed or state lazy-definition) 3623 to = redirect.to; 3624 toParams = redirect.toParams; 3625 options = redirect.options; 3626 toState = findState(to, options.relative); 3627 3628 if (!isDefined(toState)) { 3629 if (!options.relative) throw new Error("No such state '" + to + "'"); 3630 throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'"); 3631 } 3632 } 3633 if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'"); 3634 if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState); 3635 if (!toState.params.$$validates(toParams)) return TransitionFailed; 3636 3637 toParams = toState.params.$$values(toParams); 3638 to = toState; 3639 3640 var toPath = to.path; 3641 3642 // Starting from the root of the path, keep all levels that haven't changed 3643 var keep = 0, state = toPath[keep], locals = root.locals, toLocals = []; 3644 3645 if (!options.reload) { 3646 while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) { 3647 locals = toLocals[keep] = state.locals; 3648 keep++; 3649 state = toPath[keep]; 3650 } 3651 } else if (isString(options.reload) || isObject(options.reload)) { 3652 if (isObject(options.reload) && !options.reload.name) { 3653 throw new Error('Invalid reload state object'); 3654 } 3655 3656 var reloadState = options.reload === true ? fromPath[0] : findState(options.reload); 3657 if (options.reload && !reloadState) { 3658 throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'"); 3659 } 3660 3661 while (state && state === fromPath[keep] && state !== reloadState) { 3662 locals = toLocals[keep] = state.locals; 3663 keep++; 3664 state = toPath[keep]; 3665 } 3666 } 3667 3668 // If we're going to the same state and all locals are kept, we've got nothing to do. 3669 // But clear 'transition', as we still want to cancel any other pending transitions. 3670 // TODO: We may not want to bump 'transition' if we're called from a location change 3671 // that we've initiated ourselves, because we might accidentally abort a legitimate 3672 // transition initiated from code? 3673 if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) { 3674 if (hash) toParams['#'] = hash; 3675 $state.params = toParams; 3676 copy($state.params, $stateParams); 3677 copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams); 3678 if (options.location && to.navigable && to.navigable.url) { 3679 $urlRouter.push(to.navigable.url, toParams, { 3680 $$avoidResync: true, replace: options.location === 'replace' 3681 }); 3682 $urlRouter.update(true); 3683 } 3684 $state.transition = null; 3685 return $q.when($state.current); 3686 } 3687 3688 // Filter parameters before we pass them to event handlers etc. 3689 toParams = filterByKeys(to.params.$$keys(), toParams || {}); 3690 3691 // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart 3692 if (hash) toParams['#'] = hash; 3693 3694 // Broadcast start event and cancel the transition if requested 3695 if (options.notify) { 3696 /** 3697 * @ngdoc event 3698 * @name ui.router.state.$state#$stateChangeStart 3699 * @eventOf ui.router.state.$state 3700 * @eventType broadcast on root scope
3701 * @description 3702 * Fired when the state transition **begins**. You can use `event.preventDefault()` 3703 * to prevent the transition from happening and then the transition promise will be 3704 * rejected with a `'transition prevented'` value. 3705 * 3706 * @param {Object} event Event object. 3707 * @param {State} toState The state being transitioned to. 3708 * @param {Object} toParams The params supplied to the `toState`. 3709 * @param {State} fromState The current state, pre-transition. 3710 * @param {Object} fromParams The params supplied to the `fromState`. 3711 * 3712 * @example 3713 * 3714 * <pre> 3715 * $rootScope.$on('$stateChangeStart', 3716 * function(event, toState, toParams, fromState, fromParams){ 3717 * event.preventDefault(); 3718 * // transitionTo() promise will be rejected with 3719 * // a 'transition prevented' error 3720 * }) 3721 * </pre> 3722 */ 3723 if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) { 3724 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); 3725 //Don't update and resync url if there's been a new transition started. see issue #2238, #600 3726 if ($state.transition == null) $urlRouter.update(); 3727 return TransitionPrevented; 3728 } 3729 } 3730 3731 // Resolve locals for the remaining states, but don't update any global state just 3732 // yet -- if anything fails to resolve the current state needs to remain untouched. 3733 // We also set up an inheritance chain for the locals here. This allows the view directive 3734 // to quickly look up the correct definition for each view in the current state. Even 3735 // though we create the locals object itself outside resolveState(), it is initially 3736 // empty and gets filled asynchronously. We need to keep track of the promise for the 3737 // (fully resolved) current locals, and pass this down the chain. 3738 var resolved = $q.when(locals); 3739 3740 for (var l = keep; l < toPath.length; l++, state = toPath[l]) { 3741 locals = toLocals[l] = inherit(locals); 3742 resolved = resolveState(state, toParams, state === to, resolved, locals, options); 3743 } 3744 3745 // Once everything is resolved, we are ready to perform the actual transition 3746 // and return a promise for the new state. We also keep track of what the 3747 // current promise is, so that we can detect overlapping transitions and 3748 // keep only the outcome of the last transition. 3749 var transition = $state.transition = resolved.then(function () { 3750 var l, entering, exiting; 3751 3752 if ($state.transition !== transition) { 3753 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); 3754 return TransitionSuperseded; 3755 } 3756 3757 // Exit 'from' states not kept 3758 for (l = fromPath.length - 1; l >= keep; l--) { 3759 exiting = fromPath[l]; 3760 if (exiting.self.onExit) { 3761 $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals); 3762 } 3763 exiting.locals = null; 3764 } 3765 3766 // Enter 'to' states not kept 3767 for (l = keep; l < toPath.length; l++) { 3768 entering = toPath[l]; 3769 entering.locals = toLocals[l]; 3770 if (entering.self.onEnter) { 3771 $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals); 3772 } 3773 } 3774 3775 // Run it again, to catch any transitions in callbacks 3776 if ($state.transition !== transition) { 3777 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); 3778 return TransitionSuperseded; 3779 } 3780 3781 // Update globals in $state 3782 $state.$current = to; 3783 $state.current = to.self; 3784 $state.params = toParams; 3785 copy($state.params, $stateParams); 3786 $state.transition = null; 3787 3788 if (options.location && to.navigable) { 3789 $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, { 3790 $$avoidResync: true, replace: options.location === 'replace' 3791 }); 3792 } 3793 3794 if (options.notify) { 3795 /** 3796 * @ngdoc event 3797 * @name ui.router.state.$state#$stateChangeSuccess 3798 * @eventOf ui.router.state.$state 3799 * @eventType broadcast on root scope
3800 * @description 3801 * Fired once the state transition is **complete**. 3802 * 3803 * @param {Object} event Event object. 3804 * @param {State} toState The state being transitioned to. 3805 * @param {Object} toParams The params supplied to the `toState`. 3806 * @param {State} fromState The current state, pre-transition. 3807 * @param {Object} fromParams The params supplied to the `fromState`. 3808 */ 3809 $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams); 3810 } 3811 $urlRouter.update(true); 3812 3813 return $state.current; 3814 }).then(null, function (error) { 3815 // propagate TransitionSuperseded error without emitting $stateChangeCancel 3816 // as it was already emitted in the success handler above 3817 if (error === TransitionSupersededError) return TransitionSuperseded; 3818 3819 if ($state.transition !== transition) { 3820 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); 3821 return TransitionSuperseded; 3822 } 3823 3824 $state.transition = null; 3825 /** 3826 * @ngdoc event 3827 * @name ui.router.state.$state#$stateChangeError 3828 * @eventOf ui.router.state.$state 3829 * @eventType broadcast on root scope 3830 * @description 3831 * Fired when an **error occurs** during transition. It's important to note that if you 3832 * have any errors in your resolve functions (javascript errors, non-existent services, etc) 3833 * they will not throw traditionally. You must listen for this $stateChangeError event to 3834 * catch **ALL** errors. 3835 * 3836 * @param {Object} event Event object. 3837 * @param {State} toState The state being transitioned to. 3838 * @param {Object} toParams The params supplied to the `toState`. 3839 * @param {State} fromState The current state, pre-transition. 3840 * @param {Object} fromParams The params supplied to the `fromState`. 3841 * @param {Error} error The resolve error object. 3842 */ 3843 evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error); 3844 3845 if (!evt.defaultPrevented) { 3846 $urlRouter.update(); 3847 } 3848 3849 return $q.reject(error); 3850 }); 3851 3852 silenceUncaughtInPromise(transition); 3853 return transition; 3854 }; 3855 3856 /** 3857 * @ngdoc function 3858 * @name ui.router.state.$state#is 3859 * @methodOf ui.router.state.$state 3860 * 3861 * @description 3862 * Similar to {@link ui.router.state.$state#methods_includes $state.includes}, 3863 * but only checks for the full state name. If params is supplied then it will be 3864 * tested for strict equality against the current active params object, so all params 3865 * must match with none missing and no extras. 3866 * 3867 * @example 3868 * <pre> 3869 * $state.$current.name = 'contacts.details.item'; 3870 * 3871 * // absolute name 3872 * $state.is('contact.details.item'); // returns true 3873 * $state.is(contactDetailItemStateObject); // returns true 3874 * 3875 * // relative name (. and ^), typically from a template 3876 * // E.g. from the 'contacts.details' template 3877 * <div ng-class="{highlighted: $state.is('.item')}">Item</div> 3878 * </pre> 3879 * 3880 * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check. 3881 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like 3882 * to test against the current active state. 3883 * @param {object=} options An options object. The options are: 3884 * 3885 * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will 3886 * test relative to `options.relative` state (or name). 3887 * 3888 * @returns {boolean} Returns true if it is the state. 3889 */ 3890 $state.is = function is(stateOrName, params, options) { 3891 options = extend({ relative: $state.$current }, options || {}); 3892 var state = findState(stateOrName, options.relative); 3893 3894 if (!isDefined(state)) { return undefined; } 3895 if ($state.$current !== state) { return false; } 3896 3897 return !params || objectKeys(params).reduce(function(acc, key) { 3898 var paramDef = state.params[key]; 3899 return acc && (!paramDef || paramDef.type.equals($stateParams[key], params[key])); 3900 }, true); 3901 }; 3902 3903 /** 3904 * @ngdoc function 3905 * @name ui.router.state.$state#includes 3906 * @methodOf ui.router.state.$state 3907 *
3908 * @description 3909 * A method to determine if the current active state is equal to or is the child of the 3910 * state stateName. If any params are passed then they will be tested for a match as well. 3911 * Not all the parameters need to be passed, just the ones you'd like to test for equality. 3912 * 3913 * @example 3914 * Partial and relative names 3915 * <pre> 3916 * $state.$current.name = 'contacts.details.item'; 3917 * 3918 * // Using partial names 3919 * $state.includes("contacts"); // returns true 3920 * $state.includes("contacts.details"); // returns true 3921 * $state.includes("contacts.details.item"); // returns true 3922 * $state.includes("contacts.list"); // returns false 3923 * $state.includes("about"); // returns false 3924 * 3925 * // Using relative names (. and ^), typically from a template 3926 * // E.g. from the 'contacts.details' template 3927 * <div ng-class="{highlighted: $state.includes('.item')}">Item</div> 3928 * </pre> 3929 * 3930 * Basic globbing patterns 3931 * <pre> 3932 * $state.$current.name = 'contacts.details.item.url'; 3933 * 3934 * $state.includes("*.details.*.*"); // returns true 3935 * $state.includes("*.details.**"); // returns true 3936 * $state.includes("**.item.**"); // returns true 3937 * $state.includes("*.details.item.url"); // returns true 3938 * $state.includes("*.details.*.url"); // returns true 3939 * $state.includes("*.details.*"); // returns false 3940 * $state.includes("item.**"); // returns false 3941 * </pre> 3942 * 3943 * @param {string} stateOrName A partial name, relative name, or glob pattern 3944 * to be searched for within the current state name. 3945 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, 3946 * that you'd like to test against the current active state. 3947 * @param {object=} options An options object. The options are: 3948 * 3949 * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set, 3950 * .includes will test relative to `options.relative` state (or name). 3951 * 3952 * @returns {boolean} Returns true if it does include the state 3953 */ 3954 $state.includes = function includes(stateOrName, params, options) { 3955 options = extend({ relative: $state.$current }, options || {}); 3956 if (isString(stateOrName) && isGlob(stateOrName)) { 3957 if (!doesStateMatchGlob(stateOrName)) { 3958 return false; 3959 } 3960 stateOrName = $state.$current.name; 3961 } 3962 3963 var state = findState(stateOrName, options.relative); 3964 if (!isDefined(state)) { return undefined; } 3965 if (!isDefined($state.$current.includes[state.name])) { return false; } 3966 if (!params) { return true; } 3967 3968 var keys = objectKeys(params); 3969 for (var i = 0; i < keys.length; i++) { 3970 var key = keys[i], paramDef = state.params[key]; 3971 if (paramDef && !paramDef.type.equals($stateParams[key], params[key])) { 3972 return false; 3973 } 3974 } 3975 3976 return objectKeys(params).reduce(function(acc, key) { 3977 var paramDef = state.params[key]; 3978 return acc && !paramDef || paramDef.type.equals($stateParams[key], params[key]); 3979 }, true); 3980 }; 3981 3982 3983 /** 3984 * @ngdoc function 3985 * @name ui.router.state.$state#href 3986 * @methodOf ui.router.state.$state 3987 * 3988 * @description 3989 * A url generation method that returns the compiled url for the given state populated with the given params. 3990 * 3991 * @example 3992 * <pre> 3993 * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob"); 3994 * </pre> 3995 * 3996 * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. 3997 * @param {object=} params An object of parameter values to fill the state's required parameters. 3998 * @param {object=} options Options object. The options are: 3999 * 4000 * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the 4001 * first parameter, then the constructed href url will be built from the first navigable ancestor (aka 4002 * ancestor with a valid url). 4003 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. 4004 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), 4005 * defines which state to be relative from. 4006 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". 4007 * 4008 * @returns {string} compiled state url 4009 */ 4010 $state.href = function href(stateOrName, params, options) { 4011 options = extend({ 4012 lossy: true, 4013 inherit: true, 4014 absolute: false, 4015 relative: $state.$current 4016 }, options || {}); 4017 4018 var state = findState(stateOrName, options.relative); 4019 4020 if (!isDefined(state)) return null; 4021 if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state); 4022 4023 var nav = (state && options.lossy) ? state.navigable : state; 4024 4025 if (!nav || nav.url === undefined || nav.url === null) { 4026 return null; 4027 } 4028 return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), { 4029 absolute: options.absolute 4030 }); 4031 }; 4032 4033 /** 4034 * @ngdoc function 4035 * @name ui.router.state.$state#get 4036 * @methodOf ui.router.state.$state 4037 *
4038 * @description 4039 * Returns the state configuration object for any specific state or all states. 4040 * 4041 * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for 4042 * the requested state. If not provided, returns an array of ALL state configs. 4043 * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context. 4044 * @returns {Object|Array} State configuration object or array of all objects. 4045 */ 4046 $state.get = function (stateOrName, context) { 4047 if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; }); 4048 var state = findState(stateOrName, context || $state.$current); 4049 return (state && state.self) ? state.self : null; 4050 }; 4051 4052 function resolveState(state, params, paramsAreFiltered, inherited, dst, options) { 4053 // Make a restricted $stateParams with only the parameters that apply to this state if 4054 // necessary. In addition to being available to the controller and onEnter/onExit callbacks, 4055 // we also need $stateParams to be available for any $injector calls we make during the 4056 // dependency resolution process. 4057 var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params); 4058 var locals = { $stateParams: $stateParams }; 4059 4060 // Resolve 'global' dependencies for the state, i.e. those not specific to a view. 4061 // We're also including $stateParams in this; that way the parameters are restricted 4062 // to the set that should be visible to the state, and are independent of when we update 4063 // the global $state and $stateParams values. 4064 dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state); 4065 var promises = [dst.resolve.then(function (globals) { 4066 dst.globals = globals; 4067 })]; 4068 if (inherited) promises.push(inherited); 4069 4070 function resolveViews() { 4071 var viewsPromises = []; 4072 4073 // Resolve template and dependencies for all views. 4074 forEach(state.views, function (view, name) { 4075 var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); 4076 injectables.$template = [ function () { 4077 return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || ''; 4078 }]; 4079 4080 viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) { 4081 // References to the controller (only instantiated at link time) 4082 if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { 4083 var injectLocals = angular.extend({}, injectables, dst.globals); 4084 result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); 4085 } else { 4086 result.$$controller = view.controller; 4087 } 4088 // Provide access to the state itself for internal use 4089 result.$$state = state; 4090 result.$$controllerAs = view.controllerAs; 4091 result.$$resolveAs = view.resolveAs; 4092 dst[name] = result; 4093 })); 4094 }); 4095 4096 return $q.all(viewsPromises).then(function(){ 4097 return dst.globals; 4098 }); 4099 } 4100 4101 // Wait for all the promises and then return the activation object 4102 return $q.all(promises).then(resolveViews).then(function (values) { 4103 return dst; 4104 }); 4105 } 4106 4107 return $state; 4108 } 4109 4110 function shouldSkipReload(to, toParams, from, fromParams, locals, options) { 4111 // Return true if there are no differences in non-search (path/object) params, false if there are differences 4112 function nonSearchParamsEqual(fromAndToState, fromParams, toParams) { 4113 // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params. 4114 function notSearchParam(key) { 4115 return fromAndToState.params[key].location != "search"; 4116 } 4117 var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam); 4118 var nonQueryParams = pick.apply({}
4118, [fromAndToState.params].concat(nonQueryParamKeys)); 4119 var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams); 4120 return nonQueryParamSet.$$equals(fromParams, toParams); 4121 } 4122 4123 // If reload was not explicitly requested 4124 // and we're transitioning to the same state we're already in 4125 // and the locals didn't change 4126 // or they changed in a way that doesn't merit reloading 4127 // (reloadOnParams:false, or reloadOnSearch.false and only search params changed) 4128 // Then return true. 4129 if (!options.reload && to === from && 4130 (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) { 4131 return true; 4132 } 4133 } 4134} 4135 4136angular.module('ui.router.state') 4137 .factory('$stateParams', function () { return {}; }) 4138 .constant("$state.runtime", { autoinject: true }) 4139 .provider('$state', $StateProvider) 4140 // Inject $state to initialize when entering runtime. #2574 4141 .run(['$injector', function ($injector) { 4142 // Allow tests (stateSpec.js) to turn this off by defining this constant 4143 if ($injector.get("$state.runtime").autoinject) { 4144 $injector.get('$state'); 4145 } 4146 }]); 4147 4148 4149$ViewProvider.$inject = []; 4150function $ViewProvider() { 4151 4152 this.$get = $get; 4153 /** 4154 * @ngdoc object 4155 * @name ui.router.state.$view 4156 * 4157 * @requires ui.router.util.$templateFactory 4158 * @requires $rootScope 4159 * 4160 * @description 4161 * 4162 */ 4163 $get.$inject = ['$rootScope', '$templateFactory']; 4164 function $get( $rootScope, $templateFactory) { 4165 return { 4166 // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... }) 4167 /** 4168 * @ngdoc function 4169 * @name ui.router.state.$view#load 4170 * @methodOf ui.router.state.$view 4171 * 4172 * @description 4173 * 4174 * @param {string} name name 4175 * @param {object} options option object. 4176 */ 4177 load: function load(name, options) { 4178 var result, defaults = { 4179 template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {} 4180 }; 4181 options = extend(defaults, options); 4182 4183 if (options.view) { 4184 result = $templateFactory.fromConfig(options.view, options.params, options.locals); 4185 } 4186 return result; 4187 } 4188 }; 4189 } 4190} 4191 4192angular.module('ui.router.state').provider('$view', $ViewProvider); 4193 4194/** 4195 * @ngdoc object 4196 * @name ui.router.state.$uiViewScrollProvider 4197 * 4198 * @description 4199 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function. 4200 */ 4201function $ViewScrollProvider() { 4202 4203 var useAnchorScroll = false; 4204 4205 /** 4206 * @ngdoc function 4207 * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll 4208 * @methodOf ui.router.state.$uiViewScrollProvider 4209 * 4210 * @description 4211 * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for 4212 * scrolling based on the url anchor. 4213 */ 4214 this.useAnchorScroll = function () { 4215 useAnchorScroll = true; 4216 }; 4217 4218 /** 4219 * @ngdoc object 4220 * @name ui.router.state.$uiViewScroll 4221 * 4222 * @requires $anchorScroll 4223 * @requires $timeout 4224 * 4225 * @description 4226 * When called with a jqLite element, it scrolls the element into view (after a 4227 * `$timeout` so the DOM has time to refresh). 4228 * 4229 * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, 4230 * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}. 4231 */ 4232 this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { 4233 if (useAnchorScroll) { 4234 return $anchorScroll; 4235 } 4236 4237 return function ($element) { 4238 return $timeout(function () { 4239 $element[0].scrollIntoView(); 4240 }, 0, false); 4241 }; 4242 }]; 4243} 4244 4245angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); 4246 4247/** 4248 * @ngdoc directive 4249 * @name ui.router.state.directive:ui-view 4250 * 4251 * @requires ui.router.state.$state 4252 * @requires $compile 4253 * @requires $controller 4254 * @requires $injector 4255 * @requires ui.router.state.$uiViewScroll 4256 * @requires $document 4257 * 4258 * @restrict ECA 4259 * 4260 * @description 4261 * The ui-view directive tells $state where to place your templates. 4262 * 4263 * @param {string=} name A view name. The name should be unique amongst the other views in the 4264 * same state. You can have views of the same name that live in different states. 4265 * 4266 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window 4267 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll 4268 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you 4269 * scroll ui-view elements into view when they are populated during a state activation. 4270 * 4271 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) 4272 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* 4273 * 4274 * @param {string=} onload Expression to evaluate whenever the view updates. 4275 * 4276 * @example 4277 * A view can be unnamed or named. 4278 * <pre> 4279 * <!-- Unnamed --> 4280 * <div ui-view></div> 4281 * 4282 * <!-- Named --> 4283 * <div ui-view="viewName"></div> 4284 * </pre> 4285 * 4286 * You can only have one unnamed view within any template (or root html). If you are only using a 4287 * single view and it is unnamed then you can populate it like so: 4288 * <pre> 4289 * <div ui-view></div> 4290 * $stateProvider.state("home", { 4291 * template: "<h1>HELLO!</h1>" 4292 * }) 4293 * </pre> 4294 * 4295 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#methods_state `views`} 4296 * config property, by name, in this case an empty name: 4297 * <pre> 4298 * $stateProvider.state("home", { 4299 * views: { 4300 * "": { 4301 * template: "<h1>HELLO!</h1>" 4302 * } 4303 * } 4304 * }) 4305 * </pre> 4306 * 4307 * But typically you'll only use the views property if you name your view or have more than one view 4308 * in the same template. There's not really a compelling reason to name a view if its the only one, 4309 * but you could if you wanted, like so: 4310 * <pre> 4311 * <div ui-view="main"></div> 4312 * </pre> 4313 * <pre> 4314 * $stateProvider.state("home", { 4315 * views: { 4316 * "main": { 4317 * template: "<h1>HELLO!</h1>" 4318 * } 4319 * } 4320 * }) 4321 * </pre> 4322 * 4323 * Really though, you'll use views to set up multiple views: 4324 * <pre> 4325 * <div ui-view></div> 4326 * <div ui-view="chart"></div> 4327 * <div ui-view="data"></div> 4328 * </pre> 4329 * 4330 * <pre> 4331 * $stateProvider.state("home", { 4332 * views: { 4333 * "": { 4334 * template: "<h1>HELLO!</h1>" 4335 * }, 4336 * "chart": { 4337 * template: "<chart_thing/>" 4338 * }, 4339 * "data": { 4340 * template: "<data_thing/>" 4341 * } 4342 * } 4343 * }) 4344 * </pre> 4345 * 4346 * Examples for `autoscroll`: 4347 * 4348 * <pre> 4349 * <!-- If autoscroll present with no expression, 4350 * then scroll ui-view into view --> 4351 * <ui-view autoscroll/> 4352 * 4353 * <!-- If autoscroll present with valid expression, 4354 * then scroll ui-view into view if expression evaluates to true --> 4355 * <ui-view autoscroll='true'/> 4356 * <ui-view autoscroll='false'/> 4357 * <ui-view autoscroll='scopeVariable'/> 4358 * </pre> 4359 * 4360 * Resolve data: 4361 * 4362 * The resolved data from the state's `resolve` block is placed on the scope as `$resolve` (this 4363 * can be customized using [[ViewDeclaration.resolveAs]]). This can be then accessed from the template. 4364 *
4365 * Note that when `controllerAs` is being used, `$resolve` is set on the controller instance *after* the 4366 * controller is instantiated. The `$onInit()` hook can be used to perform initialization code which 4367 * depends on `$resolve` data. 4368 * 4369 * Example usage of $resolve in a view template 4370 * <pre> 4371 * $stateProvider.state('home', { 4372 * template: '<my-component user="$resolve.user"></my-component>', 4373 * resolve: { 4374 * user: function(UserService) { return UserService.fetchUser(); } 4375 * } 4376 * }); 4377 * </pre> 4378 */ 4379$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate', '$q']; 4380function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate, $q) { 4381 4382 function getService() { 4383 return ($injector.has) ? function(service) { 4384 return $injector.has(service) ? $injector.get(service) : null; 4385 } : function(service) { 4386 try { 4387 return $injector.get(service); 4388 } catch (e) { 4389 return null; 4390 } 4391 }; 4392 } 4393 4394 var service = getService(), 4395 $animator = service('$animator'), 4396 $animate = service('$animate'); 4397 4398 // Returns a set of DOM manipulation functions based on which Angular version 4399 // it should use 4400 function getRenderer(attrs, scope) { 4401 var statics = function() { 4402 return { 4403 enter: function (element, target, cb) { target.after(element); cb(); }, 4404 leave: function (element, cb) { element.remove(); cb(); } 4405 }; 4406 }; 4407 4408 if ($animate) { 4409 return { 4410 enter: function(element, target, cb) { 4411 if (angular.version.minor > 2) { 4412 $animate.enter(element, null, target).then(cb); 4413 } else { 4414 $animate.enter(element, null, target, cb); 4415 } 4416 }, 4417 leave: function(element, cb) { 4418 if (angular.version.minor > 2) { 4419 $animate.leave(element).then(cb); 4420 } else { 4421 $animate.leave(element, cb); 4422 } 4423 } 4424 }; 4425 } 4426 4427 if ($animator) { 4428 var animate = $animator && $animator(scope, attrs); 4429 4430 return { 4431 enter: function(element, target, cb) {animate.enter(element, null, target); cb(); }, 4432 leave: function(element, cb) { animate.leave(element); cb(); } 4433 }; 4434 } 4435 4436 return statics(); 4437 } 4438 4439 var directive = { 4440 restrict: 'ECA', 4441 terminal: true, 4442 priority: 400, 4443 transclude: 'element', 4444 compile: function (tElement, tAttrs, $transclude) { 4445 return function (scope, $element, attrs) { 4446 var previousEl, currentEl, currentScope, latestLocals, 4447 onloadExp = attrs.onload || '', 4448 autoScrollExp = attrs.autoscroll, 4449 renderer = getRenderer(attrs, scope), 4450 inherited = $element.inheritedData('$uiView'); 4451 4452 scope.$on('$stateChangeSuccess', function() { 4453 updateView(false); 4454 }); 4455 4456 updateView(true); 4457 4458 function cleanupLastView() { 4459 if (previousEl) { 4460 previousEl.remove(); 4461 previousEl = null; 4462 } 4463 4464 if (currentScope) { 4465 currentScope.$destroy(); 4466 currentScope = null; 4467 } 4468 4469 if (currentEl) { 4470 var $uiViewData = currentEl.data('$uiViewAnim'); 4471 renderer.leave(currentEl, function() { 4472 $uiViewData.$$animLeave.resolve(); 4473 previousEl = null; 4474 }); 4475 4476 previousEl = currentEl; 4477 currentEl = null; 4478 } 4479 } 4480 4481 function updateView(firstTime) { 4482 var newScope, 4483 name = getUiViewName(scope, attrs, $element, $interpolate), 4484 previousLocals = name && $state.$current && $state.$current.locals[name]; 4485 4486 if (!firstTime && previousLocals === latestLocals) return; // nothing to do 4487 newScope = scope.$new(); 4488 latestLocals = $state.$current.locals[name]; 4489 4490 /** 4491 * @ngdoc event 4492 * @name ui.router.state.directive:ui-view#$viewContentLoading 4493 * @eventOf ui.router.state.directive:ui-view 4494 * @eventType emits on ui-view directive scope
4495 * @description 4496 * 4497 * Fired once the view **begins loading**, *before* the DOM is rendered. 4498 * 4499 * @param {Object} event Event object. 4500 * @param {string} viewName Name of the view. 4501 */ 4502 newScope.$emit('$viewContentLoading', name); 4503 4504 var clone = $transclude(newScope, function(clone) { 4505 var animEnter = $q.defer(), animLeave = $q.defer(); 4506 var viewAnimData = { 4507 $animEnter: animEnter.promise, 4508 $animLeave: animLeave.promise, 4509 $$animLeave: animLeave 4510 }; 4511 4512 clone.data('$uiViewAnim', viewAnimData); 4513 renderer.enter(clone, $element, function onUiViewEnter() { 4514 animEnter.resolve(); 4515 if(currentScope) { 4516 currentScope.$emit('$viewContentAnimationEnded'); 4517 } 4518 4519 if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { 4520 $uiViewScroll(clone); 4521 } 4522 }); 4523 cleanupLastView(); 4524 }); 4525 4526 currentEl = clone; 4527 currentScope = newScope; 4528 /** 4529 * @ngdoc event 4530 * @name ui.router.state.directive:ui-view#$viewContentLoaded 4531 * @eventOf ui.router.state.directive:ui-view 4532 * @eventType emits on ui-view directive scope 4533 * @description 4534 * Fired once the view is **loaded**, *after* the DOM is rendered. 4535 * 4536 * @param {Object} event Event object. 4537 * @param {string} viewName Name of the view. 4538 */ 4539 currentScope.$emit('$viewContentLoaded', name); 4540 currentScope.$eval(onloadExp); 4541 } 4542 }; 4543 } 4544 }; 4545 4546 return directive; 4547} 4548 4549$ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate']; 4550function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) { 4551 return { 4552 restrict: 'ECA', 4553 priority: -400, 4554 compile: function (tElement) { 4555 var initial = tElement.html(); 4556 if (tElement.empty) { 4557 tElement.empty(); 4558 } else { 4559 // ng 1.0.0 doesn't have empty(), which cleans up data and handlers 4560 tElement[0].innerHTML = null; 4561 } 4562 4563 return function (scope, $element, attrs) { 4564 var current = $state.$current, 4565 name = getUiViewName(scope, attrs, $element, $interpolate), 4566 locals = current && current.locals[name]; 4567 4568 if (! locals) { 4569 $element.html(initial); 4570 $compile($element.contents())(scope); 4571 return; 4572 } 4573 4574 $element.data('$uiView', { name: name, state: locals.$$state }); 4575 $element.html(locals.$template ? locals.$template : initial); 4576 4577 var resolveData = angular.extend({}, locals); 4578 scope[locals.$$resolveAs] = resolveData; 4579 4580 var link = $compile($element.contents()); 4581 4582 if (locals.$$controller) { 4583 locals.$scope = scope; 4584 locals.$element = $element; 4585 var controller = $controller(locals.$$controller, locals); 4586 if (locals.$$controllerAs) { 4587 scope[locals.$$controllerAs] = controller; 4588 scope[locals.$$controllerAs][locals.$$resolveAs] = resolveData; 4589 } 4590 if (isFunction(controller.$onInit)) controller.$onInit(); 4591 $element.data('$ngControllerController', controller); 4592 $element.children().data('$ngControllerController', controller); 4593 } 4594 4595 link(scope); 4596 }; 4597 } 4598 }; 4599} 4600 4601/** 4602 * Shared ui-view code for both directives: 4603 * Given scope, element, and its attributes, return the view's name 4604 */ 4605function getUiViewName(scope, attrs, element, $interpolate) { 4606 var name = $interpolate(attrs.uiView || attrs.name || '')(scope); 4607 var uiViewCreatedBy = element.inheritedData('$uiView'); 4608 return name.indexOf('@') >= 0 ? name : (name + '@' + (uiViewCreatedBy ? uiViewCreatedBy.state.name : '')); 4609} 4610 4611angular.module('ui.router.state').directive('uiView', $ViewDirective); 4612angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill); 4613 4614function parseStateRef(ref, current) { 4615 var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed; 4616 if (preparsed) ref = current + '(' + preparsed[1] + ')'; 4617 parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); 4618 if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); 4619 return { state: parsed[1], paramExpr: parsed[3] || null }; 4620} 4621 4622function stateContext(el) { 4623 var stateData = el.parent().inheritedData('$uiView'); 4624 4625 if (stateData && stateData.state && stateData.state.name) { 4626 return stateData.state; 4627 } 4628} 4629 4630function getTypeInfo(el) { 4631 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 4632 var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]'; 4633 var isForm = el[0].nodeName === "FORM"; 4634 4635 return { 4636 attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'), 4637 isAnchor: el.prop("tagName").toUpperCase() === "A", 4638 clickable: !isForm 4639 }; 4640} 4641 4642function clickHook(el, $state, $timeout, type, current) { 4643 return function(e) { 4644 var button = e.which || e.button, target = current(); 4645 4646 if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) { 4647 // HACK: This is to allow ng-clicks to be processed before the transition is initiated: 4648 var transition = $timeout(function() { 4649 $state.go(target.state, target.params, target.options); 4650 }); 4651 e.preventDefault(); 4652 4653 // if the state has no URL, ignore one preventDefault from the <a> directive. 4654 var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0; 4655 4656 e.preventDefault = function() { 4657 if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition); 4658 }; 4659 } 4660 }; 4661} 4662 4663function defaultOpts(el, $state) { 4664 return { relative: stateContext(el) || $state.$current, inherit: true }; 4665} 4666 4667/** 4668 * @ngdoc directive 4669 * @name ui.router.state.directive:ui-sref 4670 * 4671 * @requires ui.router.state.$state 4672 * @requires $timeout 4673 * 4674 * @restrict A 4675 * 4676 * @description 4677 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated 4678 * URL, the directive will automatically generate & update the `href` attribute via 4679 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking 4680 * the link will trigger a state transition with optional parameters. 4681 * 4682 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be 4683 * handled natively by the browser. 4684 * 4685 * You can also use relative state paths within ui-sref, just like the relative 4686 * paths passed to `$state.go()`. You just need to be aware that the path is relative 4687 * to the state that the link lives in, in other words the state that loaded the 4688 * template containing the link. 4689 * 4690 * You can specify options to pass to {@link ui.router.state.$state#methods_go $state.go()} 4691 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`, 4692 * and `reload`. 4693 * 4694 * @example 4695 * Here's an example of how you'd use ui-sref and how it would compile. If you have the 4696 * following template: 4697 * <pre> 4698 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a> 4699 * 4700 * <ul> 4701 * <li ng-repeat="contact in contacts">
4702 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a> 4703 * </li> 4704 * </ul> 4705 * </pre> 4706 * 4707 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts): 4708 * <pre> 4709 * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a> 4710 * 4711 * <ul> 4712 * <li ng-repeat="contact in contacts"> 4713 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a> 4714 * </li> 4715 * <li ng-repeat="contact in contacts"> 4716 * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a> 4717 * </li> 4718 * <li ng-repeat="contact in contacts"> 4719 * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a> 4720 * </li> 4721 * </ul> 4722 * 4723 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a> 4724 * </pre> 4725 * 4726 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state 4727 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()} 4728 */ 4729$StateRefDirective.$inject = ['$state', '$timeout']; 4730function $StateRefDirective($state, $timeout) { 4731 return { 4732 restrict: 'A', 4733 require: ['?^uiSrefActive', '?^uiSrefActiveEq'], 4734 link: function(scope, element, attrs, uiSrefActive) { 4735 var ref = parseStateRef(attrs.uiSref, $state.current.name); 4736 var def = { state: ref.state, href: null, params: null }; 4737 var type = getTypeInfo(element); 4738 var active = uiSrefActive[1] || uiSrefActive[0]; 4739 var unlinkInfoFn = null; 4740 var hookFn; 4741 4742 def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {}); 4743 4744 var update = function(val) { 4745 if (val) def.params = angular.copy(val); 4746 def.href = $state.href(ref.state, def.params, def.options); 4747 4748 if (unlinkInfoFn) unlinkInfoFn(); 4749 if (active) unlinkInfoFn = active.$$addStateInfo(ref.state, def.params); 4750 if (def.href !== null) attrs.$set(type.attr, def.href); 4751 }; 4752 4753 if (ref.paramExpr) { 4754 scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true); 4755 def.params = angular.copy(scope.$eval(ref.paramExpr)); 4756 } 4757 update(); 4758 4759 if (!type.clickable) return; 4760 hookFn = clickHook(element, $state, $timeout, type, function() { return def; }); 4761 element[element.on ? 'on' : 'bind']("click", hookFn); 4762 scope.$on('$destroy', function() { 4763 element[element.off ? 'off' : 'unbind']("click", hookFn); 4764 }); 4765 } 4766 }; 4767} 4768 4769/** 4770 * @ngdoc directive 4771 * @name ui.router.state.directive:ui-state 4772 * 4773 * @requires ui.router.state.uiSref 4774 * 4775 * @restrict A 4776 * 4777 * @description 4778 * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition, 4779 * params and override options. 4780 * 4781 * @param {string} ui-state 'stateName' can be any valid absolute or relative state 4782 * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#methods_href $state.href()} 4783 * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()} 4784 */ 4785$StateRefDynamicDirective.$inject = ['$state', '$timeout']; 4786function $StateRefDynamicDirective($state, $timeout) { 4787 return { 4788 restrict: 'A', 4789 require: ['?^uiSrefActive', '?^uiSrefActiveEq'], 4790 link: function(scope, element, attrs, uiSrefActive) { 4791 var type = getTypeInfo(element); 4792 var active = uiSrefActive[1] || uiSrefActive[0]; 4793 var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null]; 4794 var watch = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']'; 4795 var def = { state: null, params: null, options: null, href: null }; 4796 var unlinkInfoFn = null; 4797 var hookFn; 4798 4799 function runStateRefLink (group) { 4800 def.state = group[0]; def.params = group[1]; def.options = group[2]; 4801 def.href = $state.href(def.state, def.params, def.options); 4802 4803 if (unlinkInfoFn) unlinkInfoFn(); 4804 if (active) unlinkInfoFn = active.$$addStateInfo(def.state, def.params); 4805 if (def.href) attrs.$set(type.attr, def.href); 4806 } 4807 4808 scope.$watch(watch, runStateRefLink, true); 4809 runStateRefLink(scope.$eval(watch)); 4810 4811 if (!type.clickable) return; 4812 hookFn = clickHook(element, $state, $timeout, type, function() { return def; }); 4813 element[element.on ? 'on' : 'bind']("click", hookFn); 4814 scope.$on('$destroy', function() { 4815 element[element.off ? 'off' : 'unbind']("click", hookFn); 4816 }); 4817 } 4818 }; 4819} 4820 4821 4822/** 4823 * @ngdoc directive 4824 * @name ui.router.state.directive:ui-sref-active 4825 * 4826 * @requires ui.router.state.$state 4827 * @requires ui.router.state.$stateParams 4828 * @requires $interpolate 4829 * 4830 * @restrict A 4831 * 4832 * @description 4833 * A directive working alongside ui-sref to add classes to an element when the 4834 * related ui-sref directive's state is active, and removing them when it is inactive. 4835 * The primary use-case is to simplify the special appearance of navigation menus 4836 * relying on `ui-sref`, by having the "active" state's menu button appear different, 4837 * distinguishing it from the inactive menu items. 4838 * 4839 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first 4840 * ui-sref-active found at the same level or above the ui-sref will be used. 4841 * 4842 * Will activate when the ui-sref's target state or any child state is active. If you 4843 * need to activate only when the ui-sref target state is active and *not* any of 4844 * it's children, then you will use 4845 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq} 4846 * 4847 * @example 4848 * Given the following template: 4849 * <pre> 4850 * <ul> 4851 * <li ui-sref-active="active" class="item">
4852 * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a> 4853 * </li> 4854 * </ul> 4855 * </pre> 4856 * 4857 * 4858 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins", 4859 * the resulting HTML will appear as (note the 'active' class): 4860 * <pre> 4861 * <ul> 4862 * <li ui-sref-active="active" class="item active"> 4863 * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a> 4864 * </li> 4865 * </ul> 4866 * </pre> 4867 * 4868 * The class name is interpolated **once** during the directives link time (any further changes to the 4869 * interpolated value are ignored). 4870 * 4871 * Multiple classes may be specified in a space-separated format: 4872 * <pre> 4873 * <ul> 4874 * <li ui-sref-active='class1 class2 class3'> 4875 * <a ui-sref="app.user">link</a> 4876 * </li> 4877 * </ul> 4878 * </pre> 4879 * 4880 * It is also possible to pass ui-sref-active an expression that evaluates 4881 * to an object hash, whose keys represent active class names and whose 4882 * values represent the respective state names/globs. 4883 * ui-sref-active will match if the current active state **includes** any of 4884 * the specified state names/globs, even the abstract ones. 4885 * 4886 * @Example 4887 * Given the following template, with "admin" being an abstract state: 4888 * <pre> 4889 * <div ui-sref-active="{'active': 'admin.*'}"> 4890 * <a ui-sref-active="active" ui-sref="admin.roles">Roles</a> 4891 * </div> 4892 * </pre> 4893 * 4894 * When the current state is "admin.roles" the "active" class will be applied 4895 * to both the <div> and <a> elements. It is important to note that the state 4896 * names/globs passed to ui-sref-active shadow the state provided by ui-sref. 4897 */ 4898 4899/** 4900 * @ngdoc directive 4901 * @name ui.router.state.directive:ui-sref-active-eq 4902 * 4903 * @requires ui.router.state.$state 4904 * @requires ui.router.state.$stateParams 4905 * @requires $interpolate 4906 * 4907 * @restrict A 4908 * 4909 * @description 4910 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate 4911 * when the exact target state used in the `ui-sref` is active; no child states. 4912 * 4913 */ 4914$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate']; 4915function $StateRefActiveDirective($state, $stateParams, $interpolate) { 4916 return { 4917 restrict: "A", 4918 controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) { 4919 var states = [], activeClasses = {}, activeEqClass, uiSrefActive; 4920 4921 // There probably isn't much point in $observing this 4922 // uiSrefActive and uiSrefActiveEq share the same directive object with some 4923 // slight difference in logic routing 4924 activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope); 4925 4926 try { 4927 uiSrefActive = $scope.$eval($attrs.uiSrefActive); 4928 } catch (e) { 4929 // Do nothing. uiSrefActive is not a valid expression. 4930 // Fall back to using $interpolate below 4931 } 4932 uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope); 4933 if (isObject(uiSrefActive)) { 4934 forEach(uiSrefActive, function(stateOrName, activeClass) { 4935 if (isString(stateOrName)) { 4936 var ref = parseStateRef(stateOrName, $state.current.name); 4937 addState(ref.state, $scope.$eval(ref.paramExpr), activeClass); 4938 } 4939 }); 4940 } 4941 4942 // Allow uiSref to communicate with uiSrefActive[Equals] 4943 this.$$addStateInfo = function (newState, newParams) { 4944 // we already got an explicit state provided by ui-sref-active, so we 4945 // shadow the one that comes from ui-sref 4946 if (isObject(uiSrefActive) && states.length > 0) { 4947 return; 4948 } 4949 var deregister = addState(newState, newParams, uiSrefActive); 4950 update(); 4951 return deregister; 4952 }; 4953 4954 $scope.$on('$stateChangeSuccess', update); 4955 4956 function addState(stateName, stateParams, activeClass) { 4957 var state = $state.get(stateName, stateContext($element)); 4958 var stateHash = createStateHash(stateName, stateParams); 4959 4960 var stateInfo = { 4961 state: state || { name: stateName }, 4962 params: stateParams, 4963 hash: stateHash 4964 }; 4965 4966 states.push(stateInfo); 4967 activeClasses[stateHash] = activeClass; 4968 4969 return function removeState() { 4970 var idx = states.indexOf(stateInfo); 4971 if (idx !== -1) states.splice(idx, 1); 4972 }; 4973 } 4974 4975 /** 4976 * @param {string} state 4977 * @param {Object|string} [params] 4978 * @return {string} 4979 */ 4980 function createStateHash(state, params) { 4981 if (!isString(state)) { 4982 throw new Error('state should be a string'); 4983 } 4984 if (isObject(params)) {
4985 return state + toJson(params); 4986 } 4987 params = $scope.$eval(params); 4988 if (isObject(params)) { 4989 return state + toJson(params); 4990 } 4991 return state; 4992 } 4993 4994 // Update route state 4995 function update() { 4996 for (var i = 0; i < states.length; i++) { 4997 if (anyMatch(states[i].state, states[i].params)) { 4998 addClass($element, activeClasses[states[i].hash]); 4999 } else { 5000 removeClass($element, activeClasses[states[i].hash]); 5001 } 5002 5003 if (exactMatch(states[i].state, states[i].params)) { 5004 addClass($element, activeEqClass); 5005 } else { 5006 removeClass($element, activeEqClass); 5007 } 5008 } 5009 } 5010 5011 function addClass(el, className) { $timeout(function () { el.addClass(className); }); } 5012 function removeClass(el, className) { el.removeClass(className); } 5013 function anyMatch(state, params) { return $state.includes(state.name, params); } 5014 function exactMatch(state, params) { return $state.is(state.name, params); } 5015 5016 update(); 5017 }] 5018 }; 5019} 5020 5021angular.module('ui.router.state') 5022 .directive('uiSref', $StateRefDirective) 5023 .directive('uiSrefActive', $StateRefActiveDirective) 5024 .directive('uiSrefActiveEq', $StateRefActiveDirective) 5025 .directive('uiState', $StateRefDynamicDirective); 5026 5027/** 5028 * @ngdoc filter 5029 * @name ui.router.state.filter:isState 5030 * 5031 * @requires ui.router.state.$state 5032 * 5033 * @description 5034 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}. 5035 */ 5036$IsStateFilter.$inject = ['$state']; 5037function $IsStateFilter($state) { 5038 var isFilter = function (state, params) { 5039 return $state.is(state, params); 5040 }; 5041 isFilter.$stateful = true; 5042 return isFilter; 5043} 5044 5045/** 5046 * @ngdoc filter 5047 * @name ui.router.state.filter:includedByState 5048 * 5049 * @requires ui.router.state.$state 5050 * 5051 * @description 5052 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}. 5053 */ 5054$IncludedByStateFilter.$inject = ['$state']; 5055function $IncludedByStateFilter($state) { 5056 var includesFilter = function (state, params, options) { 5057 return $state.includes(state, params, options); 5058 }; 5059 includesFilter.$stateful = true; 5060 return includesFilter; 5061} 5062 5063angular.module('ui.router.state') 5064 .filter('isState', $IsStateFilter) 5065 .filter('includedByState', $IncludedByStateFilter); 5066})(window, window.angular); 5067},{}],4:[function(require,module,exports){ 5068/** 5069 * @license AngularJS v1.5.1 5070 * (c) 2010-2016 Google, Inc. http://angularjs.org 5071 * License: MIT 5072 */ 5073(function(window, document, undefined) {'use strict'; 5074 5075/** 5076 * @description 5077 * 5078 * This object provides a utility for producing rich Error messages within 5079 * Angular. It can be called as follows: 5080 * 5081 * var exampleMinErr = minErr('example'); 5082 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 5083 * 5084 * The above creates an instance of minErr in the example namespace. The 5085 * resulting error will have a namespaced error code of example.one. The 5086 * resulting error will replace {0} with the value of foo, and {1} with the 5087 * value of bar. The object is not restricted in the number of arguments it can 5088 * take. 5089 * 5090 * If fewer arguments are specified than necessary for interpolation, the extra 5091 * interpolation markers will be preserved in the final string. 5092 * 5093 * Since data will be parsed statically during a build step, some restrictions 5094 * are applied with respect to how minErr instances are created and called. 5095 * Instances should have names of the form namespaceMinErr for a minErr created 5096 * using minErr('namespace') . Error codes, namespaces and template strings 5097 * should all be static strings, not variables or general expressions. 5098 * 5099 * @param {string} module The namespace to use for the new minErr instance. 5100 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 5101 * error from returned function, for cases when a particular type of error is useful. 5102 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 5103 */ 5104 5105function minErr(module, ErrorConstructor) { 5106 ErrorConstructor = ErrorConstructor || Error; 5107 return function() { 5108 var SKIP_INDEXES = 2; 5109 5110 var templateArgs = arguments, 5111 code = templateArgs[0], 5112 message = '[' + (module ? module + ':' : '') + code + '] ', 5113 template = templateArgs[1], 5114 paramPrefix, i; 5115 5116 message += template.replace(/\{\d+\}/g, function(match) { 5117 var index = +match.slice(1, -1), 5118 shiftedIndex = index + SKIP_INDEXES; 5119 5120 if (shiftedIndex < templateArgs.length) { 5121 return toDebugString(templateArgs[shiftedIndex]); 5122 } 5123 5124 return match; 5125 }); 5126 5127 message += '\nhttp://errors.angularjs.org/1.5.1/' + 5128 (module ? module + '/' : '') + code; 5129 5130 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
5131 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + 5132 encodeURIComponent(toDebugString(templateArgs[i])); 5133 } 5134 5135 return new ErrorConstructor(message); 5136 }; 5137} 5138 5139/* We need to tell jshint what variables are being exported */ 5140/* global angular: true, 5141 msie: true, 5142 jqLite: true, 5143 jQuery: true, 5144 slice: true, 5145 splice: true, 5146 push: true, 5147 toString: true, 5148 ngMinErr: true, 5149 angularModule: true, 5150 uid: true, 5151 REGEX_STRING_REGEXP: true, 5152 VALIDITY_STATE_PROPERTY: true, 5153 5154 lowercase: true, 5155 uppercase: true, 5156 manualLowercase: true, 5157 manualUppercase: true, 5158 nodeName_: true, 5159 isArrayLike: true, 5160 forEach: true, 5161 forEachSorted: true, 5162 reverseParams: true, 5163 nextUid: true, 5164 setHashKey: true, 5165 extend: true, 5166 toInt: true, 5167 inherit: true, 5168 merge: true, 5169 noop: true, 5170 identity: true, 5171 valueFn: true, 5172 isUndefined: true, 5173 isDefined: true, 5174 isObject: true, 5175 isBlankObject: true, 5176 isString: true, 5177 isNumber: true, 5178 isDate: true, 5179 isArray: true, 5180 isFunction: true, 5181 isRegExp: true, 5182 isWindow: true, 5183 isScope: true, 5184 isFile: true, 5185 isFormData: true, 5186 isBlob: true, 5187 isBoolean: true, 5188 isPromiseLike: true, 5189 trim: true, 5190 escapeForRegexp: true, 5191 isElement: true, 5192 makeMap: true, 5193 includes: true, 5194 arrayRemove: true, 5195 copy: true, 5196 shallowCopy: true, 5197 equals: true, 5198 csp: true, 5199 jq: true, 5200 concat: true, 5201 sliceArgs: true, 5202 bind: true, 5203 toJsonReplacer: true, 5204 toJson: true, 5205 fromJson: true, 5206 convertTimezoneToLocal: true, 5207 timezoneToOffset: true, 5208 startingTag: true, 5209 tryDecodeURIComponent: true, 5210 parseKeyValue: true, 5211 toKeyValue: true, 5212 encodeUriSegment: true, 5213 encodeUriQuery: true, 5214 angularInit: true, 5215 bootstrap: true, 5216 getTestability: true, 5217 snake_case: true, 5218 bindJQuery: true, 5219 assertArg: true, 5220 assertArgFn: true, 5221 assertNotHasOwnProperty: true, 5222 getter: true, 5223 getBlockNodes: true, 5224 hasOwnProperty: true, 5225 createMap: true, 5226 5227 NODE_TYPE_ELEMENT: true, 5228 NODE_TYPE_ATTRIBUTE: true, 5229 NODE_TYPE_TEXT: true, 5230 NODE_TYPE_COMMENT: true, 5231 NODE_TYPE_DOCUMENT: true, 5232 NODE_TYPE_DOCUMENT_FRAGMENT: true, 5233*/ 5234 5235//////////////////////////////////// 5236 5237/** 5238 * @ngdoc module 5239 * @name ng 5240 * @module ng 5241 * @description 5242 * 5243 * # ng (core module) 5244 * The ng module is loaded by default when an AngularJS application is started. The module itself 5245 * contains the essential components for an AngularJS application to function. The table below 5246 * lists a high level breakdown of each of the services/factories, filters, directives and testing 5247 * components available within this core module. 5248 * 5249 * <div doc-module-components="ng"></div> 5250 */ 5251 5252var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 5253 5254// The name of a form control's ValidityState property. 5255// This is used so that it's possible for internal tests to create mock ValidityStates. 5256var VALIDITY_STATE_PROPERTY = 'validity'; 5257 5258var hasOwnProperty = Object.prototype.hasOwnProperty; 5259 5260var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 5261var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 5262 5263 5264var manualLowercase = function(s) { 5265 /* jshint bitwise: false */ 5266 return isString(s) 5267 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 5268 : s; 5269}; 5270var manualUppercase = function(s) { 5271 /* jshint bitwise: false */ 5272 return isString(s) 5273 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 5274 : s; 5275}; 5276 5277 5278// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 5279// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 5280// with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387 5281if ('i' !== 'I'.toLowerCase()) { 5282 lowercase = manualLowercase; 5283 uppercase = manualUppercase; 5284} 5285 5286 5287var 5288 msie, // holds major version number for IE, or NaN if UA is not IE. 5289 jqLite, // delay binding since jQuery could be loaded after us. 5290 jQuery, // delay binding 5291 slice = [].slice, 5292 splice = [].splice, 5293 push = [].push, 5294 toString = Object.prototype.toString, 5295 getPrototypeOf = Object.getPrototypeOf, 5296 ngMinErr = minErr('ng'), 5297 5298 /** @name angular */ 5299 angular = window.angular || (window.angular = {}), 5300 angularModule, 5301 uid = 0; 5302 5303/** 5304 * documentMode is an IE-only property
5305 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 5306 */ 5307msie = document.documentMode; 5308 5309 5310/** 5311 * @private 5312 * @param {*} obj 5313 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 5314 * String ...) 5315 */ 5316function isArrayLike(obj) { 5317 5318 // `null`, `undefined` and `window` are not array-like 5319 if (obj == null || isWindow(obj)) return false; 5320 5321 // arrays, strings and jQuery/jqLite objects are array like 5322 // * jqLite is either the jQuery or jqLite constructor function 5323 // * we have to check the existence of jqLite first as this method is called 5324 // via the forEach method when constructing the jqLite object in the first place 5325 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; 5326 5327 // Support: iOS 8.2 (not reproducible in simulator) 5328 // "length" in obj used to prevent JIT error (gh-11508) 5329 var length = "length" in Object(obj) && obj.length; 5330 5331 // NodeList objects (with `item` method) and 5332 // other objects with suitable length characteristics are array-like 5333 return isNumber(length) && 5334 (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item == 'function'); 5335 5336} 5337 5338/** 5339 * @ngdoc function 5340 * @name angular.forEach 5341 * @module ng 5342 * @kind function 5343 * 5344 * @description 5345 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 5346 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 5347 * is the value of an object property or an array element, `key` is the object property key or 5348 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 5349 * 5350 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 5351 * using the `hasOwnProperty` method. 5352 * 5353 * Unlike ES262's 5354 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 5355 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 5356 * return the value provided. 5357 * 5358 ```js 5359 var values = {name: 'misko', gender: 'male'}; 5360 var log = []; 5361 angular.forEach(values, function(value, key) { 5362 this.push(key + ': ' + value); 5363 }, log); 5364 expect(log).toEqual(['name: misko', 'gender: male']); 5365 ``` 5366 * 5367 * @param {Object|Array} obj Object to iterate over. 5368 * @param {Function} iterator Iterator function. 5369 * @param {Object=} context Object to become context (`this`) for the iterator function. 5370 * @returns {Object|Array} Reference to `obj`. 5371 */ 5372 5373function forEach(obj, iterator, context) { 5374 var key, length; 5375 if (obj) { 5376 if (isFunction(obj)) { 5377 for (key in obj) { 5378 // Need to check if hasOwnProperty exists, 5379 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 5380 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 5381 iterator.call(context, obj[key], key, obj); 5382 } 5383 } 5384 } else if (isArray(obj) || isArrayLike(obj)) { 5385 var isPrimitive = typeof obj !== 'object'; 5386 for (key = 0, length = obj.length; key < length; key++) { 5387 if (isPrimitive || key in obj) { 5388 iterator.call(context, obj[key], key, obj); 5389 } 5390 } 5391 } else if (obj.forEach && obj.forEach !== forEach) { 5392 obj.forEach(iterator, context, obj); 5393 } else if (isBlankObject(obj)) { 5394 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 5395 for (key in obj) { 5396 iterator.call(context, obj[key], key, obj); 5397 } 5398 } else if (typeof obj.hasOwnProperty === 'function') { 5399 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 5400 for (key in obj) { 5401 if (obj.hasOwnProperty(key)) { 5402 iterator.call(context, obj[key], key, obj); 5403 } 5404 } 5405 } else { 5406 // Slow path for objects which do not have a method `hasOwnProperty` 5407 for (key in obj) { 5408 if (hasOwnProperty.call(obj, key)) { 5409 iterator.call(context, obj[key], key, obj); 5410 } 5411 } 5412 } 5413 } 5414 return obj; 5415} 5416 5417function forEachSorted(obj, iterator, context) { 5418 var keys = Object.keys(obj).sort(); 5419 for (var i = 0; i < keys.length; i++) { 5420 iterator.call(context, obj[keys[i]], keys[i]); 5421 } 5422 return keys; 5423} 5424 5425 5426/**
vendor: 10,542 bytes, lines 5427-5875
5427 * when using forEach the params are value, key, but it is often useful to have key, value. 5428 * @param {function(string, *)} iteratorFn 5429 * @returns {function(*, string)} 5430 */ 5431function reverseParams(iteratorFn) { 5432 return function(value, key) {iteratorFn(key, value);}; 5433} 5434 5435/** 5436 * A consistent way of creating unique IDs in angular. 5437 * 5438 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 5439 * we hit number precision issues in JavaScript. 5440 * 5441 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 5442 * 5443 * @returns {number} an unique alpha-numeric string 5444 */ 5445function nextUid() { 5446 return ++uid; 5447} 5448 5449 5450/** 5451 * Set or clear the hashkey for an object. 5452 * @param obj object 5453 * @param h the hashkey (!truthy to delete the hashkey) 5454 */ 5455function setHashKey(obj, h) { 5456 if (h) { 5457 obj.$$hashKey = h; 5458 } else { 5459 delete obj.$$hashKey; 5460 } 5461} 5462 5463 5464function baseExtend(dst, objs, deep) { 5465 var h = dst.$$hashKey; 5466 5467 for (var i = 0, ii = objs.length; i < ii; ++i) { 5468 var obj = objs[i]; 5469 if (!isObject(obj) && !isFunction(obj)) continue; 5470 var keys = Object.keys(obj); 5471 for (var j = 0, jj = keys.length; j < jj; j++) { 5472 var key = keys[j]; 5473 var src = obj[key]; 5474 5475 if (deep && isObject(src)) { 5476 if (isDate(src)) { 5477 dst[key] = new Date(src.valueOf()); 5478 } else if (isRegExp(src)) { 5479 dst[key] = new RegExp(src); 5480 } else if (src.nodeName) { 5481 dst[key] = src.cloneNode(true); 5482 } else if (isElement(src)) { 5483 dst[key] = src.clone(); 5484 } else { 5485 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 5486 baseExtend(dst[key], [src], true); 5487 } 5488 } else { 5489 dst[key] = src; 5490 } 5491 } 5492 } 5493 5494 setHashKey(dst, h); 5495 return dst; 5496} 5497 5498/** 5499 * @ngdoc function 5500 * @name angular.extend 5501 * @module ng 5502 * @kind function 5503 * 5504 * @description 5505 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 5506 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 5507 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 5508 * 5509 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 5510 * {@link angular.merge} for this. 5511 * 5512 * @param {Object} dst Destination object. 5513 * @param {...Object} src Source object(s). 5514 * @returns {Object} Reference to `dst`. 5515 */ 5516function extend(dst) { 5517 return baseExtend(dst, slice.call(arguments, 1), false); 5518} 5519 5520 5521/** 5522* @ngdoc function 5523* @name angular.merge 5524* @module ng 5525* @kind function 5526* 5527* @description 5528* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 5529* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 5530* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 5531* 5532* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 5533* objects, performing a deep copy. 5534* 5535* @param {Object} dst Destination object. 5536* @param {...Object} src Source object(s). 5537* @returns {Object} Reference to `dst`. 5538*/ 5539function merge(dst) { 5540 return baseExtend(dst, slice.call(arguments, 1), true); 5541} 5542 5543 5544 5545function toInt(str) { 5546 return parseInt(str, 10); 5547} 5548 5549 5550function inherit(parent, extra) { 5551 return extend(Object.create(parent), extra); 5552} 5553 5554/** 5555 * @ngdoc function 5556 * @name angular.noop 5557 * @module ng 5558 * @kind function 5559 * 5560 * @description 5561 * A function that performs no operations. This function can be useful when writing code in the 5562 * functional style. 5563 ```js 5564 function foo(callback) { 5565 var result = calculateResult(); 5566 (callback || angular.noop)(result); 5567 } 5568 ``` 5569 */ 5570function noop() {} 5571noop.$inject = []; 5572 5573 5574/** 5575 * @ngdoc function 5576 * @name angular.identity 5577 * @module ng 5578 * @kind function 5579 * 5580 * @description 5581 * A function that returns its first argument. This function is useful when writing code in the 5582 * functional style. 5583 * 5584 ```js 5585 function transformer(transformationFn, value) { 5586 return (transformationFn || angular.identity)(value); 5587 }; 5588 ``` 5589 * @param {*} value to be returned. 5590 * @returns {*} the value passed in. 5591 */ 5592function identity($) {return $;} 5593identity.$inject = []; 5594 5595 5596function valueFn(value) {return function() {return value;};} 5597 5598function hasCustomToString(obj) { 5599 return isFunction(obj.toString) && obj.toString !== toString; 5600} 5601 5602 5603/** 5604 * @ngdoc function 5605 * @name angular.isUndefined 5606 * @module ng 5607 * @kind function 5608 * 5609 * @description 5610 * Determines if a reference is undefined. 5611 * 5612 * @param {*} value Reference to check. 5613 * @returns {boolean} True if `value` is undefined. 5614 */ 5615function isUndefined(value) {return typeof value === 'undefined';} 5616 5617 5618/** 5619 * @ngdoc function 5620 * @name angular.isDefined 5621 * @module ng 5622 * @kind function 5623 * 5624 * @description 5625 * Determines if a reference is defined. 5626 * 5627 * @param {*} value Reference to check. 5628 * @returns {boolean} True if `value` is defined. 5629 */ 5630function isDefined(value) {return typeof value !== 'undefined';} 5631 5632 5633/** 5634 * @ngdoc function 5635 * @name angular.isObject 5636 * @module ng 5637 * @kind function 5638 * 5639 * @description 5640 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 5641 * considered to be objects. Note that JavaScript arrays are objects. 5642 * 5643 * @param {*} value Reference to check. 5644 * @returns {boolean} True if `value` is an `Object` but not `null`. 5645 */ 5646function isObject(value) { 5647 // http://jsperf.com/isobject4 5648 return value !== null && typeof value === 'object'; 5649} 5650 5651 5652/** 5653 * Determine if a value is an object with a null prototype 5654 * 5655 * @returns {boolean} True if `value` is an `Object` with a null prototype 5656 */ 5657function isBlankObject(value) { 5658 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 5659} 5660 5661 5662/** 5663 * @ngdoc function 5664 * @name angular.isString 5665 * @module ng 5666 * @kind function 5667 * 5668 * @description 5669 * Determines if a reference is a `String`. 5670 * 5671 * @param {*} value Reference to check. 5672 * @returns {boolean} True if `value` is a `String`. 5673 */ 5674function isString(value) {return typeof value === 'string';} 5675 5676 5677/** 5678 * @ngdoc function 5679 * @name angular.isNumber 5680 * @module ng 5681 * @kind function 5682 * 5683 * @description 5684 * Determines if a reference is a `Number`. 5685 * 5686 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 5687 * 5688 * If you wish to exclude these then you can use the native 5689 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 5690 * method. 5691 * 5692 * @param {*} value Reference to check. 5693 * @returns {boolean} True if `value` is a `Number`. 5694 */ 5695function isNumber(value) {return typeof value === 'number';} 5696 5697 5698/** 5699 * @ngdoc function 5700 * @name angular.isDate 5701 * @module ng 5702 * @kind function 5703 * 5704 * @description 5705 * Determines if a value is a date. 5706 * 5707 * @param {*} value Reference to check. 5708 * @returns {boolean} True if `value` is a `Date`. 5709 */ 5710function isDate(value) { 5711 return toString.call(value) === '[object Date]'; 5712} 5713 5714 5715/** 5716 * @ngdoc function 5717 * @name angular.isArray 5718 * @module ng 5719 * @kind function 5720 * 5721 * @description 5722 * Determines if a reference is an `Array`. 5723 * 5724 * @param {*} value Reference to check. 5725 * @returns {boolean} True if `value` is an `Array`. 5726 */ 5727var isArray = Array.isArray; 5728 5729/** 5730 * @ngdoc function 5731 * @name angular.isFunction 5732 * @module ng 5733 * @kind function 5734 * 5735 * @description 5736 * Determines if a reference is a `Function`. 5737 * 5738 * @param {*} value Reference to check. 5739 * @returns {boolean} True if `value` is a `Function`. 5740 */ 5741function isFunction(value) {return typeof value === 'function';} 5742 5743 5744/** 5745 * Determines if a value is a regular expression object. 5746 * 5747 * @private 5748 * @param {*} value Reference to check. 5749 * @returns {boolean} True if `value` is a `RegExp`. 5750 */ 5751function isRegExp(value) { 5752 return toString.call(value) === '[object RegExp]'; 5753} 5754 5755 5756/** 5757 * Checks if `obj` is a window object. 5758 * 5759 * @private 5760 * @param {*} obj Object to check 5761 * @returns {boolean} True if `obj` is a window obj. 5762 */ 5763function isWindow(obj) { 5764 return obj && obj.window === obj; 5765} 5766 5767 5768function isScope(obj) { 5769 return obj && obj.$evalAsync && obj.$watch; 5770} 5771 5772 5773function isFile(obj) { 5774 return toString.call(obj) === '[object File]'; 5775} 5776 5777 5778function isFormData(obj) { 5779 return toString.call(obj) === '[object FormData]'; 5780} 5781 5782 5783function isBlob(obj) { 5784 return toString.call(obj) === '[object Blob]'; 5785} 5786 5787 5788function isBoolean(value) { 5789 return typeof value === 'boolean'; 5790} 5791 5792 5793function isPromiseLike(obj) { 5794 return obj && isFunction(obj.then); 5795} 5796 5797 5798var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/; 5799function isTypedArray(value) { 5800 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); 5801} 5802 5803function isArrayBuffer(obj) { 5804 return toString.call(obj) === '[object ArrayBuffer]'; 5805} 5806 5807 5808var trim = function(value) { 5809 return isString(value) ? value.trim() : value; 5810}; 5811 5812// Copied from: 5813// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 5814// Prereq: s is a string. 5815var escapeForRegexp = function(s) { 5816 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 5817 replace(/\x08/g, '\\x08'); 5818}; 5819 5820 5821/** 5822 * @ngdoc function 5823 * @name angular.isElement 5824 * @module ng 5825 * @kind function 5826 * 5827 * @description 5828 * Determines if a reference is a DOM element (or wrapped jQuery element). 5829 * 5830 * @param {*} value Reference to check. 5831 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 5832 */ 5833function isElement(node) { 5834 return !!(node && 5835 (node.nodeName // we are a direct element 5836 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 5837} 5838 5839/** 5840 * @param str 'key1,key2,...' 5841 * @returns {object} in the form of {key1:true, key2:true, ...} 5842 */ 5843function makeMap(str) { 5844 var obj = {}, items = str.split(','), i; 5845 for (i = 0; i < items.length; i++) { 5846 obj[items[i]] = true; 5847 } 5848 return obj; 5849} 5850 5851 5852function nodeName_(element) { 5853 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 5854} 5855 5856function includes(array, obj) { 5857 return Array.prototype.indexOf.call(array, obj) != -1; 5858} 5859 5860function arrayRemove(array, value) { 5861 var index = array.indexOf(value); 5862 if (index >= 0) { 5863 array.splice(index, 1); 5864 } 5865 return index; 5866} 5867 5868/** 5869 * @ngdoc function 5870 * @name angular.copy 5871 * @module ng 5872 * @kind function 5873 * 5874 * @description 5875 * Creates a deep copy of `source`, which should be an object or an array.
5876 * 5877 * * If no destination is supplied, a copy of the object or array is created. 5878 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 5879 * are deleted and then all elements/properties from the source are copied to it. 5880 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 5881 * * If `source` is identical to 'destination' an exception will be thrown. 5882 * 5883 * @param {*} source The source that will be used to make a copy. 5884 * Can be any type, including primitives, `null`, and `undefined`. 5885 * @param {(Object|Array)=} destination Destination into which the source is copied. If 5886 * provided, must be of the same type as `source`. 5887 * @returns {*} The copy or updated `destination`, if `destination` was specified. 5888 * 5889 * @example 5890 <example module="copyExample"> 5891 <file name="index.html"> 5892 <div ng-controller="ExampleController"> 5893 <form novalidate class="simple-form"> 5894 Name: <input type="text" ng-model="user.name" /><br /> 5895 E-mail: <input type="email" ng-model="user.email" /><br /> 5896 Gender: <input type="radio" ng-model="user.gender" value="male" />male 5897 <input type="radio" ng-model="user.gender" value="female" />female<br /> 5898 <button ng-click="reset()">RESET</button> 5899 <button ng-click="update(user)">SAVE</button> 5900 </form> 5901 <pre>form = {{user | json}}</pre> 5902 <pre>master = {{master | json}}</pre> 5903 </div> 5904 5905 <script> 5906 angular.module('copyExample', []) 5907 .controller('ExampleController', ['$scope', function($scope) { 5908 $scope.master= {}; 5909 5910 $scope.update = function(user) { 5911 // Example with 1 argument 5912 $scope.master= angular.copy(user); 5913 }; 5914 5915 $scope.reset = function() { 5916 // Example with 2 arguments 5917 angular.copy($scope.master, $scope.user); 5918 }; 5919 5920 $scope.reset(); 5921 }]); 5922 </script> 5923 </file> 5924 </example> 5925 */ 5926function copy(source, destination) { 5927 var stackSource = []; 5928 var stackDest = []; 5929 5930 if (destination) { 5931 if (isTypedArray(destination) || isArrayBuffer(destination)) { 5932 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated."); 5933 } 5934 if (source === destination) { 5935 throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); 5936 } 5937 5938 // Empty the destination object 5939 if (isArray(destination)) { 5940 destination.length = 0; 5941 } else { 5942 forEach(destination, function(value, key) { 5943 if (key !== '$$hashKey') { 5944 delete destination[key]; 5945 } 5946 }); 5947 } 5948 5949 stackSource.push(source); 5950 stackDest.push(destination); 5951 return copyRecurse(source, destination); 5952 } 5953 5954 return copyElement(source); 5955 5956 function copyRecurse(source, destination) { 5957 var h = destination.$$hashKey; 5958 var result, key; 5959 if (isArray(source)) { 5960 for (var i = 0, ii = source.length; i < ii; i++) { 5961 destination.push(copyElement(source[i])); 5962 } 5963 } else if (isBlankObject(source)) { 5964 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 5965 for (key in source) { 5966 destination[key] = copyElement(source[key]); 5967 } 5968 } else if (source && typeof source.hasOwnProperty === 'function') { 5969 // Slow path, which must rely on hasOwnProperty 5970 for (key in source) { 5971 if (source.hasOwnProperty(key)) { 5972 destination[key] = copyElement(source[key]); 5973 } 5974 } 5975 } else { 5976 // Slowest path --- hasOwnProperty can't be called as a method 5977 for (key in source) { 5978 if (hasOwnProperty.call(source, key)) { 5979 destination[key] = copyElement(source[key]); 5980 } 5981 } 5982 } 5983 setHashKey(destination, h); 5984 return destination; 5985 } 5986 5987 function copyElement(source) { 5988 // Simple values 5989 if (!isObject(source)) { 5990 return source; 5991 } 5992 5993 // Already copied values 5994 var index = stackSource.indexOf(source); 5995 if (index !== -1) { 5996 return stackDest[index]; 5997 } 5998 5999 if (isWindow(source) || isScope(source)) { 6000 throw ngMinErr('cpws', 6001 "Can't copy! Making copies of Window or Scope instances is not supported."); 6002 } 6003 6004 var needsRecurse = false;
vendor: 15,778 bytes, lines 6005-6534
6005 var destination = copyType(source); 6006 6007 if (destination === undefined) { 6008 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source)); 6009 needsRecurse = true; 6010 } 6011 6012 stackSource.push(source); 6013 stackDest.push(destination); 6014 6015 return needsRecurse 6016 ? copyRecurse(source, destination) 6017 : destination; 6018 } 6019 6020 function copyType(source) { 6021 switch (toString.call(source)) { 6022 case '[object Int8Array]': 6023 case '[object Int16Array]': 6024 case '[object Int32Array]': 6025 case '[object Float32Array]': 6026 case '[object Float64Array]': 6027 case '[object Uint8Array]': 6028 case '[object Uint8ClampedArray]': 6029 case '[object Uint16Array]': 6030 case '[object Uint32Array]': 6031 return new source.constructor(copyElement(source.buffer)); 6032 6033 case '[object ArrayBuffer]': 6034 //Support: IE10 6035 if (!source.slice) { 6036 var copied = new ArrayBuffer(source.byteLength); 6037 new Uint8Array(copied).set(new Uint8Array(source)); 6038 return copied; 6039 } 6040 return source.slice(0); 6041 6042 case '[object Boolean]': 6043 case '[object Number]': 6044 case '[object String]': 6045 case '[object Date]': 6046 return new source.constructor(source.valueOf()); 6047 6048 case '[object RegExp]': 6049 var re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 6050 re.lastIndex = source.lastIndex; 6051 return re; 6052 6053 case '[object Blob]': 6054 return new source.constructor([source], {type: source.type}); 6055 } 6056 6057 if (isFunction(source.cloneNode)) { 6058 return source.cloneNode(true); 6059 } 6060 } 6061} 6062 6063/** 6064 * Creates a shallow copy of an object, an array or a primitive. 6065 * 6066 * Assumes that there are no proto properties for objects. 6067 */ 6068function shallowCopy(src, dst) { 6069 if (isArray(src)) { 6070 dst = dst || []; 6071 6072 for (var i = 0, ii = src.length; i < ii; i++) { 6073 dst[i] = src[i]; 6074 } 6075 } else if (isObject(src)) { 6076 dst = dst || {}; 6077 6078 for (var key in src) { 6079 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 6080 dst[key] = src[key]; 6081 } 6082 } 6083 } 6084 6085 return dst || src; 6086} 6087 6088 6089/** 6090 * @ngdoc function 6091 * @name angular.equals 6092 * @module ng 6093 * @kind function 6094 * 6095 * @description 6096 * Determines if two objects or two values are equivalent. Supports value types, regular 6097 * expressions, arrays and objects. 6098 * 6099 * Two objects or values are considered equivalent if at least one of the following is true: 6100 * 6101 * * Both objects or values pass `===` comparison. 6102 * * Both objects or values are of the same type and all of their properties are equal by 6103 * comparing them with `angular.equals`. 6104 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 6105 * * Both values represent the same regular expression (In JavaScript, 6106 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 6107 * representation matches). 6108 * 6109 * During a property comparison, properties of `function` type and properties with names 6110 * that begin with `$` are ignored. 6111 * 6112 * Scope and DOMWindow objects are being compared only by identify (`===`). 6113 * 6114 * @param {*} o1 Object or value to compare. 6115 * @param {*} o2 Object or value to compare. 6116 * @returns {boolean} True if arguments are equal. 6117 */ 6118function equals(o1, o2) { 6119 if (o1 === o2) return true; 6120 if (o1 === null || o2 === null) return false; 6121 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 6122 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 6123 if (t1 == t2 && t1 == 'object') { 6124 if (isArray(o1)) { 6125 if (!isArray(o2)) return false; 6126 if ((length = o1.length) == o2.length) { 6127 for (key = 0; key < length; key++) { 6128 if (!equals(o1[key], o2[key])) return false; 6129 } 6130 return true; 6131 } 6132 } else if (isDate(o1)) { 6133 if (!isDate(o2)) return false; 6134 return equals(o1.getTime(), o2.getTime()); 6135 } else if (isRegExp(o1)) { 6136 if (!isRegExp(o2)) return false; 6137 return o1.toString() == o2.toString(); 6138 } else { 6139 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 6140 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 6141 keySet = createMap(); 6142 for (key in o1) { 6143 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 6144 if (!equals(o1[key], o2[key])) return false; 6145 keySet[key] = true; 6146 } 6147 for (key in o2) { 6148 if (!(key in keySet) && 6149 key.charAt(0) !== '$' && 6150 isDefined(o2[key]) && 6151 !isFunction(o2[key])) return false; 6152 } 6153 return true; 6154 } 6155 } 6156 return false; 6157} 6158 6159var csp = function() { 6160 if (!isDefined(csp.rules)) { 6161 6162 6163 var ngCspElement = (document.querySelector('[ng-csp]') || 6164 document.querySelector('[data-ng-csp]')); 6165 6166 if (ngCspElement) { 6167 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 6168 ngCspElement.getAttribute('data-ng-csp'); 6169 csp.rules = { 6170 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 6171 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 6172 }; 6173 } else { 6174 csp.rules = { 6175 noUnsafeEval: noUnsafeEval(), 6176 noInlineStyle: false 6177 }; 6178 } 6179 } 6180 6181 return csp.rules; 6182 6183 function noUnsafeEval() { 6184 try { 6185 /* jshint -W031, -W054 */ 6186 new Function(''); 6187 /* jshint +W031, +W054 */ 6188 return false; 6189 } catch (e) { 6190 return true; 6191 } 6192 } 6193}; 6194 6195/** 6196 * @ngdoc directive 6197 * @module ng 6198 * @name ngJq 6199 * 6200 * @element ANY 6201 * @param {string=} ngJq the name of the library available under `window` 6202 * to be used for angular.element 6203 * @description 6204 * Use this directive to force the angular.element library. This should be 6205 * used to force either jqLite by leaving ng-jq blank or setting the name of 6206 * the jquery variable under window (eg. jQuery). 6207 * 6208 * Since angular looks for this directive when it is loaded (doesn't wait for the 6209 * DOMContentLoaded event), it must be placed on an element that comes before the script 6210 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 6211 * others ignored. 6212 * 6213 * @example 6214 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 6215 ```html 6216 <!doctype html> 6217 <html ng-app ng-jq> 6218 ... 6219 ... 6220 </html> 6221 ``` 6222 * @example 6223 * This example shows how to use a jQuery based library of a different name. 6224 * The library name must be available at the top most 'window'. 6225 ```html 6226 <!doctype html> 6227 <html ng-app ng-jq="jQueryLib"> 6228 ... 6229 ... 6230 </html> 6231 ``` 6232 */ 6233var jq = function() { 6234 if (isDefined(jq.name_)) return jq.name_; 6235 var el; 6236 var i, ii = ngAttrPrefixes.length, prefix, name; 6237 for (i = 0; i < ii; ++i) { 6238 prefix = ngAttrPrefixes[i]; 6239 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 6240 name = el.getAttribute(prefix + 'jq'); 6241 break; 6242 } 6243 } 6244 6245 return (jq.name_ = name); 6246}; 6247 6248function concat(array1, array2, index) { 6249 return array1.concat(slice.call(array2, index)); 6250} 6251 6252function sliceArgs(args, startIndex) { 6253 return slice.call(args, startIndex || 0); 6254} 6255 6256 6257/* jshint -W101 */ 6258/** 6259 * @ngdoc function 6260 * @name angular.bind 6261 * @module ng 6262 * @kind function 6263 * 6264 * @description 6265 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 6266 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 6267 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 6268 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 6269 * 6270 * @param {Object} self Context which `fn` should be evaluated in. 6271 * @param {function()} fn Function to be bound. 6272 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 6273 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 6274 */ 6275/* jshint +W101 */ 6276function bind(self, fn) { 6277 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 6278 if (isFunction(fn) && !(fn instanceof RegExp)) { 6279 return curryArgs.length 6280 ? function() { 6281 return arguments.length 6282 ? fn.apply(self, concat(curryArgs, arguments, 0)) 6283 : fn.apply(self, curryArgs); 6284 } 6285 : function() { 6286 return arguments.length 6287 ? fn.apply(self, arguments) 6288 : fn.call(self); 6289 }; 6290 } else { 6291 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 6292 return fn; 6293 } 6294} 6295 6296 6297function toJsonReplacer(key, value) { 6298 var val = value; 6299 6300 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 6301 val = undefined; 6302 } else if (isWindow(value)) { 6303 val = '$WINDOW'; 6304 } else if (value && document === value) { 6305 val = '$DOCUMENT'; 6306 } else if (isScope(value)) { 6307 val = '$SCOPE'; 6308 } 6309 6310 return val; 6311} 6312 6313 6314/** 6315 * @ngdoc function 6316 * @name angular.toJson 6317 * @module ng 6318 * @kind function 6319 * 6320 * @description 6321 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 6322 * stripped since angular uses this notation internally. 6323 * 6324 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 6325 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 6326 * If set to an integer, the JSON output will contain that many spaces per indentation. 6327 * @returns {string|undefined} JSON-ified string representing `obj`. 6328 */ 6329function toJson(obj, pretty) { 6330 if (isUndefined(obj)) return undefined; 6331 if (!isNumber(pretty)) { 6332 pretty = pretty ? 2 : null; 6333 } 6334 return JSON.stringify(obj, toJsonReplacer, pretty); 6335} 6336 6337 6338/** 6339 * @ngdoc function 6340 * @name angular.fromJson 6341 * @module ng 6342 * @kind function 6343 * 6344 * @description 6345 * Deserializes a JSON string. 6346 * 6347 * @param {string} json JSON string to deserialize. 6348 * @returns {Object|Array|string|number} Deserialized JSON string. 6349 */ 6350function fromJson(json) { 6351 return isString(json) 6352 ? JSON.parse(json) 6353 : json; 6354} 6355 6356 6357var ALL_COLONS = /:/g; 6358function timezoneToOffset(timezone, fallback) { 6359 // IE/Edge do not "understand" colon (`:`) in timezone 6360 timezone = timezone.replace(ALL_COLONS, ''); 6361 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 6362 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 6363} 6364 6365 6366function addDateMinutes(date, minutes) { 6367 date = new Date(date.getTime()); 6368 date.setMinutes(date.getMinutes() + minutes); 6369 return date; 6370} 6371 6372 6373function convertTimezoneToLocal(date, timezone, reverse) { 6374 reverse = reverse ? -1 : 1; 6375 var dateTimezoneOffset = date.getTimezoneOffset(); 6376 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 6377 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 6378} 6379 6380 6381/** 6382 * @returns {string} Returns the string representation of the element. 6383 */ 6384function startingTag(element) { 6385 element = jqLite(element).clone(); 6386 try { 6387 // turns out IE does not let you set .html() on elements which 6388 // are not allowed to have children. So we just ignore it. 6389 element.empty(); 6390 } catch (e) {} 6391 var elemHtml = jqLite('<div>').append(element).html(); 6392 try { 6393 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 6394 elemHtml. 6395 match(/^(<[^>]+>)/)[1]. 6396 replace(/^<([\w\-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);}); 6397 } catch (e) { 6398 return lowercase(elemHtml); 6399 } 6400 6401} 6402 6403 6404///////////////////////////////////////////////// 6405 6406/** 6407 * Tries to decode the URI component without throwing an exception. 6408 * 6409 * @private 6410 * @param str value potential URI component to check. 6411 * @returns {boolean} True if `value` can be decoded 6412 * with the decodeURIComponent function. 6413 */ 6414function tryDecodeURIComponent(value) { 6415 try { 6416 return decodeURIComponent(value); 6417 } catch (e) { 6418 // Ignore any invalid uri component 6419 } 6420} 6421 6422 6423/** 6424 * Parses an escaped url query string into key-value pairs. 6425 * @returns {Object.<string,boolean|Array>} 6426 */ 6427function parseKeyValue(/**string*/keyValue) { 6428 var obj = {}; 6429 forEach((keyValue || "").split('&'), function(keyValue) { 6430 var splitPoint, key, val; 6431 if (keyValue) { 6432 key = keyValue = keyValue.replace(/\+/g,'%20'); 6433 splitPoint = keyValue.indexOf('='); 6434 if (splitPoint !== -1) { 6435 key = keyValue.substring(0, splitPoint); 6436 val = keyValue.substring(splitPoint + 1); 6437 } 6438 key = tryDecodeURIComponent(key); 6439 if (isDefined(key)) { 6440 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 6441 if (!hasOwnProperty.call(obj, key)) { 6442 obj[key] = val; 6443 } else if (isArray(obj[key])) { 6444 obj[key].push(val); 6445 } else { 6446 obj[key] = [obj[key],val]; 6447 } 6448 } 6449 } 6450 }); 6451 return obj; 6452} 6453 6454function toKeyValue(obj) { 6455 var parts = []; 6456 forEach(obj, function(value, key) { 6457 if (isArray(value)) { 6458 forEach(value, function(arrayValue) { 6459 parts.push(encodeUriQuery(key, true) + 6460 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 6461 }); 6462 } else { 6463 parts.push(encodeUriQuery(key, true) + 6464 (value === true ? '' : '=' + encodeUriQuery(value, true))); 6465 } 6466 }); 6467 return parts.length ? parts.join('&') : ''; 6468} 6469 6470 6471/** 6472 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 6473 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 6474 * segments: 6475 * segment = *pchar 6476 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 6477 * pct-encoded = "%" HEXDIG HEXDIG 6478 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 6479 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 6480 * / "*" / "+" / "," / ";" / "=" 6481 */ 6482function encodeUriSegment(val) { 6483 return encodeUriQuery(val, true). 6484 replace(/%26/gi, '&'). 6485 replace(/%3D/gi, '='). 6486 replace(/%2B/gi, '+'); 6487} 6488 6489 6490/** 6491 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 6492 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 6493 * encoded per http://tools.ietf.org/html/rfc3986: 6494 * query = *( pchar / "/" / "?" ) 6495 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 6496 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 6497 * pct-encoded = "%" HEXDIG HEXDIG 6498 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 6499 * / "*" / "+" / "," / ";" / "=" 6500 */ 6501function encodeUriQuery(val, pctEncodeSpaces) { 6502 return encodeURIComponent(val). 6503 replace(/%40/gi, '@'). 6504 replace(/%3A/gi, ':'). 6505 replace(/%24/g, '$'). 6506 replace(/%2C/gi, ','). 6507 replace(/%3B/gi, ';'). 6508 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 6509} 6510 6511var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 6512 6513function getNgAttribute(element, ngAttr) { 6514 var attr, i, ii = ngAttrPrefixes.length; 6515 for (i = 0; i < ii; ++i) { 6516 attr = ngAttrPrefixes[i] + ngAttr; 6517 if (isString(attr = element.getAttribute(attr))) { 6518 return attr; 6519 } 6520 } 6521 return null; 6522} 6523 6524/** 6525 * @ngdoc directive 6526 * @name ngApp 6527 * @module ng 6528 * 6529 * @element ANY 6530 * @param {angular.Module} ngApp an optional application 6531 * {@link angular.module module} name to load. 6532 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 6533 * created in "strict-di" mode. This means that the application will fail to invoke functions which 6534 * do not use explicit function annotation (and are thus unsuitable for minification), as de
6534scribed 6535 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 6536 * tracking down the root of these bugs. 6537 * 6538 * @description 6539 * 6540 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 6541 * designates the **root element** of the application and is typically placed near the root element 6542 * of the page - e.g. on the `<body>` or `<html>` tags. 6543 * 6544 * There are a few things to keep in mind when using `ngApp`: 6545 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 6546 * found in the document will be used to define the root element to auto-bootstrap as an 6547 * application. To run multiple applications in an HTML document you must manually bootstrap them using 6548 * {@link angular.bootstrap} instead. 6549 * - AngularJS applications cannot be nested within each other. 6550 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`. 6551 * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and 6552 * {@link ngRoute.ngView `ngView`}. 6553 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 6554 * causing animations to stop working and making the injector inaccessible from outside the app. 6555 * 6556 * You can specify an **AngularJS module** to be used as the root module for the application. This 6557 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 6558 * should contain the application code needed or have dependencies on other modules that will 6559 * contain the code. See {@link angular.module} for more information. 6560 * 6561 * In the example below if the `ngApp` directive were not placed on the `html` element then the 6562 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 6563 * would not be resolved to `3`. 6564 * 6565 * `ngApp` is the easiest, and most common way to bootstrap an application. 6566 * 6567 <example module="ngAppDemo"> 6568 <file name="index.html"> 6569 <div ng-controller="ngAppDemoController"> 6570 I can add: {{a}} + {{b}} = {{ a+b }} 6571 </div> 6572 </file> 6573 <file name="script.js"> 6574 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 6575 $scope.a = 1; 6576 $scope.b = 2; 6577 }); 6578 </file> 6579 </example> 6580 * 6581 * Using `ngStrictDi`, you would see something like this: 6582 * 6583 <example ng-app-included="true"> 6584 <file name="index.html"> 6585 <div ng-app="ngAppStrictDemo" ng-strict-di> 6586 <div ng-controller="GoodController1"> 6587 I can add: {{a}} + {{b}} = {{ a+b }} 6588 6589 <p>This renders because the controller does not fail to 6590 instantiate, by using explicit annotation style (see 6591 script.js for details) 6592 </p> 6593 </div> 6594 6595 <div ng-controller="GoodController2"> 6596 Name: <input ng-model="name"><br /> 6597 Hello, {{name}}! 6598 6599 <p>This renders because the controller does not fail to 6600 instantiate, by using explicit annotation style 6601 (see script.js for details) 6602 </p> 6603 </div> 6604 6605 <div ng-controller="BadController"> 6606 I can add: {{a}} + {{b}} = {{ a+b }} 6607 6608 <p>The controller could not be instantiated, due to relying 6609 on automatic function annotations (which are disabled in 6610 strict mode). As such, the content of this section is not 6611 interpolated, and there should be an error in your web console. 6612 </p> 6613 </div> 6614 </div> 6615 </file> 6616 <file name="script.js"> 6617 angular.module('ngAppStrictDemo', []) 6618 // BadController will fail to instantiate, due to relying on automatic function annotation, 6619 // rather than an explicit annotation 6620 .controller('BadController', function($scope) { 6621 $scope.a = 1; 6622 $scope.b = 2; 6623 }) 6624 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 6625 // due to using explicit annotations using the array style and $inject property, respectively. 6626 .controller('GoodController1', ['$scope', function($scope) { 6627 $scope.a = 1; 6628 $scope.b = 2; 6629 }]) 6630 .controller('GoodController2', GoodController2); 6631 function GoodController2($scope) { 6632 $scope.name = "World"; 6633 } 6634 GoodController2.$inject = ['$scope']; 6635 </file> 6636 <file name="style.css"> 6637 div[ng-controller] { 6638 margin-bottom: 1em; 6639 -webkit-border-radius: 4px; 6640 border-radius: 4px; 6641 border: 1px solid; 6642 padding: .5em; 6643 } 6644 div[ng-controller^=Good] { 6645 border-color: #d6e9c6; 6646 background-color: #dff0d8; 6647 color: #3c763d; 6648 } 6649 div[ng-controller^=Bad] { 6650 border-color: #ebccd1; 6651 background-color: #f2dede; 6652 color: #a94442; 6653 margin-bottom: 0; 6654 } 6655 </file> 6656 </example> 6657 */ 6658function angularInit(element, bootstrap) { 6659 var appElement, 6660 module, 6661 config = {}; 6662 6663 // The element `element` has priority over any other element 6664 forEach(ngAttrPrefixes, function(prefix) { 6665 var name = prefix + 'app'; 6666 6667 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 6668 appElement = element; 6669 module = element.getAttribute(name); 6670 } 6671 }); 6672 forEach(ngAttrPrefixes, function(prefix) { 6673 var name = prefix + 'app'; 6674 var candidate; 6675 6676 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 6677 appElement = candidate; 6678 module = candidate.getAttribute(name); 6679 } 6680 }); 6681 if (appElement) { 6682 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 6683 bootstrap(appElement, module ? [module] : [], config); 6684 } 6685} 6686 6687/** 6688 * @ngdoc function 6689 * @name angular.bootstrap 6690 * @module ng 6691 * @description 6692 * Use this function to manually start up angular application. 6693 * 6694 * For more information, see the {@link guide/bootstrap Bootstrap guide}. 6695 * 6696 * Angular will detect if it has been loaded into the browser more than once and only allow the 6697 * first loaded script to be bootstrapped and will report a warning to the browser console for 6698 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 6699 * multiple instances of Angular try to work on the DOM. 6700 * 6701 * <div class="alert alert-warning">
6702 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually. 6703 * They must use {@link ng.directive:ngApp ngApp}. 6704 * </div> 6705 * 6706 * <div class="alert alert-warning"> 6707 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion}, 6708 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}. 6709 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 6710 * causing animations to stop working and making the injector inaccessible from outside the app. 6711 * </div> 6712 * 6713 * ```html 6714 * <!doctype html> 6715 * <html> 6716 * <body> 6717 * <div ng-controller="WelcomeController"> 6718 * {{greeting}} 6719 * </div> 6720 * 6721 * <script src="angular.js"></script> 6722 * <script> 6723 * var app = angular.module('demo', []) 6724 * .controller('WelcomeController', function($scope) { 6725 * $scope.greeting = 'Welcome!'; 6726 * }); 6727 * angular.bootstrap(document, ['demo']); 6728 * </script> 6729 * </body> 6730 * </html> 6731 * ``` 6732 * 6733 * @param {DOMElement} element DOM element which is the root of angular application. 6734 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 6735 * Each item in the array should be the name of a predefined module or a (DI annotated) 6736 * function that will be invoked by the injector as a `config` block. 6737 * See: {@link angular.module modules} 6738 * @param {Object=} config an object for defining configuration options for the application. The 6739 * following keys are supported: 6740 * 6741 * * `strictDi` - disable automatic function annotation for the application. This is meant to 6742 * assist in finding bugs which break minified code. Defaults to `false`. 6743 * 6744 * @returns {auto.$injector} Returns the newly created injector for this app. 6745 */ 6746function bootstrap(element, modules, config) { 6747 if (!isObject(config)) config = {}; 6748 var defaultConfig = { 6749 strictDi: false 6750 }; 6751 config = extend(defaultConfig, config); 6752 var doBootstrap = function() { 6753 element = jqLite(element); 6754 6755 if (element.injector()) { 6756 var tag = (element[0] === document) ? 'document' : startingTag(element); 6757 //Encode angle brackets to prevent input from being sanitized to empty string #8683 6758 throw ngMinErr( 6759 'btstrpd', 6760 "App Already Bootstrapped with this Element '{0}'", 6761 tag.replace(/</,'<').replace(/>/,'>')); 6762 } 6763 6764 modules = modules || []; 6765 modules.unshift(['$provide', function($provide) { 6766 $provide.value('$rootElement', element); 6767 }]); 6768 6769 if (config.debugInfoEnabled) { 6770 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 6771 modules.push(['$compileProvider', function($compileProvider) { 6772 $compileProvider.debugInfoEnabled(true); 6773 }]); 6774 } 6775 6776 modules.unshift('ng'); 6777 var injector = createInjector(modules, config.strictDi); 6778 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 6779 function bootstrapApply(scope, element, compile, injector) { 6780 scope.$apply(function() { 6781 element.data('$injector', injector); 6782 compile(element)(scope); 6783 }); 6784 }] 6785 ); 6786 return injector; 6787 }; 6788 6789 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 6790 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 6791 6792 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 6793 config.debugInfoEnabled = true; 6794 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 6795 } 6796 6797 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 6798 return doBootstrap(); 6799 } 6800 6801 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 6802 angular.resumeBootstrap = function(extraModules) { 6803 forEach(extraModules, function(module) { 6804 modules.push(module); 6805 }); 6806 return doBootstrap(); 6807 }; 6808 6809 if (isFunction(angular.resumeDeferredBootstrap)) { 6810 angular.resumeDeferredBootstrap(); 6811 } 6812} 6813 6814/** 6815 * @ngdoc function 6816 * @name angular.reloadWithDebugInfo 6817 * @module ng 6818 * @description 6819 * Use this function to reload the current application with debug information turned on. 6820 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 6821 * 6822 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 6823 */ 6824function reloadWithDebugInfo() { 6825 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 6826 window.location.reload(); 6827} 6828 6829/** 6830 * @name angular.getTestability 6831 * @module ng 6832 * @description 6833 * Get the testability service for the instance of Angular on the given 6834 * element. 6835 * @param {DOMElement} element DOM element which is the root of angular application. 6836 */ 6837function getTestability(rootElement) { 6838 var injector = angular.element(rootElement).injector(); 6839 if (!injector) { 6840 throw ngMinErr('test', 6841 'no injector found for element argument to getTestability'); 6842 } 6843 return injector.get('$$testability'); 6844} 6845 6846var SNAKE_CASE_REGEXP = /[A-Z]/g; 6847function snake_case(name, separator) { 6848 separator = separator || '_'; 6849 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 6850 return (pos ? separator : '') + letter.toLowerCase(); 6851 }); 6852} 6853 6854var bindJQueryFired = false;
vendor: 8,937 bytes, lines 6855-7139
6855function bindJQuery() { 6856 var originalCleanData; 6857 6858 if (bindJQueryFired) { 6859 return; 6860 } 6861 6862 // bind to jQuery if present; 6863 var jqName = jq(); 6864 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 6865 !jqName ? undefined : // use jqLite 6866 window[jqName]; // use jQuery specified by `ngJq` 6867 6868 // Use jQuery if it exists with proper functionality, otherwise default to us. 6869 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 6870 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 6871 // versions. It will not work for sure with jQuery <1.7, though. 6872 if (jQuery && jQuery.fn.on) { 6873 jqLite = jQuery; 6874 extend(jQuery.fn, { 6875 scope: JQLitePrototype.scope, 6876 isolateScope: JQLitePrototype.isolateScope, 6877 controller: JQLitePrototype.controller, 6878 injector: JQLitePrototype.injector, 6879 inheritedData: JQLitePrototype.inheritedData 6880 }); 6881 6882 // All nodes removed from the DOM via various jQuery APIs like .remove() 6883 // are passed through jQuery.cleanData. Monkey-patch this method to fire 6884 // the $destroy event on all removed nodes. 6885 originalCleanData = jQuery.cleanData; 6886 jQuery.cleanData = function(elems) { 6887 var events; 6888 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 6889 events = jQuery._data(elem, "events"); 6890 if (events && events.$destroy) { 6891 jQuery(elem).triggerHandler('$destroy'); 6892 } 6893 } 6894 originalCleanData(elems); 6895 }; 6896 } else { 6897 jqLite = JQLite; 6898 } 6899 6900 angular.element = jqLite; 6901 6902 // Prevent double-proxying. 6903 bindJQueryFired = true; 6904} 6905 6906/** 6907 * throw error if the argument is falsy. 6908 */ 6909function assertArg(arg, name, reason) { 6910 if (!arg) { 6911 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 6912 } 6913 return arg; 6914} 6915 6916function assertArgFn(arg, name, acceptArrayAnnotation) { 6917 if (acceptArrayAnnotation && isArray(arg)) { 6918 arg = arg[arg.length - 1]; 6919 } 6920 6921 assertArg(isFunction(arg), name, 'not a function, got ' + 6922 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 6923 return arg; 6924} 6925 6926/** 6927 * throw error if the name given is hasOwnProperty 6928 * @param {String} name the name to test 6929 * @param {String} context the context in which the name is used, such as module or directive 6930 */ 6931function assertNotHasOwnProperty(name, context) { 6932 if (name === 'hasOwnProperty') { 6933 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 6934 } 6935} 6936 6937/** 6938 * Return the value accessible from the object by path. Any undefined traversals are ignored 6939 * @param {Object} obj starting object 6940 * @param {String} path path to traverse 6941 * @param {boolean} [bindFnToScope=true] 6942 * @returns {Object} value as accessible by path 6943 */ 6944//TODO(misko): this function needs to be removed 6945function getter(obj, path, bindFnToScope) { 6946 if (!path) return obj; 6947 var keys = path.split('.'); 6948 var key; 6949 var lastInstance = obj; 6950 var len = keys.length; 6951 6952 for (var i = 0; i < len; i++) { 6953 key = keys[i]; 6954 if (obj) { 6955 obj = (lastInstance = obj)[key]; 6956 } 6957 } 6958 if (!bindFnToScope && isFunction(obj)) { 6959 return bind(lastInstance, obj); 6960 } 6961 return obj; 6962} 6963 6964/** 6965 * Return the DOM siblings between the first and last node in the given array. 6966 * @param {Array} array like object 6967 * @returns {Array} the inputted object or a jqLite collection containing the nodes 6968 */ 6969function getBlockNodes(nodes) { 6970 // TODO(perf): update `nodes` instead of creating a new object? 6971 var node = nodes[0]; 6972 var endNode = nodes[nodes.length - 1]; 6973 var blockNodes; 6974 6975 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 6976 if (blockNodes || nodes[i] !== node) { 6977 if (!blockNodes) { 6978 blockNodes = jqLite(slice.call(nodes, 0, i)); 6979 } 6980 blockNodes.push(node); 6981 } 6982 } 6983 6984 return blockNodes || nodes; 6985} 6986 6987 6988/** 6989 * Creates a new object without a prototype. This object is useful for lookup without having to 6990 * guard against prototypically inherited properties via hasOwnProperty. 6991 * 6992 * Related micro-benchmarks: 6993 * - http://jsperf.com/object-create2 6994 * - http://jsperf.com/proto-map-lookup/2 6995 * - http://jsperf.com/for-in-vs-object-keys2 6996 * 6997 * @returns {Object} 6998 */ 6999function createMap() { 7000 return Object.create(null); 7001} 7002 7003var NODE_TYPE_ELEMENT = 1; 7004var NODE_TYPE_ATTRIBUTE = 2; 7005var NODE_TYPE_TEXT = 3; 7006var NODE_TYPE_COMMENT = 8; 7007var NODE_TYPE_DOCUMENT = 9; 7008var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 7009 7010/** 7011 * @ngdoc type 7012 * @name angular.Module 7013 * @module ng 7014 * @description 7015 * 7016 * Interface for configuring angular {@link angular.module modules}. 7017 */ 7018 7019function setupModuleLoader(window) { 7020 7021 var $injectorMinErr = minErr('$injector'); 7022 var ngMinErr = minErr('ng'); 7023 7024 function ensure(obj, name, factory) { 7025 return obj[name] || (obj[name] = factory()); 7026 } 7027 7028 var angular = ensure(window, 'angular', Object); 7029 7030 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 7031 angular.$$minErr = angular.$$minErr || minErr; 7032 7033 return ensure(angular, 'module', function() { 7034 /** @type {Object.<string, angular.Module>} */ 7035 var modules = {}; 7036 7037 /** 7038 * @ngdoc function 7039 * @name angular.module 7040 * @module ng 7041 * @description 7042 * 7043 * The `angular.module` is a global place for creating, registering and retrieving Angular 7044 * modules. 7045 * All modules (angular core or 3rd party) that should be available to an application must be 7046 * registered using this mechanism. 7047 * 7048 * Passing one argument retrieves an existing {@link angular.Module}, 7049 * whereas passing more than one argument creates a new {@link angular.Module} 7050 * 7051 * 7052 * # Module 7053 * 7054 * A module is a collection of services, directives, controllers, filters, and configuration information. 7055 * `angular.module` is used to configure the {@link auto.$injector $injector}. 7056 * 7057 * ```js 7058 * // Create a new module 7059 * var myModule = angular.module('myModule', []); 7060 * 7061 * // register a new service 7062 * myModule.value('appName', 'MyCoolApp'); 7063 * 7064 * // configure existing services inside initialization blocks. 7065 * myModule.config(['$locationProvider', function($locationProvider) { 7066 * // Configure existing providers 7067 * $locationProvider.hashPrefix('!'); 7068 * }]); 7069 * ``` 7070 * 7071 * Then you can create an injector and load your modules like this: 7072 * 7073 * ```js 7074 * var injector = angular.injector(['ng', 'myModule']) 7075 * ``` 7076 * 7077 * However it's more likely that you'll just use 7078 * {@link ng.directive:ngApp ngApp} or 7079 * {@link angular.bootstrap} to simplify this process for you. 7080 * 7081 * @param {!string} name The name of the module to create or retrieve. 7082 * @param {!Array.<string>=} requires If specified then new module is being created. If 7083 * unspecified then the module is being retrieved for further configuration. 7084 * @param {Function=} configFn Optional configuration function for the module. Same as 7085 * {@link angular.Module#config Module#config()}. 7086 * @returns {angular.Module} new module with the {@link angular.Module} api. 7087 */ 7088 return function module(name, requires, configFn) { 7089 var assertNotHasOwnProperty = function(name, context) { 7090 if (name === 'hasOwnProperty') { 7091 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 7092 } 7093 }; 7094 7095 assertNotHasOwnProperty(name, 'module'); 7096 if (requires && modules.hasOwnProperty(name)) { 7097 modules[name] = null; 7098 } 7099 return ensure(modules, name, function() { 7100 if (!requires) { 7101 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 7102 "the module name or forgot to load it. If registering a module ensure that you " + 7103 "specify the dependencies as the second argument.", name); 7104 } 7105 7106 /** @type {!Array.<Array.<*>>} */ 7107 var invokeQueue = []; 7108 7109 /** @type {!Array.<Function>} */ 7110 var configBlocks = []; 7111 7112 /** @type {!Array.<Function>} */ 7113 var runBlocks = []; 7114 7115 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 7116 7117 /** @type {angular.Module} */ 7118 var moduleInstance = { 7119 // Private state 7120 _invokeQueue: invokeQueue, 7121 _configBlocks: configBlocks, 7122 _runBlocks: runBlocks, 7123 7124 /** 7125 * @ngdoc property 7126 * @name angular.Module#requires 7127 * @module ng 7128 * 7129 * @description 7130 * Holds the list of modules which the injector will load before the current module is 7131 * loaded. 7132 */ 7133 requires: requires, 7134 7135 /** 7136 * @ngdoc property 7137 * @name angular.Module#name 7138 * @module ng 7139 *
7140 * @description 7141 * Name of the module. 7142 */ 7143 name: name, 7144 7145 7146 /** 7147 * @ngdoc method 7148 * @name angular.Module#provider 7149 * @module ng 7150 * @param {string} name service name 7151 * @param {Function} providerType Construction function for creating new instance of the 7152 * service. 7153 * @description 7154 * See {@link auto.$provide#provider $provide.provider()}. 7155 */ 7156 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 7157 7158 /** 7159 * @ngdoc method 7160 * @name angular.Module#factory 7161 * @module ng 7162 * @param {string} name service name 7163 * @param {Function} providerFunction Function for creating new instance of the service. 7164 * @description 7165 * See {@link auto.$provide#factory $provide.factory()}. 7166 */ 7167 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 7168 7169 /** 7170 * @ngdoc method 7171 * @name angular.Module#service 7172 * @module ng 7173 * @param {string} name service name 7174 * @param {Function} constructor A constructor function that will be instantiated. 7175 * @description 7176 * See {@link auto.$provide#service $provide.service()}. 7177 */ 7178 service: invokeLaterAndSetModuleName('$provide', 'service'), 7179 7180 /** 7181 * @ngdoc method 7182 * @name angular.Module#value 7183 * @module ng 7184 * @param {string} name service name 7185 * @param {*} object Service instance object. 7186 * @description 7187 * See {@link auto.$provide#value $provide.value()}. 7188 */ 7189 value: invokeLater('$provide', 'value'), 7190 7191 /** 7192 * @ngdoc method 7193 * @name angular.Module#constant 7194 * @module ng 7195 * @param {string} name constant name 7196 * @param {*} object Constant value. 7197 * @description 7198 * Because the constants are fixed, they get applied before other provide methods. 7199 * See {@link auto.$provide#constant $provide.constant()}. 7200 */ 7201 constant: invokeLater('$provide', 'constant', 'unshift'), 7202 7203 /** 7204 * @ngdoc method 7205 * @name angular.Module#decorator 7206 * @module ng 7207 * @param {string} The name of the service to decorate. 7208 * @param {Function} This function will be invoked when the service needs to be 7209 * instantiated and should return the decorated service instance. 7210 * @description 7211 * See {@link auto.$provide#decorator $provide.decorator()}. 7212 */ 7213 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 7214 7215 /** 7216 * @ngdoc method 7217 * @name angular.Module#animation 7218 * @module ng 7219 * @param {string} name animation name 7220 * @param {Function} animationFactory Factory function for creating new instance of an 7221 * animation. 7222 * @description 7223 * 7224 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 7225 * 7226 * 7227 * Defines an animation hook that can be later used with 7228 * {@link $animate $animate} service and directives that use this service. 7229 * 7230 * ```js 7231 * module.animation('.animation-name', function($inject1, $inject2) { 7232 * return { 7233 * eventName : function(element, done) { 7234 * //code to run the animation 7235 * //once complete, then run done() 7236 * return function cancellationFunction(element) { 7237 * //code to cancel the animation 7238 * } 7239 * } 7240 * } 7241 * }) 7242 * ``` 7243 * 7244 * See {@link ng.$animateProvider#register $animateProvider.register()} and 7245 * {@link ngAnimate ngAnimate module} for more information. 7246 */ 7247 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 7248 7249 /** 7250 * @ngdoc method 7251 * @name angular.Module#filter 7252 * @module ng 7253 * @param {string} name Filter name - this must be a valid angular expression identifier 7254 * @param {Function} filterFactory Factory function for creating new instance of filter.
7255 * @description 7256 * See {@link ng.$filterProvider#register $filterProvider.register()}. 7257 * 7258 * <div class="alert alert-warning"> 7259 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 7260 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 7261 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 7262 * (`myapp_subsection_filterx`). 7263 * </div> 7264 */ 7265 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 7266 7267 /** 7268 * @ngdoc method 7269 * @name angular.Module#controller 7270 * @module ng 7271 * @param {string|Object} name Controller name, or an object map of controllers where the 7272 * keys are the names and the values are the constructors. 7273 * @param {Function} constructor Controller constructor function. 7274 * @description 7275 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 7276 */ 7277 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 7278 7279 /** 7280 * @ngdoc method 7281 * @name angular.Module#directive 7282 * @module ng 7283 * @param {string|Object} name Directive name, or an object map of directives where the 7284 * keys are the names and the values are the factories. 7285 * @param {Function} directiveFactory Factory function for creating new instance of 7286 * directives. 7287 * @description 7288 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 7289 */ 7290 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 7291 7292 /** 7293 * @ngdoc method 7294 * @name angular.Module#component 7295 * @module ng 7296 * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp) 7297 * @param {Object} options Component definition object (a simplified 7298 * {@link ng.$compile#directive-definition-object directive definition object}) 7299 * 7300 * @description 7301 * See {@link ng.$compileProvider#component $compileProvider.component()}. 7302 */ 7303 component: invokeLaterAndSetModuleName('$compileProvider', 'component'), 7304 7305 /** 7306 * @ngdoc method 7307 * @name angular.Module#config 7308 * @module ng 7309 * @param {Function} configFn Execute this function on module load. Useful for service 7310 * configuration. 7311 * @description 7312 * Use this method to register work which needs to be performed on module loading. 7313 * For more about how to configure services, see 7314 * {@link providers#provider-recipe Provider Recipe}. 7315 */ 7316 config: config, 7317 7318 /** 7319 * @ngdoc method 7320 * @name angular.Module#run 7321 * @module ng 7322 * @param {Function} initializationFn Execute this function after injector creation. 7323 * Useful for application initialization. 7324 * @description 7325 * Use this method to register work which should be performed when the injector is done 7326 * loading all modules. 7327 */ 7328 run: function(block) { 7329 runBlocks.push(block); 7330 return this; 7331 } 7332 }; 7333 7334 if (configFn) { 7335 config(configFn); 7336 } 7337 7338 return moduleInstance; 7339 7340 /** 7341 * @param {string} provider 7342 * @param {string} method 7343 * @param {String=} insertMethod 7344 * @returns {angular.Module} 7345 */ 7346 function invokeLater(provider, method, insertMethod, queue) { 7347 if (!queue) queue = invokeQueue; 7348 return function() { 7349 queue[insertMethod || 'push']([provider, method, arguments]); 7350 return moduleInstance; 7351 }; 7352 } 7353 7354 /** 7355 * @param {string} provider 7356 * @param {string} method 7357 * @returns {angular.Module} 7358 */
vendor: 4,717 bytes, lines 7359-7558
7359 function invokeLaterAndSetModuleName(provider, method) { 7360 return function(recipeName, factoryFunction) { 7361 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 7362 invokeQueue.push([provider, method, arguments]); 7363 return moduleInstance; 7364 }; 7365 } 7366 }); 7367 }; 7368 }); 7369 7370} 7371 7372/* global: toDebugString: true */ 7373 7374function serializeObject(obj) { 7375 var seen = []; 7376 7377 return JSON.stringify(obj, function(key, val) { 7378 val = toJsonReplacer(key, val); 7379 if (isObject(val)) { 7380 7381 if (seen.indexOf(val) >= 0) return '...'; 7382 7383 seen.push(val); 7384 } 7385 return val; 7386 }); 7387} 7388 7389function toDebugString(obj) { 7390 if (typeof obj === 'function') { 7391 return obj.toString().replace(/ \{[\s\S]*$/, ''); 7392 } else if (isUndefined(obj)) { 7393 return 'undefined'; 7394 } else if (typeof obj !== 'string') { 7395 return serializeObject(obj); 7396 } 7397 return obj; 7398} 7399 7400/* global angularModule: true, 7401 version: true, 7402 7403 $CompileProvider, 7404 7405 htmlAnchorDirective, 7406 inputDirective, 7407 inputDirective, 7408 formDirective, 7409 scriptDirective, 7410 selectDirective, 7411 styleDirective, 7412 optionDirective, 7413 ngBindDirective, 7414 ngBindHtmlDirective, 7415 ngBindTemplateDirective, 7416 ngClassDirective, 7417 ngClassEvenDirective, 7418 ngClassOddDirective, 7419 ngCloakDirective, 7420 ngControllerDirective, 7421 ngFormDirective, 7422 ngHideDirective, 7423 ngIfDirective, 7424 ngIncludeDirective, 7425 ngIncludeFillContentDirective, 7426 ngInitDirective, 7427 ngNonBindableDirective, 7428 ngPluralizeDirective, 7429 ngRepeatDirective, 7430 ngShowDirective, 7431 ngStyleDirective, 7432 ngSwitchDirective, 7433 ngSwitchWhenDirective, 7434 ngSwitchDefaultDirective, 7435 ngOptionsDirective, 7436 ngTranscludeDirective, 7437 ngModelDirective, 7438 ngListDirective, 7439 ngChangeDirective, 7440 patternDirective, 7441 patternDirective, 7442 requiredDirective, 7443 requiredDirective, 7444 minlengthDirective, 7445 minlengthDirective, 7446 maxlengthDirective, 7447 maxlengthDirective, 7448 ngValueDirective, 7449 ngModelOptionsDirective, 7450 ngAttributeAliasDirectives, 7451 ngEventDirectives, 7452 7453 $AnchorScrollProvider, 7454 $AnimateProvider, 7455 $CoreAnimateCssProvider, 7456 $$CoreAnimateJsProvider, 7457 $$CoreAnimateQueueProvider, 7458 $$AnimateRunnerFactoryProvider, 7459 $$AnimateAsyncRunFactoryProvider, 7460 $BrowserProvider, 7461 $CacheFactoryProvider, 7462 $ControllerProvider, 7463 $DateProvider, 7464 $DocumentProvider, 7465 $ExceptionHandlerProvider, 7466 $FilterProvider, 7467 $$ForceReflowProvider, 7468 $InterpolateProvider, 7469 $IntervalProvider, 7470 $$HashMapProvider, 7471 $HttpProvider, 7472 $HttpParamSerializerProvider, 7473 $HttpParamSerializerJQLikeProvider, 7474 $HttpBackendProvider, 7475 $xhrFactoryProvider, 7476 $LocationProvider, 7477 $LogProvider, 7478 $ParseProvider, 7479 $RootScopeProvider, 7480 $QProvider, 7481 $$QProvider, 7482 $$SanitizeUriProvider, 7483 $SceProvider, 7484 $SceDelegateProvider, 7485 $SnifferProvider, 7486 $TemplateCacheProvider, 7487 $TemplateRequestProvider, 7488 $$TestabilityProvider, 7489 $TimeoutProvider, 7490 $$RAFProvider, 7491 $WindowProvider, 7492 $$jqLiteProvider, 7493 $$CookieReaderProvider 7494*/ 7495 7496 7497/** 7498 * @ngdoc object 7499 * @name angular.version 7500 * @module ng 7501 * @description 7502 * An object that contains information about the current AngularJS version. 7503 * 7504 * This object has the following properties: 7505 * 7506 * - `full` â `{string}` â Full version string, such as "0.9.18". 7507 * - `major` â `{number}` â Major version number, such as "0". 7508 * - `minor` â `{number}` â Minor version number, such as "9". 7509 * - `dot` â `{number}` â Dot version number, such as "18". 7510 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 7511 */ 7512var version = { 7513 full: '1.5.1', // all of these placeholder strings will be replaced by grunt's 7514 major: 1, // package task 7515 minor: 5, 7516 dot: 1, 7517 codeName: 'equivocal-sophistication' 7518}; 7519 7520 7521function publishExternalAPI(angular) { 7522 extend(angular, { 7523 'bootstrap': bootstrap, 7524 'copy': copy, 7525 'extend': extend, 7526 'merge': merge, 7527 'equals': equals, 7528 'element': jqLite, 7529 'forEach': forEach, 7530 'injector': createInjector, 7531 'noop': noop, 7532 'bind': bind, 7533 'toJson': toJson, 7534 'fromJson': fromJson, 7535 'identity': identity, 7536 'isUndefined': isUndefined, 7537 'isDefined': isDefined, 7538 'isString': isString, 7539 'isFunction': isFunction, 7540 'isObject': isObject, 7541 'isNumber': isNumber, 7542 'isElement': isElement, 7543 'isArray': isArray, 7544 'version': version, 7545 'isDate': isDate, 7546 'lowercase': lowercase, 7547 'uppercase': uppercase, 7548 'callbacks': {counter: 0}, 7549 'getTestability': getTestability, 7550 '$$minErr': minErr, 7551 '$$csp': csp, 7552 'reloadWithDebugInfo': reloadWithDebugInfo 7553 }); 7554 7555 angularModule = setupModuleLoader(window); 7556 7557 angularModule('ng', ['ngLocale'], ['$provide', 7558 function ngModule($provide) {
7559 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 7560 $provide.provider({ 7561 $$sanitizeUri: $$SanitizeUriProvider 7562 }); 7563 $provide.provider('$compile', $CompileProvider). 7564 directive({ 7565 a: htmlAnchorDirective, 7566 input: inputDirective, 7567 textarea: inputDirective, 7568 form: formDirective, 7569 script: scriptDirective, 7570 select: selectDirective, 7571 style: styleDirective, 7572 option: optionDirective, 7573 ngBind: ngBindDirective, 7574 ngBindHtml: ngBindHtmlDirective, 7575 ngBindTemplate: ngBindTemplateDirective, 7576 ngClass: ngClassDirective, 7577 ngClassEven: ngClassEvenDirective, 7578 ngClassOdd: ngClassOddDirective, 7579 ngCloak: ngCloakDirective, 7580 ngController: ngControllerDirective, 7581 ngForm: ngFormDirective, 7582 ngHide: ngHideDirective, 7583 ngIf: ngIfDirective, 7584 ngInclude: ngIncludeDirective, 7585 ngInit: ngInitDirective, 7586 ngNonBindable: ngNonBindableDirective, 7587 ngPluralize: ngPluralizeDirective, 7588 ngRepeat: ngRepeatDirective, 7589 ngShow: ngShowDirective, 7590 ngStyle: ngStyleDirective, 7591 ngSwitch: ngSwitchDirective, 7592 ngSwitchWhen: ngSwitchWhenDirective, 7593 ngSwitchDefault: ngSwitchDefaultDirective, 7594 ngOptions: ngOptionsDirective, 7595 ngTransclude: ngTranscludeDirective, 7596 ngModel: ngModelDirective, 7597 ngList: ngListDirective, 7598 ngChange: ngChangeDirective, 7599 pattern: patternDirective, 7600 ngPattern: patternDirective, 7601 required: requiredDirective, 7602 ngRequired: requiredDirective, 7603 minlength: minlengthDirective, 7604 ngMinlength: minlengthDirective, 7605 maxlength: maxlengthDirective, 7606 ngMaxlength: maxlengthDirective, 7607 ngValue: ngValueDirective, 7608 ngModelOptions: ngModelOptionsDirective 7609 }). 7610 directive({ 7611 ngInclude: ngIncludeFillContentDirective 7612 }). 7613 directive(ngAttributeAliasDirectives). 7614 directive(ngEventDirectives); 7615 $provide.provider({ 7616 $anchorScroll: $AnchorScrollProvider, 7617 $animate: $AnimateProvider, 7618 $animateCss: $CoreAnimateCssProvider, 7619 $$animateJs: $$CoreAnimateJsProvider, 7620 $$animateQueue: $$CoreAnimateQueueProvider, 7621 $$AnimateRunner: $$AnimateRunnerFactoryProvider, 7622 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, 7623 $browser: $BrowserProvider, 7624 $cacheFactory: $CacheFactoryProvider, 7625 $controller: $ControllerProvider, 7626 $document: $DocumentProvider, 7627 $exceptionHandler: $ExceptionHandlerProvider, 7628 $filter: $FilterProvider, 7629 $$forceReflow: $$ForceReflowProvider, 7630 $interpolate: $InterpolateProvider, 7631 $interval: $IntervalProvider, 7632 $http: $HttpProvider, 7633 $httpParamSerializer: $HttpParamSerializerProvider, 7634 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 7635 $httpBackend: $HttpBackendProvider, 7636 $xhrFactory: $xhrFactoryProvider, 7637 $location: $LocationProvider, 7638 $log: $LogProvider, 7639 $parse: $ParseProvider, 7640 $rootScope: $RootScopeProvider, 7641 $q: $QProvider, 7642 $$q: $$QProvider, 7643 $sce: $SceProvider, 7644 $sceDelegate: $SceDelegateProvider, 7645 $sniffer: $SnifferProvider, 7646 $templateCache: $TemplateCacheProvider, 7647 $templateRequest: $TemplateRequestProvider, 7648 $$testability: $$TestabilityProvider, 7649 $timeout: $TimeoutProvider, 7650 $window: $WindowProvider, 7651 $$rAF: $$RAFProvider, 7652 $$jqLite: $$jqLiteProvider, 7653 $$HashMap: $$HashMapProvider, 7654 $$cookieReader: $$CookieReaderProvider 7655 }); 7656 } 7657 ]); 7658} 7659 7660/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 7661 * Any commits to this file should be reviewed with security in mind. * 7662 * Changes to this file can potentially create security vulnerabilities. * 7663 * An approval from 2 Core members with history of modifying * 7664 * this file is required. * 7665 * * 7666 * Does the change somehow allow for arbitrary javascript to be executed? * 7667 * Or allows for someone to change the prototype of built-in objects? * 7668 * Or gives undesired access to variables likes document or window? * 7669 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 7670
7671/* global JQLitePrototype: true, 7672 addEventListenerFn: true, 7673 removeEventListenerFn: true, 7674 BOOLEAN_ATTR: true, 7675 ALIASED_ATTR: true, 7676*/ 7677 7678////////////////////////////////// 7679//JQLite 7680////////////////////////////////// 7681 7682/** 7683 * @ngdoc function 7684 * @name angular.element 7685 * @module ng 7686 * @kind function 7687 * 7688 * @description 7689 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 7690 * 7691 * If jQuery is available, `angular.element` is an alias for the 7692 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 7693 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**. 7694 * 7695 * jqLite is a tiny, API-compatible subset of jQuery that allows 7696 * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most 7697 * commonly needed functionality with the goal of having a very small footprint. 7698 * 7699 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the 7700 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a 7701 * specific version of jQuery if multiple versions exist on the page. 7702 * 7703 * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or 7704 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div> 7705 * 7706 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements 7707 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` 7708 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div> 7709 * 7710 * ## Angular's jqLite 7711 * jqLite provides only the following jQuery methods: 7712 * 7713 * - [`addClass()`](http://api.jquery.com/addClass/) 7714 * - [`after()`](http://api.jquery.com/after/) 7715 * - [`append()`](http://api.jquery.com/append/) 7716 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 7717 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 7718 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 7719 * - [`clone()`](http://api.jquery.com/clone/) 7720 * - [`contents()`](http://api.jquery.com/contents/) 7721 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. 7722 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. 7723 * - [`data()`](http://api.jquery.com/data/) 7724 * - [`detach()`](http://api.jquery.com/detach/) 7725 * - [`empty()`](http://api.jquery.com/empty/) 7726 * - [`eq()`](http://api.jquery.com/eq/) 7727 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 7728 * - [`hasClass()`](http://api.jquery.com/hasClass/) 7729 * - [`html()`](http://api.jquery.com/html/) 7730 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 7731 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 7732 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 7733 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 7734 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 7735 * - [`prepend()`](http://api.jquery.com/prepend/) 7736 * - [`prop()`](http://api.jquery.com/prop/) 7737 * - [`ready()`](http://api.jquery.com/ready/) 7738 * - [`remove()`](http://api.jquery.com/remove/) 7739 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 7740 * - [`removeClass()`](http://api.jquery.com/removeClass/) 7741 * - [`removeData()`](http://api.jquery.com/removeData/) 7742 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 7743 * - [`text()`](http://api.jquery.com/text/) 7744 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 7745 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 7746 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter 7747 * - [`val()`](http://api.jquery.com/val/) 7748 * - [`wrap()`](http://api.jquery.com/wrap/) 7749 * 7750 * ## jQuery/jqLite Extras 7751 * Angular also provides the following additional methods and events to both jQuery and jqLite: 7752 * 7753 * ### Events 7754 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
7754pis and fires this event 7755 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 7756 * element before it is removed. 7757 * 7758 * ### Methods 7759 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 7760 * retrieves controller associated with the `ngController` directive. If `name` is provided as 7761 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 7762 * `'ngModel'`). 7763 * - `injector()` - retrieves the injector of the current element or its parent. 7764 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 7765 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 7766 * be enabled. 7767 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 7768 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 7769 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 7770 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 7771 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 7772 * parent element is reached. 7773 * 7774 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 7775 * @returns {Object} jQuery object. 7776 */ 7777 7778JQLite.expando = 'ng339'; 7779 7780var jqCache = JQLite.cache = {}, 7781 jqId = 1, 7782 addEventListenerFn = function(element, type, fn) { 7783 element.addEventListener(type, fn, false); 7784 }, 7785 removeEventListenerFn = function(element, type, fn) { 7786 element.removeEventListener(type, fn, false); 7787 }; 7788 7789/* 7790 * !!! This is an undocumented "private" function !!! 7791 */ 7792JQLite._data = function(node) { 7793 //jQuery always returns an object on cache miss 7794 return this.cache[node[this.expando]] || {}; 7795}; 7796 7797function jqNextId() { return ++jqId; } 7798 7799 7800var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 7801var MOZ_HACK_REGEXP = /^moz([A-Z])/; 7802var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 7803var jqLiteMinErr = minErr('jqLite'); 7804 7805/** 7806 * Converts snake_case to camelCase. 7807 * Also there is special case for Moz prefix starting with upper case letter. 7808 * @param name Name to normalize 7809 */ 7810function camelCase(name) { 7811 return name. 7812 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 7813 return offset ? letter.toUpperCase() : letter; 7814 }). 7815 replace(MOZ_HACK_REGEXP, 'Moz$1'); 7816} 7817 7818var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 7819var HTML_REGEXP = /<|&#?\w+;/; 7820var TAG_NAME_REGEXP = /<([\w:-]+)/; 7821var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 7822 7823var wrapMap = { 7824 'option': [1, '<select multiple="multiple">', '</select>'], 7825 7826 'thead': [1, '<table>', '</table>'], 7827 'col': [2, '<table><colgroup>', '</colgroup></table>'], 7828 'tr': [2, '<table><tbody>', '</tbody></table>'], 7829 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 7830 '_default': [0, "", ""] 7831}; 7832 7833wrapMap.optgroup = wrapMap.option; 7834wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 7835wrapMap.th = wrapMap.td; 7836 7837 7838function jqLiteIsTextNode(html) { 7839 return !HTML_REGEXP.test(html); 7840} 7841 7842function jqLiteAcceptsData(node) { 7843 // The window object can accept data but has no nodeType 7844 // Otherwise we are only interested in elements (1) and documents (9) 7845 var nodeType = node.nodeType; 7846 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 7847} 7848 7849function jqLiteHasData(node) { 7850 for (var key in jqCache[node.ng339]) { 7851 return true; 7852 } 7853 return false; 7854} 7855 7856function jqLiteCleanData(nodes) { 7857 for (var i = 0, ii = nodes.length; i < ii; i++) { 7858 jqLiteRemoveData(nodes[i]); 7859 } 7860} 7861 7862function jqLiteBuildFragment(html, context) { 7863 var tmp, tag, wrap, 7864 fragment = context.createDocumentFragment(), 7865 nodes = [], i; 7866 7867 if (jqLiteIsTextNode(html)) { 7868 // Convert non-html into a text node 7869 nodes.push(context.createTextNode(html)); 7870 } else { 7871 // Convert html into DOM nodes 7872 tmp = tmp || fragment.appendChild(context.createElement("div")); 7873 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 7874 wrap = wrapMap[tag] || wrapMap._default; 7875 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 7876 7877 // Descend through wrappers to the right content 7878 i = wrap[0]; 7879 while (i--) { 7880 tmp = tmp.lastChild; 7881 } 7882 7883 nodes = concat(nodes, tmp.childNodes); 7884 7885 tmp = fragment.firstChild; 7886 tmp.textContent = ""; 7887 } 7888 7889 // Remove wrapper from fragment 7890 fragment.textContent = ""; 7891 fragment.innerHTML = ""; // Clear inner HTML 7892 forEach(nodes, function(node) { 7893 fragment.appendChild(node); 7894 }); 7895 7896 return fragment; 7897} 7898 7899function jqLiteParseHTML(html, context) { 7900 context = context || document; 7901 var parsed; 7902 7903 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 7904 return [context.createElement(parsed[1])]; 7905 } 7906 7907 if ((parsed = jqLiteBuildFragment(html, context))) { 7908 return parsed.childNodes; 7909 } 7910 7911 return []; 7912} 7913 7914function jqLiteWrapNode(node, wrapper) { 7915 var parent = node.parentNode; 7916 7917 if (parent) { 7918 parent.replaceChild(wrapper, node); 7919 } 7920 7921 wrapper.appendChild(node); 7922} 7923 7924 7925// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 7926var jqLiteContains = Node.prototype.contains || function(arg) { 7927 // jshint bitwise: false 7928 return !!(this.compareDocumentPosition(arg) & 16); 7929 // jshint bitwise: true 7930}; 7931 7932///////////////////////////////////////////// 7933function JQLite(element) { 7934 if (element instanceof JQLite) { 7935 return element; 7936 } 7937 7938 var argIsString; 7939 7940 if (isString(element)) { 7941 element = trim(element); 7942 argIsString = true; 7943 } 7944 if (!(this instanceof JQLite)) { 7945 if (argIsString && element.charAt(0) != '<') { 7946 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 7947 } 7948 return new JQLite(element); 7949 } 7950 7951 if (argIsString) { 7952 jqLiteAddNodes(this, jqLiteParseHTML(element)); 7953 } else { 7954 jqLiteAddNodes(this, element); 7955 } 7956} 7957 7958function jqLiteClone(element) { 7959 return element.cloneNode(true); 7960} 7961 7962function jqLiteDealoc(element, onlyDescendants) { 7963 if (!onlyDescendants) jqLiteRemoveData(element); 7964 7965 if (element.querySelectorAll) { 7966 var descendants = element.querySelectorAll('*');
7967 for (var i = 0, l = descendants.length; i < l; i++) { 7968 jqLiteRemoveData(descendants[i]); 7969 } 7970 } 7971} 7972 7973function jqLiteOff(element, type, fn, unsupported) { 7974 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 7975 7976 var expandoStore = jqLiteExpandoStore(element); 7977 var events = expandoStore && expandoStore.events; 7978 var handle = expandoStore && expandoStore.handle; 7979 7980 if (!handle) return; //no listeners registered 7981 7982 if (!type) { 7983 for (type in events) { 7984 if (type !== '$destroy') { 7985 removeEventListenerFn(element, type, handle); 7986 } 7987 delete events[type]; 7988 } 7989 } else { 7990 7991 var removeHandler = function(type) { 7992 var listenerFns = events[type]; 7993 if (isDefined(fn)) { 7994 arrayRemove(listenerFns || [], fn); 7995 } 7996 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { 7997 removeEventListenerFn(element, type, handle); 7998 delete events[type]; 7999 } 8000 }; 8001 8002 forEach(type.split(' '), function(type) { 8003 removeHandler(type); 8004 if (MOUSE_EVENT_MAP[type]) { 8005 removeHandler(MOUSE_EVENT_MAP[type]); 8006 } 8007 }); 8008 } 8009} 8010 8011function jqLiteRemoveData(element, name) { 8012 var expandoId = element.ng339; 8013 var expandoStore = expandoId && jqCache[expandoId]; 8014 8015 if (expandoStore) { 8016 if (name) { 8017 delete expandoStore.data[name]; 8018 return; 8019 } 8020 8021 if (expandoStore.handle) { 8022 if (expandoStore.events.$destroy) { 8023 expandoStore.handle({}, '$destroy'); 8024 } 8025 jqLiteOff(element); 8026 } 8027 delete jqCache[expandoId]; 8028 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 8029 } 8030} 8031 8032 8033function jqLiteExpandoStore(element, createIfNecessary) { 8034 var expandoId = element.ng339, 8035 expandoStore = expandoId && jqCache[expandoId]; 8036 8037 if (createIfNecessary && !expandoStore) { 8038 element.ng339 = expandoId = jqNextId(); 8039 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 8040 } 8041 8042 return expandoStore; 8043} 8044 8045 8046function jqLiteData(element, key, value) { 8047 if (jqLiteAcceptsData(element)) { 8048 8049 var isSimpleSetter = isDefined(value); 8050 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 8051 var massGetter = !key; 8052 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 8053 var data = expandoStore && expandoStore.data; 8054 8055 if (isSimpleSetter) { // data('key', value) 8056 data[key] = value; 8057 } else { 8058 if (massGetter) { // data() 8059 return data; 8060 } else { 8061 if (isSimpleGetter) { // data('key') 8062 // don't force creation of expandoStore if it doesn't exist yet 8063 return data && data[key]; 8064 } else { // mass-setter: data({key1: val1, key2: val2}) 8065 extend(data, key); 8066 } 8067 } 8068 } 8069 } 8070} 8071 8072function jqLiteHasClass(element, selector) { 8073 if (!element.getAttribute) return false; 8074 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 8075 indexOf(" " + selector + " ") > -1); 8076} 8077 8078function jqLiteRemoveClass(element, cssClasses) { 8079 if (cssClasses && element.setAttribute) { 8080 forEach(cssClasses.split(' '), function(cssClass) { 8081 element.setAttribute('class', trim( 8082 (" " + (element.getAttribute('class') || '') + " ") 8083 .replace(/[\n\t]/g, " ") 8084 .replace(" " + trim(cssClass) + " ", " ")) 8085 ); 8086 }); 8087 } 8088} 8089 8090function jqLiteAddClass(element, cssClasses) { 8091 if (cssClasses && element.setAttribute) { 8092 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 8093 .replace(/[\n\t]/g, " "); 8094 8095 forEach(cssClasses.split(' '), function(cssClass) { 8096 cssClass = trim(cssClass); 8097 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 8098 existingClasses += cssClass + ' '; 8099 } 8100 }); 8101 8102 element.setAttribute('class', trim(existingClasses)); 8103 } 8104} 8105 8106 8107function jqLiteAddNodes(root, elements) { 8108 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 8109 8110 if (elements) { 8111 8112 // if a Node (the most common case) 8113 if (elements.nodeType) { 8114 root[root.length++] = elements; 8115 } else { 8116 var length = elements.length; 8117 8118 // if an Array or NodeList and not a Window 8119 if (typeof length === 'number' && elements.window !== elements) { 8120 if (length) { 8121 for (var i = 0; i < length; i++) { 8122 root[root.length++] = elements[i]; 8123 } 8124 } 8125 } else { 8126 root[root.length++] = elements; 8127 } 8128 } 8129 } 8130} 8131 8132 8133function jqLiteController(element, name) { 8134 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 8135} 8136 8137function jqLiteInheritedData(element, name, value) { 8138 // if element is the document object work with the html element instead 8139 // this makes $(document).scope() possible 8140 if (element.nodeType == NODE_TYPE_DOCUMENT) { 8141 element = element.documentElement; 8142 } 8143 var names = isArray(name) ? name : [name]; 8144 8145 while (element) { 8146 for (var i = 0, ii = names.length; i < ii; i++) { 8147 if (isDefined(value = jqLite.data(element, names[i]))) return value; 8148 } 8149 8150 // If dealing with a document fragment node with a host element, and no parent, use the host 8151 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 8152 // to lookup parent controllers. 8153 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 8154 } 8155} 8156 8157function jqLiteEmpty(element) { 8158 jqLiteDealoc(element, true); 8159 while (element.firstChild) { 8160 element.removeChild(element.firstChild); 8161 } 8162} 8163 8164function jqLiteRemove(element, keepData) { 8165 if (!keepData) jqLiteDealoc(element); 8166 var parent = element.parentNode; 8167 if (parent) parent.removeChild(element); 8168} 8169 8170 8171function jqLiteDocumentLoaded(action, win) { 8172 win = win || window; 8173 if (win.document.readyState === 'complete') { 8174 // Force the action to be run async for consistent behavior 8175 // from the action's point of view 8176 // i.e. it will definitely not be in a $apply 8177 win.setTimeout(action); 8178 } else { 8179 // No need to unbind this handler as load is only ever called once 8180 jqLite(win).on('load', action); 8181 } 8182} 8183
8184////////////////////////////////////////// 8185// Functions which are declared directly. 8186////////////////////////////////////////// 8187var JQLitePrototype = JQLite.prototype = { 8188 ready: function(fn) { 8189 var fired = false; 8190 8191 function trigger() { 8192 if (fired) return; 8193 fired = true; 8194 fn(); 8195 } 8196 8197 // check if document is already loaded 8198 if (document.readyState === 'complete') { 8199 setTimeout(trigger); 8200 } else { 8201 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 8202 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 8203 // jshint -W064 8204 JQLite(window).on('load', trigger); // fallback to window.onload for others 8205 // jshint +W064 8206 } 8207 }, 8208 toString: function() { 8209 var value = []; 8210 forEach(this, function(e) { value.push('' + e);}); 8211 return '[' + value.join(', ') + ']'; 8212 }, 8213 8214 eq: function(index) { 8215 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 8216 }, 8217 8218 length: 0, 8219 push: push, 8220 sort: [].sort, 8221 splice: [].splice 8222}; 8223 8224////////////////////////////////////////// 8225// Functions iterating getter/setters. 8226// these functions return self on setter and 8227// value on get. 8228////////////////////////////////////////// 8229var BOOLEAN_ATTR = {}; 8230forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 8231 BOOLEAN_ATTR[lowercase(value)] = value; 8232}); 8233var BOOLEAN_ELEMENTS = {}; 8234forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 8235 BOOLEAN_ELEMENTS[value] = true; 8236}); 8237var ALIASED_ATTR = { 8238 'ngMinlength': 'minlength', 8239 'ngMaxlength': 'maxlength', 8240 'ngMin': 'min', 8241 'ngMax': 'max', 8242 'ngPattern': 'pattern' 8243}; 8244 8245function getBooleanAttrName(element, name) { 8246 // check dom last since we will most likely fail on name 8247 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 8248 8249 // booleanAttr is here twice to minimize DOM access 8250 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 8251} 8252 8253function getAliasedAttrName(name) { 8254 return ALIASED_ATTR[name]; 8255} 8256 8257forEach({ 8258 data: jqLiteData, 8259 removeData: jqLiteRemoveData, 8260 hasData: jqLiteHasData, 8261 cleanData: jqLiteCleanData 8262}, function(fn, name) { 8263 JQLite[name] = fn; 8264}); 8265 8266forEach({ 8267 data: jqLiteData, 8268 inheritedData: jqLiteInheritedData, 8269 8270 scope: function(element) { 8271 // Can't use jqLiteData here directly so we stay compatible with jQuery! 8272 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 8273 }, 8274 8275 isolateScope: function(element) { 8276 // Can't use jqLiteData here directly so we stay compatible with jQuery! 8277 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 8278 }, 8279 8280 controller: jqLiteController, 8281 8282 injector: function(element) { 8283 return jqLiteInheritedData(element, '$injector'); 8284 }, 8285 8286 removeAttr: function(element, name) { 8287 element.removeAttribute(name); 8288 }, 8289 8290 hasClass: jqLiteHasClass, 8291 8292 css: function(element, name, value) { 8293 name = camelCase(name); 8294 8295 if (isDefined(value)) { 8296 element.style[name] = value; 8297 } else { 8298 return element.style[name]; 8299 } 8300 }, 8301 8302 attr: function(element, name, value) { 8303 var nodeType = element.nodeType; 8304 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 8305 return; 8306 } 8307 var lowercasedName = lowercase(name); 8308 if (BOOLEAN_ATTR[lowercasedName]) { 8309 if (isDefined(value)) { 8310 if (!!value) { 8311 element[name] = true; 8312 element.setAttribute(name, lowercasedName); 8313 } else { 8314 element[name] = false; 8315 element.removeAttribute(lowercasedName); 8316 } 8317 } else { 8318 return (element[name] || 8319 (element.attributes.getNamedItem(name) || noop).specified) 8320 ? lowercasedName 8321 : undefined; 8322 } 8323 } else if (isDefined(value)) { 8324 element.setAttribute(name, value); 8325 } else if (element.getAttribute) { 8326 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 8327 // some elements (e.g. Document) don't have get attribute, so return undefined 8328 var ret = element.getAttribute(name, 2); 8329 // normalize non-existing attributes to undefined (as jQuery) 8330 return ret === null ? undefined : ret; 8331 } 8332 }, 8333 8334 prop: function(element, name, value) { 8335 if (isDefined(value)) { 8336 element[name] = value; 8337 } else { 8338 return element[name]; 8339 } 8340 }, 8341 8342 text: (function() { 8343 getText.$dv = ''; 8344 return getText; 8345 8346 function getText(element, value) { 8347 if (isUndefined(value)) { 8348 var nodeType = element.nodeType; 8349 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 8350 } 8351 element.textContent = value; 8352 } 8353 })(), 8354 8355 val: function(element, value) { 8356 if (isUndefined(value)) { 8357 if (element.multiple && nodeName_(element) === 'select') { 8358 var result = [];
vendor: 10,249 bytes, lines 8359-8698
8359 forEach(element.options, function(option) { 8360 if (option.selected) { 8361 result.push(option.value || option.text); 8362 } 8363 }); 8364 return result.length === 0 ? null : result; 8365 } 8366 return element.value; 8367 } 8368 element.value = value; 8369 }, 8370 8371 html: function(element, value) { 8372 if (isUndefined(value)) { 8373 return element.innerHTML; 8374 } 8375 jqLiteDealoc(element, true); 8376 element.innerHTML = value; 8377 }, 8378 8379 empty: jqLiteEmpty 8380}, function(fn, name) { 8381 /** 8382 * Properties: writes return selection, reads return first value 8383 */ 8384 JQLite.prototype[name] = function(arg1, arg2) { 8385 var i, key; 8386 var nodeCount = this.length; 8387 8388 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 8389 // in a way that survives minification. 8390 // jqLiteEmpty takes no arguments but is a setter. 8391 if (fn !== jqLiteEmpty && 8392 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 8393 if (isObject(arg1)) { 8394 8395 // we are a write, but the object properties are the key/values 8396 for (i = 0; i < nodeCount; i++) { 8397 if (fn === jqLiteData) { 8398 // data() takes the whole object in jQuery 8399 fn(this[i], arg1); 8400 } else { 8401 for (key in arg1) { 8402 fn(this[i], key, arg1[key]); 8403 } 8404 } 8405 } 8406 // return self for chaining 8407 return this; 8408 } else { 8409 // we are a read, so read the first child. 8410 // TODO: do we still need this? 8411 var value = fn.$dv; 8412 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 8413 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 8414 for (var j = 0; j < jj; j++) { 8415 var nodeValue = fn(this[j], arg1, arg2); 8416 value = value ? value + nodeValue : nodeValue; 8417 } 8418 return value; 8419 } 8420 } else { 8421 // we are a write, so apply to all children 8422 for (i = 0; i < nodeCount; i++) { 8423 fn(this[i], arg1, arg2); 8424 } 8425 // return self for chaining 8426 return this; 8427 } 8428 }; 8429}); 8430 8431function createEventHandler(element, events) { 8432 var eventHandler = function(event, type) { 8433 // jQuery specific api 8434 event.isDefaultPrevented = function() { 8435 return event.defaultPrevented; 8436 }; 8437 8438 var eventFns = events[type || event.type]; 8439 var eventFnsLength = eventFns ? eventFns.length : 0; 8440 8441 if (!eventFnsLength) return; 8442 8443 if (isUndefined(event.immediatePropagationStopped)) { 8444 var originalStopImmediatePropagation = event.stopImmediatePropagation; 8445 event.stopImmediatePropagation = function() { 8446 event.immediatePropagationStopped = true; 8447 8448 if (event.stopPropagation) { 8449 event.stopPropagation(); 8450 } 8451 8452 if (originalStopImmediatePropagation) { 8453 originalStopImmediatePropagation.call(event); 8454 } 8455 }; 8456 } 8457 8458 event.isImmediatePropagationStopped = function() { 8459 return event.immediatePropagationStopped === true; 8460 }; 8461 8462 // Some events have special handlers that wrap the real handler 8463 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; 8464 8465 // Copy event handlers in case event handlers array is modified during execution. 8466 if ((eventFnsLength > 1)) { 8467 eventFns = shallowCopy(eventFns); 8468 } 8469 8470 for (var i = 0; i < eventFnsLength; i++) { 8471 if (!event.isImmediatePropagationStopped()) { 8472 handlerWrapper(element, event, eventFns[i]); 8473 } 8474 } 8475 }; 8476 8477 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 8478 // events on `element` 8479 eventHandler.elem = element; 8480 return eventHandler; 8481} 8482 8483function defaultHandlerWrapper(element, event, handler) { 8484 handler.call(element, event); 8485} 8486 8487function specialMouseHandlerWrapper(target, event, handler) { 8488 // Refer to jQuery's implementation of mouseenter & mouseleave 8489 // Read about mouseenter and mouseleave: 8490 // http://www.quirksmode.org/js/events_mouse.html#link8 8491 var related = event.relatedTarget; 8492 // For mousenter/leave call the handler if related is outside the target. 8493 // NB: No relatedTarget if the mouse left/entered the browser window 8494 if (!related || (related !== target && !jqLiteContains.call(target, related))) { 8495 handler.call(target, event); 8496 } 8497} 8498 8499////////////////////////////////////////// 8500// Functions iterating traversal. 8501// These functions chain results into a single 8502// selector. 8503////////////////////////////////////////// 8504forEach({ 8505 removeData: jqLiteRemoveData, 8506 8507 on: function jqLiteOn(element, type, fn, unsupported) { 8508 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 8509 8510 // Do not add event handlers to non-elements because they will not be cleaned up. 8511 if (!jqLiteAcceptsData(element)) { 8512 return; 8513 } 8514 8515 var expandoStore = jqLiteExpandoStore(element, true); 8516 var events = expandoStore.events; 8517 var handle = expandoStore.handle; 8518 8519 if (!handle) { 8520 handle = expandoStore.handle = createEventHandler(element, events); 8521 } 8522 8523 // http://jsperf.com/string-indexof-vs-split 8524 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 8525 var i = types.length; 8526 8527 var addHandler = function(type, specialHandlerWrapper, noEventListener) { 8528 var eventFns = events[type]; 8529 8530 if (!eventFns) { 8531 eventFns = events[type] = []; 8532 eventFns.specialHandlerWrapper = specialHandlerWrapper; 8533 if (type !== '$destroy' && !noEventListener) { 8534 addEventListenerFn(element, type, handle); 8535 } 8536 } 8537 8538 eventFns.push(fn); 8539 }; 8540 8541 while (i--) { 8542 type = types[i]; 8543 if (MOUSE_EVENT_MAP[type]) { 8544 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); 8545 addHandler(type, undefined, true); 8546 } else { 8547 addHandler(type); 8548 } 8549 } 8550 }, 8551 8552 off: jqLiteOff, 8553 8554 one: function(element, type, fn) { 8555 element = jqLite(element); 8556 8557 //add the listener twice so that when it is called 8558 //you can remove the original function and still be 8559 //able to call element.off(ev, fn) normally 8560 element.on(type, function onFn() { 8561 element.off(type, fn); 8562 element.off(type, onFn); 8563 }); 8564 element.on(type, fn); 8565 }, 8566 8567 replaceWith: function(element, replaceNode) { 8568 var index, parent = element.parentNode; 8569 jqLiteDealoc(element); 8570 forEach(new JQLite(replaceNode), function(node) { 8571 if (index) { 8572 parent.insertBefore(node, index.nextSibling); 8573 } else { 8574 parent.replaceChild(node, element); 8575 } 8576 index = node; 8577 }); 8578 }, 8579 8580 children: function(element) { 8581 var children = []; 8582 forEach(element.childNodes, function(element) { 8583 if (element.nodeType === NODE_TYPE_ELEMENT) { 8584 children.push(element); 8585 } 8586 }); 8587 return children; 8588 }, 8589 8590 contents: function(element) { 8591 return element.contentDocument || element.childNodes || []; 8592 }, 8593 8594 append: function(element, node) { 8595 var nodeType = element.nodeType; 8596 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 8597 8598 node = new JQLite(node); 8599 8600 for (var i = 0, ii = node.length; i < ii; i++) { 8601 var child = node[i]; 8602 element.appendChild(child); 8603 } 8604 }, 8605 8606 prepend: function(element, node) { 8607 if (element.nodeType === NODE_TYPE_ELEMENT) { 8608 var index = element.firstChild; 8609 forEach(new JQLite(node), function(child) { 8610 element.insertBefore(child, index); 8611 }); 8612 } 8613 }, 8614 8615 wrap: function(element, wrapNode) { 8616 jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); 8617 }, 8618 8619 remove: jqLiteRemove, 8620 8621 detach: function(element) { 8622 jqLiteRemove(element, true); 8623 }, 8624 8625 after: function(element, newElement) { 8626 var index = element, parent = element.parentNode; 8627 newElement = new JQLite(newElement); 8628 8629 for (var i = 0, ii = newElement.length; i < ii; i++) { 8630 var node = newElement[i]; 8631 parent.insertBefore(node, index.nextSibling); 8632 index = node; 8633 } 8634 }, 8635 8636 addClass: jqLiteAddClass, 8637 removeClass: jqLiteRemoveClass, 8638 8639 toggleClass: function(element, selector, condition) { 8640 if (selector) { 8641 forEach(selector.split(' '), function(className) { 8642 var classCondition = condition; 8643 if (isUndefined(classCondition)) { 8644 classCondition = !jqLiteHasClass(element, className); 8645 } 8646 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 8647 }); 8648 } 8649 }, 8650 8651 parent: function(element) { 8652 var parent = element.parentNode; 8653 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 8654 }, 8655 8656 next: function(element) { 8657 return element.nextElementSibling; 8658 }, 8659 8660 find: function(element, selector) { 8661 if (element.getElementsByTagName) { 8662 return element.getElementsByTagName(selector); 8663 } else { 8664 return []; 8665 } 8666 }, 8667 8668 clone: jqLiteClone, 8669 8670 triggerHandler: function(element, event, extraParameters) { 8671 8672 var dummyEvent, eventFnsCopy, handlerArgs; 8673 var eventName = event.type || event; 8674 var expandoStore = jqLiteExpandoStore(element); 8675 var events = expandoStore && expandoStore.events; 8676 var eventFns = events && events[eventName]; 8677 8678 if (eventFns) { 8679 // Create a dummy event to pass to the handlers 8680 dummyEvent = { 8681 preventDefault: function() { this.defaultPrevented = true; }, 8682 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 8683 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 8684 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 8685 stopPropagation: noop, 8686 type: eventName, 8687 target: element 8688 }; 8689 8690 // If a custom event was provided then extend our dummy event with it 8691 if (event.type) { 8692 dummyEvent = extend(dummyEvent, event); 8693 } 8694 8695 // Copy event handlers in case event handlers array is modified during execution. 8696 eventFnsCopy = shallowCopy(eventFns); 8697 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 8698
8699 forEach(eventFnsCopy, function(fn) { 8700 if (!dummyEvent.isImmediatePropagationStopped()) { 8701 fn.apply(element, handlerArgs); 8702 } 8703 }); 8704 } 8705 } 8706}, function(fn, name) { 8707 /** 8708 * chaining functions 8709 */ 8710 JQLite.prototype[name] = function(arg1, arg2, arg3) { 8711 var value; 8712 8713 for (var i = 0, ii = this.length; i < ii; i++) { 8714 if (isUndefined(value)) { 8715 value = fn(this[i], arg1, arg2, arg3); 8716 if (isDefined(value)) { 8717 // any function which returns a value needs to be wrapped 8718 value = jqLite(value); 8719 } 8720 } else { 8721 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 8722 } 8723 } 8724 return isDefined(value) ? value : this; 8725 }; 8726 8727 // bind legacy bind/unbind to on/off 8728 JQLite.prototype.bind = JQLite.prototype.on; 8729 JQLite.prototype.unbind = JQLite.prototype.off; 8730}); 8731 8732 8733// Provider for private $$jqLite service 8734function $$jqLiteProvider() { 8735 this.$get = function $$jqLite() { 8736 return extend(JQLite, { 8737 hasClass: function(node, classes) { 8738 if (node.attr) node = node[0]; 8739 return jqLiteHasClass(node, classes); 8740 }, 8741 addClass: function(node, classes) { 8742 if (node.attr) node = node[0]; 8743 return jqLiteAddClass(node, classes); 8744 }, 8745 removeClass: function(node, classes) { 8746 if (node.attr) node = node[0]; 8747 return jqLiteRemoveClass(node, classes); 8748 } 8749 }); 8750 }; 8751} 8752 8753/** 8754 * Computes a hash of an 'obj'. 8755 * Hash of a: 8756 * string is string 8757 * number is number as string 8758 * object is either result of calling $$hashKey function on the object or uniquely generated id, 8759 * that is also assigned to the $$hashKey property of the object. 8760 * 8761 * @param obj 8762 * @returns {string} hash string such that the same input will have the same hash string. 8763 * The resulting string key is in 'type:hashKey' format. 8764 */ 8765function hashKey(obj, nextUidFn) { 8766 var key = obj && obj.$$hashKey; 8767 8768 if (key) { 8769 if (typeof key === 'function') { 8770 key = obj.$$hashKey(); 8771 } 8772 return key; 8773 } 8774 8775 var objType = typeof obj; 8776 if (objType == 'function' || (objType == 'object' && obj !== null)) { 8777 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 8778 } else { 8779 key = objType + ':' + obj; 8780 } 8781 8782 return key; 8783} 8784 8785/** 8786 * HashMap which can use objects as keys 8787 */ 8788function HashMap(array, isolatedUid) { 8789 if (isolatedUid) { 8790 var uid = 0; 8791 this.nextUid = function() { 8792 return ++uid; 8793 }; 8794 } 8795 forEach(array, this.put, this); 8796} 8797HashMap.prototype = { 8798 /** 8799 * Store key value pair 8800 * @param key key to store can be any type 8801 * @param value value to store can be any type 8802 */ 8803 put: function(key, value) { 8804 this[hashKey(key, this.nextUid)] = value; 8805 }, 8806 8807 /** 8808 * @param key 8809 * @returns {Object} the value for the key 8810 */ 8811 get: function(key) { 8812 return this[hashKey(key, this.nextUid)]; 8813 }, 8814 8815 /** 8816 * Remove the key/value pair 8817 * @param key 8818 */ 8819 remove: function(key) { 8820 var value = this[key = hashKey(key, this.nextUid)]; 8821 delete this[key]; 8822 return value; 8823 } 8824}; 8825 8826var $$HashMapProvider = [function() { 8827 this.$get = [function() { 8828 return HashMap; 8829 }]; 8830}]; 8831 8832/** 8833 * @ngdoc function 8834 * @module ng 8835 * @name angular.injector 8836 * @kind function 8837 * 8838 * @description 8839 * Creates an injector object that can be used for retrieving services as well as for 8840 * dependency injection (see {@link guide/di dependency injection}). 8841 * 8842 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 8843 * {@link angular.module}. The `ng` module must be explicitly added. 8844 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 8845 * disallows argument name annotation inference. 8846 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 8847 * 8848 * @example 8849 * Typical usage 8850 * ```js 8851 * // create an injector 8852 * var $injector = angular.injector(['ng']); 8853 * 8854 * // use the injector to kick off your application 8855 * // use the type inference to auto inject arguments, or use impl
8855icit injection 8856 * $injector.invoke(function($rootScope, $compile, $document) { 8857 * $compile($document)($rootScope); 8858 * $rootScope.$digest(); 8859 * }); 8860 * ``` 8861 * 8862 * Sometimes you want to get access to the injector of a currently running Angular app 8863 * from outside Angular. Perhaps, you want to inject and compile some markup after the 8864 * application has been bootstrapped. You can do this using the extra `injector()` added 8865 * to JQuery/jqLite elements. See {@link angular.element}. 8866 * 8867 * *This is fairly rare but could be the case if a third party library is injecting the 8868 * markup.* 8869 * 8870 * In the following example a new block of HTML containing a `ng-controller` 8871 * directive is added to the end of the document body by JQuery. We then compile and link 8872 * it into the current AngularJS scope. 8873 * 8874 * ```js 8875 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 8876 * $(document.body).append($div); 8877 * 8878 * angular.element(document).injector().invoke(function($compile) { 8879 * var scope = angular.element($div).scope(); 8880 * $compile($div)(scope); 8881 * }); 8882 * ``` 8883 */ 8884 8885 8886/** 8887 * @ngdoc module 8888 * @name auto 8889 * @description 8890 * 8891 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 8892 */ 8893 8894var ARROW_ARG = /^([^\(]+?)=>/; 8895var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; 8896var FN_ARG_SPLIT = /,/; 8897var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 8898var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 8899var $injectorMinErr = minErr('$injector'); 8900 8901function extractArgs(fn) { 8902 var fnText = fn.toString().replace(STRIP_COMMENTS, ''), 8903 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); 8904 return args; 8905} 8906 8907function anonFn(fn) { 8908 // For anonymous functions, showing at the very least the function signature can help in 8909 // debugging. 8910 var args = extractArgs(fn); 8911 if (args) { 8912 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 8913 } 8914 return 'fn'; 8915} 8916 8917function annotate(fn, strictDi, name) { 8918 var $inject, 8919 argDecl, 8920 last; 8921 8922 if (typeof fn === 'function') { 8923 if (!($inject = fn.$inject)) { 8924 $inject = []; 8925 if (fn.length) { 8926 if (strictDi) { 8927 if (!isString(name) || !name) { 8928 name = fn.name || anonFn(fn); 8929 } 8930 throw $injectorMinErr('strictdi', 8931 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 8932 } 8933 argDecl = extractArgs(fn); 8934 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 8935 arg.replace(FN_ARG, function(all, underscore, name) { 8936 $inject.push(name); 8937 }); 8938 }); 8939 } 8940 fn.$inject = $inject; 8941 } 8942 } else if (isArray(fn)) { 8943 last = fn.length - 1; 8944 assertArgFn(fn[last], 'fn'); 8945 $inject = fn.slice(0, last); 8946 } else { 8947 assertArgFn(fn, 'fn', true); 8948 } 8949 return $inject; 8950} 8951 8952/////////////////////////////////////// 8953 8954/** 8955 * @ngdoc service 8956 * @name $injector 8957 * 8958 * @description 8959 * 8960 * `$injector` is used to retrieve object instances as defined by 8961 * {@link auto.$provide provider}, instantiate types, invoke methods, 8962 * and load modules. 8963 * 8964 * The following always holds true: 8965 * 8966 * ```js 8967 * var $injector = angular.injector(); 8968 * expect($injector.get('$injector')).toBe($injector); 8969 * expect($injector.invoke(function($injector) { 8970 * return $injector; 8971 * })).toBe($injector); 8972 * ``` 8973 * 8974 * # Injection Function Annotation 8975 * 8976 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 8977 * following are all valid ways of annotating function with injection arguments and are equivalent. 8978 * 8979 * ```js 8980 * // inferred (only works if code not minified/obfuscated) 8981 * $injector.invoke(function(serviceA){}); 8982 * 8983 * // annotated 8984 * function explicit(serviceA) {}; 8985 * explicit.$inject = ['serviceA']; 8986 * $injector.invoke(explicit); 8987 * 8988 * // inline 8989 * $injector.invoke(['serviceA', function(serviceA){}]); 8990 * ``` 8991 * 8992 * ## Inference 8993 * 8994 * In JavaScript calling `toString()` on a function returns the function definition. The definition 8995 * can then be parsed and the function arguments can be extracted. This method of discovering 8996 * annotations is disallowed when the injector is in strict mode. 8997 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 8998 * argument names. 8999 * 9000 * ## `$inject` Annotation 9001 * By adding an `$inject` property onto a function the injection parameters can be specified. 9002 * 9003 * ## Inline 9004 * As an array of injection names, where the last item in the array is the function to call. 9005 */ 9006 9007/** 9008 * @ngdoc method 9009 * @name $injector#get 9010 * 9011 * @description 9012 * Return an instance of the service. 9013 * 9014 * @param {string} name The name of the instance to retrieve. 9015 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 9016 * @return {*} The instance. 9017 */ 9018 9019/** 9020 * @ngdoc method 9021 * @name $injector#invoke 9022 * 9023 * @description 9024 * Invoke the method and supply the method arguments from the `$injector`. 9025 * 9026 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 9027 * injected according to the {@link guide/di $inject Annotation} rules. 9028 * @param {Object=} self The `this` for the invoked method. 9029 * @param {Object=} locals Optional object. If preset then any argument names are read from this 9030 * object first, before the `$injector` is consulted. 9031 * @returns {*} the value returned by the invoked `fn` function. 9032 */ 9033 9034/** 9035 * @ngdoc method 9036 * @name $injector#has 9037 * 9038 * @description 9039 * Allows the user to query if the particular service exists. 9040 * 9041 * @param {string} name Name of the service to query. 9042 * @returns {boolean} `true` if injector has given service. 9043 */ 9044 9045/** 9046 * @ngdoc method 9047 * @name $injector#instantiate 9048 * @description 9049 * Create a new instance of JS type. The method takes a constructor function, invokes the new 9050 * operator, and supplies all of the arguments to the constructor function as specified by the 9051 * constructor annotation. 9052 * 9053 * @param {Function} Type Annotated constructor function. 9054 * @param {Object=} locals Optional object. If preset then any argument names are read from this 9055 * object first, before the `$injector` is consulted. 9056 * @returns {Object} new instance of `Type`. 9057 */ 9058 9059/** 9060 * @ngdoc method 9061 * @name $injector#annotate 9062 * 9063 * @description 9064 * Returns an array of service names which the function is requesting for injection. This API is 9065 * used by the injector to determine which services need to be injected into the function when the 9066 * function is invoked. There are three ways in which the function can be annotated with the needed 9067 * dependencies. 9068 * 9069 * # Argument names 9070 * 9071 * The simplest form is to extract the dependencies from the arguments of the function. This is done
9072 * by converting the function into a string using `toString()` method and extracting the argument 9073 * names. 9074 * ```js 9075 * // Given 9076 * function MyController($scope, $route) { 9077 * // ... 9078 * } 9079 * 9080 * // Then 9081 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 9082 * ``` 9083 * 9084 * You can disallow this method by using strict injection mode. 9085 * 9086 * This method does not work with code minification / obfuscation. For this reason the following 9087 * annotation strategies are supported. 9088 * 9089 * # The `$inject` property 9090 * 9091 * If a function has an `$inject` property and its value is an array of strings, then the strings 9092 * represent names of services to be injected into the function. 9093 * ```js 9094 * // Given 9095 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 9096 * // ... 9097 * } 9098 * // Define function dependencies 9099 * MyController['$inject'] = ['$scope', '$route']; 9100 * 9101 * // Then 9102 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 9103 * ``` 9104 * 9105 * # The array notation 9106 * 9107 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 9108 * is very inconvenient. In these situations using the array notation to specify the dependencies in 9109 * a way that survives minification is a better choice: 9110 * 9111 * ```js 9112 * // We wish to write this (not minification / obfuscation safe) 9113 * injector.invoke(function($compile, $rootScope) { 9114 * // ... 9115 * }); 9116 * 9117 * // We are forced to write break inlining 9118 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 9119 * // ... 9120 * }; 9121 * tmpFn.$inject = ['$compile', '$rootScope']; 9122 * injector.invoke(tmpFn); 9123 * 9124 * // To better support inline function the inline annotation is supported 9125 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 9126 * // ... 9127 * }]); 9128 * 9129 * // Therefore 9130 * expect(injector.annotate( 9131 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 9132 * ).toEqual(['$compile', '$rootScope']); 9133 * ``` 9134 * 9135 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 9136 * be retrieved as described above. 9137 * 9138 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 9139 * 9140 * @returns {Array.<string>} The names of the services which the function requires. 9141 */ 9142 9143 9144 9145 9146/** 9147 * @ngdoc service 9148 * @name $provide 9149 * 9150 * @description 9151 * 9152 * The {@link auto.$provide $provide} service has a number of methods for registering components 9153 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 9154 * {@link angular.Module}. 9155 * 9156 * An Angular **service** is a singleton object created by a **service factory**. These **service 9157 * factories** are functions which, in turn, are created by a **service provider**. 9158 * The **service providers** are constructor functions. When instantiated they must contain a 9159 * property called `$get`, which holds the **service factory** function. 9160 * 9161 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 9162 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 9163 * function to get the instance of the **service**. 9164 * 9165 * Often services have no configuration options and there is no need to add methods to the service 9166 * provider. The provider will be no more than a constructor function with a `$get` property. For 9167 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 9168 * services without specifying a provider. 9169 * 9170 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 9171 * {@link auto.$injector $injector} 9172 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 9173 * providers and services. 9174 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 9175 * services, not providers. 9176 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 9177 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 9178 * given factory function. 9179 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 9180 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 9181 * a new object using the given constructor function. 9182 * 9183 * See the individual methods for more information and examples. 9184 */ 9185 9186/** 9187 * @ngdoc method 9188 * @name $provide#provider 9189 * @description 9190 * 9191 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 9192 * are constructor functions, whose instances are responsible for "providing" a factory for a 9193 * service. 9194 * 9195 * Service provider names start with the name of the service they provide followed by `Provider`. 9196 * For example, the {@link ng.$log $log} service has a provider called 9197 * {@link ng.$logProvider $logProvider}. 9198 * 9199 * Service provider objects can have additional methods which allow configuration of the provider 9200 * and its service. Importantly, you can configure what kind of service is created by the `$get` 9201 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 9202 * method {@link ng.$logProvider#debugEnabled debugEnabled} 9203 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 9204 * console or not. 9205 * 9206 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 9207 'Provider'` key. 9208 * @param {(Object|function())} provider If the provider is: 9209 * 9210 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 9211 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 9212 * - `Constructor`: a new instance of the provider will be created using 9213 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 9214 * 9215 * @returns {Object} registered provider instance 9216 9217 * @example 9218 * 9219 * The following example shows how to create a simple event tracking service and register it using 9220 * {@link auto.$provide#provider $provide.provider()}. 9221 * 9222 * ```js 9223 * // Define the eventTracker provider 9224 * function EventTrackerProvider() { 9225 * var trackingUrl = '/track'; 9226 * 9227 * // A provider method for configuring where the tracked events should been saved 9228 * this.setTrackingUrl = function(url) { 9229 * trackingUrl = url; 9230 * }; 9231 * 9232 * // The service factory function 9233 * this.$get = ['$http', function($http) { 9234 * var trackedEvents = {}; 9235 * return { 9236 * // Call this to track an event 9237 * event: function(event) { 9238 * var count = trackedEvents[event] || 0; 9239 * count += 1; 9240 * trackedEvents[event] = count; 9241 * return count; 9242 * }, 9243 * // Call this to save the tracked events to the trackingUrl 9244 * save: function() { 9245 * $http.post(trackingUrl, trackedEvents); 9246 * } 9247 * }; 9248 * }]; 9249 * } 9250 * 9251 * describe('eventTracker', function() { 9252 * var postSpy; 9253 * 9254 * beforeEach(module(function($provide) { 9255 * // Register the eventTracker provider 9256 * $provide.provider('eventTracker', EventTrackerProvider); 9257 * })); 9258 * 9259 * beforeEach(module(function(eventTrackerProvider) { 9260 * // Configure eventTracker provider 9261 * eventTrackerProvider.setTrackingUrl('/custom-track'); 9262 * })); 9263 * 9264 * it('tracks events', inject(function(eventTracker) { 9265 * expect(eventTracker.event('login')).toEqual(1); 9266 * expect(eventTracker.event('login')).toEqual(2); 9267 * })); 9268 * 9269 * it('saves to the tracking url', inject(function(eventTracker, $http) { 9270 * postSpy = spyOn($http, 'post'); 9271 * eventTracker.event('login'); 9272 * eventTracker.save(); 9273 * expect(postSpy).toHaveBeenCalled(); 9274 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 9275 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 9276 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 9277 * })); 9278 * }); 9279 * ``` 9280 */ 9281 9282/** 9283 * @ngdoc method 9284 * @name $provide#factory 9285 * @description 9286 * 9287 * Register a **service factory**, which will be called to return the service instance. 9288 * This is short for registering a service where its provider consists of only a `$get` property, 9289 * which is the given service factory function. 9290 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 9291 * configure your service in a provider. 9292 * 9293 * @param {string} name The name of the instance. 9294 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 9295 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 9296 * @returns {Object} registered provider instance 9297 * 9298 * @example 9299 * Here is an example of registering a service 9300 * ```js 9301 * $provide.factory('ping', ['$http', function($http) { 9302 * return function ping() { 9303 * return $http.send('/ping'); 9304 * }; 9305 * }]); 9306 * ``` 9307 * You would then inject and use this service like this: 9308 * ```js 9309 * someModule.controller('Ctrl', ['ping', function(ping) { 9310 * ping(); 9311 * }]); 9312 * ``` 9313 */ 9314 9315 9316/** 9317 * @ngdoc method 9318 * @name $provide#service 9319 * @description 9320 * 9321 * Register a **service constructor**, which will be invoked with `new` to create the service 9322 * instance. 9323 * This is short for registering a service where its provider's `$get` property is a factory 9324 * function that returns an instance instantiated by the injector from the service constructor 9325 * function. 9326 * 9327 * Internally it looks a bit like this: 9328 * 9329 * ``` 9330 * { 9331 * $get: function() { 9332 * return $injector.instantiate(constructor); 9333 * } 9334 * } 9335 * ``` 9336 * 9337 * 9338 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 9339 * as a type/class. 9340 * 9341 * @param {string} name The name of the instance. 9342 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 9343 * that will be instantiated. 9344 * @returns {Object} registered provider instance 9345 * 9346 * @example 9347 * Here is an example of registering a service using 9348 * {@link auto.$provide#service $provide.service(class)}. 9349 * ```js 9350 * var Ping = function($http) { 9351 * this.$http = $http; 9352 * }; 9353 * 9354 * Ping.$inject = ['$http']; 9355 * 9356 * Ping.prototype.send = function() { 9357 * return this.$http.get('/ping'); 9358 * }; 9359 * $provide.service('ping', Ping); 9360 * ``` 9361 * You would then inject and use this service like this: 9362 * ```js 9363 * someModule.controller('Ctrl', ['ping', function(ping) { 9364 * ping.send(); 9365 * }]); 9366 * ``` 9367 */ 9368 9369 9370/** 9371 * @ngdoc method 9372 * @name $provide#value 9373 * @description 9374 * 9375 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 9376 * number, an array, an object or a function. This is short for registering a service where its 9377 * provider's `$get` property is a factory function that takes no arguments and returns the **value 9378 * service**. That also means it is not possible to inject other services into a value service. 9379 * 9380 * Value services are similar to constant services, except that they cannot be injected into a 9381 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 9382 * an Angular {@link auto.$provide#decorator decorator}. 9383 * 9384 * @param {string} name The name of the instance. 9385 * @param {*} value The value. 9386 * @returns {Object} registered provider instance 9387 * 9388 * @example 9389 * Here are some examples of creating value services. 9390 * ```js 9391 * $provide.value('ADMIN_USER', 'admin'); 9392 *
9393 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 9394 * 9395 * $provide.value('halfOf', function(value) { 9396 * return value / 2; 9397 * }); 9398 * ``` 9399 */ 9400 9401 9402/** 9403 * @ngdoc method 9404 * @name $provide#constant 9405 * @description 9406 * 9407 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string, 9408 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not 9409 * possible to inject other services into a constant. 9410 * 9411 * But unlike {@link auto.$provide#value value}, a constant can be 9412 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 9413 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 9414 * 9415 * @param {string} name The name of the constant. 9416 * @param {*} value The constant value. 9417 * @returns {Object} registered instance 9418 * 9419 * @example 9420 * Here a some examples of creating constants: 9421 * ```js 9422 * $provide.constant('SHARD_HEIGHT', 306); 9423 * 9424 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 9425 * 9426 * $provide.constant('double', function(value) { 9427 * return value * 2; 9428 * }); 9429 * ``` 9430 */ 9431 9432 9433/** 9434 * @ngdoc method 9435 * @name $provide#decorator 9436 * @description 9437 * 9438 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 9439 * intercepts the creation of a service, allowing it to override or modify the behavior of the 9440 * service. The object returned by the decorator may be the original service, or a new service 9441 * object which replaces or wraps and delegates to the original service. 9442 * 9443 * @param {string} name The name of the service to decorate. 9444 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 9445 * instantiated and should return the decorated service instance. The function is called using 9446 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 9447 * Local injection arguments: 9448 * 9449 * * `$delegate` - The original service instance, which can be monkey patched, configured, 9450 * decorated or delegated to. 9451 * 9452 * @example 9453 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 9454 * calls to {@link ng.$log#error $log.warn()}. 9455 * ```js 9456 * $provide.decorator('$log', ['$delegate', function($delegate) { 9457 * $delegate.warn = $delegate.error; 9458 * return $delegate; 9459 * }]); 9460 * ``` 9461 */ 9462 9463 9464function createInjector(modulesToLoad, strictDi) { 9465 strictDi = (strictDi === true); 9466 var INSTANTIATING = {}, 9467 providerSuffix = 'Provider', 9468 path = [], 9469 loadedModules = new HashMap([], true), 9470 providerCache = { 9471 $provide: { 9472 provider: supportObject(provider), 9473 factory: supportObject(factory), 9474 service: supportObject(service), 9475 value: supportObject(value), 9476 constant: supportObject(constant), 9477 decorator: decorator 9478 } 9479 }, 9480 providerInjector = (providerCache.$injector = 9481 createInternalInjector(providerCache, function(serviceName, caller) { 9482 if (angular.isString(caller)) { 9483 path.push(caller); 9484 } 9485 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 9486 })), 9487 instanceCache = {}, 9488 protoInstanceInjector = 9489 createInternalInjector(instanceCache, function(serviceName, caller) { 9490 var provider = providerInjector.get(serviceName + providerSuffix, caller); 9491 return instanceInjector.invoke( 9492 provider.$get, provider, undefined, serviceName); 9493 }), 9494 instanceInjector = protoInstanceInjector; 9495 9496 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; 9497 var runBlocks = loadModules(modulesToLoad); 9498 instanceInjector = protoInstanceInjector.get('$injector'); 9499 instanceInjector.strictDi = strictDi; 9500 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); 9501 9502 return instanceInjector; 9503 9504 //////////////////////////////////// 9505 // $provider 9506 //////////////////////////////////// 9507 9508 function supportObject(delegate) { 9509 return function(key, value) { 9510 if (isObject(key)) {
vendor: 10,003 bytes, lines 9511-9789
9511 forEach(key, reverseParams(delegate)); 9512 } else { 9513 return delegate(key, value); 9514 } 9515 }; 9516 } 9517 9518 function provider(name, provider_) { 9519 assertNotHasOwnProperty(name, 'service'); 9520 if (isFunction(provider_) || isArray(provider_)) { 9521 provider_ = providerInjector.instantiate(provider_); 9522 } 9523 if (!provider_.$get) { 9524 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 9525 } 9526 return providerCache[name + providerSuffix] = provider_; 9527 } 9528 9529 function enforceReturnValue(name, factory) { 9530 return function enforcedReturnValue() { 9531 var result = instanceInjector.invoke(factory, this); 9532 if (isUndefined(result)) { 9533 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 9534 } 9535 return result; 9536 }; 9537 } 9538 9539 function factory(name, factoryFn, enforce) { 9540 return provider(name, { 9541 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 9542 }); 9543 } 9544 9545 function service(name, constructor) { 9546 return factory(name, ['$injector', function($injector) { 9547 return $injector.instantiate(constructor); 9548 }]); 9549 } 9550 9551 function value(name, val) { return factory(name, valueFn(val), false); } 9552 9553 function constant(name, value) { 9554 assertNotHasOwnProperty(name, 'constant'); 9555 providerCache[name] = value; 9556 instanceCache[name] = value; 9557 } 9558 9559 function decorator(serviceName, decorFn) { 9560 var origProvider = providerInjector.get(serviceName + providerSuffix), 9561 orig$get = origProvider.$get; 9562 9563 origProvider.$get = function() { 9564 var origInstance = instanceInjector.invoke(orig$get, origProvider); 9565 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 9566 }; 9567 } 9568 9569 //////////////////////////////////// 9570 // Module Loading 9571 //////////////////////////////////// 9572 function loadModules(modulesToLoad) { 9573 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 9574 var runBlocks = [], moduleFn; 9575 forEach(modulesToLoad, function(module) { 9576 if (loadedModules.get(module)) return; 9577 loadedModules.put(module, true); 9578 9579 function runInvokeQueue(queue) { 9580 var i, ii; 9581 for (i = 0, ii = queue.length; i < ii; i++) { 9582 var invokeArgs = queue[i], 9583 provider = providerInjector.get(invokeArgs[0]); 9584 9585 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 9586 } 9587 } 9588 9589 try { 9590 if (isString(module)) { 9591 moduleFn = angularModule(module); 9592 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 9593 runInvokeQueue(moduleFn._invokeQueue); 9594 runInvokeQueue(moduleFn._configBlocks); 9595 } else if (isFunction(module)) { 9596 runBlocks.push(providerInjector.invoke(module)); 9597 } else if (isArray(module)) { 9598 runBlocks.push(providerInjector.invoke(module)); 9599 } else { 9600 assertArgFn(module, 'module'); 9601 } 9602 } catch (e) { 9603 if (isArray(module)) { 9604 module = module[module.length - 1]; 9605 } 9606 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 9607 // Safari & FF's stack traces don't contain error.message content 9608 // unlike those of Chrome and IE 9609 // So if stack doesn't contain message, we create a new string that contains both. 9610 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 9611 /* jshint -W022 */ 9612 e = e.message + '\n' + e.stack; 9613 } 9614 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 9615 module, e.stack || e.message || e); 9616 } 9617 }); 9618 return runBlocks; 9619 } 9620 9621 //////////////////////////////////// 9622 // internal Injector 9623 //////////////////////////////////// 9624 9625 function createInternalInjector(cache, factory) { 9626 9627 function getService(serviceName, caller) { 9628 if (cache.hasOwnProperty(serviceName)) { 9629 if (cache[serviceName] === INSTANTIATING) { 9630 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 9631 serviceName + ' <- ' + path.join(' <- ')); 9632 } 9633 return cache[serviceName]; 9634 } else { 9635 try { 9636 path.unshift(serviceName); 9637 cache[serviceName] = INSTANTIATING; 9638 return cache[serviceName] = factory(serviceName, caller); 9639 } catch (err) { 9640 if (cache[serviceName] === INSTANTIATING) { 9641 delete cache[serviceName]; 9642 } 9643 throw err; 9644 } finally { 9645 path.shift(); 9646 } 9647 } 9648 } 9649 9650 9651 function injectionArgs(fn, locals, serviceName) { 9652 var args = [], 9653 $inject = createInjector.$$annotate(fn, strictDi, serviceName); 9654 9655 for (var i = 0, length = $inject.length; i < length; i++) { 9656 var key = $inject[i]; 9657 if (typeof key !== 'string') { 9658 throw $injectorMinErr('itkn', 9659 'Incorrect injection token! Expected service name as string, got {0}', key); 9660 } 9661 args.push(locals && locals.hasOwnProperty(key) ? locals[key] : 9662 getService(key, serviceName)); 9663 } 9664 return args; 9665 } 9666 9667 function isClass(func) { 9668 // IE 9-11 do not support classes and IE9 leaks with the code below. 9669 if (msie <= 11) { 9670 return false; 9671 } 9672 // Workaround for MS Edge. 9673 // Check https://connect.microsoft.com/IE/Feedback/Details/2211653 9674 return typeof func === 'function' 9675 && /^(?:class\s|constructor\()/.test(Function.prototype.toString.call(func)); 9676 } 9677 9678 function invoke(fn, self, locals, serviceName) { 9679 if (typeof locals === 'string') { 9680 serviceName = locals; 9681 locals = null; 9682 } 9683 9684 var args = injectionArgs(fn, locals, serviceName); 9685 if (isArray(fn)) { 9686 fn = fn[fn.length - 1]; 9687 } 9688 9689 if (!isClass(fn)) { 9690 // http://jsperf.com/angularjs-invoke-apply-vs-switch 9691 // #5388 9692 return fn.apply(self, args); 9693 } else { 9694 args.unshift(null); 9695 return new (Function.prototype.bind.apply(fn, args))(); 9696 } 9697 } 9698 9699 9700 function instantiate(Type, locals, serviceName) { 9701 // Check if Type is annotated and use just the given function at n-1 as parameter 9702 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 9703 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); 9704 var args = injectionArgs(Type, locals, serviceName); 9705 // Empty object at position 0 is ignored for invocation with `new`, but required. 9706 args.unshift(null); 9707 return new (Function.prototype.bind.apply(ctor, args))(); 9708 } 9709 9710 9711 return { 9712 invoke: invoke, 9713 instantiate: instantiate, 9714 get: getService, 9715 annotate: createInjector.$$annotate, 9716 has: function(name) { 9717 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 9718 } 9719 }; 9720 } 9721} 9722 9723createInjector.$$annotate = annotate; 9724 9725/** 9726 * @ngdoc provider 9727 * @name $anchorScrollProvider 9728 * 9729 * @description 9730 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 9731 * {@link ng.$location#hash $location.hash()} changes. 9732 */ 9733function $AnchorScrollProvider() { 9734 9735 var autoScrollingEnabled = true; 9736 9737 /** 9738 * @ngdoc method 9739 * @name $anchorScrollProvider#disableAutoScrolling 9740 * 9741 * @description 9742 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 9743 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 9744 * Use this method to disable automatic scrolling. 9745 * 9746 * If automatic scrolling is disabled, one must explicitly call 9747 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 9748 * current hash. 9749 */ 9750 this.disableAutoScrolling = function() { 9751 autoScrollingEnabled = false; 9752 }; 9753 9754 /** 9755 * @ngdoc service 9756 * @name $anchorScroll 9757 * @kind function 9758 * @requires $window 9759 * @requires $location 9760 * @requires $rootScope 9761 * 9762 * @description 9763 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 9764 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 9765 * in the 9766 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document). 9767 * 9768 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 9769 * match any anchor whenever it changes. This can be disabled by calling 9770 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 9771 * 9772 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 9773 * vertical scroll-offset (either fixed or dynamic). 9774 * 9775 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 9776 * {@link ng.$location#hash $location.hash()} will be used. 9777 * 9778 * @property {(number|function|jqLite)} yOffset 9779 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 9780 * positioned elements at the top of the page, such as navbars, headers etc. 9781 * 9782 * `yOffset` can be specified in various ways: 9783 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 9784 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 9785 * a number representing the offset (in pixels).<br /><br /> 9786 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 9787 * the top of the page to the element's bottom will be used as offset.<br /> 9788 * **Note**: The element will be taken into account only as long as its `position` is set to 9789 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adju
9789st 9790 * their height and/or positioning according to the viewport's size. 9791 * 9792 * <br /> 9793 * <div class="alert alert-warning"> 9794 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 9795 * not some child element. 9796 * </div> 9797 * 9798 * @example 9799 <example module="anchorScrollExample"> 9800 <file name="index.html"> 9801 <div id="scrollArea" ng-controller="ScrollController"> 9802 <a ng-click="gotoBottom()">Go to bottom</a> 9803 <a id="bottom"></a> You're at the bottom! 9804 </div> 9805 </file> 9806 <file name="script.js"> 9807 angular.module('anchorScrollExample', []) 9808 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 9809 function ($scope, $location, $anchorScroll) { 9810 $scope.gotoBottom = function() { 9811 // set the location.hash to the id of 9812 // the element you wish to scroll to. 9813 $location.hash('bottom'); 9814 9815 // call $anchorScroll() 9816 $anchorScroll(); 9817 }; 9818 }]); 9819 </file> 9820 <file name="style.css"> 9821 #scrollArea { 9822 height: 280px; 9823 overflow: auto; 9824 } 9825 9826 #bottom { 9827 display: block; 9828 margin-top: 2000px; 9829 } 9830 </file> 9831 </example> 9832 * 9833 * <hr /> 9834 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 9835 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 9836 * 9837 * @example 9838 <example module="anchorScrollOffsetExample"> 9839 <file name="index.html"> 9840 <div class="fixed-header" ng-controller="headerCtrl"> 9841 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 9842 Go to anchor {{x}} 9843 </a> 9844 </div> 9845 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 9846 Anchor {{x}} of 5 9847 </div> 9848 </file> 9849 <file name="script.js"> 9850 angular.module('anchorScrollOffsetExample', []) 9851 .run(['$anchorScroll', function($anchorScroll) { 9852 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 9853 }]) 9854 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 9855 function ($anchorScroll, $location, $scope) {
vendor: 10,126 bytes, lines 9856-10181
9856 $scope.gotoAnchor = function(x) { 9857 var newHash = 'anchor' + x; 9858 if ($location.hash() !== newHash) { 9859 // set the $location.hash to `newHash` and 9860 // $anchorScroll will automatically scroll to it 9861 $location.hash('anchor' + x); 9862 } else { 9863 // call $anchorScroll() explicitly, 9864 // since $location.hash hasn't changed 9865 $anchorScroll(); 9866 } 9867 }; 9868 } 9869 ]); 9870 </file> 9871 <file name="style.css"> 9872 body { 9873 padding-top: 50px; 9874 } 9875 9876 .anchor { 9877 border: 2px dashed DarkOrchid; 9878 padding: 10px 10px 200px 10px; 9879 } 9880 9881 .fixed-header { 9882 background-color: rgba(0, 0, 0, 0.2); 9883 height: 50px; 9884 position: fixed; 9885 top: 0; left: 0; right: 0; 9886 } 9887 9888 .fixed-header > a { 9889 display: inline-block; 9890 margin: 5px 15px; 9891 } 9892 </file> 9893 </example> 9894 */ 9895 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 9896 var document = $window.document; 9897 9898 // Helper function to get first anchor from a NodeList 9899 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 9900 // and working in all supported browsers.) 9901 function getFirstAnchor(list) { 9902 var result = null; 9903 Array.prototype.some.call(list, function(element) { 9904 if (nodeName_(element) === 'a') { 9905 result = element; 9906 return true; 9907 } 9908 }); 9909 return result; 9910 } 9911 9912 function getYOffset() { 9913 9914 var offset = scroll.yOffset; 9915 9916 if (isFunction(offset)) { 9917 offset = offset(); 9918 } else if (isElement(offset)) { 9919 var elem = offset[0]; 9920 var style = $window.getComputedStyle(elem); 9921 if (style.position !== 'fixed') { 9922 offset = 0; 9923 } else { 9924 offset = elem.getBoundingClientRect().bottom; 9925 } 9926 } else if (!isNumber(offset)) { 9927 offset = 0; 9928 } 9929 9930 return offset; 9931 } 9932 9933 function scrollTo(elem) { 9934 if (elem) { 9935 elem.scrollIntoView(); 9936 9937 var offset = getYOffset(); 9938 9939 if (offset) { 9940 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 9941 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 9942 // top of the viewport. 9943 // 9944 // IF the number of pixels from the top of `elem` to the end of the page's content is less 9945 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 9946 // way down the page. 9947 // 9948 // This is often the case for elements near the bottom of the page. 9949 // 9950 // In such cases we do not need to scroll the whole `offset` up, just the difference between 9951 // the top of the element and the offset, which is enough to align the top of `elem` at the 9952 // desired position. 9953 var elemTop = elem.getBoundingClientRect().top; 9954 $window.scrollBy(0, elemTop - offset); 9955 } 9956 } else { 9957 $window.scrollTo(0, 0); 9958 } 9959 } 9960 9961 function scroll(hash) { 9962 hash = isString(hash) ? hash : $location.hash(); 9963 var elm; 9964 9965 // empty hash, scroll to the top of the page 9966 if (!hash) scrollTo(null); 9967 9968 // element with given id 9969 else if ((elm = document.getElementById(hash))) scrollTo(elm); 9970 9971 // first anchor with given name :-D 9972 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 9973 9974 // no element and hash == 'top', scroll to the top of the page 9975 else if (hash === 'top') scrollTo(null); 9976 } 9977 9978 // does not scroll when user clicks on anchor link that is currently on 9979 // (no url change, no $location.hash() change), browser native does scroll 9980 if (autoScrollingEnabled) { 9981 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 9982 function autoScrollWatchAction(newVal, oldVal) { 9983 // skip the initial scroll if $location.hash is empty 9984 if (newVal === oldVal && newVal === '') return; 9985 9986 jqLiteDocumentLoaded(function() { 9987 $rootScope.$evalAsync(scroll); 9988 }); 9989 }); 9990 } 9991 9992 return scroll; 9993 }]; 9994} 9995 9996var $animateMinErr = minErr('$animate'); 9997var ELEMENT_NODE = 1; 9998var NG_ANIMATE_CLASSNAME = 'ng-animate'; 9999 10000function mergeClasses(a,b) { 10001 if (!a && !b) return ''; 10002 if (!a) return b; 10003 if (!b) return a; 10004 if (isArray(a)) a = a.join(' '); 10005 if (isArray(b)) b = b.join(' '); 10006 return a + ' ' + b; 10007} 10008 10009function extractElementNode(element) { 10010 for (var i = 0; i < element.length; i++) { 10011 var elm = element[i]; 10012 if (elm.nodeType === ELEMENT_NODE) { 10013 return elm; 10014 } 10015 } 10016} 10017 10018function splitClasses(classes) { 10019 if (isString(classes)) { 10020 classes = classes.split(' '); 10021 } 10022 10023 // Use createMap() to prevent class assumptions involving property names in 10024 // Object.prototype 10025 var obj = createMap(); 10026 forEach(classes, function(klass) { 10027 // sometimes the split leaves empty string values 10028 // incase extra spaces were applied to the options 10029 if (klass.length) { 10030 obj[klass] = true; 10031 } 10032 }); 10033 return obj; 10034} 10035 10036// if any other type of options value besides an Object value is 10037// passed into the $animate.method() animation then this helper code 10038// will be run which will ignore it. While this patch is not the 10039// greatest solution to this, a lot of existing plugins depend on 10040// $animate to either call the callback (< 1.2) or return a promise 10041// that can be changed. This helper function ensures that the options 10042// are wiped clean incase a callback function is provided. 10043function prepareAnimateOptions(options) { 10044 return isObject(options) 10045 ? options 10046 : {}; 10047} 10048 10049var $$CoreAnimateJsProvider = function() { 10050 this.$get = noop; 10051}; 10052 10053// this is prefixed with Core since it conflicts with 10054// the animateQueueProvider defined in ngAnimate/animateQueue.js 10055var $$CoreAnimateQueueProvider = function() { 10056 var postDigestQueue = new HashMap(); 10057 var postDigestElements = []; 10058 10059 this.$get = ['$$AnimateRunner', '$rootScope', 10060 function($$AnimateRunner, $rootScope) { 10061 return { 10062 enabled: noop, 10063 on: noop, 10064 off: noop, 10065 pin: noop, 10066 10067 push: function(element, event, options, domOperation) { 10068 domOperation && domOperation(); 10069 10070 options = options || {}; 10071 options.from && element.css(options.from); 10072 options.to && element.css(options.to); 10073 10074 if (options.addClass || options.removeClass) { 10075 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 10076 } 10077 10078 var runner = new $$AnimateRunner(); // jshint ignore:line 10079 10080 // since there are no animations to run the runner needs to be 10081 // notified that the animation call is complete. 10082 runner.complete(); 10083 return runner; 10084 } 10085 }; 10086 10087 10088 function updateData(data, classes, value) { 10089 var changed = false; 10090 if (classes) { 10091 classes = isString(classes) ? classes.split(' ') : 10092 isArray(classes) ? classes : []; 10093 forEach(classes, function(className) { 10094 if (className) { 10095 changed = true; 10096 data[className] = value; 10097 } 10098 }); 10099 } 10100 return changed; 10101 } 10102 10103 function handleCSSClassChanges() { 10104 forEach(postDigestElements, function(element) { 10105 var data = postDigestQueue.get(element); 10106 if (data) { 10107 var existing = splitClasses(element.attr('class')); 10108 var toAdd = ''; 10109 var toRemove = ''; 10110 forEach(data, function(status, className) { 10111 var hasClass = !!existing[className]; 10112 if (status !== hasClass) { 10113 if (status) { 10114 toAdd += (toAdd.length ? ' ' : '') + className; 10115 } else { 10116 toRemove += (toRemove.length ? ' ' : '') + className; 10117 } 10118 } 10119 }); 10120 10121 forEach(element, function(elm) { 10122 toAdd && jqLiteAddClass(elm, toAdd); 10123 toRemove && jqLiteRemoveClass(elm, toRemove); 10124 }); 10125 postDigestQueue.remove(element); 10126 } 10127 }); 10128 postDigestElements.length = 0; 10129 } 10130 10131 10132 function addRemoveClassesPostDigest(element, add, remove) { 10133 var data = postDigestQueue.get(element) || {}; 10134 10135 var classesAdded = updateData(data, add, true); 10136 var classesRemoved = updateData(data, remove, false); 10137 10138 if (classesAdded || classesRemoved) { 10139 10140 postDigestQueue.put(element, data); 10141 postDigestElements.push(element); 10142 10143 if (postDigestElements.length === 1) { 10144 $rootScope.$$postDigest(handleCSSClassChanges); 10145 } 10146 } 10147 } 10148 }]; 10149}; 10150 10151/** 10152 * @ngdoc provider 10153 * @name $animateProvider 10154 * 10155 * @description 10156 * Default implementation of $animate that doesn't perform any animations, instead just 10157 * synchronously performs DOM updates and resolves the returned runner promise. 10158 * 10159 * In order to enable animations the `ngAnimate` module has to be loaded. 10160 * 10161 * To see the functional implementation check out `src/ngAnimate/animate.js`. 10162 */ 10163var $AnimateProvider = ['$provide', function($provide) { 10164 var provider = this; 10165 10166 this.$$registeredAnimations = Object.create(null); 10167 10168 /** 10169 * @ngdoc method 10170 * @name $animateProvider#register 10171 * 10172 * @description 10173 * Registers a new injectable animation factory function. The factory function produces the 10174 * animation object which contains callback functions for each event that is expected to be 10175 * animated. 10176 * 10177 * * `eventFn`: `function(element, ... , doneFunction, options)` 10178 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 10179 * on the type of animation additional arguments will be injected into the animation function. The 10180 * list below explains the function signatures for the different animation methods: 10181 *
10182 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 10183 * - addClass: function(element, addedClasses, doneFunction, options) 10184 * - removeClass: function(element, removedClasses, doneFunction, options) 10185 * - enter, leave, move: function(element, doneFunction, options) 10186 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 10187 * 10188 * Make sure to trigger the `doneFunction` once the animation is fully complete. 10189 * 10190 * ```js 10191 * return { 10192 * //enter, leave, move signature 10193 * eventFn : function(element, done, options) { 10194 * //code to run the animation 10195 * //once complete, then run done() 10196 * return function endFunction(wasCancelled) { 10197 * //code to cancel the animation 10198 * } 10199 * } 10200 * } 10201 * ``` 10202 * 10203 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 10204 * @param {Function} factory The factory function that will be executed to return the animation 10205 * object. 10206 */ 10207 this.register = function(name, factory) { 10208 if (name && name.charAt(0) !== '.') { 10209 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 10210 } 10211 10212 var key = name + '-animation'; 10213 provider.$$registeredAnimations[name.substr(1)] = key; 10214 $provide.factory(key, factory); 10215 }; 10216 10217 /** 10218 * @ngdoc method 10219 * @name $animateProvider#classNameFilter 10220 * 10221 * @description 10222 * Sets and/or returns the CSS class regular expression that is checked when performing 10223 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 10224 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 10225 * When setting the `classNameFilter` value, animations will only be performed on elements 10226 * that successfully match the filter expression. This in turn can boost performance 10227 * for low-powered devices as well as applications containing a lot of structural operations. 10228 * @param {RegExp=} expression The className expression which will be checked against all animations 10229 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 10230 */ 10231 this.classNameFilter = function(expression) { 10232 if (arguments.length === 1) { 10233 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 10234 if (this.$$classNameFilter) { 10235 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 10236 if (reservedRegex.test(this.$$classNameFilter.toString())) { 10237 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 10238 10239 } 10240 } 10241 } 10242 return this.$$classNameFilter; 10243 }; 10244 10245 this.$get = ['$$animateQueue', function($$animateQueue) { 10246 function domInsert(element, parentElement, afterElement) { 10247 // if for some reason the previous element was removed 10248 // from the dom sometime before this code runs then let's 10249 // just stick to using the parent element as the anchor 10250 if (afterElement) { 10251 var afterNode = extractElementNode(afterElement); 10252 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 10253 afterElement = null; 10254 } 10255 } 10256 afterElement ? afterElement.after(element) : parentElement.prepend(element); 10257 } 10258 10259 /** 10260 * @ngdoc service 10261 * @name $animate 10262 * @description The $animate service exposes a series of DOM utility methods that provide support 10263 * for animation hooks. The default behavior is the application of DOM operations, however, 10264 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 10265 * to ensure that animation runs with the triggered DOM operation. 10266 * 10267 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't 10268 * included and only when it is active then the animation hooks that `$animate` triggers will be 10269 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 10270 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 10271 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 10272 * 10273 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 10274 * 10275 * To learn more about enabling animation support, click here to visit the 10276 * {@link ngAnimate ngAnimate module page}. 10277 */ 10278 return { 10279 // we don't call it directly since non-existant arguments may 10280 // be interpreted as null within the sub enabled function 10281 10282 /** 10283 * 10284 * @ngdoc method 10285 * @name $animate#on 10286 * @kind function
10287 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 10288 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 10289 * is fired with the following params: 10290 * 10291 * ```js 10292 * $animate.on('enter', container, 10293 * function callback(element, phase) { 10294 * // cool we detected an enter animation within the container 10295 * } 10296 * ); 10297 * ``` 10298 * 10299 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 10300 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 10301 * as well as among its children 10302 * @param {Function} callback the callback function that will be fired when the listener is triggered 10303 * 10304 * The arguments present in the callback function are: 10305 * * `element` - The captured DOM element that the animation was fired on. 10306 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 10307 */ 10308 on: $$animateQueue.on, 10309 10310 /** 10311 * 10312 * @ngdoc method 10313 * @name $animate#off 10314 * @kind function 10315 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 10316 * can be used in three different ways depending on the arguments: 10317 * 10318 * ```js 10319 * // remove all the animation event listeners listening for `enter` 10320 * $animate.off('enter'); 10321 * 10322 * // remove all the animation event listeners listening for `enter` on the given element and its children 10323 * $animate.off('enter', container); 10324 * 10325 * // remove the event listener function provided by `callback` that is set 10326 * // to listen for `enter` on the given `container` as well as its children 10327 * $animate.off('enter', container, callback); 10328 * ``` 10329 * 10330 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) 10331 * @param {DOMElement=} container the container element the event listener was placed on 10332 * @param {Function=} callback the callback function that was registered as the listener 10333 */ 10334 off: $$animateQueue.off, 10335 10336 /** 10337 * @ngdoc method 10338 * @name $animate#pin 10339 * @kind function 10340 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 10341 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 10342 * element despite being outside the realm of the application or within another application. Say for example if the application 10343 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 10344 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 10345 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 10346 * 10347 * Note that this feature is only active when the `ngAnimate` module is used. 10348 * 10349 * @param {DOMElement} element the external element that will be pinned 10350 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 10351 */ 10352 pin: $$animateQueue.pin, 10353 10354 /** 10355 * 10356 * @ngdoc method 10357 * @name $animate#enabled 10358 * @kind function 10359 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 10360 * function can be called in four ways: 10361 * 10362 * ```js 10363 * // returns true or false 10364 * $animate.enabled(); 10365 * 10366 * // changes the enabled state for all animations 10367 * $animate.enabled(false); 10368 * $animate.enabled(true); 10369 * 10370 * // returns true or false if animations are enabled for an element 10371 * $animate.enabled(element); 10372 * 10373 * // changes the enabled state for an element and its children 10374 * $animate.enabled(element, true); 10375 * $animate.enabled(element, false); 10376 * ``` 10377 * 10378 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 10379 * @param {boolean=} enabled whether or not the animations will be enabled for the element 10380 * 10381 * @return {boolean} whether or not animations are enabled 10382 */ 10383 enabled: $$animateQueue.enabled, 10384 10385 /** 10386 * @ngdoc method 10387 * @name $animate#cancel 10388 * @kind function
10389 * @description Cancels the provided animation. 10390 * 10391 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 10392 */ 10393 cancel: function(runner) { 10394 runner.end && runner.end(); 10395 }, 10396 10397 /** 10398 * 10399 * @ngdoc method 10400 * @name $animate#enter 10401 * @kind function 10402 * @description Inserts the element into the DOM either after the `after` element (if provided) or 10403 * as the first child within the `parent` element and then triggers an animation. 10404 * A promise is returned that will be resolved during the next digest once the animation 10405 * has completed. 10406 * 10407 * @param {DOMElement} element the element which will be inserted into the DOM 10408 * @param {DOMElement} parent the parent element which will append the element as 10409 * a child (so long as the after element is not present) 10410 * @param {DOMElement=} after the sibling element after which the element will be appended 10411 * @param {object=} options an optional collection of options/styles that will be applied to the element 10412 * 10413 * @return {Promise} the animation callback promise 10414 */ 10415 enter: function(element, parent, after, options) { 10416 parent = parent && jqLite(parent); 10417 after = after && jqLite(after); 10418 parent = parent || after.parent(); 10419 domInsert(element, parent, after); 10420 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 10421 }, 10422 10423 /** 10424 * 10425 * @ngdoc method 10426 * @name $animate#move 10427 * @kind function 10428 * @description Inserts (moves) the element into its new position in the DOM either after 10429 * the `after` element (if provided) or as the first child within the `parent` element 10430 * and then triggers an animation. A promise is returned that will be resolved 10431 * during the next digest once the animation has completed. 10432 * 10433 * @param {DOMElement} element the element which will be moved into the new DOM position 10434 * @param {DOMElement} parent the parent element which will append the element as 10435 * a child (so long as the after element is not present) 10436 * @param {DOMElement=} after the sibling element after which the element will be appended 10437 * @param {object=} options an optional collection of options/styles that will be applied to the element 10438 * 10439 * @return {Promise} the animation callback promise 10440 */ 10441 move: function(element, parent, after, options) { 10442 parent = parent && jqLite(parent); 10443 after = after && jqLite(after); 10444 parent = parent || after.parent(); 10445 domInsert(element, parent, after); 10446 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 10447 }, 10448 10449 /** 10450 * @ngdoc method 10451 * @name $animate#leave 10452 * @kind function 10453 * @description Triggers an animation and then removes the element from the DOM. 10454 * When the function is called a promise is returned that will be resolved during the next 10455 * digest once the animation has completed. 10456 * 10457 * @param {DOMElement} element the element which will be removed from the DOM 10458 * @param {object=} options an optional collection of options/styles that will be applied to the element 10459 * 10460 * @return {Promise} the animation callback promise 10461 */ 10462 leave: function(element, options) { 10463 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 10464 element.remove(); 10465 }); 10466 }, 10467 10468 /** 10469 * @ngdoc method 10470 * @name $animate#addClass 10471 * @kind function 10472 * 10473 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 10474 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 10475 * animation if element already contains the CSS class or if the class is removed at a later step. 10476 * Note that class-based animations are treated differently compared to structural animations 10477 * (like enter, move and leave) since the CSS classes may be added/removed at different points 10478 * depending if CSS or JavaScript animations are used. 10479 * 10480 * @param {DOMElement} element the element which the CSS classes will be applied to 10481 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 10482 * @param {object=} options an optional collection of options/styles that will be applied to the element 10483 * 10484 * @return {Promise} the animation callback promise 10485 */ 10486 addClass: function(element, className, options) { 10487 options = prepareAnimateOptions(options); 10488 options.addClass = mergeClasses(options.addclass, className); 10489 return $$animateQueue.push(element, 'addClass', options); 10490 }, 10491 10492 /** 10493 * @ngdoc method 10494 * @name $animate#removeClass 10495 * @kind function 10496 *
10497 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 10498 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 10499 * animation if element does not contain the CSS class or if the class is added at a later step. 10500 * Note that class-based animations are treated differently compared to structural animations 10501 * (like enter, move and leave) since the CSS classes may be added/removed at different points 10502 * depending if CSS or JavaScript animations are used. 10503 * 10504 * @param {DOMElement} element the element which the CSS classes will be applied to 10505 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 10506 * @param {object=} options an optional collection of options/styles that will be applied to the element 10507 * 10508 * @return {Promise} the animation callback promise 10509 */ 10510 removeClass: function(element, className, options) { 10511 options = prepareAnimateOptions(options); 10512 options.removeClass = mergeClasses(options.removeClass, className); 10513 return $$animateQueue.push(element, 'removeClass', options); 10514 }, 10515 10516 /** 10517 * @ngdoc method 10518 * @name $animate#setClass 10519 * @kind function 10520 * 10521 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 10522 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 10523 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 10524 * passed. Note that class-based animations are treated differently compared to structural animations 10525 * (like enter, move and leave) since the CSS classes may be added/removed at different points 10526 * depending if CSS or JavaScript animations are used. 10527 * 10528 * @param {DOMElement} element the element which the CSS classes will be applied to 10529 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 10530 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 10531 * @param {object=} options an optional collection of options/styles that will be applied to the element 10532 * 10533 * @return {Promise} the animation callback promise 10534 */ 10535 setClass: function(element, add, remove, options) { 10536 options = prepareAnimateOptions(options); 10537 options.addClass = mergeClasses(options.addClass, add); 10538 options.removeClass = mergeClasses(options.removeClass, remove); 10539 return $$animateQueue.push(element, 'setClass', options); 10540 }, 10541 10542 /** 10543 * @ngdoc method 10544 * @name $animate#animate 10545 * @kind function 10546 * 10547 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 10548 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take 10549 * on the provided styles. For example, if a transition animation is set for the given classNamem, then the provided `from` and 10550 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding 10551 * style in `to`, the style in `from` is applied immediately, and no animation is run. 10552 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` 10553 * method (or as part of the `options` parameter): 10554 * 10555 * ```js 10556 * ngModule.animation('.my-inline-animation', function() { 10557 * return { 10558 * animate : function(element, from, to, done, options) { 10559 * //animation 10560 * done(); 10561 * } 10562 * } 10563 * }); 10564 * ``` 10565 * 10566 * @param {DOMElement} element the element which the CSS styles will be applied to 10567 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 10568 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 10569 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 10570 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 10571 * (Note that if no animation is detected then this value will not be applied to the element.) 10572 * @param {object=} options an optional collection of options/styles that will be applied to the element 10573 * 10574 * @return {Promise} the animation callback promise 10575 */ 10576 animate: function(element, from, to, className, options) { 10577 options = prepareAnimateOptions(options); 10578 options.from = options.from ? extend(options.from, from) : from; 10579 options.to = options.to ? extend(options.to, to) : to; 10580 10581 className = className || 'ng-inline-animate';
vendor: 4,147 bytes, lines 10582-10759
10582 options.tempClasses = mergeClasses(options.tempClasses, className); 10583 return $$animateQueue.push(element, 'animate', options); 10584 } 10585 }; 10586 }]; 10587}]; 10588 10589var $$AnimateAsyncRunFactoryProvider = function() { 10590 this.$get = ['$$rAF', function($$rAF) { 10591 var waitQueue = []; 10592 10593 function waitForTick(fn) { 10594 waitQueue.push(fn); 10595 if (waitQueue.length > 1) return; 10596 $$rAF(function() { 10597 for (var i = 0; i < waitQueue.length; i++) { 10598 waitQueue[i](); 10599 } 10600 waitQueue = []; 10601 }); 10602 } 10603 10604 return function() { 10605 var passed = false; 10606 waitForTick(function() { 10607 passed = true; 10608 }); 10609 return function(callback) { 10610 passed ? callback() : waitForTick(callback); 10611 }; 10612 }; 10613 }]; 10614}; 10615 10616var $$AnimateRunnerFactoryProvider = function() { 10617 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', 10618 function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { 10619 10620 var INITIAL_STATE = 0; 10621 var DONE_PENDING_STATE = 1; 10622 var DONE_COMPLETE_STATE = 2; 10623 10624 AnimateRunner.chain = function(chain, callback) { 10625 var index = 0; 10626 10627 next(); 10628 function next() { 10629 if (index === chain.length) { 10630 callback(true); 10631 return; 10632 } 10633 10634 chain[index](function(response) { 10635 if (response === false) { 10636 callback(false); 10637 return; 10638 } 10639 index++; 10640 next(); 10641 }); 10642 } 10643 }; 10644 10645 AnimateRunner.all = function(runners, callback) { 10646 var count = 0; 10647 var status = true; 10648 forEach(runners, function(runner) { 10649 runner.done(onProgress); 10650 }); 10651 10652 function onProgress(response) { 10653 status = status && response; 10654 if (++count === runners.length) { 10655 callback(status); 10656 } 10657 } 10658 }; 10659 10660 function AnimateRunner(host) { 10661 this.setHost(host); 10662 10663 var rafTick = $$animateAsyncRun(); 10664 var timeoutTick = function(fn) { 10665 $timeout(fn, 0, false); 10666 }; 10667 10668 this._doneCallbacks = []; 10669 this._tick = function(fn) { 10670 var doc = $document[0]; 10671 10672 // the document may not be ready or attached 10673 // to the module for some internal tests 10674 if (doc && doc.hidden) { 10675 timeoutTick(fn); 10676 } else { 10677 rafTick(fn); 10678 } 10679 }; 10680 this._state = 0; 10681 } 10682 10683 AnimateRunner.prototype = { 10684 setHost: function(host) { 10685 this.host = host || {}; 10686 }, 10687 10688 done: function(fn) { 10689 if (this._state === DONE_COMPLETE_STATE) { 10690 fn(); 10691 } else { 10692 this._doneCallbacks.push(fn); 10693 } 10694 }, 10695 10696 progress: noop, 10697 10698 getPromise: function() { 10699 if (!this.promise) { 10700 var self = this; 10701 this.promise = $q(function(resolve, reject) { 10702 self.done(function(status) { 10703 status === false ? reject() : resolve(); 10704 }); 10705 }); 10706 } 10707 return this.promise; 10708 }, 10709 10710 then: function(resolveHandler, rejectHandler) { 10711 return this.getPromise().then(resolveHandler, rejectHandler); 10712 }, 10713 10714 'catch': function(handler) { 10715 return this.getPromise()['catch'](handler); 10716 }, 10717 10718 'finally': function(handler) { 10719 return this.getPromise()['finally'](handler); 10720 }, 10721 10722 pause: function() { 10723 if (this.host.pause) { 10724 this.host.pause(); 10725 } 10726 }, 10727 10728 resume: function() { 10729 if (this.host.resume) { 10730 this.host.resume(); 10731 } 10732 }, 10733 10734 end: function() { 10735 if (this.host.end) { 10736 this.host.end(); 10737 } 10738 this._resolve(true); 10739 }, 10740 10741 cancel: function() { 10742 if (this.host.cancel) { 10743 this.host.cancel(); 10744 } 10745 this._resolve(false); 10746 }, 10747 10748 complete: function(response) { 10749 var self = this; 10750 if (self._state === INITIAL_STATE) { 10751 self._state = DONE_PENDING_STATE; 10752 self._tick(function() { 10753 self._resolve(response); 10754 }); 10755 } 10756 }, 10757 10758 _resolve: function(response) { 10759 if (this._state !== DONE_COMPLETE_STATE) {
10760 forEach(this._doneCallbacks, function(fn) { 10761 fn(response); 10762 }); 10763 this._doneCallbacks.length = 0; 10764 this._state = DONE_COMPLETE_STATE; 10765 } 10766 } 10767 }; 10768 10769 return AnimateRunner; 10770 }]; 10771}; 10772 10773/** 10774 * @ngdoc service 10775 * @name $animateCss 10776 * @kind object 10777 * 10778 * @description 10779 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 10780 * then the `$animateCss` service will actually perform animations. 10781 * 10782 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 10783 */ 10784var $CoreAnimateCssProvider = function() { 10785 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { 10786 10787 return function(element, initialOptions) { 10788 // all of the animation functions should create 10789 // a copy of the options data, however, if a 10790 // parent service has already created a copy then 10791 // we should stick to using that 10792 var options = initialOptions || {}; 10793 if (!options.$$prepared) { 10794 options = copy(options); 10795 } 10796 10797 // there is no point in applying the styles since 10798 // there is no animation that goes on at all in 10799 // this version of $animateCss. 10800 if (options.cleanupStyles) { 10801 options.from = options.to = null; 10802 } 10803 10804 if (options.from) { 10805 element.css(options.from); 10806 options.from = null; 10807 } 10808 10809 /* jshint newcap: false */ 10810 var closed, runner = new $$AnimateRunner(); 10811 return { 10812 start: run, 10813 end: run 10814 }; 10815 10816 function run() { 10817 $$rAF(function() { 10818 applyAnimationContents(); 10819 if (!closed) { 10820 runner.complete(); 10821 } 10822 closed = true; 10823 }); 10824 return runner; 10825 } 10826 10827 function applyAnimationContents() { 10828 if (options.addClass) { 10829 element.addClass(options.addClass); 10830 options.addClass = null; 10831 } 10832 if (options.removeClass) { 10833 element.removeClass(options.removeClass); 10834 options.removeClass = null; 10835 } 10836 if (options.to) { 10837 element.css(options.to); 10838 options.to = null; 10839 } 10840 } 10841 }; 10842 }]; 10843}; 10844 10845/* global stripHash: true */ 10846 10847/** 10848 * ! This is a private undocumented service ! 10849 * 10850 * @name $browser 10851 * @requires $log 10852 * @description 10853 * This object has two goals: 10854 * 10855 * - hide all the global state in the browser caused by the window object 10856 * - abstract away all the browser specific features and inconsistencies 10857 * 10858 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 10859 * service, which can be used for convenient testing of the application without the interaction with 10860 * the real browser apis. 10861 */ 10862/** 10863 * @param {object} window The global window object. 10864 * @param {object} document jQuery wrapped document. 10865 * @param {object} $log window.console or an object with the same interface. 10866 * @param {object} $sniffer $sniffer service 10867 */ 10868function Browser(window, document, $log, $sniffer) { 10869 var self = this, 10870 rawDocument = document[0], 10871 location = window.location, 10872 history = window.history, 10873 setTimeout = window.setTimeout, 10874 clearTimeout = window.clearTimeout, 10875 pendingDeferIds = {}; 10876 10877 self.isMock = false; 10878 10879 var outstandingRequestCount = 0; 10880 var outstandingRequestCallbacks = []; 10881 10882 // TODO(vojta): remove this temporary api 10883 self.$$completeOutstandingRequest = completeOutstandingRequest; 10884 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 10885 10886 /** 10887 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 10888 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 10889 */ 10890 function completeOutstandingRequest(fn) { 10891 try { 10892 fn.apply(null, sliceArgs(arguments, 1)); 10893 } finally { 10894 outstandingRequestCount--; 10895 if (outstandingRequestCount === 0) { 10896 while (outstandingRequestCallbacks.length) { 10897 try { 10898 outstandingRequestCallbacks.pop()(); 10899 } catch (e) { 10900 $log.error(e); 10901 } 10902 } 10903 } 10904 } 10905 } 10906 10907 function getHash(url) { 10908 var index = url.indexOf('#'); 10909 return index === -1 ? '' : url.substr(index); 10910 } 10911 10912 /** 10913 * @private 10914 * Note: this method is used only by scenario runner 10915 * TODO(vojta): prefix this method with $$ ? 10916 * @param {function()} callback Function that will be called when no outstanding request 10917 */ 10918 self.notifyWhenNoOutstandingRequests = function(callback) { 10919 if (outstandingRequestCount === 0) { 10920 callback(); 10921 } else { 10922 outstandingRequestCallbacks.push(callback); 10923 } 10924 }; 10925 10926 ////////////////////////////////////////////////////////////// 10927 // URL API 10928 ////////////////////////////////////////////////////////////// 10929 10930 var cachedState, lastHistoryState, 10931 lastBrowserUrl = location.href, 10932 baseElement = document.find('base'), 10933 pendingLocation = null; 10934 10935 cacheState(); 10936 lastHistoryState = cachedState; 10937 10938 /** 10939 * @name $browser#url 10940 * 10941 * @description 10942 * GETTER: 10943 * Without any argument, this method just returns current value of location.href. 10944 * 10945 * SETTER: 10946 * With at least one argument, this method sets url to new value. 10947 * If html5 history api supported, pushState/replaceState is used, otherwise 10948 * location.href/location.replace is used. 10949 * Returns its own instance to allow chaining 10950 * 10951 * NOTE: this api is intended for use only by the $location service. Please use the 10952 * {@link ng.$location $location service} to change url. 10953 * 10954 * @param {string} url New url (when used as setter) 10955 * @param {boolean=} replace Should new url replace current history record? 10956 * @param {object=} state object to use with pushState/replaceState 10957 */ 10958 self.url = function(url, replace, state) { 10959 // In modern browsers `history.state` is `null` by default; treating it separately 10960 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 10961 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 10962 if (isUndefined(state)) { 10963 state = null; 10964 } 10965 10966 // Android Browser BFCache causes location, history reference to become stale. 10967 if (location !== window.location) location = window.location; 10968 if (history !== window.history) history = window.history; 10969 10970 // setter 10971 if (url) { 10972 var sameState = lastHistoryState === state; 10973 10974 // Don't change anything if previous and current URLs and states match. This also prevents 10975 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 10976 // See https://github.com/angular/angular.js/commit/ffb2701 10977 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 10978 return self; 10979 } 10980 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 10981 lastBrowserUrl = url; 10982 lastHistoryState = state; 10983 // Don't use history API if only the hash changed 10984 // due to a bug in IE10/IE11 which leads 10985 // to not firing a `hashchange` nor `popstate` event 10986 // in some cases (see #9143). 10987 if ($sniffer.history && (!sameBase || !sameState)) { 10988 history[replace ? 'replaceState' : 'pushState'](state, '', url); 10989 cacheState(); 10990 // Do the assignment again so that those two variables are referentially identical. 10991 lastHistoryState = cachedState; 10992 } else { 10993 if (!sameBase || pendingLocation) { 10994 pendingLocation = url; 10995 } 10996 if (replace) { 10997 location.replace(url); 10998 } else if (!sameBase) { 10999 location.href = url; 11000 } else { 11001 location.hash = getHash(url); 11002 } 11003 if (location.href !== url) { 11004 pendingLocation = url; 11005 } 11006 } 11007 return self; 11008 // getter 11009 } else { 11010 // - pendingLocation is needed as browsers don't allow to read out 11011 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 11012 // https://openradar.appspot.com/22186109). 11013 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 11014 return pendingLocation || location.href.replace(/%27/g,"'"); 11015 } 11016 }; 11017 11018 /** 11019 * @name $browser#state 11020 * 11021 * @description 11022 * This method is a getter. 11023 * 11024 * Return history.state or null if history.state is undefined. 11025 * 11026 * @returns {object} state 11027 */ 11028 self.state = function() { 11029 return cachedState; 11030 }; 11031 11032 var urlChangeListeners = [], 11033 urlChangeInit = false;
11034 11035 function cacheStateAndFireUrlChange() { 11036 pendingLocation = null; 11037 cacheState(); 11038 fireUrlChange(); 11039 } 11040 11041 function getCurrentState() { 11042 try { 11043 return history.state; 11044 } catch (e) { 11045 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 11046 } 11047 } 11048 11049 // This variable should be used *only* inside the cacheState function. 11050 var lastCachedState = null; 11051 function cacheState() { 11052 // This should be the only place in $browser where `history.state` is read. 11053 cachedState = getCurrentState(); 11054 cachedState = isUndefined(cachedState) ? null : cachedState; 11055 11056 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 11057 if (equals(cachedState, lastCachedState)) { 11058 cachedState = lastCachedState; 11059 } 11060 lastCachedState = cachedState; 11061 } 11062 11063 function fireUrlChange() { 11064 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 11065 return; 11066 } 11067 11068 lastBrowserUrl = self.url(); 11069 lastHistoryState = cachedState; 11070 forEach(urlChangeListeners, function(listener) { 11071 listener(self.url(), cachedState); 11072 }); 11073 } 11074 11075 /** 11076 * @name $browser#onUrlChange 11077 * 11078 * @description 11079 * Register callback function that will be called, when url changes. 11080 * 11081 * It's only called when the url is changed from outside of angular: 11082 * - user types different url into address bar 11083 * - user clicks on history (forward/back) button 11084 * - user clicks on a link 11085 * 11086 * It's not called when url is changed by $browser.url() method 11087 * 11088 * The listener gets called with new url as parameter. 11089 * 11090 * NOTE: this api is intended for use only by the $location service. Please use the 11091 * {@link ng.$location $location service} to monitor url changes in angular apps. 11092 * 11093 * @param {function(string)} listener Listener function to be called when url changes. 11094 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 11095 */ 11096 self.onUrlChange = function(callback) { 11097 // TODO(vojta): refactor to use node's syntax for events 11098 if (!urlChangeInit) { 11099 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 11100 // don't fire popstate when user change the address bar and don't fire hashchange when url 11101 // changed by push/replaceState 11102 11103 // html5 history api - popstate event 11104 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 11105 // hashchange event 11106 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 11107 11108 urlChangeInit = true; 11109 } 11110 11111 urlChangeListeners.push(callback); 11112 return callback; 11113 }; 11114 11115 /** 11116 * @private 11117 * Remove popstate and hashchange handler from window. 11118 * 11119 * NOTE: this api is intended for use only by $rootScope. 11120 */ 11121 self.$$applicationDestroyed = function() { 11122 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 11123 }; 11124 11125 /** 11126 * Checks whether the url has changed outside of Angular. 11127 * Needs to be exported to be able to check for changes that have been done in sync, 11128 * as hashchange/popstate events fire in async. 11129 */ 11130 self.$$checkUrlChange = fireUrlChange; 11131 11132 ////////////////////////////////////////////////////////////// 11133 // Misc API 11134 ////////////////////////////////////////////////////////////// 11135 11136 /** 11137 * @name $browser#baseHref 11138 * 11139 * @description 11140 * Returns current <base href> 11141 * (always relative - without domain) 11142 * 11143 * @returns {string} The current base href 11144 */ 11145 self.baseHref = function() { 11146 var href = baseElement.attr('href'); 11147 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 11148 }; 11149 11150 /** 11151 * @name $browser#defer 11152 * @param {function()} fn A function, who's execution should be deferred. 11153 * @param {number=} [delay=0] of milliseconds to defer the function execution. 11154 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 11155 * 11156 * @description 11157 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 11158 * 11159 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 11160 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 11161 * via `$browser.defer.flush()`. 11162 * 11163 */ 11164 self.defer = function(fn, delay) { 11165 var timeoutId; 11166 outstandingRequestCount++; 11167 timeoutId = setTimeout(function() {
11168 delete pendingDeferIds[timeoutId]; 11169 completeOutstandingRequest(fn); 11170 }, delay || 0); 11171 pendingDeferIds[timeoutId] = true; 11172 return timeoutId; 11173 }; 11174 11175 11176 /** 11177 * @name $browser#defer.cancel 11178 * 11179 * @description 11180 * Cancels a deferred task identified with `deferId`. 11181 * 11182 * @param {*} deferId Token returned by the `$browser.defer` function. 11183 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 11184 * canceled. 11185 */ 11186 self.defer.cancel = function(deferId) { 11187 if (pendingDeferIds[deferId]) { 11188 delete pendingDeferIds[deferId]; 11189 clearTimeout(deferId); 11190 completeOutstandingRequest(noop); 11191 return true; 11192 } 11193 return false; 11194 }; 11195 11196} 11197 11198function $BrowserProvider() { 11199 this.$get = ['$window', '$log', '$sniffer', '$document', 11200 function($window, $log, $sniffer, $document) { 11201 return new Browser($window, $document, $log, $sniffer); 11202 }]; 11203} 11204 11205/** 11206 * @ngdoc service 11207 * @name $cacheFactory 11208 * 11209 * @description 11210 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 11211 * them. 11212 * 11213 * ```js 11214 * 11215 * var cache = $cacheFactory('cacheId'); 11216 * expect($cacheFactory.get('cacheId')).toBe(cache); 11217 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 11218 * 11219 * cache.put("key", "value"); 11220 * cache.put("another key", "another value"); 11221 * 11222 * // We've specified no options on creation 11223 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 11224 * 11225 * ``` 11226 * 11227 * 11228 * @param {string} cacheId Name or id of the newly created cache. 11229 * @param {object=} options Options object that specifies the cache behavior. Properties: 11230 * 11231 * - `{number=}` `capacity` â turns the cache into LRU cache. 11232 * 11233 * @returns {object} Newly created cache object with the following set of methods: 11234 * 11235 * - `{object}` `info()` â Returns id, size, and options of cache. 11236 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 11237 * it. 11238 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 11239 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 11240 * - `{void}` `removeAll()` â Removes all cached values. 11241 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 11242 * 11243 * @example 11244 <example module="cacheExampleApp"> 11245 <file name="index.html"> 11246 <div ng-controller="CacheController"> 11247 <input ng-model="newCacheKey" placeholder="Key"> 11248 <input ng-model="newCacheValue" placeholder="Value"> 11249 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 11250 11251 <p ng-if="keys.length">Cached Values</p> 11252 <div ng-repeat="key in keys"> 11253 <span ng-bind="key"></span> 11254 <span>: </span> 11255 <b ng-bind="cache.get(key)"></b> 11256 </div> 11257 11258 <p>Cache Info</p> 11259 <div ng-repeat="(key, value) in cache.info()"> 11260 <span ng-bind="key"></span> 11261 <span>: </span> 11262 <b ng-bind="value"></b> 11263 </div> 11264 </div> 11265 </file> 11266 <file name="script.js"> 11267 angular.module('cacheExampleApp', []). 11268 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 11269 $scope.keys = []; 11270 $scope.cache = $cacheFactory('cacheId'); 11271 $scope.put = function(key, value) { 11272 if (angular.isUndefined($scope.cache.get(key))) { 11273 $scope.keys.push(key); 11274 } 11275 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 11276 }; 11277 }]); 11278 </file> 11279 <file name="style.css"> 11280 p { 11281 margin: 10px 0 3px; 11282 } 11283 </file> 11284 </example> 11285 */ 11286function $CacheFactoryProvider() { 11287 11288 this.$get = function() { 11289 var caches = {}; 11290 11291 function cacheFactory(cacheId, options) { 11292 if (cacheId in caches) { 11293 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 11294 } 11295 11296 var size = 0, 11297 stats = extend({}, options, {id: cacheId}), 11298 data = createMap(), 11299 capacity = (options && options.capacity) || Number.MAX_VALUE, 11300 lruHash = createMap(), 11301 freshEnd = null, 11302 staleEnd = null; 11303 11304 /** 11305 * @ngdoc type 11306 * @name $cacheFactory.Cache 11307 *
11308 * @description 11309 * A cache object used to store and retrieve data, primarily used by 11310 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 11311 * templates and other data. 11312 * 11313 * ```js 11314 * angular.module('superCache') 11315 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 11316 * return $cacheFactory('super-cache'); 11317 * }]); 11318 * ``` 11319 * 11320 * Example test: 11321 * 11322 * ```js 11323 * it('should behave like a cache', inject(function(superCache) { 11324 * superCache.put('key', 'value'); 11325 * superCache.put('another key', 'another value'); 11326 * 11327 * expect(superCache.info()).toEqual({ 11328 * id: 'super-cache', 11329 * size: 2 11330 * }); 11331 * 11332 * superCache.remove('another key'); 11333 * expect(superCache.get('another key')).toBeUndefined(); 11334 * 11335 * superCache.removeAll(); 11336 * expect(superCache.info()).toEqual({ 11337 * id: 'super-cache', 11338 * size: 0 11339 * }); 11340 * })); 11341 * ``` 11342 */ 11343 return caches[cacheId] = { 11344 11345 /** 11346 * @ngdoc method 11347 * @name $cacheFactory.Cache#put 11348 * @kind function 11349 * 11350 * @description 11351 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 11352 * retrieved later, and incrementing the size of the cache if the key was not already 11353 * present in the cache. If behaving like an LRU cache, it will also remove stale 11354 * entries from the set. 11355 * 11356 * It will not insert undefined values into the cache. 11357 * 11358 * @param {string} key the key under which the cached data is stored. 11359 * @param {*} value the value to store alongside the key. If it is undefined, the key 11360 * will not be stored. 11361 * @returns {*} the value stored. 11362 */ 11363 put: function(key, value) { 11364 if (isUndefined(value)) return; 11365 if (capacity < Number.MAX_VALUE) { 11366 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 11367 11368 refresh(lruEntry); 11369 } 11370 11371 if (!(key in data)) size++; 11372 data[key] = value; 11373 11374 if (size > capacity) { 11375 this.remove(staleEnd.key); 11376 } 11377 11378 return value; 11379 }, 11380 11381 /** 11382 * @ngdoc method 11383 * @name $cacheFactory.Cache#get 11384 * @kind function 11385 * 11386 * @description 11387 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 11388 * 11389 * @param {string} key the key of the data to be retrieved 11390 * @returns {*} the value stored. 11391 */ 11392 get: function(key) { 11393 if (capacity < Number.MAX_VALUE) { 11394 var lruEntry = lruHash[key]; 11395 11396 if (!lruEntry) return; 11397 11398 refresh(lruEntry); 11399 } 11400 11401 return data[key]; 11402 }, 11403 11404 11405 /** 11406 * @ngdoc method 11407 * @name $cacheFactory.Cache#remove 11408 * @kind function 11409 * 11410 * @description 11411 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 11412 * 11413 * @param {string} key the key of the entry to be removed 11414 */ 11415 remove: function(key) { 11416 if (capacity < Number.MAX_VALUE) { 11417 var lruEntry = lruHash[key]; 11418 11419 if (!lruEntry) return; 11420 11421 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 11422 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 11423 link(lruEntry.n,lruEntry.p); 11424 11425 delete lruHash[key]; 11426 } 11427 11428 if (!(key in data)) return; 11429 11430 delete data[key]; 11431 size--; 11432 }, 11433 11434 11435 /** 11436 * @ngdoc method 11437 * @name $cacheFactory.Cache#removeAll 11438 * @kind function 11439 * 11440 * @description 11441 * Clears the cache object of any entries. 11442 */ 11443 removeAll: function() { 11444 data = createMap(); 11445 size = 0; 11446 lruHash = createMap(); 11447 freshEnd = staleEnd = null; 11448 }, 11449 11450 11451 /** 11452 * @ngdoc method 11453 * @name $cacheFactory.Cache#destroy 11454 * @kind function 11455 *
11456 * @description 11457 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 11458 * removing it from the {@link $cacheFactory $cacheFactory} set. 11459 */ 11460 destroy: function() { 11461 data = null; 11462 stats = null; 11463 lruHash = null; 11464 delete caches[cacheId]; 11465 }, 11466 11467 11468 /** 11469 * @ngdoc method 11470 * @name $cacheFactory.Cache#info 11471 * @kind function 11472 * 11473 * @description 11474 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 11475 * 11476 * @returns {object} an object with the following properties: 11477 * <ul> 11478 * <li>**id**: the id of the cache instance</li> 11479 * <li>**size**: the number of entries kept in the cache instance</li> 11480 * <li>**...**: any additional properties from the options object when creating the 11481 * cache.</li> 11482 * </ul> 11483 */ 11484 info: function() { 11485 return extend({}, stats, {size: size}); 11486 } 11487 }; 11488 11489 11490 /** 11491 * makes the `entry` the freshEnd of the LRU linked list 11492 */ 11493 function refresh(entry) { 11494 if (entry != freshEnd) { 11495 if (!staleEnd) { 11496 staleEnd = entry; 11497 } else if (staleEnd == entry) { 11498 staleEnd = entry.n; 11499 } 11500 11501 link(entry.n, entry.p); 11502 link(entry, freshEnd); 11503 freshEnd = entry; 11504 freshEnd.n = null; 11505 } 11506 } 11507 11508 11509 /** 11510 * bidirectionally links two entries of the LRU linked list 11511 */ 11512 function link(nextEntry, prevEntry) { 11513 if (nextEntry != prevEntry) { 11514 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 11515 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 11516 } 11517 } 11518 } 11519 11520 11521 /** 11522 * @ngdoc method 11523 * @name $cacheFactory#info 11524 * 11525 * @description 11526 * Get information about all the caches that have been created 11527 * 11528 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 11529 */ 11530 cacheFactory.info = function() { 11531 var info = {}; 11532 forEach(caches, function(cache, cacheId) { 11533 info[cacheId] = cache.info(); 11534 }); 11535 return info; 11536 }; 11537 11538 11539 /** 11540 * @ngdoc method 11541 * @name $cacheFactory#get 11542 * 11543 * @description 11544 * Get access to a cache object by the `cacheId` used when it was created. 11545 * 11546 * @param {string} cacheId Name or id of a cache to access. 11547 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 11548 */ 11549 cacheFactory.get = function(cacheId) { 11550 return caches[cacheId]; 11551 }; 11552 11553 11554 return cacheFactory; 11555 }; 11556} 11557 11558/** 11559 * @ngdoc service 11560 * @name $templateCache 11561 * 11562 * @description 11563 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 11564 * can load templates directly into the cache in a `script` tag, or by consuming the 11565 * `$templateCache` service directly. 11566 * 11567 * Adding via the `script` tag: 11568 * 11569 * ```html 11570 * <script type="text/ng-template" id="templateId.html"> 11571 * <p>This is the content of the template</p> 11572 * </script> 11573 * ``` 11574 * 11575 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 11576 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 11577 * element with ng-app attribute), otherwise the template will be ignored. 11578 * 11579 * Adding via the `$templateCache` service: 11580 * 11581 * ```js 11582 * var myApp = angular.module('myApp', []); 11583 * myApp.run(function($templateCache) { 11584 * $templateCache.put('templateId.html', 'This is the content of the template'); 11585 * }); 11586 * ``` 11587 * 11588 * To retrieve the template later, simply use it in your HTML: 11589 * ```html 11590 * <div ng-include=" 'templateId.html' "></div> 11591 * ``` 11592 * 11593 * or get it via Javascript: 11594 * ```js 11595 * $templateCache.get('templateId.html') 11596 * ``` 11597 * 11598 * See {@link ng.$cacheFactory $cacheFactory}. 11599 * 11600 */ 11601function $TemplateCacheProvider() { 11602 this.$get = ['$cacheFactory', function($cacheFactory) { 11603 return $cacheFactory('templates'); 11604 }]; 11605} 11606 11607/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 11608 * Any commits to this file should be reviewed with security in mind. * 11609 * Changes to this file can potentially create security vulnerabilities. * 11610 * An approval from 2 Core members with history of modifying * 11611 * this file is required. * 11612 * * 11613 * Does the change somehow allow for arbitrary javascript to be executed? * 11614 * Or allows for someone to change the prototype of built-in objects? * 11615 * Or gives undesired access to variables likes document or window? * 11616 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 11617
11618/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 11619 * 11620 * DOM-related variables: 11621 * 11622 * - "node" - DOM Node 11623 * - "element" - DOM Element or Node 11624 * - "$node" or "$element" - jqLite-wrapped node or element 11625 * 11626 * 11627 * Compiler related stuff: 11628 * 11629 * - "linkFn" - linking fn of a single directive 11630 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 11631 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 11632 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 11633 */ 11634 11635 11636/** 11637 * @ngdoc service 11638 * @name $compile 11639 * @kind function 11640 * 11641 * @description 11642 * Compiles an HTML string or DOM into a template and produces a template function, which 11643 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 11644 * 11645 * The compilation is a process of walking the DOM tree and matching DOM elements to 11646 * {@link ng.$compileProvider#directive directives}. 11647 * 11648 * <div class="alert alert-warning"> 11649 * **Note:** This document is an in-depth reference of all directive options. 11650 * For a gentle introduction to directives with examples of common use cases, 11651 * see the {@link guide/directive directive guide}. 11652 * </div> 11653 * 11654 * ## Comprehensive Directive API 11655 * 11656 * There are many different options for a directive. 11657 * 11658 * The difference resides in the return value of the factory function. 11659 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 11660 * or just the `postLink` function (all other properties will have the default values). 11661 * 11662 * <div class="alert alert-success"> 11663 * **Best Practice:** It's recommended to use the "directive definition object" form. 11664 * </div> 11665 * 11666 * Here's an example directive declared with a Directive Definition Object: 11667 * 11668 * ```js 11669 * var myModule = angular.module(...); 11670 * 11671 * myModule.directive('directiveName', function factory(injectables) { 11672 * var directiveDefinitionObject = { 11673 * priority: 0, 11674 * template: '<div></div>
11674', // or // function(tElement, tAttrs) { ... }, 11675 * // or 11676 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 11677 * transclude: false, 11678 * restrict: 'A', 11679 * templateNamespace: 'html', 11680 * scope: false, 11681 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 11682 * controllerAs: 'stringIdentifier', 11683 * bindToController: false, 11684 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 11685 * compile: function compile(tElement, tAttrs, transclude) { 11686 * return { 11687 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 11688 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 11689 * } 11690 * // or 11691 * // return function postLink( ... ) { ... } 11692 * }, 11693 * // or 11694 * // link: { 11695 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 11696 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 11697 * // } 11698 * // or 11699 * // link: function postLink( ... ) { ... } 11700 * }; 11701 * return directiveDefinitionObject; 11702 * }); 11703 * ``` 11704 * 11705 * <div class="alert alert-warning"> 11706 * **Note:** Any unspecified options will use the default value. You can see the default values below. 11707 * </div> 11708 * 11709 * Therefore the above can be simplified as: 11710 * 11711 * ```js 11712 * var myModule = angular.module(...); 11713 * 11714 * myModule.directive('directiveName', function factory(injectables) { 11715 * var directiveDefinitionObject = { 11716 * link: function postLink(scope, iElement, iAttrs) { ... } 11717 * }; 11718 * return directiveDefinitionObject; 11719 * // or 11720 * // return function postLink(scope, iElement, iAttrs) { ... } 11721 * }); 11722 * ``` 11723 * 11724 * 11725 * 11726 * ### Directive Definition Object 11727 * 11728 * The directive definition object provides instructions to the {@link ng.$compile 11729 * compiler}. The attributes are: 11730 * 11731 * #### `multiElement` 11732 * When this property is set to true, the HTML compiler will collect DOM nodes between 11733 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 11734 * together as the directive elements. It is recommended that this feature be used on directives 11735 * which are not strictly behavioral (such as {@link ngClick}), and which 11736 * do not manipulate or replace child nodes (such as {@link ngInclude}). 11737 * 11738 * #### `priority` 11739 * When there are multiple directives defined on a single DOM element, sometimes it 11740 * is necessary to specify the order in which the directives are applied. The `priority` is used 11741 * to sort the directives before their `compile` functions get called. Priority is defined as a 11742 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 11743 * are also run in priority order, but post-link functions are run in reverse order. The order 11744 * of directives with the same priority is undefined. The default priority is `0`. 11745 * 11746 * #### `terminal` 11747 * If set to true then the current `priority` will be the last set of directives 11748 * which will execute (any directives at the current priority will still execute 11749 * as the order of execution on same `priority` is undefined). Note that expressions 11750 * and other directives used in the directive's template will also be excluded from execution. 11751 * 11752 * #### `scope` 11753 * The scope property can be `true`, an object or a falsy value: 11754 * 11755 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. 11756 * 11757 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 11758 * the directive's element. If multiple directives on the same element request a new scope, 11759 * only one new scope is created. The new scope rule does not apply for the root of the template 11760 * since the root of the template always gets a new scope. 11761 * 11762 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The 11763 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent 11764 * scope. This is useful when creating reusable components, which should not accidentally read or modify 11765 * data in the parent scope. 11766 * 11767 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 11768 * directive's element. These local properties are useful for aliasing values for templates. The keys in 11769 * the object hash map to the name of the property on the isolate scope; the values define how the property 11770 * is bound to the parent scope, via matching attributes on the directive's element: 11771 * 11772 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
11773 * always a string since DOM attributes are strings. If no `attr` name is specified then the 11774 * attribute name is assumed to be the same as the local name. Given `<my-component 11775 * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`, 11776 * the directive's scope property `localName` will reflect the interpolated value of `hello 11777 * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's 11778 * scope. The `name` is read from the parent scope (not the directive's scope). 11779 * 11780 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression 11781 * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope. 11782 * If no `attr` name is specified then the attribute name is assumed to be the same as the local 11783 * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: { 11784 * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the 11785 * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in 11786 * `localModel` and vice versa. Optional attributes should be marked as such with a question mark: 11787 * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't 11788 * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`}) 11789 * will be thrown upon discovering changes to the local value, since it will be impossible to sync 11790 * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`} 11791 * method is used for tracking changes, and the equality check is based on object identity. 11792 * However, if an object literal or an array literal is passed as the binding expression, the 11793 * equality check is done by value (using the {@link angular.equals} function). It's also possible 11794 * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection 11795 * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional). 11796 * 11797 * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an 11798 * expression passed via the attribute `attr`. The expression is evaluated in the context of the 11799 * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the 11800 * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`. 11801 * 11802 * For example, given `<my-component my-attr="parentModel">` and directive definition of 11803 * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the 11804 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 11805 * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however 11806 * two caveats: 11807 * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply 11808 * sets the same value. That means if your bound value is an object, changes to its properties 11809 * in the isolated scope will be reflected in the parent scope (because both reference the same object). 11810 * 2. one-way binding watches changes to the **identity** of the parent value. That means the 11811 * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference 11812 * to the value has changed. In most cases, this should not be of concern, but can be important 11813 * to know if you one-way bind to an object, and then replace that object in the isolated scope. 11814 * If you now change a property of the object in your parent scope, the change will not be 11815 * propagated to the isolated scope, because the identity of the object on the parent scope 11816 * has not changed. Instead you must assign a new object. 11817 * 11818 * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings 11819 * back to the parent. However, it does not make this completely impossible. 11820 * 11821 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If 11822 * no `attr` name is specified then the attribute name is assumed to be the same as the local name. 11823 * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: { 11824 * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for 11825 * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope 11826 * via an expression to the parent scope. This can be done by passing a map of local variable names 11827 * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` 11828 * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. 11829 * 11830 * In general it's possible to apply more than one directive to one element, but there might be limitations 11831 * depending on the type of scope required by the directives. The following points will help explain these limitations. 11832 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 11833 * 11834 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 11835 * * **child scope** + **no scope** => Both directives will share one single child scope 11836 * * **child scope** + **child scope** => Both directives will share one single child scope 11837 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use 11838 * its parent's scope 11839 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 11840 * be applied to the same element. 11841 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 11842 * cannot be applied to the same element. 11843 * 11844 * 11845 * #### `bindToController` 11846 * This property is used to bind scope properties directly to the controller. It can be either 11847 * `true` or an object hash with the same format as the `scope` property. Additionally, a controller 11848 * alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller 11849 * definition: `controller: 'myCtrl as myAlias'`. 11850 * 11851 * When an isolate scope is used for a directive (see above), `bindToController: true` will 11852 * allow a component to have its properties bound to the controller, rather than to scope. 11853 * 11854 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller 11855 * properties. You can access these bindings once they have been initialized by providing a controller method called 11856 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings 11857 * initialized. 11858 * 11859 * <div class="alert alert-warning"> 11860 * **Deprecation warning:** although bindings for non-ES6 class controllers are currently
11861 * bound to `this` before the controller constructor is called, this use is now deprecated. Please place initialization 11862 * code that relies upon bindings inside a `$onInit` method on the controller, instead. 11863 * </div> 11864 * 11865 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. 11866 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used 11867 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate 11868 * scope (useful for component directives). 11869 * 11870 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. 11871 * 11872 * 11873 * #### `controller` 11874 * Controller constructor function. The controller is instantiated before the 11875 * pre-linking phase and can be accessed by other directives (see 11876 * `require` attribute). This allows the directives to communicate with each other and augment 11877 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 11878 * 11879 * * `$scope` - Current scope associated with the element 11880 * * `$element` - Current element 11881 * * `$attrs` - Current attributes object for the element 11882 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 11883 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: 11884 * * `scope`: (optional) override the scope. 11885 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. 11886 * * `futureParentElement` (optional): 11887 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 11888 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 11889 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 11890 * and when the `cloneLinkinFn` is passed, 11891 * as those elements need to created and cloned in a special way when they are defined outside their 11892 * usual containers (e.g. like `<svg>`). 11893 * * See also the `directive.templateNamespace` property. 11894 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) 11895 * then the default translusion is provided. 11896 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns 11897 * `true` if the specified slot contains content (i.e. one or more DOM nodes). 11898 * 11899 * The controller can provide the following methods that act as life-cycle hooks: 11900 * * `$onInit` - Called on each controller after all the controllers on an element have been constructed and 11901 * had their bindings initialized (and before the pre & post linking functions for the directives on 11902 * this element). This is a good place to put initialization code for your controller. 11903 * 11904 * #### `require` 11905 * Require another directive and inject its controller as the fourth argument to the linking function. The 11906 * `require` property can be a string, an array or an object: 11907 * * a **string** containing the name of the directive to pass to the linking function 11908 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the 11909 * linking function will be an array of controllers in the same order as the names in the `require` property 11910 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument 11911 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding 11912 * controllers. 11913 * 11914 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are 11915 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers 11916 * have been constructed but before `$onInit` is called. 11917 * See the {@link $compileProvider#component} helper for an example of how this can be used. 11918 * 11919 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is 11920 * raised (unless no link function is specified and the required controllers are not being bound to the directive 11921 * controller, in which case error checking is skipped). The name can be prefixed with: 11922 * 11923 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 11924 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 11925 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 11926 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 11927 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 11928 * `null` to the `link` fn if not found. 11929 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 11930 * `null` to the `link` fn if not found. 11931 * 11932 * 11933 * #### `controllerAs` 11934 * Identifier name for a reference to the controller in the directive's scope. 11935 * This allows the controller to be referenced from the directive template. This is especially 11936 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 11937 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 11938 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 11939 * 11940 * 11941 * #### `restrict` 11942 * String of subset of `EACM` which restricts the directive to a specific directive 11943 * declaration style. If omitted, the defaults (elements and attributes) are used. 11944 * 11945 * * `E` - Element name (default): `<my-directive></my-directive>` 11946 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 11947 * * `C` - Class: `<div class="my-directive: exp;"></div>` 11948 * * `M` - Comment: `<!-- directive: my-directive exp -->` 11949 * 11950 * 11951 * #### `templateNamespace` 11952 * String representing the document type used by the markup in the template. 11953 * AngularJS needs this information as those elements need to be created and cloned 11954 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 11955 * 11956 * * `html` - All root nodes in the template are HTML. Root nodes may also be 11957 * top-level elements such as `<svg>` or `<math>`. 11958 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 11959 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 11960 * 11961 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 11962 * 11963 * #### `template` 11964 * HTML markup that may: 11965 * * Replace the contents of the directive's element (default). 11966 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 11967 * * Wrap the contents of the directive's element (if `transclude` is true). 11968 * 11969 * Value may be: 11970 * 11971 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 11972 * * A function which takes two arguments `tElement` and `tAttrs` (described
11972in the `compile` 11973 * function api below) and returns a string value. 11974 * 11975 * 11976 * #### `templateUrl` 11977 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 11978 * 11979 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 11980 * for later when the template has been resolved. In the meantime it will continue to compile and link 11981 * sibling and parent elements as though this element had not contained any directives. 11982 * 11983 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 11984 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 11985 * case when only one deeply nested directive has `templateUrl`. 11986 * 11987 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 11988 * 11989 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 11990 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 11991 * a string value representing the url. In either case, the template URL is passed through {@link 11992 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 11993 * 11994 * 11995 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 11996 * specify what the template should replace. Defaults to `false`. 11997 * 11998 * * `true` - the template will replace the directive's element. 11999 * * `false` - the template will replace the contents of the directive's element. 12000 * 12001 * The replacement process migrates all of the attributes / classes from the old element to the new 12002 * one. See the {@link guide/directive#template-expanding-directive 12003 * Directives Guide} for an example. 12004 * 12005 * There are very few scenarios where element replacement is required for the application function, 12006 * the main one being reusable custom components that are used within SVG contexts 12007 * (because SVG doesn't work with custom elements in the DOM tree). 12008 * 12009 * #### `transclude` 12010 * Extract the contents of the element where the directive appears and make it available to the directive. 12011 * The contents are compiled and provided to the directive as a **transclusion function**. See the 12012 * {@link $compile#transclusion Transclusion} section below. 12013 * 12014 * 12015 * #### `compile` 12016 * 12017 * ```js 12018 * function compile(tElement, tAttrs, transclude) { ... } 12019 * ``` 12020 * 12021 * The compile function deals with transforming the template DOM. Since most directives do not do 12022 * template transformation, it is not used often. The compile function takes the following arguments: 12023 * 12024 * * `tElement` - template element - The element where the directive has been declared. It is 12025 * safe to do template transformation on the element and child elements only. 12026 * 12027 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 12028 * between all directive compile functions. 12029 * 12030 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 12031 * 12032 * <div class="alert alert-warning"> 12033 * **Note:** The template instance and the link instance may be different objects if the template has 12034 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 12035 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 12036 * should be done in a linking function rather than in a compile function. 12037 * </div> 12038 12039 * <div class="alert alert-warning"> 12040 * **Note:** The compile function cannot handle directives that recursively use themselves in their 12041 * own templates or compile functions. Compiling these directives results in an infinite loop and 12042 * stack overflow errors. 12043 * 12044 * This can be avoided by manually using $compile in the postLink function to imperatively compile 12045 * a directive's template instead of relying on automatic template compilation via `template` or 12046 * `templateUrl` declaration or manual compilation inside the compile function. 12047 * </div> 12048 * 12049 * <div class="alert alert-danger"> 12050 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 12051 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 12052 * to the link function instead. 12053 * </div> 12054 12055 * A compile function can have a return value which can be either a function or an object. 12056 * 12057 * * returning a (post-link) function - is equivalent to registering the linking function via the 12058 * `link` property of the config object when the compile function is empty. 12059 * 12060 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 12061 * control when a linking function should be called during the linking phase. See info about 12062 * pre-linking and post-linking functions below. 12063 * 12064 * 12065 * #### `link` 12066 * This property is used only if the `compile` property is not defined. 12067 * 12068 * ```js 12069 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 12070 * ``` 12071 * 12072 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 12073 * executed after the template has been cloned. This is where most of the directive logic will be 12074 * put. 12075 * 12076 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 12077 * directive for registering {@link ng.$rootScope.Scope#$watch w
12077atches}. 12078 * 12079 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 12080 * manipulate the children of the element only in `postLink` function since the children have 12081 * already been linked. 12082 * 12083 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 12084 * between all directive linking functions. 12085 * 12086 * * `controller` - the directive's required controller instance(s) - Instances are shared 12087 * among all directives, which allows the directives to use the controllers as a communication 12088 * channel. The exact value depends on the directive's `require` property: 12089 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 12090 * * `string`: the controller instance 12091 * * `array`: array of controller instances 12092 * 12093 * If a required controller cannot be found, and it is optional, the instance is `null`, 12094 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 12095 * 12096 * Note that you can also require the directive's own controller - it will be made available like 12097 * any other controller. 12098 * 12099 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 12100 * This is the same as the `$transclude` 12101 * parameter of directive controllers, see there for details. 12102 * `function([scope], cloneLinkingFn, futureParentElement)`. 12103 * 12104 * #### Pre-linking function 12105 * 12106 * Executed before the child elements are linked. Not safe to do DOM transformation since the 12107 * compiler linking function will fail to locate the correct elements for linking. 12108 * 12109 * #### Post-linking function 12110 * 12111 * Executed after the child elements are linked. 12112 *
12113 * Note that child elements that contain `templateUrl` directives will not have been compiled 12114 * and linked since they are waiting for their template to load asynchronously and their own 12115 * compilation and linking has been suspended until that occurs. 12116 * 12117 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 12118 * for their async templates to be resolved. 12119 * 12120 * 12121 * ### Transclusion 12122 * 12123 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 12124 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 12125 * scope from where they were taken. 12126 * 12127 * Transclusion is used (often with {@link ngTransclude}) to insert the 12128 * original contents of a directive's element into a specified place in the template of the directive. 12129 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 12130 * content has access to the properties on the scope from which it was taken, even if the directive 12131 * has isolated scope. 12132 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 12133 * 12134 * This makes it possible for the widget to have private state for its template, while the transcluded 12135 * content has access to its originating scope. 12136 * 12137 * <div class="alert alert-warning"> 12138 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 12139 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 12140 * Testing Transclusion Directives}. 12141 * </div> 12142 * 12143 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the 12144 * directive's element, the entire element or multiple parts of the element contents: 12145 * 12146 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 12147 * * `'element'` - transclude the whole of the directive's element including any directives on this 12148 * element that defined at a lower priority than this directive. When used, the `template` 12149 * property is ignored. 12150 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. 12151 * 12152 * **Mult-slot transclusion** is declared by providing an object for the `transclude` property. 12153 * 12154 * This object is a map where the keys are the name of the slot to fill and the value is an element selector 12155 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) 12156 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). 12157 * 12158 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 12159 * 12160 * If the element selector is prefixed with a `?` then that slot is optional. 12161 * 12162 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to 12163 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. 12164 * 12165 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements 12166 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call 12167 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and 12168 * injectable into the directive's controller. 12169 * 12170 * 12171 * #### Transclusion Functions 12172 * 12173 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 12174 * function** to the directive's `link` function and `controller`. This transclusion function is a special 12175 * **linking function** that will return the compiled contents linked to a new transclusion scope. 12176 * 12177 * <div class="alert alert-info"> 12178 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 12179 * ngTransclude will deal with it for us. 12180 * </div> 12181 * 12182 * If you want to manually control the insertion and removal of the transcluded content in your directive 12183 * then you must use this transclude function. When you call a transclude function it returns a a jq
12183Lite/JQuery 12184 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 12185 * 12186 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 12187 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 12188 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 12189 * 12190 * <div class="alert alert-info"> 12191 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function 12192 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 12193 * </div> 12194 * 12195 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 12196 * attach function**: 12197 * 12198 * ```js 12199 * var transcludedContent, transclusionScope; 12200 * 12201 * $transclude(function(clone, scope) { 12202 * element.append(clone); 12203 * transcludedContent = clone; 12204 * transclusionScope = scope; 12205 * }); 12206 * ``` 12207 * 12208 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 12209 * associated transclusion scope: 12210 * 12211 * ```js 12212 * transcludedContent.remove(); 12213 * transclusionScope.$destroy(); 12214 * ``` 12215 * 12216 * <div class="alert alert-info"> 12217 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 12218 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 12219 * then you are also responsible for calling `$destroy` on the transclusion scope. 12220 * </div> 12221 * 12222 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 12223 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if 12224 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 12225 * 12226 * 12227 * #### Transclusion Scopes 12228 * 12229 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 12230 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 12231 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 12232 * was taken. 12233 * 12234 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 12235 * like this: 12236 * 12237 * ```html 12238 * <div ng-app> 12239 * <div isolate> 12240 * <div transclusion> 12241 * </div> 12242 * </div> 12243 * </div> 12244 * ``` 12245 * 12246 * The `$parent` scope hierarchy will look like this: 12247 * 12248 ``` 12249 - $rootScope 12250 - isolate 12251 - transclusion 12252 ``` 12253 * 12254 * but the scopes will inherit prototypically from different scopes to their `$parent`. 12255 * 12256 ``` 12257 - $rootScope 12258 - transclusion 12259 - isolate 12260 ``` 12261 * 12262 * 12263 * ### Attributes 12264 * 12265 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 12266 * `link()` or `compile()` functions. It has a variety of uses. 12267 * 12268 * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways: 12269 * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access 12270 * to the attributes. 12271 * 12272 * * *Directive inter-communication:* All directives share the same instance of the attributes 12273 * object which allows the directives to use the attributes object as inter directive 12274 * communication. 12275 * 12276 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 12277 * allowing other directives to read the interpolated value. 12278 * 12279 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 12280 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 12281 * the only way to easily get the actual value because during the linking phase the interpolation 12282 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 12283 * 12284 * ```js 12285 * function linkingFn(scope, elm, attrs, ctrl) { 12286 * // get the attribute value 12287 * console.log(attrs.ngModel); 12288 * 12289 * // change the attribute 12290 * attrs.$set('ngModel', 'new value'); 12291 * 12292 * // observe changes to interpolated attribute 12293 * attrs.$observe('ngModel', function(value) { 12294 * console.log('ngModel has changed value to ' + value); 12295 * }); 12296 * } 12297 * ``` 12298 * 12299 * ## Example 12300 * 12301 * <div class="alert alert-warning"> 12302 * **Note**: Typically directives are registered with `module.directive`. The example below is 12303 * to illustrate how `$compile` works. 12304 * </div> 12305 * 12306 <example module="compileExample"> 12307 <file name="index.html"> 12308 <script>
12309 angular.module('compileExample', [], function($compileProvider) { 12310 // configure new 'compile' directive by passing a directive 12311 // factory function. The factory function injects the '$compile' 12312 $compileProvider.directive('compile', function($compile) { 12313 // directive factory creates a link function 12314 return function(scope, element, attrs) { 12315 scope.$watch( 12316 function(scope) { 12317 // watch the 'compile' expression for changes 12318 return scope.$eval(attrs.compile); 12319 }, 12320 function(value) { 12321 // when the 'compile' expression changes 12322 // assign it into the current DOM 12323 element.html(value); 12324 12325 // compile the new DOM and link it to the current 12326 // scope. 12327 // NOTE: we only compile .childNodes so that 12328 // we don't get into infinite loop compiling ourselves 12329 $compile(element.contents())(scope); 12330 } 12331 ); 12332 }; 12333 }); 12334 }) 12335 .controller('GreeterController', ['$scope', function($scope) { 12336 $scope.name = 'Angular'; 12337 $scope.html = 'Hello {{name}}'; 12338 }]); 12339 </script> 12340 <div ng-controller="GreeterController"> 12341 <input ng-model="name"> <br/> 12342 <textarea ng-model="html"></textarea> <br/> 12343 <div compile="html"></div> 12344 </div> 12345 </file> 12346 <file name="protractor.js" type="protractor"> 12347 it('should auto compile', function() { 12348 var textarea = $('textarea'); 12349 var output = $('div[compile]'); 12350 // The initial state reads 'Hello Angular'. 12351 expect(output.getText()).toBe('Hello Angular'); 12352 textarea.clear(); 12353 textarea.sendKeys('{{name}}!'); 12354 expect(output.getText()).toBe('Angular!'); 12355 }); 12356 </file> 12357 </example> 12358 12359 * 12360 * 12361 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 12362 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 12363 * 12364 * <div class="alert alert-danger"> 12365 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 12366 * e.g. will not use the right outer scope. Please pass the transclude function as a 12367 * `parentBoundTranscludeFn` to the link function instead. 12368 * </div> 12369 * 12370 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 12371 * root element(s), not their children) 12372 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 12373 * (a DOM element/tree) to a scope. Where: 12374 * 12375 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 12376 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 12377 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 12378 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 12379 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 12380 * 12381 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 12382 * * `scope` - is the current scope with which the linking function is working with. 12383 * 12384 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 12385 * keys may be used to control linking behavior: 12386 * 12387 * * `parentBoundTranscludeFn` - the transclude function made available to 12388 * directives; if given, it will be passed through to the link functions of 12389 * directives found in `element` during compilation. 12390 * * `transcludeControllers` - an object hash with keys that map controller names 12391 * to a hash with the key `instance`, which maps to the controller instance; 12392 * if given, it will make the controllers available to directives on the compileNode: 12393 * ``` 12394 * { 12395 * parent: { 12396 * instance: parentControllerInstance 12397 * } 12398 * } 12399 * ``` 12400 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 12401 * the cloned elements; only needed for transcludes that are allowed to contain non html 12402 * elements (e.g. SVG elements). See also the directive.controller property. 12403 * 12404 * Calling the linking function returns the element of the template. It is either the original 12405 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 12406 * 12407 * After linking the view is not updated until after a call to $digest which typically is done by 12408 * Angular automatically. 12409 * 12410 * If you need access to the bound view, there are two ways to do it: 12411 * 12412 * - If you are not asking the linking function to clone the template, create the DOM element(s) 12413 * before you send them to the compiler and keep this reference around. 12414 * ```js 12415 * var element = $compile('<p>{{total}}</p>')(scope); 12416 * ``` 12417 * 12418 * - if on the other hand, you need the element to be cloned, the view reference from the original 12419 * example would not point to the clone, but rather to the original template that was cloned. In 12420 * this case, you can access the clone via the cloneAttachFn: 12421 * ```js 12422 * var templateElement = angular.element('<p>{{total}}</p>'), 12423 * scope = ....; 12424 * 12425 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 12426 * //attach the clone to DOM document at the right place 12427 * }); 12428 * 12429 * //now we have reference to the cloned DOM via `clonedElement` 12430 * ``` 12431 * 12432 * 12433 * For information on how the compiler works, see the 12434 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 12435 */ 12436 12437var $compileMinErr = minErr('$compile'); 12438 12439/** 12440 * @ngdoc provider 12441 * @name $compileProvider 12442 * 12443 * @description 12444 */
vendor: 4,870 bytes, lines 12445-12568
12445$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 12446function $CompileProvider($provide, $$sanitizeUriProvider) { 12447 var hasDirectives = {}, 12448 Suffix = 'Directive', 12449 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 12450 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 12451 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 12452 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 12453 12454 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 12455 // The assumption is that future DOM event attribute names will begin with 12456 // 'on' and be composed of only English letters. 12457 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 12458 var bindingCache = createMap(); 12459 12460 function parseIsolateBindings(scope, directiveName, isController) { 12461 var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/; 12462 12463 var bindings = {}; 12464 12465 forEach(scope, function(definition, scopeName) { 12466 if (definition in bindingCache) { 12467 bindings[scopeName] = bindingCache[definition]; 12468 return; 12469 } 12470 var match = definition.match(LOCAL_REGEXP); 12471 12472 if (!match) { 12473 throw $compileMinErr('iscp', 12474 "Invalid {3} for directive '{0}'." + 12475 " Definition: {... {1}: '{2}' ...}", 12476 directiveName, scopeName, definition, 12477 (isController ? "controller bindings definition" : 12478 "isolate scope definition")); 12479 } 12480 12481 bindings[scopeName] = { 12482 mode: match[1][0], 12483 collection: match[2] === '*', 12484 optional: match[3] === '?', 12485 attrName: match[4] || scopeName 12486 }; 12487 if (match[4]) { 12488 bindingCache[definition] = bindings[scopeName]; 12489 } 12490 }); 12491 12492 return bindings; 12493 } 12494 12495 function parseDirectiveBindings(directive, directiveName) { 12496 var bindings = { 12497 isolateScope: null, 12498 bindToController: null 12499 }; 12500 if (isObject(directive.scope)) { 12501 if (directive.bindToController === true) { 12502 bindings.bindToController = parseIsolateBindings(directive.scope, 12503 directiveName, true); 12504 bindings.isolateScope = {}; 12505 } else { 12506 bindings.isolateScope = parseIsolateBindings(directive.scope, 12507 directiveName, false); 12508 } 12509 } 12510 if (isObject(directive.bindToController)) { 12511 bindings.bindToController = 12512 parseIsolateBindings(directive.bindToController, directiveName, true); 12513 } 12514 if (isObject(bindings.bindToController)) { 12515 var controller = directive.controller; 12516 var controllerAs = directive.controllerAs; 12517 if (!controller) { 12518 // There is no controller, there may or may not be a controllerAs property 12519 throw $compileMinErr('noctrl', 12520 "Cannot bind to controller without directive '{0}'s controller.", 12521 directiveName); 12522 } else if (!identifierForController(controller, controllerAs)) { 12523 // There is a controller, but no identifier or controllerAs property 12524 throw $compileMinErr('noident', 12525 "Cannot bind to controller without identifier for directive '{0}'.", 12526 directiveName); 12527 } 12528 } 12529 return bindings; 12530 } 12531 12532 function assertValidDirectiveName(name) { 12533 var letter = name.charAt(0); 12534 if (!letter || letter !== lowercase(letter)) { 12535 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name); 12536 } 12537 if (name !== name.trim()) { 12538 throw $compileMinErr('baddir', 12539 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 12540 name); 12541 } 12542 } 12543 12544 /** 12545 * @ngdoc method 12546 * @name $compileProvider#directive 12547 * @kind function 12548 * 12549 * @description 12550 * Register a new directive with the compiler. 12551 * 12552 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 12553 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 12554 * names and the values are the factories. 12555 * @param {Function|Array} directiveFactory An injectable directive factory function. See the 12556 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. 12557 * @returns {ng.$compileProvider} Self for chaining. 12558 */ 12559 this.directive = function registerDirective(name, directiveFactory) { 12560 assertNotHasOwnProperty(name, 'directive'); 12561 if (isString(name)) { 12562 assertValidDirectiveName(name); 12563 assertArg(directiveFactory, 'directiveFactory'); 12564 if (!hasDirectives.hasOwnProperty(name)) { 12565 hasDirectives[name] = []; 12566 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 12567 function($injector, $exceptionHandler) { 12568 var directives = [];
12569 forEach(hasDirectives[name], function(directiveFactory, index) { 12570 try { 12571 var directive = $injector.invoke(directiveFactory); 12572 if (isFunction(directive)) { 12573 directive = { compile: valueFn(directive) }; 12574 } else if (!directive.compile && directive.link) { 12575 directive.compile = valueFn(directive.link); 12576 } 12577 directive.priority = directive.priority || 0; 12578 directive.index = index; 12579 directive.name = directive.name || name; 12580 directive.require = directive.require || (directive.controller && directive.name); 12581 directive.restrict = directive.restrict || 'EA'; 12582 directive.$$moduleName = directiveFactory.$$moduleName; 12583 directives.push(directive); 12584 } catch (e) { 12585 $exceptionHandler(e); 12586 } 12587 }); 12588 return directives; 12589 }]); 12590 } 12591 hasDirectives[name].push(directiveFactory); 12592 } else { 12593 forEach(name, reverseParams(registerDirective)); 12594 } 12595 return this; 12596 }; 12597 12598 /** 12599 * @ngdoc method 12600 * @name $compileProvider#component 12601 * @module ng 12602 * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`) 12603 * @param {Object} options Component definition object (a simplified 12604 * {@link ng.$compile#directive-definition-object directive definition object}), 12605 * with the following properties (all optional): 12606 * 12607 * - `controller` â `{(string|function()=}` â controller constructor function that should be 12608 * associated with newly created scope or the name of a {@link ng.$compile#-controller- 12609 * registered controller} if passed as a string. An empty `noop` function by default. 12610 * - `controllerAs` â `{string=}` â identifier name for to reference the controller in the component's scope. 12611 * If present, the controller will be published to scope under the `controllerAs` name. 12612 * If not present, this will default to be `$ctrl`. 12613 * - `template` â `{string=|function()=}` â html template as a string or a function that 12614 * returns an html template as a string which should be used as the contents of this component. 12615 * Empty string by default. 12616 * 12617 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with 12618 * the following locals: 12619 * 12620 * - `$element` - Current element 12621 * - `$attrs` - Current attributes object for the element 12622 * 12623 * - `templateUrl` â `{string=|function()=}` â path or function that returns a path to an html 12624 * template that should be used as the contents of this component. 12625 * 12626 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with 12627 * the following locals: 12628 * 12629 * - `$element` - Current element 12630 * - `$attrs` - Current attributes object for the element 12631 * 12632 * - `bindings` â `{object=}` â defines bindings between DOM attributes and component properties. 12633 * Component properties are always bound to the component controller and not to the scope. 12634 * See {@link ng.$compile#-bindtocontroller- `bindToController`}. 12635 * - `transclude` â `{boolean=}` â whether {@link $compile#transclusion content transclusion} is enabled. 12636 * Disabled by default. 12637 * - `$...` â additional properties to attach to the directive factory function and the controller 12638 * constructor function. (This is used by the component router to annotate) 12639 * 12640 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. 12641 * @description 12642 * Register a **component definition** with the compiler. This is a shorthand for registering a special 12643 * type of directive, which represents a self-contained UI component in your application. Such components 12644 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). 12645 * 12646 * Component definitions are very simple and do not require as much configuration as defining general 12647 * directives. Component definitions usually consist only of a template and a controller backing it. 12648 * 12649 * In order to make the definition easier, components enforce best practices like use of `controllerAs`, 12650 * `bindToController`. They always have **isolate scope** and are restricted to elements. 12651 * 12652 * Here are a few examples of how you would usually define components: 12653 * 12654 * ```js 12655 * var myMod = angular.module(...); 12656 * myMod.component('myComp', { 12657 * template: '<div>My name is {{$ctrl.name}}</div>', 12658 * controller: function() { 12659 * this.name = 'shahar'; 12660 * } 12661 * }); 12662 * 12663 * myMod.component('myComp', { 12664 * template: '<div>My name is {{$ctrl.name}}</div>', 12665 * bindings: {name: '@'} 12666 * }); 12667 * 12668 * myMod.component('myComp', { 12669 * templateUrl: 'views/my-comp.html', 12670 * controller: 'MyCtrl', 12671 * controllerAs: 'ctrl', 12672 * bindings: {name: '@'} 12673 * }); 12674 * 12675 * ``` 12676 * For more examples, and an in-depth guide, see the {@link guide/component component guide}. 12677 * 12678 * <br /> 12679 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. 12680 */ 12681 this.component = function registerComponent(name, options) { 12682 var controller = options.controller || noop; 12683 12684 function factory($injector) { 12685 function makeInjectable(fn) { 12686 if (isFunction(fn) || isArray(fn)) {
12687 return function(tElement, tAttrs) { 12688 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); 12689 }; 12690 } else { 12691 return fn; 12692 } 12693 } 12694 12695 var template = (!options.template && !options.templateUrl ? '' : options.template); 12696 return { 12697 controller: controller, 12698 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', 12699 template: makeInjectable(template), 12700 templateUrl: makeInjectable(options.templateUrl), 12701 transclude: options.transclude, 12702 scope: {}, 12703 bindToController: options.bindings || {}, 12704 restrict: 'E', 12705 require: options.require 12706 }; 12707 } 12708 12709 // Copy any annotation properties (starting with $) over to the factory function 12710 // These could be used by libraries such as the new component router 12711 forEach(options, function(val, key) { 12712 if (key.charAt(0) === '$') { 12713 factory[key] = val; 12714 controller[key] = val; 12715 } 12716 }); 12717 12718 factory.$inject = ['$injector']; 12719 12720 return this.directive(name, factory); 12721 }; 12722 12723 12724 /** 12725 * @ngdoc method 12726 * @name $compileProvider#aHrefSanitizationWhitelist 12727 * @kind function 12728 * 12729 * @description 12730 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12731 * urls during a[href] sanitization. 12732 * 12733 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 12734 * 12735 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 12736 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 12737 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 12738 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12739 * 12740 * @param {RegExp=} regexp New regexp to whitelist urls with. 12741 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12742 * chaining otherwise. 12743 */ 12744 this.aHrefSanitizationWhitelist = function(regexp) { 12745 if (isDefined(regexp)) { 12746 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 12747 return this; 12748 } else { 12749 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 12750 } 12751 }; 12752 12753 12754 /** 12755 * @ngdoc method 12756 * @name $compileProvider#imgSrcSanitizationWhitelist 12757 * @kind function 12758 * 12759 * @description 12760 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12761 * urls during img[src] sanitization. 12762 * 12763 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12764 * 12765 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 12766 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 12767 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 12768 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12769 * 12770 * @param {RegExp=} regexp New regexp to whitelist urls with. 12771 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12772 * chaining otherwise. 12773 */ 12774 this.imgSrcSanitizationWhitelist = function(regexp) { 12775 if (isDefined(regexp)) { 12776 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 12777 return this; 12778 } else { 12779 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 12780 } 12781 }; 12782 12783 /** 12784 * @ngdoc method 12785 * @name $compileProvider#debugInfoEnabled 12786 * 12787 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 12788 * current debugInfoEnabled state 12789 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12790 * 12791 * @kind function 12792 * 12793 * @description 12794 * Call this method to enable/disable various debug runtime information in the compiler such as adding 12795 * binding information and a reference to the current scope on to DOM elements. 12796 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 12797 * * `ng-binding` CSS class 12798 * * `$binding` data property containing an array of the binding expressions 12799 * 12800 * You may want to disable this in production for a significant performance boost. See 12801 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 12802 * 12803 * The default value is true. 12804 */ 12805 var debugInfoEnabled = true; 12806 this.debugInfoEnabled = function(enabled) { 12807 if (isDefined(enabled)) { 12808 debugInfoEnabled = enabled; 12809 return this; 12810 } 12811 return debugInfoEnabled; 12812 }; 12813 12814 this.$get = [ 12815 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 12816 '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', 12817 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 12818 $controller, $rootScope, $sce, $animate, $$sanitizeUri) { 12819 12820 var SIMPLE_ATTR_NAME = /^\w/; 12821 var specialAttrHolder = document.createElement('div'); 12822 var Attributes = function(element, attributesToCopy) { 12823 if (attributesToCopy) { 12824 var keys = Object.keys(attributesToCopy); 12825 var i, l, key; 12826
12827 for (i = 0, l = keys.length; i < l; i++) { 12828 key = keys[i]; 12829 this[key] = attributesToCopy[key]; 12830 } 12831 } else { 12832 this.$attr = {}; 12833 } 12834 12835 this.$$element = element; 12836 }; 12837 12838 Attributes.prototype = { 12839 /** 12840 * @ngdoc method 12841 * @name $compile.directive.Attributes#$normalize 12842 * @kind function 12843 * 12844 * @description 12845 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 12846 * `data-`) to its normalized, camelCase form. 12847 * 12848 * Also there is special case for Moz prefix starting with upper case letter. 12849 * 12850 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 12851 * 12852 * @param {string} name Name to normalize 12853 */ 12854 $normalize: directiveNormalize, 12855 12856 12857 /** 12858 * @ngdoc method 12859 * @name $compile.directive.Attributes#$addClass 12860 * @kind function 12861 * 12862 * @description 12863 * Adds the CSS class value specified by the classVal parameter to the element. If animations 12864 * are enabled then an animation will be triggered for the class addition. 12865 * 12866 * @param {string} classVal The className value that will be added to the element 12867 */ 12868 $addClass: function(classVal) { 12869 if (classVal && classVal.length > 0) { 12870 $animate.addClass(this.$$element, classVal); 12871 } 12872 }, 12873 12874 /** 12875 * @ngdoc method 12876 * @name $compile.directive.Attributes#$removeClass 12877 * @kind function 12878 * 12879 * @description 12880 * Removes the CSS class value specified by the classVal parameter from the element. If 12881 * animations are enabled then an animation will be triggered for the class removal. 12882 * 12883 * @param {string} classVal The className value that will be removed from the element 12884 */ 12885 $removeClass: function(classVal) { 12886 if (classVal && classVal.length > 0) { 12887 $animate.removeClass(this.$$element, classVal); 12888 } 12889 }, 12890 12891 /** 12892 * @ngdoc method 12893 * @name $compile.directive.Attributes#$updateClass 12894 * @kind function 12895 * 12896 * @description 12897 * Adds and removes the appropriate CSS class values to the element based on the difference 12898 * between the new and old CSS class values (specified as newClasses and oldClasses). 12899 * 12900 * @param {string} newClasses The current CSS className value 12901 * @param {string} oldClasses The former CSS className value 12902 */ 12903 $updateClass: function(newClasses, oldClasses) { 12904 var toAdd = tokenDifference(newClasses, oldClasses); 12905 if (toAdd && toAdd.length) { 12906 $animate.addClass(this.$$element, toAdd); 12907 } 12908 12909 var toRemove = tokenDifference(oldClasses, newClasses); 12910 if (toRemove && toRemove.length) { 12911 $animate.removeClass(this.$$element, toRemove); 12912 } 12913 }, 12914 12915 /** 12916 * Set a normalized attribute on the element in a way such that all directives 12917 * can share the attribute. This function properly handles boolean attributes. 12918 * @param {string} key Normalized key. (ie ngAttribute) 12919 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 12920 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 12921 * Defaults to true. 12922 * @param {string=} attrName Optional none normalized name. Defaults to key. 12923 */ 12924 $set: function(key, value, writeAttr, attrName) { 12925 // TODO: decide whether or not to throw an error if "class" 12926 //is set through this function since it may cause $updateClass to 12927 //become unstable. 12928 12929 var node = this.$$element[0], 12930 booleanKey = getBooleanAttrName(node, key), 12931 aliasedKey = getAliasedAttrName(key), 12932 observer = key, 12933 nodeName; 12934 12935 if (booleanKey) { 12936 this.$$element.prop(key, value); 12937 attrName = booleanKey; 12938 } else if (aliasedKey) { 12939 this[aliasedKey] = value; 12940 observer = aliasedKey; 12941 } 12942 12943 this[key] = value; 12944 12945 // translate normalized key to actual key 12946 if (attrName) { 12947 this.$attr[key] = attrName; 12948 } else { 12949 attrName = this.$attr[key]; 12950 if (!attrName) { 12951 this.$attr[key] = attrName = snake_case(key, '-'); 12952 } 12953 } 12954 12955 nodeName = nodeName_(this.$$element); 12956 12957 if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || 12958 (nodeName === 'img' && key === 'src')) { 12959 // sanitize a[href] and img[src] values 12960 this[key] = value = $$sanitizeUri(value, key === 'src'); 12961 } else if (nodeName === 'img' && key === 'srcset') { 12962 // sanitize img[srcset] values 12963 var result = ""; 12964
12965 // first check if there are spaces because it's not the same pattern 12966 var trimmedSrcset = trim(value); 12967 // ( 999x ,| 999w ,| ,|, ) 12968 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 12969 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 12970 12971 // split srcset into tuple of uri and descriptor except for the last item 12972 var rawUris = trimmedSrcset.split(pattern); 12973 12974 // for each tuples 12975 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 12976 for (var i = 0; i < nbrUrisWith2parts; i++) { 12977 var innerIdx = i * 2; 12978 // sanitize the uri 12979 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 12980 // add the descriptor 12981 result += (" " + trim(rawUris[innerIdx + 1])); 12982 } 12983 12984 // split the last item into uri and descriptor 12985 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 12986 12987 // sanitize the last uri 12988 result += $$sanitizeUri(trim(lastTuple[0]), true); 12989 12990 // and add the last descriptor if any 12991 if (lastTuple.length === 2) { 12992 result += (" " + trim(lastTuple[1])); 12993 } 12994 this[key] = value = result; 12995 } 12996 12997 if (writeAttr !== false) { 12998 if (value === null || isUndefined(value)) { 12999 this.$$element.removeAttr(attrName); 13000 } else { 13001 if (SIMPLE_ATTR_NAME.test(attrName)) { 13002 this.$$element.attr(attrName, value); 13003 } else { 13004 setSpecialAttr(this.$$element[0], attrName, value); 13005 } 13006 } 13007 } 13008 13009 // fire observers 13010 var $$observers = this.$$observers; 13011 $$observers && forEach($$observers[observer], function(fn) { 13012 try { 13013 fn(value); 13014 } catch (e) { 13015 $exceptionHandler(e); 13016 } 13017 }); 13018 }, 13019 13020 13021 /** 13022 * @ngdoc method 13023 * @name $compile.directive.Attributes#$observe 13024 * @kind function 13025 * 13026 * @description 13027 * Observes an interpolated attribute. 13028 * 13029 * The observer function will be invoked once during the next `$digest` following 13030 * compilation. The observer is then invoked whenever the interpolated value 13031 * changes. 13032 * 13033 * @param {string} key Normalized key. (ie ngAttribute) . 13034 * @param {function(interpolatedValue)} fn Function that will be called whenever 13035 the interpolated value of the attribute changes. 13036 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation 13037 * guide} for more info. 13038 * @returns {function()} Returns a deregistration function for this observer. 13039 */ 13040 $observe: function(key, fn) { 13041 var attrs = this, 13042 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 13043 listeners = ($$observers[key] || ($$observers[key] = [])); 13044 13045 listeners.push(fn); 13046 $rootScope.$evalAsync(function() { 13047 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 13048 // no one registered attribute interpolation function, so lets call it manually 13049 fn(attrs[key]); 13050 } 13051 }); 13052 13053 return function() { 13054 arrayRemove(listeners, fn); 13055 }; 13056 } 13057 }; 13058 13059 function setSpecialAttr(element, attrName, value) { 13060 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` 13061 // so we have to jump through some hoops to get such an attribute 13062 // https://github.com/angular/angular.js/pull/13318 13063 specialAttrHolder.innerHTML = "<span " + attrName + ">"; 13064 var attributes = specialAttrHolder.firstChild.attributes; 13065 var attribute = attributes[0]; 13066 // We have to remove the attribute from its container element before we can add it to the destination element 13067 attributes.removeNamedItem(attribute.name); 13068 attribute.value = value; 13069 element.attributes.setNamedItem(attribute); 13070 } 13071 13072 function safeAddClass($element, className) {
vendor: 12,676 bytes, lines 13073-13379
13073 try { 13074 $element.addClass(className); 13075 } catch (e) { 13076 // ignore, since it means that we are trying to set class on 13077 // SVG element, where class name is read-only. 13078 } 13079 } 13080 13081 13082 var startSymbol = $interpolate.startSymbol(), 13083 endSymbol = $interpolate.endSymbol(), 13084 denormalizeTemplate = (startSymbol == '{{' && endSymbol == '}}') 13085 ? identity 13086 : function denormalizeTemplate(template) { 13087 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 13088 }, 13089 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 13090 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; 13091 13092 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 13093 var bindings = $element.data('$binding') || []; 13094 13095 if (isArray(binding)) { 13096 bindings = bindings.concat(binding); 13097 } else { 13098 bindings.push(binding); 13099 } 13100 13101 $element.data('$binding', bindings); 13102 } : noop; 13103 13104 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 13105 safeAddClass($element, 'ng-binding'); 13106 } : noop; 13107 13108 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 13109 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 13110 $element.data(dataName, scope); 13111 } : noop; 13112 13113 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 13114 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 13115 } : noop; 13116 13117 compile.$$createComment = function(directiveName, comment) { 13118 var content = ''; 13119 if (debugInfoEnabled) { 13120 content = ' ' + (directiveName || '') + ': ' + (comment || '') + ' '; 13121 } 13122 return document.createComment(content); 13123 }; 13124 13125 return compile; 13126 13127 //================================ 13128 13129 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 13130 previousCompileContext) { 13131 if (!($compileNodes instanceof jqLite)) { 13132 // jquery always rewraps, whereas we need to preserve the original selector so that we can 13133 // modify it. 13134 $compileNodes = jqLite($compileNodes); 13135 } 13136 var compositeLinkFn = 13137 compileNodes($compileNodes, transcludeFn, $compileNodes, 13138 maxPriority, ignoreDirective, previousCompileContext); 13139 compile.$$addScopeClass($compileNodes); 13140 var namespace = null; 13141 return function publicLinkFn(scope, cloneConnectFn, options) { 13142 assertArg(scope, 'scope'); 13143 13144 if (previousCompileContext && previousCompileContext.needsNewScope) { 13145 // A parent directive did a replace and a directive on this element asked 13146 // for transclusion, which caused us to lose a layer of element on which 13147 // we could hold the new transclusion scope, so we will create it manually 13148 // here. 13149 scope = scope.$parent.$new(); 13150 } 13151 13152 options = options || {}; 13153 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 13154 transcludeControllers = options.transcludeControllers, 13155 futureParentElement = options.futureParentElement; 13156 13157 // When `parentBoundTranscludeFn` is passed, it is a 13158 // `controllersBoundTransclude` function (it was previously passed 13159 // as `transclude` to directive.link) so we must unwrap it to get 13160 // its `boundTranscludeFn` 13161 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 13162 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 13163 } 13164 13165 if (!namespace) { 13166 namespace = detectNamespaceForChildElements(futureParentElement); 13167 } 13168 var $linkNode; 13169 if (namespace !== 'html') { 13170 // When using a directive with replace:true and templateUrl the $compileNodes 13171 // (or a child element inside of them) 13172 // might change, so we need to recreate the namespace adapted compileNodes 13173 // for call to the link function. 13174 // Note: This will already clone the nodes... 13175 $linkNode = jqLite( 13176 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 13177 ); 13178 } else if (cloneConnectFn) { 13179 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 13180 // and sometimes changes the structure of the DOM. 13181 $linkNode = JQLitePrototype.clone.call($compileNodes); 13182 } else { 13183 $linkNode = $compileNodes; 13184 } 13185 13186 if (transcludeControllers) { 13187 for (var controllerName in transcludeControllers) { 13188 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 13189 } 13190 } 13191 13192 compile.$$addScopeInfo($linkNode, scope); 13193 13194 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 13195 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 13196 return $linkNode; 13197 }; 13198 } 13199 13200 function detectNamespaceForChildElements(parentElement) { 13201 // TODO: Make this detect MathML as well... 13202 var node = parentElement && parentElement[0]; 13203 if (!node) { 13204 return 'html'; 13205 } else { 13206 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; 13207 } 13208 } 13209 13210 /** 13211 * Compile function matches each node in nodeList against the directives. Once all directives 13212 * for a particular node are collected their compile functions are executed. The compile 13213 * functions return values - the linking functions - are combined into a composite linking 13214 * function, which is the a linking function for the node. 13215 * 13216 * @param {NodeList} nodeList an array of nodes or NodeList to compile 13217 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 13218 * scope argument is auto-generated to the new child of the transcluded parent scope. 13219 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 13220 * the rootElement must be set the jqLite collection of the compile root. This is 13221 * needed so that the jqLite collection items can be replaced with widgets. 13222 * @param {number=} maxPriority Max directive priority. 13223 * @returns {Function} A composite linking function of all of the matched directives or null. 13224 */ 13225 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 13226 previousCompileContext) { 13227 var linkFns = [], 13228 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 13229 13230 for (var i = 0; i < nodeList.length; i++) { 13231 attrs = new Attributes(); 13232 13233 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 13234 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 13235 ignoreDirective); 13236 13237 nodeLinkFn = (directives.length) 13238 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 13239 null, [], [], previousCompileContext) 13240 : null; 13241 13242 if (nodeLinkFn && nodeLinkFn.scope) { 13243 compile.$$addScopeClass(attrs.$$element); 13244 } 13245 13246 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 13247 !(childNodes = nodeList[i].childNodes) || 13248 !childNodes.length) 13249 ? null 13250 : compileNodes(childNodes, 13251 nodeLinkFn ? ( 13252 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 13253 && nodeLinkFn.transclude) : transcludeFn); 13254 13255 if (nodeLinkFn || childLinkFn) { 13256 linkFns.push(i, nodeLinkFn, childLinkFn); 13257 linkFnFound = true; 13258 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 13259 } 13260 13261 //use the previous context only for the first element in the virtual group 13262 previousCompileContext = null; 13263 } 13264 13265 // return a linking function if we have found anything, null otherwise 13266 return linkFnFound ? compositeLinkFn : null; 13267 13268 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 13269 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 13270 var stableNodeList; 13271 13272 13273 if (nodeLinkFnFound) { 13274 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 13275 // offsets don't get screwed up 13276 var nodeListLength = nodeList.length; 13277 stableNodeList = new Array(nodeListLength); 13278 13279 // create a sparse array by only copying the elements which have a linkFn 13280 for (i = 0; i < linkFns.length; i+=3) { 13281 idx = linkFns[i]; 13282 stableNodeList[idx] = nodeList[idx]; 13283 } 13284 } else { 13285 stableNodeList = nodeList; 13286 } 13287 13288 for (i = 0, ii = linkFns.length; i < ii;) { 13289 node = stableNodeList[linkFns[i++]]; 13290 nodeLinkFn = linkFns[i++]; 13291 childLinkFn = linkFns[i++]; 13292 13293 if (nodeLinkFn) { 13294 if (nodeLinkFn.scope) { 13295 childScope = scope.$new(); 13296 compile.$$addScopeInfo(jqLite(node), childScope); 13297 } else { 13298 childScope = scope; 13299 } 13300 13301 if (nodeLinkFn.transcludeOnThisElement) { 13302 childBoundTranscludeFn = createBoundTranscludeFn( 13303 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 13304 13305 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 13306 childBoundTranscludeFn = parentBoundTranscludeFn; 13307 13308 } else if (!parentBoundTranscludeFn && transcludeFn) { 13309 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 13310 13311 } else { 13312 childBoundTranscludeFn = null; 13313 } 13314 13315 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 13316 13317 } else if (childLinkFn) { 13318 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 13319 } 13320 } 13321 } 13322 } 13323 13324 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 13325 13326 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 13327 13328 if (!transcludedScope) { 13329 transcludedScope = scope.$new(false, containingScope); 13330 transcludedScope.$$transcluded = true; 13331 } 13332 13333 return transcludeFn(transcludedScope, cloneFn, { 13334 parentBoundTranscludeFn: previousBoundTranscludeFn, 13335 transcludeControllers: controllers, 13336 futureParentElement: futureParentElement 13337 }); 13338 }; 13339 13340 // We need to attach the transclusion slots onto the `boundTranscludeFn` 13341 // so that they are available inside the `controllersBoundTransclude` function 13342 var boundSlots = boundTranscludeFn.$$slots = createMap(); 13343 for (var slotName in transcludeFn.$$slots) { 13344 if (transcludeFn.$$slots[slotName]) { 13345 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); 13346 } else { 13347 boundSlots[slotName] = null; 13348 } 13349 } 13350 13351 return boundTranscludeFn; 13352 } 13353 13354 /** 13355 * Looks for directives on the given node and adds them to the directive collection which is 13356 * sorted. 13357 * 13358 * @param node Node to search. 13359 * @param directives An array to which the directives are added to. This array is sorted before 13360 * the function returns. 13361 * @param attrs The shared attrs object which is used to populate the normalized attributes. 13362 * @param {number=} maxPriority Max directive priority. 13363 */ 13364 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 13365 var nodeType = node.nodeType, 13366 attrsMap = attrs.$attr, 13367 match, 13368 className; 13369 13370 switch (nodeType) { 13371 case NODE_TYPE_ELEMENT: /* Element */ 13372 // use the node name: <directive> 13373 addDirective(directives, 13374 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 13375 13376 // iterate over the attributes 13377 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 13378 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 13379 var attrStartName = false;
13380 var attrEndName = false; 13381 13382 attr = nAttrs[j]; 13383 name = attr.name; 13384 value = trim(attr.value); 13385 13386 // support ngAttr attribute binding 13387 ngAttrName = directiveNormalize(name); 13388 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 13389 name = name.replace(PREFIX_REGEXP, '') 13390 .substr(8).replace(/_(.)/g, function(match, letter) { 13391 return letter.toUpperCase(); 13392 }); 13393 } 13394 13395 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); 13396 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { 13397 attrStartName = name; 13398 attrEndName = name.substr(0, name.length - 5) + 'end'; 13399 name = name.substr(0, name.length - 6); 13400 } 13401 13402 nName = directiveNormalize(name.toLowerCase()); 13403 attrsMap[nName] = name; 13404 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 13405 attrs[nName] = value; 13406 if (getBooleanAttrName(node, nName)) { 13407 attrs[nName] = true; // presence means true 13408 } 13409 } 13410 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 13411 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 13412 attrEndName); 13413 } 13414 13415 // use class as directive 13416 className = node.className; 13417 if (isObject(className)) { 13418 // Maybe SVGAnimatedString 13419 className = className.animVal; 13420 } 13421 if (isString(className) && className !== '') { 13422 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 13423 nName = directiveNormalize(match[2]); 13424 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 13425 attrs[nName] = trim(match[3]); 13426 } 13427 className = className.substr(match.index + match[0].length); 13428 } 13429 } 13430 break; 13431 case NODE_TYPE_TEXT: /* Text Node */ 13432 if (msie === 11) { 13433 // Workaround for #11781 13434 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 13435 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 13436 node.parentNode.removeChild(node.nextSibling); 13437 } 13438 } 13439 addTextInterpolateDirective(directives, node.nodeValue); 13440 break; 13441 case NODE_TYPE_COMMENT: /* Comment */ 13442 try { 13443 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 13444 if (match) { 13445 nName = directiveNormalize(match[1]); 13446 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 13447 attrs[nName] = trim(match[2]); 13448 } 13449 } 13450 } catch (e) { 13451 // turns out that under some circumstances IE9 throws errors when one attempts to read 13452 // comment's node value. 13453 // Just ignore it and continue. (Can't seem to reproduce in test case.) 13454 } 13455 break; 13456 } 13457 13458 directives.sort(byPriority); 13459 return directives; 13460 } 13461 13462 /** 13463 * Given a node with an directive-start it collects all of the siblings until it finds 13464 * directive-end. 13465 * @param node 13466 * @param attrStart 13467 * @param attrEnd 13468 * @returns {*} 13469 */ 13470 function groupScan(node, attrStart, attrEnd) { 13471 var nodes = []; 13472 var depth = 0; 13473 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 13474 do { 13475 if (!node) { 13476 throw $compileMinErr('uterdir', 13477 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 13478 attrStart, attrEnd); 13479 } 13480 if (node.nodeType == NODE_TYPE_ELEMENT) { 13481 if (node.hasAttribute(attrStart)) depth++; 13482 if (node.hasAttribute(attrEnd)) depth--; 13483 } 13484 nodes.push(node); 13485 node = node.nextSibling; 13486 } while (depth > 0); 13487 } else { 13488 nodes.push(node); 13489 } 13490 13491 return jqLite(nodes); 13492 } 13493 13494 /** 13495 * Wrapper for linking function which converts normal linking function into a grouped 13496 * linking function. 13497 * @param linkFn 13498 * @param attrStart 13499 * @param attrEnd 13500 * @returns {Function} 13501 */ 13502 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 13503 return function(scope, element, attrs, controllers, transcludeFn) { 13504 element = groupScan(element[0], attrStart, attrEnd); 13505 return linkFn(scope, element, attrs, controllers, transcludeFn); 13506 }; 13507 } 13508 13509 /** 13510 * A function generator that is used to support both eager and lazy compilation 13511 * linking function. 13512 * @param eager 13513 * @param $compileNodes 13514 * @param transcludeFn 13515 * @param maxPriority 13516 * @param ignoreDirective 13517 * @param previousCompileContext 13518 * @returns {Function} 13519 */ 13520 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { 13521 var compiled; 13522 13523 if (eager) { 13524 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 13525 } 13526 return function() { 13527 if (!compiled) { 13528 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 13529 13530 // Null out all of these references in order to make them eligible for garbage collection 13531 // since this is a potentially long lived closure 13532 $compileNodes = transcludeFn = previousCompileContext = null; 13533 } 13534 return compiled.apply(this, arguments); 13535 }; 13536 } 13537 13538 /** 13539 * Once the directives have been collected, their compile functions are executed. This method 13540 * is responsible for inlining directive templates as well as terminating the application 13541 * of the directives if the terminal directive has been reached. 13542 * 13543 * @param {Array} directives Array of collected directives to execute their compile function. 13544 * this needs to be pre-sorted by priority order. 13545 * @param {Node} compileNode The raw DOM node to apply the compile functions to 13546 * @param {Object} templateAttrs The shared attribute function 13547 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 13548 * scope argument is auto-generated to the new 13549 * child of the transcluded parent scope. 13550 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 13551 * argument has the root jqLite array so that we can replace nodes 13552 * on it. 13553 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 13554 * compiling the transclusion. 13555 * @param {Array.<Function>} preLinkFns 13556 * @param {Array.<Function>} postLinkFns 13557 * @param {Object} previousCompileContext Context used for previous compilation of the current 13558 * node 13559 * @returns {Function} linkFn 13560 */ 13561 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 13562 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 13563 previousCompileContext) { 13564 previousCompileContext = previousCompileContext || {}; 13565 13566 var terminalPriority = -Number.MAX_VALUE, 13567 newScopeDirective = previousCompileContext.newScopeDirective, 13568 controllerDirectives = previousCompileContext.controllerDirectives, 13569 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 13570 templateDirective = previousCompileContext.templateDirective, 13571 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 13572 hasTranscludeDirective = false, 13573 hasTemplate = false, 13574 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 13575 $compileNode = templateAttrs.$$element = jqLite(compileNode), 13576 directive, 13577 directiveName, 13578 $template, 13579 replaceDirective = originalReplaceDirective, 13580 childTranscludeFn = transcludeFn, 13581 linkFn, 13582 didScanForMultipleTransclusion = false, 13583 mightHaveMultipleTransclusionError = false, 13584 directiveValue; 13585 13586 // executes all directives on the current element 13587 for (var i = 0, ii = directives.length; i < ii; i++) { 13588 directive = directives[i]; 13589 var attrStart = directive.$$start; 13590 var attrEnd = directive.$$end; 13591 13592 // collect multiblock sections 13593 if (attrStart) { 13594 $compileNode = groupScan(compileNode, attrStart, attrEnd); 13595 } 13596 $template = undefined; 13597 13598 if (terminalPriority > directive.priority) { 13599 break; // prevent further processing of directives 13600 } 13601 13602 if (directiveValue = directive.scope) { 13603 13604 // skip the check for directives with async templates, we'll check the derived sync 13605 // directive when the template arrives 13606 if (!directive.templateUrl) { 13607 if (isObject(directiveValue)) { 13608 // This directive is trying to add an isolated scope. 13609 // Check that there is no scope of any kind already 13610 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 13611 directive, $compileNode); 13612 newIsolateScopeDirective = directive; 13613 } else { 13614 // This directive is trying to add a child scope. 13615 // Check that there is no isolated scope already 13616 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 13617 $compileNode); 13618 } 13619 } 13620 13621 newScopeDirective = newScopeDirective || directive; 13622 } 13623 13624 directiveName = directive.name; 13625 13626 // If we encounter a condition that can result in transclusion on the directive, 13627 // then scan ahead in the remaining directives for others that may cause a multiple 13628 // transclusion error to be thrown during the compilation process. If a matching directive 13629 // is found, then we know that when we encounter a transcluded directive, we need to eagerly 13630 // compile the `transclude` function rather than doing it lazily in order to throw 13631 // exceptions at the correct time 13632 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) 13633 || (directive.transclude && !directive.$$tlb))) { 13634 var candidateDirective; 13635 13636 for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) { 13637 if ((candidateDirective.transclude && !candidateDirective.$$tlb) 13638 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { 13639 mightHaveMultipleTransclusionError = true; 13640 break; 13641 } 13642 } 13643 13644 didScanForMultipleTransclusion = true; 13645 } 13646 13647 if (!directive.templateUrl && directive.controller) { 13648 directiveValue = directive.controller; 13649 controllerDirectives = controllerDirectives || createMap(); 13650 assertNoDuplicate("'" + directiveName + "' controller", 13651 controllerDirectives[directiveName], directive, $compileNode); 13652 controllerDirectives[directiveName] = directive; 13653 } 13654 13655 if (directiveValue = directive.transclude) { 13656 hasTranscludeDirective = true; 13657 13658 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 13659 // This option should only be used by directives that know how to safely handle element transclusion, 13660 // where the transcluded nodes are added or replaced after linking. 13661 if (!directive.$$tlb) { 13662 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 13663 nonTlbTranscludeDirective = directive; 13664 } 13665 13666 if (directiveValue == 'element') { 13667 hasElementTranscludeDirective = true; 13668 terminalPriority = directive.priority; 13669 $template = $compileNode; 13670 $compileNode = templateAttrs.$$element = 13671 jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); 13672 compileNode = $compileNode[0]; 13673 replaceWith(jqCollection, sliceArgs($template), compileNode); 13674 13675 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, 13676 replaceDirective && replaceDirective.name, { 13677 // Don't pass in: 13678 // - controllerDirectives - otherwise we'll create duplicates controllers 13679 // - newIsolateScopeDirective or templateDirective - comb
vendor: 22,091 bytes, lines 13679-14183
13679ining templates with 13680 // element transclusion doesn't make sense. 13681 // 13682 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 13683 // on the same element more than once. 13684 nonTlbTranscludeDirective: nonTlbTranscludeDirective 13685 }); 13686 } else { 13687 13688 var slots = createMap(); 13689 13690 $template = jqLite(jqLiteClone(compileNode)).contents(); 13691 13692 if (isObject(directiveValue)) { 13693 13694 // We have transclusion slots, 13695 // collect them up, compile them and store their transclusion functions 13696 $template = []; 13697 13698 var slotMap = createMap(); 13699 var filledSlots = createMap(); 13700 13701 // Parse the element selectors 13702 forEach(directiveValue, function(elementSelector, slotName) { 13703 // If an element selector starts with a ? then it is optional 13704 var optional = (elementSelector.charAt(0) === '?'); 13705 elementSelector = optional ? elementSelector.substring(1) : elementSelector; 13706 13707 slotMap[elementSelector] = slotName; 13708 13709 // We explicitly assign `null` since this implies that a slot was defined but not filled. 13710 // Later when calling boundTransclusion functions with a slot name we only error if the 13711 // slot is `undefined` 13712 slots[slotName] = null; 13713 13714 // filledSlots contains `true` for all slots that are either optional or have been 13715 // filled. This is used to check that we have not missed any required slots 13716 filledSlots[slotName] = optional; 13717 }); 13718 13719 // Add the matching elements into their slot 13720 forEach($compileNode.contents(), function(node) { 13721 var slotName = slotMap[directiveNormalize(nodeName_(node))]; 13722 if (slotName) { 13723 filledSlots[slotName] = true; 13724 slots[slotName] = slots[slotName] || []; 13725 slots[slotName].push(node); 13726 } else { 13727 $template.push(node); 13728 } 13729 }); 13730 13731 // Check for required slots that were not filled 13732 forEach(filledSlots, function(filled, slotName) { 13733 if (!filled) { 13734 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); 13735 } 13736 }); 13737 13738 for (var slotName in slots) { 13739 if (slots[slotName]) { 13740 // Only define a transclusion function if the slot was filled 13741 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); 13742 } 13743 } 13744 } 13745 13746 $compileNode.empty(); // clear contents 13747 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, 13748 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); 13749 childTranscludeFn.$$slots = slots; 13750 } 13751 } 13752 13753 if (directive.template) { 13754 hasTemplate = true; 13755 assertNoDuplicate('template', templateDirective, directive, $compileNode); 13756 templateDirective = directive; 13757 13758 directiveValue = (isFunction(directive.template)) 13759 ? directive.template($compileNode, templateAttrs) 13760 : directive.template; 13761 13762 directiveValue = denormalizeTemplate(directiveValue); 13763 13764 if (directive.replace) { 13765 replaceDirective = directive; 13766 if (jqLiteIsTextNode(directiveValue)) { 13767 $template = []; 13768 } else { 13769 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 13770 } 13771 compileNode = $template[0]; 13772 13773 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 13774 throw $compileMinErr('tplrt', 13775 "Template for directive '{0}' must have exactly one root element. {1}", 13776 directiveName, ''); 13777 } 13778 13779 replaceWith(jqCollection, $compileNode, compileNode); 13780 13781 var newTemplateAttrs = {$attr: {}}; 13782 13783 // combine directives from the original node and from the template: 13784 // - take the array of directives for this element 13785 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 13786 // - collect directives from the template and sort them by priority 13787 // - combine directives as: processed + template + unprocessed 13788 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 13789 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 13790 13791 if (newIsolateScopeDirective || newScopeDirective) { 13792 // The original directive caused the current element to be replaced but this element 13793 // also needs to have a new scope, so we need to tell the template directives 13794 // that they would need to get their scope from further up, if they require transclusion 13795 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); 13796 } 13797 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 13798 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 13799 13800 ii = directives.length; 13801 } else { 13802 $compileNode.html(directiveValue); 13803 } 13804 } 13805 13806 if (directive.templateUrl) { 13807 hasTemplate = true; 13808 assertNoDuplicate('template', templateDirective, directive, $compileNode); 13809 templateDirective = directive; 13810 13811 if (directive.replace) { 13812 replaceDirective = directive; 13813 } 13814 13815 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 13816 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 13817 controllerDirectives: controllerDirectives, 13818 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 13819 newIsolateScopeDirective: newIsolateScopeDirective, 13820 templateDirective: templateDirective, 13821 nonTlbTranscludeDirective: nonTlbTranscludeDirective 13822 }); 13823 ii = directives.length; 13824 } else if (directive.compile) { 13825 try { 13826 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 13827 if (isFunction(linkFn)) { 13828 addLinkFns(null, linkFn, attrStart, attrEnd); 13829 } else if (linkFn) { 13830 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 13831 } 13832 } catch (e) { 13833 $exceptionHandler(e, startingTag($compileNode)); 13834 } 13835 } 13836 13837 if (directive.terminal) { 13838 nodeLinkFn.terminal = true; 13839 terminalPriority = Math.max(terminalPriority, directive.priority); 13840 } 13841 13842 } 13843 13844 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 13845 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 13846 nodeLinkFn.templateOnThisElement = hasTemplate; 13847 nodeLinkFn.transclude = childTranscludeFn; 13848 13849 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 13850 13851 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 13852 return nodeLinkFn; 13853 13854 //////////////////// 13855 13856 function addLinkFns(pre, post, attrStart, attrEnd) { 13857 if (pre) { 13858 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 13859 pre.require = directive.require; 13860 pre.directiveName = directiveName; 13861 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 13862 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 13863 } 13864 preLinkFns.push(pre); 13865 } 13866 if (post) { 13867 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 13868 post.require = directive.require; 13869 post.directiveName = directiveName; 13870 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 13871 post = cloneAndAnnotateFn(post, {isolateScope: true}); 13872 } 13873 postLinkFns.push(post); 13874 } 13875 } 13876 13877 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 13878 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, 13879 attrs, removeScopeBindingWatches, removeControllerBindingWatches; 13880 13881 if (compileNode === linkNode) { 13882 attrs = templateAttrs; 13883 $element = templateAttrs.$$element; 13884 } else { 13885 $element = jqLite(linkNode); 13886 attrs = new Attributes($element, templateAttrs); 13887 } 13888 13889 controllerScope = scope; 13890 if (newIsolateScopeDirective) { 13891 isolateScope = scope.$new(true); 13892 } else if (newScopeDirective) { 13893 controllerScope = scope.$parent; 13894 } 13895 13896 if (boundTranscludeFn) { 13897 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 13898 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 13899 transcludeFn = controllersBoundTransclude; 13900 transcludeFn.$$boundTransclude = boundTranscludeFn; 13901 // expose the slots on the `$transclude` function 13902 transcludeFn.isSlotFilled = function(slotName) { 13903 return !!boundTranscludeFn.$$slots[slotName]; 13904 }; 13905 } 13906 13907 if (controllerDirectives) { 13908 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); 13909 } 13910 13911 if (newIsolateScopeDirective) { 13912 // Initialize isolate scope bindings for new isolate scope directive. 13913 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 13914 templateDirective === newIsolateScopeDirective.$$originalDirective))); 13915 compile.$$addScopeClass($element, true); 13916 isolateScope.$$isolateBindings = 13917 newIsolateScopeDirective.$$isolateBindings; 13918 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope, 13919 isolateScope.$$isolateBindings, 13920 newIsolateScopeDirective); 13921 if (removeScopeBindingWatches) { 13922 isolateScope.$on('$destroy', removeScopeBindingWatches); 13923 } 13924 } 13925 13926 // Initialize bindToController bindings 13927 for (var name in elementControllers) { 13928 var controllerDirective = controllerDirectives[name]; 13929 var controller = elementControllers[name]; 13930 var bindings = controllerDirective.$$bindings.bindToController; 13931 13932 if (controller.identifier && bindings) { 13933 removeControllerBindingWatches = 13934 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 13935 } 13936 13937 var controllerResult = controller(); 13938 if (controllerResult !== controller.instance) { 13939 // If the controller constructor has a return value, overwrite the instance 13940 // from setupControllers 13941 controller.instance = controllerResult; 13942 $element.data('$' + controllerDirective.name + 'Controller', controllerResult); 13943 removeControllerBindingWatches && removeControllerBindingWatches(); 13944 removeControllerBindingWatches = 13945 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 13946 } 13947 } 13948 13949 // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy 13950 forEach(controllerDirectives, function(controllerDirective, name) { 13951 var require = controllerDirective.require; 13952 if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { 13953 extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); 13954 } 13955 }); 13956 13957 // Trigger the `$onInit` method on all controllers that have one 13958 forEach(elementControllers, function(controller) { 13959 if (isFunction(controller.instance.$onInit)) { 13960 controller.instance.$onInit(); 13961 } 13962 }); 13963 13964 // PRELINKING 13965 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 13966 linkFn = preLinkFns[i]; 13967 invokeLinkFn(linkFn, 13968 linkFn.isolateScope ? isolateScope : scope, 13969 $element, 13970 attrs, 13971 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 13972 transcludeFn 13973 ); 13974 } 13975 13976 // RECURSION 13977 // We only pass the isolate scope, if the isolate directive has a template, 13978 // otherwise the child elements do not belong to the isolate directive. 13979 var scopeToChild = scope; 13980 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 13981 scopeToChild = isolateScope; 13982 } 13983 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 13984 13985 // POSTLINKING 13986 for (i = postLinkFns.length - 1; i >= 0; i--) { 13987 linkFn = postLinkFns[i]; 13988 invokeLinkFn(linkFn, 13989 linkFn.isolateScope ? isolateScope : scope, 13990 $element, 13991 attrs, 13992 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 13993 transcludeFn 13994 ); 13995 } 13996 13997 // This is the function that is injected as `$transclude`. 13998 // Note: all arguments are optional! 13999 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { 14000 var transcludeControllers; 14001 // No scope passed in: 14002 if (!isScope(scope)) { 14003 slotName = futureParentElement; 14004 futureParentElement = cloneAttachFn; 14005 cloneAttachFn = scope; 14006 scope = undefined; 14007 } 14008 14009 if (hasElementTranscludeDirective) { 14010 transcludeControllers = elementControllers; 14011 } 14012 if (!futureParentElement) { 14013 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 14014 } 14015 if (slotName) { 14016 // slotTranscludeFn can be one of three things: 14017 // * a transclude function - a filled slot 14018 // * `null` - an optional slot that was not filled 14019 // * `undefined` - a slot that was not declared (i.e. invalid) 14020 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; 14021 if (slotTranscludeFn) { 14022 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 14023 } else if (isUndefined(slotTranscludeFn)) { 14024 throw $compileMinErr('noslot', 14025 'No parent directive that requires a transclusion with slot name "{0}". ' + 14026 'Element: {1}', 14027 slotName, startingTag($element)); 14028 } 14029 } else { 14030 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 14031 } 14032 } 14033 } 14034 } 14035 14036 function getControllers(directiveName, require, $element, elementControllers) { 14037 var value; 14038 14039 if (isString(require)) { 14040 var match = require.match(REQUIRE_PREFIX_REGEXP); 14041 var name = require.substring(match[0].length); 14042 var inheritType = match[1] || match[3]; 14043 var optional = match[2] === '?'; 14044 14045 //If only parents then start at the parent element 14046 if (inheritType === '^^') { 14047 $element = $element.parent(); 14048 //Otherwise attempt getting the controller from elementControllers in case 14049 //the element is transcluded (and has no data) and to avoid .data if possible 14050 } else { 14051 value = elementControllers && elementControllers[name]; 14052 value = value && value.instance; 14053 } 14054 14055 if (!value) { 14056 var dataName = '$' + name + 'Controller'; 14057 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 14058 } 14059 14060 if (!value && !optional) { 14061 throw $compileMinErr('ctreq', 14062 "Controller '{0}', required by directive '{1}', can't be found!", 14063 name, directiveName); 14064 } 14065 } else if (isArray(require)) { 14066 value = []; 14067 for (var i = 0, ii = require.length; i < ii; i++) { 14068 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 14069 } 14070 } else if (isObject(require)) { 14071 value = {}; 14072 forEach(require, function(controller, property) { 14073 value[property] = getControllers(directiveName, controller, $element, elementControllers); 14074 }); 14075 } 14076 14077 return value || null; 14078 } 14079 14080 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { 14081 var elementControllers = createMap(); 14082 for (var controllerKey in controllerDirectives) { 14083 var directive = controllerDirectives[controllerKey]; 14084 var locals = { 14085 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 14086 $element: $element, 14087 $attrs: attrs, 14088 $transclude: transcludeFn 14089 }; 14090 14091 var controller = directive.controller; 14092 if (controller == '@') { 14093 controller = attrs[directive.name]; 14094 } 14095 14096 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 14097 14098 // For directives with element transclusion the element is a comment. 14099 // In this case .data will not attach any data. 14100 // Instead, we save the controllers for the element in a local hash and attach to .data 14101 // later, once we have the actual element. 14102 elementControllers[directive.name] = controllerInstance; 14103 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 14104 } 14105 return elementControllers; 14106 } 14107 14108 // Depending upon the context in which a directive finds itself it might need to have a new isolated 14109 // or child scope created. For instance: 14110 // * if the directive has been pulled into a template because another directive with a higher priority 14111 // asked for element transclusion 14112 // * if the directive itself asks for transclusion but it is at the root of a template and the original 14113 // element was replaced. See https://github.com/angular/angular.js/issues/12936 14114 function markDirectiveScope(directives, isolateScope, newScope) { 14115 for (var j = 0, jj = directives.length; j < jj; j++) { 14116 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); 14117 } 14118 } 14119 14120 /** 14121 * looks up the directive and decorates it with exception handling and proper parameters. We 14122 * call this the boundDirective. 14123 * 14124 * @param {string} name name of the directive to look up. 14125 * @param {string} location The directive must be found in specific format. 14126 * String containing any of theses characters: 14127 * 14128 * * `E`: element name 14129 * * `A': attribute 14130 * * `C`: class 14131 * * `M`: comment 14132 * @returns {boolean} true if directive was added. 14133 */ 14134 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 14135 endAttrName) { 14136 if (name === ignoreDirective) return null; 14137 var match = null; 14138 if (hasDirectives.hasOwnProperty(name)) { 14139 for (var directive, directives = $injector.get(name + Suffix), 14140 i = 0, ii = directives.length; i < ii; i++) { 14141 try { 14142 directive = directives[i]; 14143 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 14144 directive.restrict.indexOf(location) != -1) { 14145 if (startAttrName) { 14146 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 14147 } 14148 if (!directive.$$bindings) { 14149 var bindings = directive.$$bindings = 14150 parseDirectiveBindings(directive, directive.name); 14151 if (isObject(bindings.isolateScope)) { 14152 directive.$$isolateBindings = bindings.isolateScope; 14153 } 14154 } 14155 tDirectives.push(directive); 14156 match = directive; 14157 } 14158 } catch (e) { $exceptionHandler(e); } 14159 } 14160 } 14161 return match; 14162 } 14163 14164 14165 /** 14166 * looks up the directive and returns true if it is a multi-element directive, 14167 * and therefore requires DOM nodes between -start and -end markers to be grouped 14168 * together. 14169 * 14170 * @param {string} name name of the directive to look up. 14171 * @returns true if directive was registered as multi-element. 14172 */ 14173 function directiveIsMultiElement(name) { 14174 if (hasDirectives.hasOwnProperty(name)) { 14175 for (var directive, directives = $injector.get(name + Suffix), 14176 i = 0, ii = directives.length; i < ii; i++) { 14177 directive = directives[i]; 14178 if (directive.multiElement) { 14179 return true; 14180 } 14181 } 14182 } 14183 return false;
14184 } 14185 14186 /** 14187 * When the element is replaced with HTML template then the new attributes 14188 * on the template need to be merged with the existing attributes in the DOM. 14189 * The desired effect is to have both of the attributes present. 14190 * 14191 * @param {object} dst destination attributes (original DOM) 14192 * @param {object} src source attributes (from the directive template) 14193 */ 14194 function mergeTemplateAttributes(dst, src) { 14195 var srcAttr = src.$attr, 14196 dstAttr = dst.$attr, 14197 $element = dst.$$element; 14198 14199 // reapply the old attributes to the new element 14200 forEach(dst, function(value, key) { 14201 if (key.charAt(0) != '$') { 14202 if (src[key] && src[key] !== value) { 14203 value += (key === 'style' ? ';' : ' ') + src[key]; 14204 } 14205 dst.$set(key, value, true, srcAttr[key]); 14206 } 14207 }); 14208 14209 // copy the new attributes on the old attrs object 14210 forEach(src, function(value, key) { 14211 if (key == 'class') { 14212 safeAddClass($element, value); 14213 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 14214 } else if (key == 'style') { 14215 $element.attr('style', $element.attr('style') + ';' + value); 14216 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 14217 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 14218 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 14219 // have an attribute like "has-own-property" or "data-has-own-property", etc. 14220 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 14221 dst[key] = value; 14222 dstAttr[key] = srcAttr[key]; 14223 } 14224 }); 14225 } 14226 14227 14228 function compileTemplateUrl(directives, $compileNode, tAttrs, 14229 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 14230 var linkQueue = [], 14231 afterTemplateNodeLinkFn, 14232 afterTemplateChildLinkFn, 14233 beforeTemplateCompileNode = $compileNode[0], 14234 origAsyncDirective = directives.shift(), 14235 derivedSyncDirective = inherit(origAsyncDirective, { 14236 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 14237 }), 14238 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 14239 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 14240 : origAsyncDirective.templateUrl, 14241 templateNamespace = origAsyncDirective.templateNamespace; 14242 14243 $compileNode.empty(); 14244 14245 $templateRequest(templateUrl) 14246 .then(function(content) { 14247 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 14248 14249 content = denormalizeTemplate(content); 14250 14251 if (origAsyncDirective.replace) { 14252 if (jqLiteIsTextNode(content)) { 14253 $template = []; 14254 } else { 14255 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 14256 } 14257 compileNode = $template[0]; 14258 14259 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 14260 throw $compileMinErr('tplrt', 14261 "Template for directive '{0}' must have exactly one root element. {1}", 14262 origAsyncDirective.name, templateUrl); 14263 } 14264 14265 tempTemplateAttrs = {$attr: {}}; 14266 replaceWith($rootElement, $compileNode, compileNode); 14267 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 14268 14269 if (isObject(origAsyncDirective.scope)) { 14270 // the original directive that caused the template to be loaded async required 14271 // an isolate scope 14272 markDirectiveScope(templateDirectives, true); 14273 } 14274 directives = templateDirectives.concat(directives); 14275 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 14276 } else { 14277 compileNode = beforeTemplateCompileNode; 14278 $compileNode.html(content); 14279 } 14280 14281 directives.unshift(derivedSyncDirective); 14282 14283 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 14284 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 14285 previousCompileContext);
vendor: 11,978 bytes, lines 14286-14579
14286 forEach($rootElement, function(node, i) { 14287 if (node == compileNode) { 14288 $rootElement[i] = $compileNode[0]; 14289 } 14290 }); 14291 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 14292 14293 while (linkQueue.length) { 14294 var scope = linkQueue.shift(), 14295 beforeTemplateLinkNode = linkQueue.shift(), 14296 linkRootElement = linkQueue.shift(), 14297 boundTranscludeFn = linkQueue.shift(), 14298 linkNode = $compileNode[0]; 14299 14300 if (scope.$$destroyed) continue; 14301 14302 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 14303 var oldClasses = beforeTemplateLinkNode.className; 14304 14305 if (!(previousCompileContext.hasElementTranscludeDirective && 14306 origAsyncDirective.replace)) { 14307 // it was cloned therefore we have to clone as well. 14308 linkNode = jqLiteClone(compileNode); 14309 } 14310 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 14311 14312 // Copy in CSS classes from original node 14313 safeAddClass(jqLite(linkNode), oldClasses); 14314 } 14315 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 14316 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 14317 } else { 14318 childBoundTranscludeFn = boundTranscludeFn; 14319 } 14320 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 14321 childBoundTranscludeFn); 14322 } 14323 linkQueue = null; 14324 }); 14325 14326 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 14327 var childBoundTranscludeFn = boundTranscludeFn; 14328 if (scope.$$destroyed) return; 14329 if (linkQueue) { 14330 linkQueue.push(scope, 14331 node, 14332 rootElement, 14333 childBoundTranscludeFn); 14334 } else { 14335 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 14336 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 14337 } 14338 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 14339 } 14340 }; 14341 } 14342 14343 14344 /** 14345 * Sorting function for bound directives. 14346 */ 14347 function byPriority(a, b) { 14348 var diff = b.priority - a.priority; 14349 if (diff !== 0) return diff; 14350 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 14351 return a.index - b.index; 14352 } 14353 14354 function assertNoDuplicate(what, previousDirective, directive, element) { 14355 14356 function wrapModuleNameIfDefined(moduleName) { 14357 return moduleName ? 14358 (' (module: ' + moduleName + ')') : 14359 ''; 14360 } 14361 14362 if (previousDirective) { 14363 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 14364 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 14365 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 14366 } 14367 } 14368 14369 14370 function addTextInterpolateDirective(directives, text) { 14371 var interpolateFn = $interpolate(text, true); 14372 if (interpolateFn) { 14373 directives.push({ 14374 priority: 0, 14375 compile: function textInterpolateCompileFn(templateNode) { 14376 var templateNodeParent = templateNode.parent(), 14377 hasCompileParent = !!templateNodeParent.length; 14378 14379 // When transcluding a template that has bindings in the root 14380 // we don't have a parent and thus need to add the class during linking fn. 14381 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 14382 14383 return function textInterpolateLinkFn(scope, node) { 14384 var parent = node.parent(); 14385 if (!hasCompileParent) compile.$$addBindingClass(parent); 14386 compile.$$addBindingInfo(parent, interpolateFn.expressions); 14387 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 14388 node[0].nodeValue = value; 14389 }); 14390 }; 14391 } 14392 }); 14393 } 14394 } 14395 14396 14397 function wrapTemplate(type, template) { 14398 type = lowercase(type || 'html'); 14399 switch (type) { 14400 case 'svg': 14401 case 'math': 14402 var wrapper = document.createElement('div'); 14403 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 14404 return wrapper.childNodes[0].childNodes; 14405 default: 14406 return template; 14407 } 14408 } 14409 14410 14411 function getTrustedContext(node, attrNormalizedName) { 14412 if (attrNormalizedName == "srcdoc") { 14413 return $sce.HTML; 14414 } 14415 var tag = nodeName_(node); 14416 // maction[xlink:href] can source SVG. It's not limited to <maction>. 14417 if (attrNormalizedName == "xlinkHref" || 14418 (tag == "form" && attrNormalizedName == "action") || 14419 (tag != "img" && (attrNormalizedName == "src" || 14420 attrNormalizedName == "ngSrc"))) { 14421 return $sce.RESOURCE_URL; 14422 } 14423 } 14424 14425 14426 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 14427 var trustedContext = getTrustedContext(node, name); 14428 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 14429 14430 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 14431 14432 // no interpolation found -> ignore 14433 if (!interpolateFn) return; 14434 14435 14436 if (name === "multiple" && nodeName_(node) === "select") { 14437 throw $compileMinErr("selmulti", 14438 "Binding to the 'multiple' attribute is not supported. Element: {0}", 14439 startingTag(node)); 14440 } 14441 14442 directives.push({ 14443 priority: 100, 14444 compile: function() { 14445 return { 14446 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 14447 var $$observers = (attr.$$observers || (attr.$$observers = createMap())); 14448 14449 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 14450 throw $compileMinErr('nodomevents', 14451 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 14452 "ng- versions (such as ng-click instead of onclick) instead."); 14453 } 14454 14455 // If the attribute has changed since last $interpolate()ed 14456 var newValue = attr[name]; 14457 if (newValue !== value) { 14458 // we need to interpolate again since the attribute value has been updated 14459 // (e.g. by another directive's compile function) 14460 // ensure unset/empty values make interpolateFn falsy 14461 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 14462 value = newValue; 14463 } 14464 14465 // if attribute was updated so that there is no interpolation going on we don't want to 14466 // register any observers 14467 if (!interpolateFn) return; 14468 14469 // initialize attr object so that it's ready in case we need the value for isolate 14470 // scope initialization, otherwise the value would not be available from isolate 14471 // directive's linking fn during linking phase 14472 attr[name] = interpolateFn(scope); 14473 14474 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 14475 (attr.$$observers && attr.$$observers[name].$$scope || scope). 14476 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 14477 //special case for class attribute addition + removal 14478 //so that class changes can tap into the animation 14479 //hooks provided by the $animate service. Be sure to 14480 //skip animations when the first digest occurs (when 14481 //both the new and the old values are the same) since 14482 //the CSS classes are the non-interpolated values 14483 if (name === 'class' && newValue != oldValue) { 14484 attr.$updateClass(newValue, oldValue); 14485 } else { 14486 attr.$set(name, newValue); 14487 } 14488 }); 14489 } 14490 }; 14491 } 14492 }); 14493 } 14494 14495 14496 /** 14497 * This is a special jqLite.replaceWith, which can replace items which 14498 * have no parents, provided that the containing jqLite collection is provided. 14499 * 14500 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 14501 * in the root of the tree. 14502 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 14503 * the shell, but replace its DOM node reference. 14504 * @param {Node} newNode The new DOM node. 14505 */ 14506 function replaceWith($rootElement, elementsToRemove, newNode) { 14507 var firstElementToRemove = elementsToRemove[0], 14508 removeCount = elementsToRemove.length, 14509 parent = firstElementToRemove.parentNode, 14510 i, ii; 14511 14512 if ($rootElement) { 14513 for (i = 0, ii = $rootElement.length; i < ii; i++) { 14514 if ($rootElement[i] == firstElementToRemove) { 14515 $rootElement[i++] = newNode; 14516 for (var j = i, j2 = j + removeCount - 1, 14517 jj = $rootElement.length; 14518 j < jj; j++, j2++) { 14519 if (j2 < jj) { 14520 $rootElement[j] = $rootElement[j2]; 14521 } else { 14522 delete $rootElement[j]; 14523 } 14524 } 14525 $rootElement.length -= removeCount - 1; 14526 14527 // If the replaced element is also the jQuery .context then replace it 14528 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 14529 // http://api.jquery.com/context/ 14530 if ($rootElement.context === firstElementToRemove) { 14531 $rootElement.context = newNode; 14532 } 14533 break; 14534 } 14535 } 14536 } 14537 14538 if (parent) { 14539 parent.replaceChild(newNode, firstElementToRemove); 14540 } 14541 14542 // Append all the `elementsToRemove` to a fragment. This will... 14543 // - remove them from the DOM 14544 // - allow them to still be traversed with .nextSibling 14545 // - allow a single fragment.qSA to fetch all elements being removed 14546 var fragment = document.createDocumentFragment(); 14547 for (i = 0; i < removeCount; i++) { 14548 fragment.appendChild(elementsToRemove[i]); 14549 } 14550 14551 if (jqLite.hasData(firstElementToRemove)) { 14552 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 14553 // data here because there's no public interface in jQuery to do that and copying over 14554 // event listeners (which is the main use of private data) wouldn't work anyway. 14555 jqLite.data(newNode, jqLite.data(firstElementToRemove)); 14556 14557 // Remove $destroy event listeners from `firstElementToRemove` 14558 jqLite(firstElementToRemove).off('$destroy'); 14559 } 14560 14561 // Cleanup any data/listeners on the elements and children. 14562 // This includes invoking the $destroy event on any elements with listeners. 14563 jqLite.cleanData(fragment.querySelectorAll('*')); 14564 14565 // Update the jqLite collection to only contain the `newNode` 14566 for (i = 1; i < removeCount; i++) { 14567 delete elementsToRemove[i]; 14568 } 14569 elementsToRemove[0] = newNode; 14570 elementsToRemove.length = 1; 14571 } 14572 14573 14574 function cloneAndAnnotateFn(fn, annotation) { 14575 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 14576 } 14577 14578 14579 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
14580 try { 14581 linkFn(scope, $element, attrs, controllers, transcludeFn); 14582 } catch (e) { 14583 $exceptionHandler(e, startingTag($element)); 14584 } 14585 } 14586 14587 14588 // Set up $watches for isolate scope and controller bindings. This process 14589 // only occurs for isolate scopes and new scopes with controllerAs. 14590 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { 14591 var removeWatchCollection = []; 14592 forEach(bindings, function(definition, scopeName) { 14593 var attrName = definition.attrName, 14594 optional = definition.optional, 14595 mode = definition.mode, // @, =, or & 14596 lastValue, 14597 parentGet, parentSet, compare, removeWatch; 14598 14599 switch (mode) { 14600 14601 case '@': 14602 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 14603 destination[scopeName] = attrs[attrName] = void 0; 14604 } 14605 attrs.$observe(attrName, function(value) { 14606 if (isString(value)) { 14607 destination[scopeName] = value; 14608 } 14609 }); 14610 attrs.$$observers[attrName].$$scope = scope; 14611 lastValue = attrs[attrName]; 14612 if (isString(lastValue)) { 14613 // If the attribute has been provided then we trigger an interpolation to ensure 14614 // the value is there for use in the link fn 14615 destination[scopeName] = $interpolate(lastValue)(scope); 14616 } else if (isBoolean(lastValue)) { 14617 // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted 14618 // the value to boolean rather than a string, so we special case this situation 14619 destination[scopeName] = lastValue; 14620 } 14621 break; 14622 14623 case '=': 14624 if (!hasOwnProperty.call(attrs, attrName)) { 14625 if (optional) break; 14626 attrs[attrName] = void 0; 14627 } 14628 if (optional && !attrs[attrName]) break; 14629 14630 parentGet = $parse(attrs[attrName]); 14631 if (parentGet.literal) { 14632 compare = equals; 14633 } else { 14634 compare = function(a, b) { return a === b || (a !== a && b !== b); }; 14635 } 14636 parentSet = parentGet.assign || function() { 14637 // reset the change, or we will throw this exception on every $digest 14638 lastValue = destination[scopeName] = parentGet(scope); 14639 throw $compileMinErr('nonassign', 14640 "Expression '{0}' in attribute '{1}' used with directive '{2}' is non-assignable!", 14641 attrs[attrName], attrName, directive.name); 14642 }; 14643 lastValue = destination[scopeName] = parentGet(scope); 14644 var parentValueWatch = function parentValueWatch(parentValue) { 14645 if (!compare(parentValue, destination[scopeName])) { 14646 // we are out of sync and need to copy 14647 if (!compare(parentValue, lastValue)) { 14648 // parent changed and it has precedence 14649 destination[scopeName] = parentValue; 14650 } else { 14651 // if the parent can be assigned then do so 14652 parentSet(scope, parentValue = destination[scopeName]); 14653 } 14654 } 14655 return lastValue = parentValue; 14656 }; 14657 parentValueWatch.$stateful = true; 14658 if (definition.collection) { 14659 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 14660 } else { 14661 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 14662 } 14663 removeWatchCollection.push(removeWatch); 14664 break; 14665 14666 case '<': 14667 if (!hasOwnProperty.call(attrs, attrName)) { 14668 if (optional) break; 14669 attrs[attrName] = void 0; 14670 } 14671 if (optional && !attrs[attrName]) break; 14672 14673 parentGet = $parse(attrs[attrName]); 14674 14675 destination[scopeName] = parentGet(scope); 14676 14677 removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) { 14678 destination[scopeName] = newParentValue; 14679 }, parentGet.literal); 14680 14681 removeWatchCollection.push(removeWatch); 14682 break; 14683 14684 case '&': 14685 // Don't assign Object.prototype method to scope 14686 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 14687 14688 // Don't assign noop to destination if expression is not val
vendor: 5,751 bytes, lines 14688-14894
14688id 14689 if (parentGet === noop && optional) break; 14690 14691 destination[scopeName] = function(locals) { 14692 return parentGet(scope, locals); 14693 }; 14694 break; 14695 } 14696 }); 14697 14698 return removeWatchCollection.length && function removeWatches() { 14699 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { 14700 removeWatchCollection[i](); 14701 } 14702 }; 14703 } 14704 }]; 14705} 14706 14707var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 14708/** 14709 * Converts all accepted directives format into proper directive name. 14710 * @param name Name to normalize 14711 */ 14712function directiveNormalize(name) { 14713 return camelCase(name.replace(PREFIX_REGEXP, '')); 14714} 14715 14716/** 14717 * @ngdoc type 14718 * @name $compile.directive.Attributes 14719 * 14720 * @description 14721 * A shared object between directive compile / linking functions which contains normalized DOM 14722 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 14723 * needed since all of these are treated as equivalent in Angular: 14724 * 14725 * ``` 14726 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 14727 * ``` 14728 */ 14729 14730/** 14731 * @ngdoc property 14732 * @name $compile.directive.Attributes#$attr 14733 * 14734 * @description 14735 * A map of DOM element attribute names to the normalized name. This is 14736 * needed to do reverse lookup from normalized name back to actual name. 14737 */ 14738 14739 14740/** 14741 * @ngdoc method 14742 * @name $compile.directive.Attributes#$set 14743 * @kind function 14744 * 14745 * @description 14746 * Set DOM element attribute value. 14747 * 14748 * 14749 * @param {string} name Normalized element attribute name of the property to modify. The name is 14750 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 14751 * property to the original name. 14752 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 14753 */ 14754 14755 14756 14757/** 14758 * Closure compiler type information 14759 */ 14760 14761function nodesetLinkingFn( 14762 /* angular.Scope */ scope, 14763 /* NodeList */ nodeList, 14764 /* Element */ rootElement, 14765 /* function(Function) */ boundTranscludeFn 14766) {} 14767 14768function directiveLinkingFn( 14769 /* nodesetLinkingFn */ nodesetLinkingFn, 14770 /* angular.Scope */ scope, 14771 /* Node */ node, 14772 /* Element */ rootElement, 14773 /* function(Function) */ boundTranscludeFn 14774) {} 14775 14776function tokenDifference(str1, str2) { 14777 var values = '', 14778 tokens1 = str1.split(/\s+/), 14779 tokens2 = str2.split(/\s+/); 14780 14781 outer: 14782 for (var i = 0; i < tokens1.length; i++) { 14783 var token = tokens1[i]; 14784 for (var j = 0; j < tokens2.length; j++) { 14785 if (token == tokens2[j]) continue outer; 14786 } 14787 values += (values.length > 0 ? ' ' : '') + token; 14788 } 14789 return values; 14790} 14791 14792function removeComments(jqNodes) { 14793 jqNodes = jqLite(jqNodes); 14794 var i = jqNodes.length; 14795 14796 if (i <= 1) { 14797 return jqNodes; 14798 } 14799 14800 while (i--) { 14801 var node = jqNodes[i]; 14802 if (node.nodeType === NODE_TYPE_COMMENT) { 14803 splice.call(jqNodes, i, 1); 14804 } 14805 } 14806 return jqNodes; 14807} 14808 14809var $controllerMinErr = minErr('$controller'); 14810 14811 14812var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; 14813function identifierForController(controller, ident) { 14814 if (ident && isString(ident)) return ident; 14815 if (isString(controller)) { 14816 var match = CNTRL_REG.exec(controller); 14817 if (match) return match[3]; 14818 } 14819} 14820 14821 14822/** 14823 * @ngdoc provider 14824 * @name $controllerProvider 14825 * @description 14826 * The {@link ng.$controller $controller service} is used by Angular to create new 14827 * controllers. 14828 * 14829 * This provider allows controller registration via the 14830 * {@link ng.$controllerProvider#register register} method. 14831 */ 14832function $ControllerProvider() { 14833 var controllers = {}, 14834 globals = false; 14835 14836 /** 14837 * @ngdoc method 14838 * @name $controllerProvider#has 14839 * @param {string} name Controller name to check. 14840 */ 14841 this.has = function(name) { 14842 return controllers.hasOwnProperty(name); 14843 }; 14844 14845 /** 14846 * @ngdoc method 14847 * @name $controllerProvider#register 14848 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 14849 * the names and the values are the constructors. 14850 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 14851 * annotations in the array notation). 14852 */ 14853 this.register = function(name, constructor) { 14854 assertNotHasOwnProperty(name, 'controller'); 14855 if (isObject(name)) { 14856 extend(controllers, name); 14857 } else { 14858 controllers[name] = constructor; 14859 } 14860 }; 14861 14862 /** 14863 * @ngdoc method 14864 * @name $controllerProvider#allowGlobals 14865 * @description If called, allows `$controller` to find controller constructors on `window` 14866 */ 14867 this.allowGlobals = function() { 14868 globals = true; 14869 }; 14870 14871 14872 this.$get = ['$injector', '$window', function($injector, $window) { 14873 14874 /** 14875 * @ngdoc service 14876 * @name $controller 14877 * @requires $injector 14878 * 14879 * @param {Function|string} constructor If called with a function then it's considered to be the 14880 * controller constructor function. Otherwise it's considered to be a string which is used 14881 * to retrieve the controller constructor using the following steps: 14882 * 14883 * * check if a controller with given name is registered via `$controllerProvider` 14884 * * check if evaluating the string on the current scope returns a constructor 14885 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 14886 * `window` object (not recommended) 14887 * 14888 * The string can use the `controller as property` syntax, where the controller instance is published 14889 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 14890 * to work correctly. 14891 * 14892 * @param {Object} locals Injection locals for Controller. 14893 * @return {Object} Instance of given controller. 14894 *
14895 * @description 14896 * `$controller` service is responsible for instantiating controllers. 14897 * 14898 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 14899 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 14900 */ 14901 return function(expression, locals, later, ident) { 14902 // PRIVATE API: 14903 // param `later` --- indicates that the controller's constructor is invoked at a later time. 14904 // If true, $controller will allocate the object with the correct 14905 // prototype chain, but will not invoke the controller until a returned 14906 // callback is invoked. 14907 // param `ident` --- An optional label which overrides the label parsed from the controller 14908 // expression, if any. 14909 var instance, match, constructor, identifier; 14910 later = later === true; 14911 if (ident && isString(ident)) { 14912 identifier = ident; 14913 } 14914 14915 if (isString(expression)) { 14916 match = expression.match(CNTRL_REG); 14917 if (!match) { 14918 throw $controllerMinErr('ctrlfmt', 14919 "Badly formed controller string '{0}'. " + 14920 "Must match `__name__ as __id__` or `__name__`.", expression); 14921 } 14922 constructor = match[1], 14923 identifier = identifier || match[3]; 14924 expression = controllers.hasOwnProperty(constructor) 14925 ? controllers[constructor] 14926 : getter(locals.$scope, constructor, true) || 14927 (globals ? getter($window, constructor, true) : undefined); 14928 14929 assertArgFn(expression, constructor, true); 14930 } 14931 14932 if (later) { 14933 // Instantiate controller later: 14934 // This machinery is used to create an instance of the object before calling the 14935 // controller's constructor itself. 14936 // 14937 // This allows properties to be added to the controller before the constructor is 14938 // invoked. Primarily, this is used for isolate scope bindings in $compile. 14939 // 14940 // This feature is not intended for use by applications, and is thus not documented 14941 // publicly. 14942 // Object creation: http://jsperf.com/create-constructor/2 14943 var controllerPrototype = (isArray(expression) ? 14944 expression[expression.length - 1] : expression).prototype; 14945 instance = Object.create(controllerPrototype || null); 14946 14947 if (identifier) { 14948 addIdentifier(locals, identifier, instance, constructor || expression.name); 14949 } 14950 14951 var instantiate; 14952 return instantiate = extend(function() { 14953 var result = $injector.invoke(expression, instance, locals, constructor); 14954 if (result !== instance && (isObject(result) || isFunction(result))) { 14955 instance = result; 14956 if (identifier) { 14957 // If result changed, re-assign controllerAs value to scope. 14958 addIdentifier(locals, identifier, instance, constructor || expression.name); 14959 } 14960 } 14961 return instance; 14962 }, { 14963 instance: instance, 14964 identifier: identifier 14965 }); 14966 } 14967 14968 instance = $injector.instantiate(expression, locals, constructor); 14969 14970 if (identifier) { 14971 addIdentifier(locals, identifier, instance, constructor || expression.name); 14972 } 14973 14974 return instance; 14975 }; 14976 14977 function addIdentifier(locals, identifier, instance, name) { 14978 if (!(locals && isObject(locals.$scope))) { 14979 throw minErr('$controller')('noscp', 14980 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 14981 name, identifier); 14982 } 14983 14984 locals.$scope[identifier] = instance; 14985 } 14986 }]; 14987} 14988 14989/** 14990 * @ngdoc service 14991 * @name $document 14992 * @requires $window 14993 * 14994 * @description 14995 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 14996 * 14997 * @example 14998 <example module="documentExample"> 14999 <file name="index.html">
15000 <div ng-controller="ExampleController"> 15001 <p>$document title: <b ng-bind="title"></b></p> 15002 <p>window.document title: <b ng-bind="windowTitle"></b></p> 15003 </div> 15004 </file> 15005 <file name="script.js"> 15006 angular.module('documentExample', []) 15007 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 15008 $scope.title = $document[0].title; 15009 $scope.windowTitle = angular.element(window.document)[0].title; 15010 }]); 15011 </file> 15012 </example> 15013 */ 15014function $DocumentProvider() { 15015 this.$get = ['$window', function(window) { 15016 return jqLite(window.document); 15017 }]; 15018} 15019 15020/** 15021 * @ngdoc service 15022 * @name $exceptionHandler 15023 * @requires ng.$log 15024 * 15025 * @description 15026 * Any uncaught exception in angular expressions is delegated to this service. 15027 * The default implementation simply delegates to `$log.error` which logs it into 15028 * the browser console. 15029 * 15030 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 15031 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 15032 * 15033 * ## Example: 15034 * 15035 * ```js 15036 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 15037 * return function(exception, cause) { 15038 * exception.message += ' (caused by "' + cause + '")'; 15039 * throw exception; 15040 * }; 15041 * }); 15042 * ``` 15043 * 15044 * This example will override the normal action of `$exceptionHandler`, to make angular 15045 * exceptions fail hard when they happen, instead of just logging to the console. 15046 * 15047 * <hr /> 15048 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 15049 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 15050 * (unless executed during a digest). 15051 * 15052 * If you wish, you can manually delegate exceptions, e.g. 15053 * `try { ... } catch(e) { $exceptionHandler(e); }` 15054 * 15055 * @param {Error} exception Exception associated with the error. 15056 * @param {string=} cause optional information about the context in which 15057 * the error was thrown. 15058 * 15059 */ 15060function $ExceptionHandlerProvider() { 15061 this.$get = ['$log', function($log) { 15062 return function(exception, cause) { 15063 $log.error.apply($log, arguments); 15064 }; 15065 }]; 15066} 15067 15068var $$ForceReflowProvider = function() { 15069 this.$get = ['$document', function($document) { 15070 return function(domNode) { 15071 //the line below will force the browser to perform a repaint so 15072 //that all the animated elements within the animation frame will 15073 //be properly updated and drawn on screen. This is required to 15074 //ensure that the preparation animation is properly flushed so that 15075 //the active state picks up from there. DO NOT REMOVE THIS LINE. 15076 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 15077 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 15078 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 15079 if (domNode) { 15080 if (!domNode.nodeType && domNode instanceof jqLite) { 15081 domNode = domNode[0]; 15082 } 15083 } else { 15084 domNode = $document[0].body; 15085 } 15086 return domNode.offsetWidth + 1; 15087 }; 15088 }]; 15089}; 15090 15091var APPLICATION_JSON = 'application/json'; 15092var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 15093var JSON_START = /^\[|^\{(?!\{)/; 15094var JSON_ENDS = { 15095 '[': /]$/, 15096 '{': /}$/ 15097}; 15098var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 15099var $httpMinErr = minErr('$http'); 15100var $httpMinErrLegacyFn = function(method) { 15101 return function() { 15102 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); 15103 }; 15104}; 15105 15106function serializeValue(v) { 15107 if (isObject(v)) { 15108 return isDate(v) ? v.toISOString() : toJson(v); 15109 } 15110 return v; 15111} 15112 15113 15114function $HttpParamSerializerProvider() { 15115 /** 15116 * @ngdoc service 15117 * @name $httpParamSerializer 15118 * @description 15119 * 15120 * Default {@link $http `$http`} params serializer that converts objects to strings 15121 * according to the following rules: 15122 * 15123 * * `{'foo': 'bar'}` results in `foo=bar` 15124 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 15125 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 15126 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 15127 * 15128 * Note that serializer will sort the request parameters alphabetically. 15129 * */ 15130 15131 this.$get = function() { 15132 return function ngParamSerializer(params) { 15133 if (!params) return ''; 15134 var parts = [];
vendor: 3,682 bytes, lines 15135-15261
15135 forEachSorted(params, function(value, key) { 15136 if (value === null || isUndefined(value)) return; 15137 if (isArray(value)) { 15138 forEach(value, function(v, k) { 15139 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 15140 }); 15141 } else { 15142 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 15143 } 15144 }); 15145 15146 return parts.join('&'); 15147 }; 15148 }; 15149} 15150 15151function $HttpParamSerializerJQLikeProvider() { 15152 /** 15153 * @ngdoc service 15154 * @name $httpParamSerializerJQLike 15155 * @description 15156 * 15157 * Alternative {@link $http `$http`} params serializer that follows 15158 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 15159 * The serializer will also sort the params alphabetically. 15160 * 15161 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 15162 * 15163 * ```js 15164 * $http({ 15165 * url: myUrl, 15166 * method: 'GET', 15167 * params: myParams, 15168 * paramSerializer: '$httpParamSerializerJQLike' 15169 * }); 15170 * ``` 15171 * 15172 * It is also possible to set it as the default `paramSerializer` in the 15173 * {@link $httpProvider#defaults `$httpProvider`}. 15174 * 15175 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 15176 * form data for submission: 15177 * 15178 * ```js 15179 * .controller(function($http, $httpParamSerializerJQLike) { 15180 * //... 15181 * 15182 * $http({ 15183 * url: myUrl, 15184 * method: 'POST', 15185 * data: $httpParamSerializerJQLike(myData), 15186 * headers: { 15187 * 'Content-Type': 'application/x-www-form-urlencoded' 15188 * } 15189 * }); 15190 * 15191 * }); 15192 * ``` 15193 * 15194 * */ 15195 this.$get = function() { 15196 return function jQueryLikeParamSerializer(params) { 15197 if (!params) return ''; 15198 var parts = []; 15199 serialize(params, '', true); 15200 return parts.join('&'); 15201 15202 function serialize(toSerialize, prefix, topLevel) { 15203 if (toSerialize === null || isUndefined(toSerialize)) return; 15204 if (isArray(toSerialize)) { 15205 forEach(toSerialize, function(value, index) { 15206 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 15207 }); 15208 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 15209 forEachSorted(toSerialize, function(value, key) { 15210 serialize(value, prefix + 15211 (topLevel ? '' : '[') + 15212 key + 15213 (topLevel ? '' : ']')); 15214 }); 15215 } else { 15216 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 15217 } 15218 } 15219 }; 15220 }; 15221} 15222 15223function defaultHttpResponseTransform(data, headers) { 15224 if (isString(data)) { 15225 // Strip json vulnerability protection prefix and trim whitespace 15226 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 15227 15228 if (tempData) { 15229 var contentType = headers('Content-Type'); 15230 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 15231 data = fromJson(tempData); 15232 } 15233 } 15234 } 15235 15236 return data; 15237} 15238 15239function isJsonLike(str) { 15240 var jsonStart = str.match(JSON_START); 15241 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 15242} 15243 15244/** 15245 * Parse headers into key value object 15246 * 15247 * @param {string} headers Raw headers as a string 15248 * @returns {Object} Parsed headers as key value object 15249 */ 15250function parseHeaders(headers) { 15251 var parsed = createMap(), i; 15252 15253 function fillInParsed(key, val) { 15254 if (key) { 15255 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 15256 } 15257 } 15258 15259 if (isString(headers)) { 15260 forEach(headers.split('\n'), function(line) { 15261 i = line.indexOf(':');
vendor: 4,132 bytes, lines 15262-15398
15262 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 15263 }); 15264 } else if (isObject(headers)) { 15265 forEach(headers, function(headerVal, headerKey) { 15266 fillInParsed(lowercase(headerKey), trim(headerVal)); 15267 }); 15268 } 15269 15270 return parsed; 15271} 15272 15273 15274/** 15275 * Returns a function that provides access to parsed headers. 15276 * 15277 * Headers are lazy parsed when first requested. 15278 * @see parseHeaders 15279 * 15280 * @param {(string|Object)} headers Headers to provide access to. 15281 * @returns {function(string=)} Returns a getter function which if called with: 15282 * 15283 * - if called with single an argument returns a single header value or null 15284 * - if called with no arguments returns an object containing all headers. 15285 */ 15286function headersGetter(headers) { 15287 var headersObj; 15288 15289 return function(name) { 15290 if (!headersObj) headersObj = parseHeaders(headers); 15291 15292 if (name) { 15293 var value = headersObj[lowercase(name)]; 15294 if (value === void 0) { 15295 value = null; 15296 } 15297 return value; 15298 } 15299 15300 return headersObj; 15301 }; 15302} 15303 15304 15305/** 15306 * Chain all given functions 15307 * 15308 * This function is used for both request and response transforming 15309 * 15310 * @param {*} data Data to transform. 15311 * @param {function(string=)} headers HTTP headers getter fn. 15312 * @param {number} status HTTP status code of the response. 15313 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 15314 * @returns {*} Transformed data. 15315 */ 15316function transformData(data, headers, status, fns) { 15317 if (isFunction(fns)) { 15318 return fns(data, headers, status); 15319 } 15320 15321 forEach(fns, function(fn) { 15322 data = fn(data, headers, status); 15323 }); 15324 15325 return data; 15326} 15327 15328 15329function isSuccess(status) { 15330 return 200 <= status && status < 300; 15331} 15332 15333 15334/** 15335 * @ngdoc provider 15336 * @name $httpProvider 15337 * @description 15338 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 15339 * */ 15340function $HttpProvider() { 15341 /** 15342 * @ngdoc property 15343 * @name $httpProvider#defaults 15344 * @description 15345 * 15346 * Object containing default values for all {@link ng.$http $http} requests. 15347 * 15348 * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with 15349 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses 15350 * by default. See {@link $http#caching $http Caching} for more information. 15351 * 15352 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 15353 * Defaults value is `'XSRF-TOKEN'`. 15354 * 15355 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 15356 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 15357 * 15358 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 15359 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 15360 * setting default headers. 15361 * - **`defaults.headers.common`** 15362 * - **`defaults.headers.post`** 15363 * - **`defaults.headers.put`** 15364 * - **`defaults.headers.patch`** 15365 * 15366 * 15367 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 15368 * used to the prepare string representation of request parameters (specified as an object). 15369 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 15370 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 15371 * 15372 **/ 15373 var defaults = this.defaults = { 15374 // transform incoming response data 15375 transformResponse: [defaultHttpResponseTransform], 15376 15377 // transform outgoing request data 15378 transformRequest: [function(d) { 15379 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 15380 }], 15381 15382 // default headers 15383 headers: { 15384 common: { 15385 'Accept': 'application/json, text/plain, */*' 15386 }, 15387 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 15388 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 15389 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 15390 }, 15391 15392 xsrfCookieName: 'XSRF-TOKEN', 15393 xsrfHeaderName: 'X-XSRF-TOKEN', 15394 15395 paramSerializer: '$httpParamSerializer' 15396 }; 15397 15398 var useApplyAsync = false;
vendor: 4,307 bytes, lines 15399-15509
15399 /** 15400 * @ngdoc method 15401 * @name $httpProvider#useApplyAsync 15402 * @description 15403 * 15404 * Configure $http service to combine processing of multiple http responses received at around 15405 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 15406 * significant performance improvement for bigger applications that make many HTTP requests 15407 * concurrently (common during application bootstrap). 15408 * 15409 * Defaults to false. If no value is specified, returns the current configured value. 15410 * 15411 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 15412 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 15413 * to load and share the same digest cycle. 15414 * 15415 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 15416 * otherwise, returns the current configured value. 15417 **/ 15418 this.useApplyAsync = function(value) { 15419 if (isDefined(value)) { 15420 useApplyAsync = !!value; 15421 return this; 15422 } 15423 return useApplyAsync; 15424 }; 15425 15426 var useLegacyPromise = true; 15427 /** 15428 * @ngdoc method 15429 * @name $httpProvider#useLegacyPromiseExtensions 15430 * @description 15431 * 15432 * Configure `$http` service to return promises without the shorthand methods `success` and `error`. 15433 * This should be used to make sure that applications work without these methods. 15434 * 15435 * Defaults to true. If no value is specified, returns the current configured value. 15436 * 15437 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods. 15438 * 15439 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 15440 * otherwise, returns the current configured value. 15441 **/ 15442 this.useLegacyPromiseExtensions = function(value) { 15443 if (isDefined(value)) { 15444 useLegacyPromise = !!value; 15445 return this; 15446 } 15447 return useLegacyPromise; 15448 }; 15449 15450 /** 15451 * @ngdoc property 15452 * @name $httpProvider#interceptors 15453 * @description 15454 * 15455 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 15456 * pre-processing of request or postprocessing of responses. 15457 * 15458 * These service factories are ordered by request, i.e. they are applied in the same order as the 15459 * array, on request, but reverse order, on response. 15460 * 15461 * {@link ng.$http#interceptors Interceptors detailed info} 15462 **/ 15463 var interceptorFactories = this.interceptors = []; 15464 15465 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 15466 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 15467 15468 var defaultCache = $cacheFactory('$http'); 15469 15470 /** 15471 * Make sure that default param serializer is exposed as a function 15472 */ 15473 defaults.paramSerializer = isString(defaults.paramSerializer) ? 15474 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 15475 15476 /** 15477 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 15478 * The reversal is needed so that we can build up the interception chain around the 15479 * server request. 15480 */ 15481 var reversedInterceptors = []; 15482 15483 forEach(interceptorFactories, function(interceptorFactory) { 15484 reversedInterceptors.unshift(isString(interceptorFactory) 15485 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 15486 }); 15487 15488 /** 15489 * @ngdoc service 15490 * @kind function 15491 * @name $http 15492 * @requires ng.$httpBackend 15493 * @requires $cacheFactory 15494 * @requires $rootScope 15495 * @requires $q 15496 * @requires $injector 15497 * 15498 * @description 15499 * The `$http` service is a core Angular service that facilitates communication with the remote 15500 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 15501 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 15502 * 15503 * For unit testing applications that use `$http` service, see 15504 * {@link ngMock.$httpBackend $httpBackend mock}. 15505 * 15506 * For a higher level of abstraction, please check out the {@link ngResource.$resource 15507 * $resource} service. 15508 * 15509 * The $http API is based on the {@link ng.$q deferred/promise APIs}
15509 exposed by 15510 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 15511 * it is important to familiarize yourself with these APIs and the guarantees they provide. 15512 * 15513 * 15514 * ## General usage 15515 * The `$http` service is a function which takes a single argument â a {@link $http#usage configuration object} â 15516 * that is used to generate an HTTP request and returns a {@link ng.$q promise}. 15517 * 15518 * ```js 15519 * // Simple GET request example: 15520 * $http({ 15521 * method: 'GET', 15522 * url: '/someUrl' 15523 * }).then(function successCallback(response) { 15524 * // this callback will be called asynchronously 15525 * // when the response is available 15526 * }, function errorCallback(response) { 15527 * // called asynchronously if an error occurs 15528 * // or server returns response with an error status. 15529 * }); 15530 * ``` 15531 * 15532 * The response object has these properties: 15533 * 15534 * - **data** â `{string|Object}` â The response body transformed with the transform 15535 * functions. 15536 * - **status** â `{number}` â HTTP status code of the response. 15537 * - **headers** â `{function([headerName])}` â Header getter function. 15538 * - **config** â `{Object}` â The configuration object that was used to generate the request. 15539 * - **statusText** â `{string}` â HTTP status text of the response. 15540 * 15541 * A response status code between 200 and 299 is considered a success status and 15542 * will result in the success callback being called. Note that if the response is a redirect, 15543 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 15544 * called for such responses. 15545 * 15546 * 15547 * ## Shortcut methods 15548 * 15549 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 15550 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 15551 * last argument. 15552 * 15553 * ```js 15554 * $http.get('/someUrl', config).then(successCallback, errorCallback); 15555 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 15556 * ``` 15557 * 15558 * Complete list of shortcut methods: 15559 * 15560 * - {@link ng.$http#get $http.get} 15561 * - {@link ng.$http#head $http.head} 15562 * - {@link ng.$http#post $http.post} 15563 * - {@link ng.$http#put $http.put} 15564 * - {@link ng.$http#delete $http.delete} 15565 * - {@link ng.$http#jsonp $http.jsonp} 15566 * - {@link ng.$http#patch $http.patch} 15567 * 15568 * 15569 * ## Writing Unit Tests that use $http 15570 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 15571 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 15572 * request using trained responses. 15573 * 15574 * ``` 15575 * $httpBackend.expectGET(...); 15576 * $http.get(...); 15577 * $httpBackend.flush(); 15578 * ``` 15579 * 15580 * ## Deprecation Notice 15581 * <div class="alert alert-danger"> 15582 * The `$http` legacy promise methods `success` and `error` have been deprecated. 15583 * Use the standard `then` method instead. 15584 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to 15585 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. 15586 * </div> 15587 * 15588 * ## Setting HTTP Headers 15589 * 15590 * The $http service will automatically add certain HTTP headers to all requests. These defaults 15591 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 15592 * object, which currently contains this default configuration: 15593 * 15594 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 15595 * - `Accept: application/json, text/plain, * / *` 15596 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 15597 * - `Content-Type: application/json` 15598 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 15599 * - `Content-Type: application/json` 15600 *
15601 * To add or overwrite these defaults, simply add or remove a property from these configuration 15602 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 15603 * with the lowercased HTTP method name as the key, e.g. 15604 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 15605 * 15606 * The defaults can also be set at runtime via the `$http.defaults` object in the same 15607 * fashion. For example: 15608 * 15609 * ``` 15610 * module.run(function($http) { 15611 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; 15612 * }); 15613 * ``` 15614 * 15615 * In addition, you can supply a `headers` property in the config object passed when 15616 * calling `$http(config)`, which overrides the defaults without changing them globally. 15617 * 15618 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 15619 * Use the `headers` property, setting the desired header to `undefined`. For example: 15620 * 15621 * ```js 15622 * var req = { 15623 * method: 'POST', 15624 * url: 'http://example.com', 15625 * headers: { 15626 * 'Content-Type': undefined 15627 * }, 15628 * data: { test: 'test' } 15629 * } 15630 * 15631 * $http(req).then(function(){...}, function(){...}); 15632 * ``` 15633 * 15634 * ## Transforming Requests and Responses 15635 * 15636 * Both requests and responses can be transformed using transformation functions: `transformRequest` 15637 * and `transformResponse`. These properties can be a single function that returns 15638 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 15639 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 15640 * 15641 * <div class="alert alert-warning"> 15642 * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. 15643 * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). 15644 * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest 15645 * function will be reflected on the scope and in any templates where the object is data-bound. 15646 * To prevent his, transform functions should have no side-effects. 15647 * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. 15648 * </div> 15649 * 15650 * ### Default Transformations 15651 * 15652 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 15653 * `defaults.transformResponse` properties. If a request does not provide its own transformations 15654 * then these will be applied. 15655 * 15656 * You can augment or replace the default transformations by modifying these properties by adding to or 15657 * replacing the array. 15658 * 15659 * Angular provides the following default transformations: 15660 * 15661 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 15662 * 15663 * - If the `data` property of the request configuration object contains an object, serialize it 15664 * into JSON format. 15665 * 15666 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 15667 * 15668 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 15669 * - If JSON response is detected, deserialize it using a JSON parser. 15670 * 15671 * 15672 * ### Overriding the Default Transformations Per Request 15673 * 15674 * If you wish override the request/response transformations only for a single request then provide 15675 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 15676 * into `$http`. 15677 * 15678 * Note that if you provide these properties on the config object the default transformations will be 15679 * overwritten. If you wish to augment the default transformations then you must include them in your 15680 * local transformation array.
15681 * 15682 * The following code demonstrates adding a new response transformation to be run after the default response 15683 * transformations have been run. 15684 * 15685 * ```js 15686 * function appendTransform(defaults, transform) { 15687 * 15688 * // We can't guarantee that the default transformation is an array 15689 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 15690 * 15691 * // Append the new transformation to the defaults 15692 * return defaults.concat(transform); 15693 * } 15694 * 15695 * $http({ 15696 * url: '...', 15697 * method: 'GET', 15698 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 15699 * return doTransform(value); 15700 * }) 15701 * }); 15702 * ``` 15703 * 15704 * 15705 * ## Caching 15706 * 15707 * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must 15708 * set the config.cache value or the default cache value to TRUE or to a cache object (created 15709 * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes 15710 * precedence over the default cache value. 15711 * 15712 * In order to: 15713 * * cache all responses - set the default cache value to TRUE or to a cache object 15714 * * cache a specific response - set config.cache value to TRUE or to a cache object 15715 * 15716 * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, 15717 * then the default `$cacheFactory($http)` object is used. 15718 * 15719 * The default cache value can be set by updating the 15720 * {@link ng.$http#defaults `$http.defaults.cache`} property or the 15721 * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. 15722 * 15723 * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using 15724 * the relevant cache object. The next time the same request is made, the response is returned 15725 * from the cache without sending a request to the server. 15726 * 15727 * Take note that: 15728 * 15729 * * Only GET and JSONP requests are cached. 15730 * * The cache key is the request URL including search parameters; headers are not considered. 15731 * * Cached responses are returned asynchronously, in the same way as responses from the server. 15732 * * If multiple identical requests are made using the same cache, which is not yet populated, 15733 * one request will be made to the server and remaining requests will return the same response. 15734 * * A cache-control header on the response does not affect if or how responses are cached. 15735 * 15736 * 15737 * ## Interceptors 15738 * 15739 * Before you start creating interceptors, be sure to understand the 15740 * {@link ng.$q $q and deferred/promise APIs}. 15741 * 15742 * For purposes of global error handling, authentication, or any kind of synchronous or 15743 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 15744 * able to intercept requests before they are handed to the server and 15745 * responses before they are handed over to the application code that 15746 * initiated these requests. The interceptors leverage the {@link ng.$q 15747 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 15748 * 15749 * The interceptors are service factories that are registered with the `$httpProvider` by 15750 * adding them to the `$httpProvider.interceptors` array. The factory is called and 15751 * injected with dependencies (if specified) and returns the interceptor. 15752 * 15753 * There are two kinds of interceptors (and two kinds of rejection interceptors): 15754 * 15755 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 15756 * modify the `config` object or create a new one. The function needs to return the `config` 15757 * object directly, or a promise containing the `config` or a new `config` object. 15758 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 15759 * resolved with a rejection. 15760 * * `response`: interceptors get called with http `response` object. The function is free to 15761 * modify the `response` object or create a new one. The function needs to return the `response` 15762 * object directly, or as a promise containing the `response` or a new `response` object. 15763 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 15764 * resolved with a rejection. 15765 * 15766 * 15767 * ```js 15768 * // register the interceptor as a service 15769 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 15770 * return { 15771 * // optional method 15772 * 'request': function(config) {
15773 * // do something on success 15774 * return config; 15775 * }, 15776 * 15777 * // optional method 15778 * 'requestError': function(rejection) { 15779 * // do something on error 15780 * if (canRecover(rejection)) { 15781 * return responseOrNewPromise 15782 * } 15783 * return $q.reject(rejection); 15784 * }, 15785 * 15786 * 15787 * 15788 * // optional method 15789 * 'response': function(response) { 15790 * // do something on success 15791 * return response; 15792 * }, 15793 * 15794 * // optional method 15795 * 'responseError': function(rejection) { 15796 * // do something on error 15797 * if (canRecover(rejection)) { 15798 * return responseOrNewPromise 15799 * } 15800 * return $q.reject(rejection); 15801 * } 15802 * }; 15803 * }); 15804 * 15805 * $httpProvider.interceptors.push('myHttpInterceptor'); 15806 * 15807 * 15808 * // alternatively, register the interceptor via an anonymous factory 15809 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 15810 * return { 15811 * 'request': function(config) { 15812 * // same as above 15813 * }, 15814 * 15815 * 'response': function(response) { 15816 * // same as above 15817 * } 15818 * }; 15819 * }); 15820 * ``` 15821 * 15822 * ## Security Considerations 15823 * 15824 * When designing web applications, consider security threats from: 15825 * 15826 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 15827 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 15828 * 15829 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 15830 * pre-configured with strategies that address these issues, but for this to work backend server 15831 * cooperation is required. 15832 * 15833 * ### JSON Vulnerability Protection 15834 * 15835 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 15836 * allows third party website to turn your JSON resource URL into 15837 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 15838 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 15839 * Angular will automatically strip the prefix before processing it as JSON. 15840 * 15841 * For example if your server needs to return: 15842 * ```js 15843 * ['one','two'] 15844 * ``` 15845 * 15846 * which is vulnerable to attack, your server can return: 15847 * ```js 15848 * )]}', 15849 * ['one','two'] 15850 * ``` 15851 * 15852 * Angular will strip the prefix, before processing the JSON. 15853 * 15854 * 15855 * ### Cross Site Request Forgery (XSRF) Protection 15856 * 15857 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by 15858 * which the attacker can trick an authenticated user into unknowingly executing actions on your 15859 * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the 15860 * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP 15861 * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the 15862 * cookie, your server can be assured that the XHR came from JavaScript running on your domain. 15863 * The header will not be set for cross-domain requests. 15864 * 15865 * To take advantage of this, your server needs to set a token in a JavaScript readable session 15866 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 15867 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 15868 * that only JavaScript running on your domain could have sent the request. The token must be 15869 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 15870 * making up its own tokens). We recommend that the token is a digest of your site's 15871 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 15872 * for added security. 15873 * 15874 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 15875 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 15876 * or the per-request config object. 15877 * 15878 * In order to prevent collisions in environments where multiple Angular apps share the 15879 * same domain or subdomain, we recommend that each application uses unique cookie name. 15880 * 15881 * @param {object} config Object describing the request to be made and how it should be 15882 * processed. The object has following properties: 15883 * 15884 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 15885 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 15886 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 15887 * with the `paramSerializer` and appended as GET parameters. 15888 * - **data** â `{string|Object}` â Data to be sent as the request message data. 15889 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 15890 * HTTP headers to send to the server. If the return value of a function is null, the 15891 * header will not be sent. Functions accept a config object as an argument. 15892 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 15893 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 15894 * - **transformRequest** â 15895 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
15895}` â 15896 * transform function or an array of such functions. The transform function takes the http 15897 * request body and headers and returns its transformed (typically serialized) version. 15898 * See {@link ng.$http#overriding-the-default-transformations-per-request 15899 * Overriding the Default Transformations} 15900 * - **transformResponse** â 15901 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 15902 * transform function or an array of such functions. The transform function takes the http 15903 * response body, headers and status and returns its transformed (typically deserialized) version. 15904 * See {@link ng.$http#overriding-the-default-transformations-per-request 15905 * Overriding the Default Transformations} 15906 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 15907 * prepare the string representation of request parameters (specified as an object). 15908 * If specified as string, it is interpreted as function registered with the 15909 * {@link $injector $injector}, which means you can create your own serializer 15910 * by registering it as a {@link auto.$provide#service service}. 15911 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 15912 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 15913 * - **cache** â `{boolean|Object}` â A boolean value or object created with 15914 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. 15915 * See {@link $http#caching $http Caching} for more information. 15916 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 15917 * that should abort the request when resolved. 15918 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 15919 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 15920 * for more information. 15921 * - **responseType** - `{string}` - see 15922 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 15923 * 15924 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object 15925 * when the request succeeds or fails. 15926 * 15927 * 15928 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 15929 * requests. This is primarily meant to be used for debugging purposes. 15930 * 15931 * 15932 * @example 15933<example module="httpExample"> 15934<file name="index.html"> 15935 <div ng-controller="FetchController"> 15936 <select ng-model="method" aria-label="Request method"> 15937 <option>GET</option> 15938 <option>JSONP</option> 15939 </select> 15940 <input type="text" ng-model="url" size="80" aria-label="URL" /> 15941 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 15942 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 15943 <button id="samplejsonpbtn" 15944 ng-click="updateModel('JSONP', 15945 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 15946 Sample JSONP 15947 </button> 15948 <button id="invalidjsonpbtn" 15949 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 15950 Invalid JSONP 15951 </button> 15952 <pre>http status code: {{status}}</pre> 15953 <pre>http response data: {{data}}</pre> 15954 </div> 15955</file> 15956<file name="script.js"> 15957 angular.module('httpExample', []) 15958 .controller('FetchController', ['$scope', '$http', '$templateCache', 15959 function($scope, $http, $templateCache) { 15960 $scope.method = 'GET'; 15961 $scope.url = 'http-hello.html'; 15962 15963 $scope.fetch = function() { 15964 $scope.code = null; 15965 $scope.response = null; 15966 15967 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 15968 then(function(response) { 15969 $scope.status = response.status; 15970 $scope.data = response.data; 15971 }, function(response) { 15972 $scope.data = response.data || "Request failed"; 15973 $scope.status = response.status; 15974 }); 15975 }; 15976 15977 $scope.updateModel = function(method, url) { 15978 $scope.method = method; 15979 $scope.url = url; 15980 }; 15981 }]); 15982</file> 15983<file name="http-hello.html"> 15984 Hello, $http! 15985</file> 15986<file name="protractor.js" type="protractor"> 15987 var status = element(by.binding('status')); 15988 var data = element(by.binding('data')); 15989 var fetchBtn = element(by.id('fetchbtn')); 15990 var sampleGetBtn = element(by.id('samplegetbtn')); 15991 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 15992 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 15993 15994 it('should make an xhr GET request', function() { 15995 sampleGetBtn.click(); 15996 fetchBtn.click(); 15997 expect(status.getText()).toMatch('200'); 15998 expect(data.getText()).toMatch(/Hello, \$http!/); 15999 }); 16000 16001// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 16002// it('should make a JSONP request to angularjs.org', function() { 16003// sampleJsonpBtn.click(); 16004// fetchBtn.click(); 16005// expect(status.getText()).toMatch('200'); 16006// expect(data.getText()).toMatch(/Super Hero!/); 16007// }); 16008
16009 it('should make JSONP request to invalid URL and invoke the error handler', 16010 function() { 16011 invalidJsonpBtn.click(); 16012 fetchBtn.click(); 16013 expect(status.getText()).toMatch('0'); 16014 expect(data.getText()).toMatch('Request failed'); 16015 }); 16016</file> 16017</example> 16018 */ 16019 function $http(requestConfig) { 16020 16021 if (!isObject(requestConfig)) { 16022 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 16023 } 16024 16025 if (!isString(requestConfig.url)) { 16026 throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); 16027 } 16028 16029 var config = extend({ 16030 method: 'get', 16031 transformRequest: defaults.transformRequest, 16032 transformResponse: defaults.transformResponse, 16033 paramSerializer: defaults.paramSerializer 16034 }, requestConfig); 16035 16036 config.headers = mergeHeaders(requestConfig); 16037 config.method = uppercase(config.method); 16038 config.paramSerializer = isString(config.paramSerializer) ? 16039 $injector.get(config.paramSerializer) : config.paramSerializer; 16040 16041 var serverRequest = function(config) { 16042 var headers = config.headers; 16043 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 16044 16045 // strip content-type if data is undefined 16046 if (isUndefined(reqData)) { 16047 forEach(headers, function(value, header) { 16048 if (lowercase(header) === 'content-type') { 16049 delete headers[header]; 16050 } 16051 }); 16052 } 16053 16054 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 16055 config.withCredentials = defaults.withCredentials; 16056 } 16057 16058 // send request 16059 return sendReq(config, reqData).then(transformResponse, transformResponse); 16060 }; 16061 16062 var chain = [serverRequest, undefined]; 16063 var promise = $q.when(config); 16064 16065 // apply interceptors 16066 forEach(reversedInterceptors, function(interceptor) { 16067 if (interceptor.request || interceptor.requestError) { 16068 chain.unshift(interceptor.request, interceptor.requestError); 16069 } 16070 if (interceptor.response || interceptor.responseError) { 16071 chain.push(interceptor.response, interceptor.responseError); 16072 } 16073 }); 16074 16075 while (chain.length) { 16076 var thenFn = chain.shift(); 16077 var rejectFn = chain.shift(); 16078 16079 promise = promise.then(thenFn, rejectFn); 16080 } 16081 16082 if (useLegacyPromise) { 16083 promise.success = function(fn) { 16084 assertArgFn(fn, 'fn'); 16085 16086 promise.then(function(response) { 16087 fn(response.data, response.status, response.headers, config); 16088 }); 16089 return promise; 16090 }; 16091 16092 promise.error = function(fn) { 16093 assertArgFn(fn, 'fn'); 16094 16095 promise.then(null, function(response) { 16096 fn(response.data, response.status, response.headers, config); 16097 }); 16098 return promise; 16099 }; 16100 } else { 16101 promise.success = $httpMinErrLegacyFn('success'); 16102 promise.error = $httpMinErrLegacyFn('error'); 16103 } 16104 16105 return promise; 16106 16107 function transformResponse(response) { 16108 // make a copy since the response must be cacheable 16109 var resp = extend({}, response); 16110 resp.data = transformData(response.data, response.headers, response.status, 16111 config.transformResponse); 16112 return (isSuccess(response.status)) 16113 ? resp 16114 : $q.reject(resp); 16115 } 16116 16117 function executeHeaderFns(headers, config) { 16118 var headerContent, processedHeaders = {}; 16119 16120 forEach(headers, function(headerFn, header) { 16121 if (isFunction(headerFn)) { 16122 headerContent = headerFn(config); 16123 if (headerContent != null) { 16124 processedHeaders[header] = headerContent; 16125 } 16126 } else { 16127 processedHeaders[header] = headerFn; 16128 } 16129 }); 16130 16131 return processedHeaders; 16132 } 16133 16134 function mergeHeaders(config) { 16135 var defHeaders = defaults.headers, 16136 reqHeaders = extend({}, config.headers), 16137 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 16138 16139 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 16140
vendor: 9,099 bytes, lines 16141-16441
16141 // using for-in instead of forEach to avoid unnecessary iteration after header has been found 16142 defaultHeadersIteration: 16143 for (defHeaderName in defHeaders) { 16144 lowercaseDefHeaderName = lowercase(defHeaderName); 16145 16146 for (reqHeaderName in reqHeaders) { 16147 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 16148 continue defaultHeadersIteration; 16149 } 16150 } 16151 16152 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 16153 } 16154 16155 // execute if header value is a function for merged headers 16156 return executeHeaderFns(reqHeaders, shallowCopy(config)); 16157 } 16158 } 16159 16160 $http.pendingRequests = []; 16161 16162 /** 16163 * @ngdoc method 16164 * @name $http#get 16165 * 16166 * @description 16167 * Shortcut method to perform `GET` request. 16168 * 16169 * @param {string} url Relative or absolute URL specifying the destination of the request 16170 * @param {Object=} config Optional configuration object 16171 * @returns {HttpPromise} Future object 16172 */ 16173 16174 /** 16175 * @ngdoc method 16176 * @name $http#delete 16177 * 16178 * @description 16179 * Shortcut method to perform `DELETE` request. 16180 * 16181 * @param {string} url Relative or absolute URL specifying the destination of the request 16182 * @param {Object=} config Optional configuration object 16183 * @returns {HttpPromise} Future object 16184 */ 16185 16186 /** 16187 * @ngdoc method 16188 * @name $http#head 16189 * 16190 * @description 16191 * Shortcut method to perform `HEAD` request. 16192 * 16193 * @param {string} url Relative or absolute URL specifying the destination of the request 16194 * @param {Object=} config Optional configuration object 16195 * @returns {HttpPromise} Future object 16196 */ 16197 16198 /** 16199 * @ngdoc method 16200 * @name $http#jsonp 16201 * 16202 * @description 16203 * Shortcut method to perform `JSONP` request. 16204 * 16205 * @param {string} url Relative or absolute URL specifying the destination of the request. 16206 * The name of the callback should be the string `JSON_CALLBACK`. 16207 * @param {Object=} config Optional configuration object 16208 * @returns {HttpPromise} Future object 16209 */ 16210 createShortMethods('get', 'delete', 'head', 'jsonp'); 16211 16212 /** 16213 * @ngdoc method 16214 * @name $http#post 16215 * 16216 * @description 16217 * Shortcut method to perform `POST` request. 16218 * 16219 * @param {string} url Relative or absolute URL specifying the destination of the request 16220 * @param {*} data Request content 16221 * @param {Object=} config Optional configuration object 16222 * @returns {HttpPromise} Future object 16223 */ 16224 16225 /** 16226 * @ngdoc method 16227 * @name $http#put 16228 * 16229 * @description 16230 * Shortcut method to perform `PUT` request. 16231 * 16232 * @param {string} url Relative or absolute URL specifying the destination of the request 16233 * @param {*} data Request content 16234 * @param {Object=} config Optional configuration object 16235 * @returns {HttpPromise} Future object 16236 */ 16237 16238 /** 16239 * @ngdoc method 16240 * @name $http#patch 16241 * 16242 * @description 16243 * Shortcut method to perform `PATCH` request. 16244 * 16245 * @param {string} url Relative or absolute URL specifying the destination of the request 16246 * @param {*} data Request content 16247 * @param {Object=} config Optional configuration object 16248 * @returns {HttpPromise} Future object 16249 */ 16250 createShortMethodsWithData('post', 'put', 'patch'); 16251 16252 /** 16253 * @ngdoc property 16254 * @name $http#defaults 16255 * 16256 * @description 16257 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 16258 * default headers, withCredentials as well as request and response transformations. 16259 * 16260 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 16261 */ 16262 $http.defaults = defaults; 16263 16264 16265 return $http; 16266 16267 16268 function createShortMethods(names) { 16269 forEach(arguments, function(name) { 16270 $http[name] = function(url, config) { 16271 return $http(extend({}, config || {}, { 16272 method: name, 16273 url: url 16274 })); 16275 }; 16276 }); 16277 } 16278 16279 16280 function createShortMethodsWithData(name) { 16281 forEach(arguments, function(name) { 16282 $http[name] = function(url, data, config) { 16283 return $http(extend({}, config || {}, { 16284 method: name, 16285 url: url, 16286 data: data 16287 })); 16288 }; 16289 }); 16290 } 16291 16292 16293 /** 16294 * Makes the request. 16295 * 16296 * !!! ACCESSES CLOSURE VARS: 16297 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 16298 */ 16299 function sendReq(config, reqData) { 16300 var deferred = $q.defer(), 16301 promise = deferred.promise, 16302 cache, 16303 cachedResp, 16304 reqHeaders = config.headers, 16305 url = buildUrl(config.url, config.paramSerializer(config.params)); 16306 16307 $http.pendingRequests.push(config); 16308 promise.then(removePendingReq, removePendingReq); 16309 16310 16311 if ((config.cache || defaults.cache) && config.cache !== false && 16312 (config.method === 'GET' || config.method === 'JSONP')) { 16313 cache = isObject(config.cache) ? config.cache 16314 : isObject(defaults.cache) ? defaults.cache 16315 : defaultCache; 16316 } 16317 16318 if (cache) { 16319 cachedResp = cache.get(url); 16320 if (isDefined(cachedResp)) { 16321 if (isPromiseLike(cachedResp)) { 16322 // cached request has already been sent, but there is no response yet 16323 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 16324 } else { 16325 // serving from cache 16326 if (isArray(cachedResp)) { 16327 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 16328 } else { 16329 resolvePromise(cachedResp, 200, {}, 'OK'); 16330 } 16331 } 16332 } else { 16333 // put the promise for the non-transformed response into cache as a placeholder 16334 cache.put(url, promise); 16335 } 16336 } 16337 16338 16339 // if we won't have the response in cache, set the xsrf headers and 16340 // send the request to the backend 16341 if (isUndefined(cachedResp)) { 16342 var xsrfValue = urlIsSameOrigin(config.url) 16343 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 16344 : undefined; 16345 if (xsrfValue) { 16346 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 16347 } 16348 16349 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 16350 config.withCredentials, config.responseType); 16351 } 16352 16353 return promise; 16354 16355 16356 /** 16357 * Callback registered to $httpBackend(): 16358 * - caches the response if desired 16359 * - resolves the raw $http promise 16360 * - calls $apply 16361 */ 16362 function done(status, response, headersString, statusText) { 16363 if (cache) { 16364 if (isSuccess(status)) { 16365 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 16366 } else { 16367 // remove promise from the cache 16368 cache.remove(url); 16369 } 16370 } 16371 16372 function resolveHttpPromise() { 16373 resolvePromise(response, status, headersString, statusText); 16374 } 16375 16376 if (useApplyAsync) { 16377 $rootScope.$applyAsync(resolveHttpPromise); 16378 } else { 16379 resolveHttpPromise(); 16380 if (!$rootScope.$$phase) $rootScope.$apply(); 16381 } 16382 } 16383 16384 16385 /** 16386 * Resolves the raw $http promise. 16387 */ 16388 function resolvePromise(response, status, headers, statusText) { 16389 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 16390 status = status >= -1 ? status : 0; 16391 16392 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 16393 data: response, 16394 status: status, 16395 headers: headersGetter(headers), 16396 config: config, 16397 statusText: statusText 16398 }); 16399 } 16400 16401 function resolvePromiseWithResult(result) { 16402 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 16403 } 16404 16405 function removePendingReq() { 16406 var idx = $http.pendingRequests.indexOf(config); 16407 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 16408 } 16409 } 16410 16411 16412 function buildUrl(url, serializedParams) { 16413 if (serializedParams.length > 0) { 16414 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 16415 } 16416 return url; 16417 } 16418 }]; 16419} 16420 16421/** 16422 * @ngdoc service 16423 * @name $xhrFactory 16424 * 16425 * @description 16426 * Factory function used to create XMLHttpRequest objects. 16427 * 16428 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 16429 * 16430 * ``` 16431 * angular.module('myApp', []) 16432 * .factory('$xhrFactory', function() { 16433 * return function createXhr(method, url) { 16434 * return new window.XMLHttpRequest({mozSystem: true}); 16435 * }; 16436 * }); 16437 * ``` 16438 * 16439 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 16440 * @param {string} url URL of the request. 16441 */
16442function $xhrFactoryProvider() { 16443 this.$get = function() { 16444 return function createXhr() { 16445 return new window.XMLHttpRequest(); 16446 }; 16447 }; 16448} 16449 16450/** 16451 * @ngdoc service 16452 * @name $httpBackend 16453 * @requires $window 16454 * @requires $document 16455 * @requires $xhrFactory 16456 * 16457 * @description 16458 * HTTP backend used by the {@link ng.$http service} that delegates to 16459 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 16460 * 16461 * You should never need to use this service directly, instead use the higher-level abstractions: 16462 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 16463 * 16464 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 16465 * $httpBackend} which can be trained with responses. 16466 */ 16467function $HttpBackendProvider() { 16468 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) { 16469 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]); 16470 }]; 16471} 16472 16473function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 16474 // TODO(vojta): fix the signature 16475 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 16476 $browser.$$incOutstandingRequestCount(); 16477 url = url || $browser.url(); 16478 16479 if (lowercase(method) == 'jsonp') { 16480 var callbackId = '_' + (callbacks.counter++).toString(36); 16481 callbacks[callbackId] = function(data) { 16482 callbacks[callbackId].data = data; 16483 callbacks[callbackId].called = true; 16484 }; 16485 16486 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 16487 callbackId, function(status, text) { 16488 completeRequest(callback, status, callbacks[callbackId].data, "", text); 16489 callbacks[callbackId] = noop; 16490 }); 16491 } else { 16492 16493 var xhr = createXhr(method, url); 16494 16495 xhr.open(method, url, true); 16496 forEach(headers, function(value, key) { 16497 if (isDefined(value)) { 16498 xhr.setRequestHeader(key, value); 16499 } 16500 }); 16501 16502 xhr.onload = function requestLoaded() { 16503 var statusText = xhr.statusText || ''; 16504 16505 // responseText is the old-school way of retrieving response (supported by IE9) 16506 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 16507 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 16508 16509 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 16510 var status = xhr.status === 1223 ? 204 : xhr.status; 16511 16512 // fix status code when it is 0 (0 status is undocumented). 16513 // Occurs when accessing file resources or on Android 4.1 stock browser 16514 // while retrieving files from application cache. 16515 if (status === 0) { 16516 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 16517 } 16518 16519 completeRequest(callback, 16520 status, 16521 response, 16522 xhr.getAllResponseHeaders(), 16523 statusText); 16524 }; 16525 16526 var requestError = function() { 16527 // The response is always empty 16528 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 16529 completeRequest(callback, -1, null, null, ''); 16530 }; 16531 16532 xhr.onerror = requestError; 16533 xhr.onabort = requestError; 16534 16535 if (withCredentials) { 16536 xhr.withCredentials = true; 16537 } 16538 16539 if (responseType) { 16540 try { 16541 xhr.responseType = responseType; 16542 } catch (e) { 16543 // WebKit added support for the json responseType value on 09/03/2013 16544 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 16545 // known to throw when setting the value "json" as the response type. Other older 16546 // browsers implementing the responseType 16547 // 16548 // The json response type can be ignored if not supported, because JSON payloads are 16549 // parsed on the client-side regardless. 16550 if (responseType !== 'json') { 16551 throw e; 16552 } 16553 } 16554 } 16555
16556 xhr.send(isUndefined(post) ? null : post); 16557 } 16558 16559 if (timeout > 0) { 16560 var timeoutId = $browserDefer(timeoutRequest, timeout); 16561 } else if (isPromiseLike(timeout)) { 16562 timeout.then(timeoutRequest); 16563 } 16564 16565 16566 function timeoutRequest() { 16567 jsonpDone && jsonpDone(); 16568 xhr && xhr.abort(); 16569 } 16570 16571 function completeRequest(callback, status, response, headersString, statusText) { 16572 // cancel timeout and subsequent timeout promise resolution 16573 if (isDefined(timeoutId)) { 16574 $browserDefer.cancel(timeoutId); 16575 } 16576 jsonpDone = xhr = null; 16577 16578 callback(status, response, headersString, statusText); 16579 $browser.$$completeOutstandingRequest(noop); 16580 } 16581 }; 16582 16583 function jsonpReq(url, callbackId, done) { 16584 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 16585 // - fetches local scripts via XHR and evals them 16586 // - adds and immediately removes script elements from the document 16587 var script = rawDocument.createElement('script'), callback = null; 16588 script.type = "text/javascript"; 16589 script.src = url; 16590 script.async = true; 16591 16592 callback = function(event) { 16593 removeEventListenerFn(script, "load", callback); 16594 removeEventListenerFn(script, "error", callback); 16595 rawDocument.body.removeChild(script); 16596 script = null; 16597 var status = -1; 16598 var text = "unknown"; 16599 16600 if (event) { 16601 if (event.type === "load" && !callbacks[callbackId].called) { 16602 event = { type: "error" }; 16603 } 16604 text = event.type; 16605 status = event.type === "error" ? 404 : 200; 16606 } 16607 16608 if (done) { 16609 done(status, text); 16610 } 16611 }; 16612 16613 addEventListenerFn(script, "load", callback); 16614 addEventListenerFn(script, "error", callback); 16615 rawDocument.body.appendChild(script); 16616 return callback; 16617 } 16618} 16619 16620var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 16621$interpolateMinErr.throwNoconcat = function(text) { 16622 throw $interpolateMinErr('noconcat', 16623 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 16624 "interpolations that concatenate multiple expressions when a trusted value is " + 16625 "required. See http://docs.angularjs.org/api/ng.$sce", text); 16626}; 16627 16628$interpolateMinErr.interr = function(text, err) { 16629 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 16630}; 16631 16632/** 16633 * @ngdoc provider 16634 * @name $interpolateProvider 16635 * 16636 * @description 16637 *
16638 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 16639 * 16640 * <div class="alert alert-danger"> 16641 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular 16642 * template within a Python Jinja template (or any other template language). Mixing templating 16643 * languages is **very dangerous**. The embedding template language will not safely escape Angular 16644 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) 16645 * security bugs! 16646 * </div> 16647 * 16648 * @example 16649<example name="custom-interpolation-markup" module="customInterpolationApp"> 16650<file name="index.html"> 16651<script> 16652 var customInterpolationApp = angular.module('customInterpolationApp', []); 16653 16654 customInterpolationApp.config(function($interpolateProvider) { 16655 $interpolateProvider.startSymbol('//'); 16656 $interpolateProvider.endSymbol('//'); 16657 }); 16658 16659 16660 customInterpolationApp.controller('DemoController', function() { 16661 this.label = "This binding is brought you by // interpolation symbols."; 16662 }); 16663</script> 16664<div ng-controller="DemoController as demo"> 16665 //demo.label// 16666</div> 16667</file> 16668<file name="protractor.js" type="protractor"> 16669 it('should interpolate binding with custom symbols', function() { 16670 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 16671 }); 16672</file> 16673</example> 16674 */ 16675function $InterpolateProvider() { 16676 var startSymbol = '{{'; 16677 var endSymbol = '}}'; 16678 16679 /** 16680 * @ngdoc method 16681 * @name $interpolateProvider#startSymbol 16682 * @description 16683 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 16684 * 16685 * @param {string=} value new value to set the starting symbol to. 16686 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 16687 */ 16688 this.startSymbol = function(value) { 16689 if (value) { 16690 startSymbol = value; 16691 return this; 16692 } else { 16693 return startSymbol; 16694 } 16695 }; 16696 16697 /** 16698 * @ngdoc method 16699 * @name $interpolateProvider#endSymbol 16700 * @description 16701 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 16702 * 16703 * @param {string=} value new value to set the ending symbol to. 16704 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 16705 */ 16706 this.endSymbol = function(value) { 16707 if (value) { 16708 endSymbol = value; 16709 return this; 16710 } else { 16711 return endSymbol; 16712 } 16713 }; 16714 16715 16716 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 16717 var startSymbolLength = startSymbol.length, 16718 endSymbolLength = endSymbol.length, 16719 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 16720 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 16721 16722 function escape(ch) { 16723 return '\\\\\\' + ch; 16724 } 16725 16726 function unescapeText(text) { 16727 return text.replace(escapedStartRegexp, startSymbol). 16728 replace(escapedEndRegexp, endSymbol); 16729 } 16730 16731 function stringify(value) { 16732 if (value == null) { // null || undefined 16733 return ''; 16734 } 16735 switch (typeof value) { 16736 case 'string': 16737 break; 16738 case 'number': 16739 value = '' + value; 16740 break; 16741 default: 16742 value = toJson(value); 16743 } 16744 16745 return value; 16746 } 16747 16748 //TODO: this is the same as the constantWatchDelegate in parse.js 16749 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { 16750 var unwatch; 16751 return unwatch = scope.$watch(function constantInterpolateWatch(scope) { 16752 unwatch(); 16753 return constantInterp(scope); 16754 }, listener, objectEquality); 16755 } 16756 16757 /** 16758 * @ngdoc service 16759 * @name $interpolate 16760 * @kind function 16761 * 16762 * @requires $parse 16763 * @requires $sce 16764 * 16765 * @description 16766 * 16767 * Compiles a string with markup into an interpolation function. This service is used by the 16768 * HTML {@link ng.$compile $compile} service for data binding. See 16769 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 16770 * interpolation markup. 16771 * 16772 * 16773 * ```js 16774 * var $interpolate = ...; // injected 16775 * var exp = $interpolate('Hello {{name | uppercase}}!'); 16776 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); 16777 * ``` 16778 * 16779 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 16780 * `true`, the interpolation function will return `undefined` unless all embedded expressions 16781 * evaluate to a value other than `undefined`. 16782 * 16783 * ```js 16784 * var $interpolate = ...; // injected 16785 * var context = {greeting: 'Hello', name: undefined }; 16786 * 16787 * // default "forgiving" mode 16788 * var exp = $interpolate('{{greeting}} {{name}}!'); 16789 * expect(exp(context)).toEqual('Hello !'); 16790 * 16791 * // "allOrNothing" mode 16792 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 16793 * expect(exp(context)).toBeUndefined(); 16794 * context.name = 'Angular'; 16795 * expect(exp(context)).toEqual('Hello Angular!'); 16796 * ``` 16797 * 16798 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 16799 * 16800 * ####Escaped Interpolation 16801 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 16802 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 16803 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 16804 * or binding. 16805 * 16806 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 16807 * degree, while also enabling code examples to work without relying on the 16808 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 16809 * 16810 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 16811 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 16812 * interpolation start/end markers with their escaped counterparts.** 16813 * 16814 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 16815 * output when the $interpolate service processes the text. So, for HTML elements interpolated 16816 * by {@link ng.$compile $compile}
16816, or otherwise interpolated with the `mustHaveExpression` parameter 16817 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 16818 * this is typically useful only when user-data is used in rendering a template from the server, or 16819 * when otherwise untrusted data is used by a directive. 16820 * 16821 * <example> 16822 * <file name="index.html"> 16823 * <div ng-init="username='A user'"> 16824 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 16825 * </p> 16826 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 16827 * application, but fails to accomplish their task, because the server has correctly 16828 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 16829 * characters.</p> 16830 * <p>Instead, the result of the attempted script injection is visible, and can be removed 16831 * from the database by an administrator.</p> 16832 * </div> 16833 * </file> 16834 * </example> 16835 * 16836 * @param {string} text The text with markup to interpolate. 16837 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 16838 * embedded expression in order to return an interpolation function. Strings with no 16839 * embedded expression will return null for the interpolation function. 16840 * @param {string=} trustedContext when provided, the returned function passes the interpolated 16841 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 16842 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 16843 * provides Strict Contextual Escaping for details. 16844 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 16845 * unless all embedded expressions evaluate to a value other than `undefined`. 16846 * @returns {function(context)} an interpolation function which is used to compute the 16847 * interpolated string. The function has these parameters: 16848 * 16849 * - `context`: evaluation context for all expressions embedded in the interpolated text 16850 */ 16851 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 16852 // Provide a quick exit and simplified result function for text with no interpolation 16853 if (!text.length || text.indexOf(startSymbol) === -1) { 16854 var constantInterp; 16855 if (!mustHaveExpression) { 16856 var unescapedText = unescapeText(text); 16857 constantInterp = valueFn(unescapedText); 16858 constantInterp.exp = text; 16859 constantInterp.expressions = []; 16860 constantInterp.$$watchDelegate = constantWatchDelegate; 16861 } 16862 return constantInterp; 16863 } 16864 16865 allOrNothing = !!allOrNothing; 16866 var startIndex, 16867 endIndex, 16868 index = 0, 16869 expressions = [], 16870 parseFns = [], 16871 textLength = text.length, 16872 exp, 16873 concat = [], 16874 expressionPositions = []; 16875 16876 while (index < textLength) { 16877 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 16878 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 16879 if (index !== startIndex) { 16880 concat.push(unescapeText(text.substring(index, startIndex))); 16881 } 16882 exp = text.substring(startIndex + startSymbolLength, endIndex); 16883 expressions.push(exp); 16884 parseFns.push($parse(exp, parseStringifyInterceptor)); 16885 index = endIndex + endSymbolLength; 16886 expressionPositions.push(concat.length); 16887 concat.push(''); 16888 } else { 16889 // we did not find an interpolation, so we have to add the remainder to the separators array 16890 if (index !== textLength) { 16891 concat.push(unescapeText(text.substring(index))); 16892 } 16893 break; 16894 } 16895 } 16896 16897 // Concatenating expressions makes it hard to reason about whether some combination of 16898 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 16899 // single expression be used for iframe[src], object[src], etc., we ensure that the value 16900 // that's used is assigned or constructed by some JS code somewhere that is more testable or 16901 // make it obvious that you bound the value to some user controlled value. This helps reduce 16902 // the load when auditing for XSS issues. 16903 if (trustedContext && concat.length > 1) { 16904 $interpolateMinErr.throwNoconcat(text); 16905 } 16906 16907 if (!mustHaveExpression || expressions.length) { 16908 var compute = function(values) { 16909 for (var i = 0, ii = expressions.length; i < ii; i++) { 16910 if (allOrNothing && isUndefined(values[i])) return; 16911 concat[expressionPositions[i]] = values[i]; 16912 } 16913 return concat.join(''); 16914 }; 16915 16916 var getValue = function(value) { 16917 return trustedContext ? 16918 $sce.getTrusted(trustedContext, value) : 16919 $sce.valueOf(value); 16920 }; 16921 16922 return extend(function interpolationFn(context) { 16923 var i = 0; 16924 var ii = expressions.length; 16925 var values = new Array(ii); 16926 16927 try { 16928 for (; i < ii; i++) { 16929 values[i] = parseFns[i](context); 16930 } 16931 16932 return compute(values); 16933 } catch (err) { 16934 $exceptionHandler($interpolateMinErr.interr(text, err)); 16935 } 16936 16937 }, { 16938 // all of these properties are undocumented for now 16939 exp: text, //just for compatibility with regular watchers created via $watch 16940 expressions: expressions, 16941 $$watchDelegate: function(scope, listener) { 16942 var lastValue; 16943 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 16944 var currValue = compute(values); 16945 if (isFunction(listener)) { 16946 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 16947 }
16948 lastValue = currValue; 16949 }); 16950 } 16951 }); 16952 } 16953 16954 function parseStringifyInterceptor(value) { 16955 try { 16956 value = getValue(value); 16957 return allOrNothing && !isDefined(value) ? value : stringify(value); 16958 } catch (err) { 16959 $exceptionHandler($interpolateMinErr.interr(text, err)); 16960 } 16961 } 16962 } 16963 16964 16965 /** 16966 * @ngdoc method 16967 * @name $interpolate#startSymbol 16968 * @description 16969 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 16970 * 16971 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 16972 * the symbol. 16973 * 16974 * @returns {string} start symbol. 16975 */ 16976 $interpolate.startSymbol = function() { 16977 return startSymbol; 16978 }; 16979 16980 16981 /** 16982 * @ngdoc method 16983 * @name $interpolate#endSymbol 16984 * @description 16985 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 16986 * 16987 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 16988 * the symbol. 16989 * 16990 * @returns {string} end symbol. 16991 */ 16992 $interpolate.endSymbol = function() { 16993 return endSymbol; 16994 }; 16995 16996 return $interpolate; 16997 }]; 16998} 16999 17000function $IntervalProvider() { 17001 this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', 17002 function($rootScope, $window, $q, $$q, $browser) { 17003 var intervals = {}; 17004 17005 17006 /** 17007 * @ngdoc service 17008 * @name $interval 17009 * 17010 * @description 17011 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 17012 * milliseconds. 17013 * 17014 * The return value of registering an interval function is a promise. This promise will be 17015 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 17016 * run indefinitely if `count` is not defined. The value of the notification will be the 17017 * number of iterations that have run. 17018 * To cancel an interval, call `$interval.cancel(promise)`. 17019 * 17020 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 17021 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 17022 * time. 17023 * 17024 * <div class="alert alert-warning"> 17025 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 17026 * with them. In particular they are not automatically destroyed when a controller's scope or a 17027 * directive's element are destroyed. 17028 * You should take this into consideration and make sure to always cancel the interval at the 17029 * appropriate moment. See the example below for more details on how and when to do this. 17030 * </div> 17031 * 17032 * @param {function()} fn A function that should be called repeatedly. 17033 * @param {number} delay Number of milliseconds between each function call. 17034 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 17035 * indefinitely. 17036 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 17037 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 17038 * @param {...*=} Pass additional parameters to the executed function. 17039 * @returns {promise} A promise which will be notified on each iteration. 17040 * 17041 * @example 17042 * <example module="intervalExample"> 17043 * <file name="index.html"> 17044 * <script> 17045 * angular.module('intervalExample', []) 17046 * .controller('ExampleController', ['$scope', '$interval', 17047 * function($scope, $interval) { 17048 * $scope.format = 'M/d/yy h:mm:ss a'; 17049 * $scope.blood_1 = 100; 17050 * $scope.blood_2 = 120; 17051 * 17052 * var stop; 17053 * $scope.fight = function() { 17054 * // Don't start a new fight if we are already fighting 17055 * if ( angular.isDefined(stop) ) return; 17056 * 17057 * stop = $interval(function() { 17058 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 17059 * $scope.blood_1 = $scope.blood_1 - 3; 17060 * $scope.blood_2 = $scope.blood_2 - 4; 17061 * } else { 17062 * $scope.stopFight(); 17063 * } 17064 * }, 100); 17065 * }; 17066 * 17067 * $scope.stopFight = function() { 17068 * if (angular.isDefined(stop)) { 17069 * $interval.cancel(stop); 17070 * stop = undefined; 17071 * } 17072 * }; 17073 * 17074 * $scope.resetFight = function() { 17075 * $scope.blood_1 = 100; 17076 * $scope.blood_2 = 120; 17077 * }; 17078 * 17079 * $scope.$on('$destroy', function() { 17080 * // Make sure that the interval is destroyed too 17081 * $scope.stopFight(); 17082 * }); 17083 * }]) 17084 * // Register the 'myCurrentTime' directive factory method. 17085 * // We inject $interval and dateFilter service since the factory method is DI. 17086 * .directive('myCurrentTime', ['$interval', 'dateFilter', 17087 * function($interval, dateFilter) { 17088 * // return the directive link function. (compile function not needed) 17089 * return function(scope, element, attrs) { 17090 * var format, // date format 17091 * stopTime; // so that we can cancel the time updates 17092 * 17093 * // used to update the UI 17094 * function updateTime() { 17095 * element.text(dateFilter(new Date(), format)); 17096 * } 17097 * 17098 * // watch the expression, and update the UI on change. 17099 * scope.$watch(attrs.myCurrentTime, function(value) { 17100 * format = value; 17101 * updateTime(); 17102 * }); 17103 * 17104 * stopTime = $interval(updateTime, 1000); 17105 * 17106 * // listen on DOM destroy (removal) event, and cancel the next UI update 17107 * // to prevent updating time after the DOM element was removed. 17108 * element.on('$destroy', function() { 17109 * $interval.cancel(stopTime); 17110 * }); 17111 * } 17112 * }]); 17113 * </script> 17114 * 17115 * <div>
17116 * <div ng-controller="ExampleController"> 17117 * <label>Date format: <input ng-model="format"></label> <hr/> 17118 * Current time is: <span my-current-time="format"></span> 17119 * <hr/> 17120 * Blood 1 : <font color='red'>{{blood_1}}</font> 17121 * Blood 2 : <font color='red'>{{blood_2}}</font> 17122 * <button type="button" data-ng-click="fight()">Fight</button> 17123 * <button type="button" data-ng-click="stopFight()">StopFight</button> 17124 * <button type="button" data-ng-click="resetFight()">resetFight</button> 17125 * </div> 17126 * </div> 17127 * 17128 * </file> 17129 * </example> 17130 */ 17131 function interval(fn, delay, count, invokeApply) { 17132 var hasParams = arguments.length > 4, 17133 args = hasParams ? sliceArgs(arguments, 4) : [], 17134 setInterval = $window.setInterval, 17135 clearInterval = $window.clearInterval, 17136 iteration = 0, 17137 skipApply = (isDefined(invokeApply) && !invokeApply), 17138 deferred = (skipApply ? $$q : $q).defer(), 17139 promise = deferred.promise; 17140 17141 count = isDefined(count) ? count : 0; 17142 17143 promise.$$intervalId = setInterval(function tick() { 17144 if (skipApply) { 17145 $browser.defer(callback); 17146 } else { 17147 $rootScope.$evalAsync(callback); 17148 } 17149 deferred.notify(iteration++); 17150 17151 if (count > 0 && iteration >= count) { 17152 deferred.resolve(iteration); 17153 clearInterval(promise.$$intervalId); 17154 delete intervals[promise.$$intervalId]; 17155 } 17156 17157 if (!skipApply) $rootScope.$apply(); 17158 17159 }, delay); 17160 17161 intervals[promise.$$intervalId] = deferred; 17162 17163 return promise; 17164 17165 function callback() { 17166 if (!hasParams) { 17167 fn(iteration); 17168 } else { 17169 fn.apply(null, args); 17170 } 17171 } 17172 } 17173 17174 17175 /** 17176 * @ngdoc method 17177 * @name $interval#cancel 17178 * 17179 * @description 17180 * Cancels a task associated with the `promise`. 17181 * 17182 * @param {Promise=} promise returned by the `$interval` function. 17183 * @returns {boolean} Returns `true` if the task was successfully canceled. 17184 */ 17185 interval.cancel = function(promise) { 17186 if (promise && promise.$$intervalId in intervals) { 17187 intervals[promise.$$intervalId].reject('canceled'); 17188 $window.clearInterval(promise.$$intervalId); 17189 delete intervals[promise.$$intervalId]; 17190 return true; 17191 } 17192 return false; 17193 }; 17194 17195 return interval; 17196 }]; 17197} 17198 17199/** 17200 * @ngdoc service 17201 * @name $locale 17202 * 17203 * @description 17204 * $locale service provides localization rules for various Angular components. As of right now the 17205 * only public api is: 17206 * 17207 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 17208 */ 17209 17210var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 17211 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 17212var $locationMinErr = minErr('$location'); 17213 17214 17215/** 17216 * Encode path using encodeUriSegment, ignoring forward slashes 17217 * 17218 * @param {string} path Path to encode 17219 * @returns {string} 17220 */ 17221function encodePath(path) { 17222 var segments = path.split('/'), 17223 i = segments.length; 17224 17225 while (i--) { 17226 segments[i] = encodeUriSegment(segments[i]); 17227 } 17228 17229 return segments.join('/'); 17230} 17231 17232function parseAbsoluteUrl(absoluteUrl, locationObj) { 17233 var parsedUrl = urlResolve(absoluteUrl); 17234 17235 locationObj.$$protocol = parsedUrl.protocol; 17236 locationObj.$$host = parsedUrl.hostname; 17237 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 17238} 17239 17240 17241function parseAppUrl(relativeUrl, locationObj) { 17242 var prefixed = (relativeUrl.charAt(0) !== '/'); 17243 if (prefixed) { 17244 relativeUrl = '/' + relativeUrl; 17245 } 17246 var match = urlResolve(relativeUrl); 17247 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 17248 match.pathname.substring(1) : match.pathname); 17249 locationObj.$$search = parseKeyValue(match.search); 17250 locationObj.$$hash = decodeURIComponent(match.hash); 17251 17252 // make sure path starts with '/'; 17253 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 17254 locationObj.$$path = '/' + locationObj.$$path; 17255 } 17256} 17257 17258 17259/** 17260 * 17261 * @param {string} begin 17262 * @param {string} whole 17263 * @returns {string} returns text from whole after begin or undefined if it does not begin with 17264 * expected string. 17265 */ 17266function beginsWith(begin, whole) { 17267 if (whole.indexOf(begin) === 0) { 17268 return whole.substr(begin.length); 17269 } 17270} 17271 17272 17273function stripHash(url) { 17274 var index = url.indexOf('#'); 17275 return index == -1 ? url : url.substr(0, index); 17276} 17277 17278function trimEmptyHash(url) { 17279 return url.replace(/(#.+)|#$/, '$1'); 17280} 17281 17282 17283function stripFile(url) { 17284 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 17285} 17286 17287/* return the server only (scheme://host:port) */ 17288function serverBase(url) { 17289 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 17290} 17291 17292 17293/** 17294 * LocationHtml5Url represents an url 17295 * This object is exposed as $location service when HTML5 mode is enabled and supported 17296 * 17297 * @constructor 17298 * @param {string} appBase application base URL 17299 * @param {string} appBaseNoFile application base URL stripped of any filename 17300 * @param {string} basePrefix url path prefix 17301 */ 17302function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 17303 this.$$html5 = true; 17304 basePrefix = basePrefix || ''; 17305 parseAbsoluteUrl(appBase, this); 17306 17307 17308 /** 17309 * Parse given html5 (regular) url string into properties 17310 * @param {string} url HTML5 url 17311 * @private 17312 */ 17313 this.$$parse = function(url) { 17314 var pathUrl = beginsWith(appBaseNoFile, url); 17315 if (!isString(pathUrl)) { 17316 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 17317 appBaseNoFile); 17318 } 17319 17320 parseAppUrl(pathUrl, this); 17321 17322 if (!this.$$path) { 17323 this.$$path = '/'; 17324 } 17325 17326 this.$$compose(); 17327 }; 17328 17329 /** 17330 * Compose url and update `absUrl` property 17331 * @private 17332 */ 17333 this.$$compose = function() {
17334 var search = toKeyValue(this.$$search), 17335 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 17336 17337 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 17338 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 17339 }; 17340 17341 this.$$parseLinkUrl = function(url, relHref) { 17342 if (relHref && relHref[0] === '#') { 17343 // special case for links to hash fragments: 17344 // keep the old url and only replace the hash fragment 17345 this.hash(relHref.slice(1)); 17346 return true; 17347 } 17348 var appUrl, prevAppUrl; 17349 var rewrittenUrl; 17350 17351 if (isDefined(appUrl = beginsWith(appBase, url))) { 17352 prevAppUrl = appUrl; 17353 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) { 17354 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 17355 } else { 17356 rewrittenUrl = appBase + prevAppUrl; 17357 } 17358 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) { 17359 rewrittenUrl = appBaseNoFile + appUrl; 17360 } else if (appBaseNoFile == url + '/') { 17361 rewrittenUrl = appBaseNoFile; 17362 } 17363 if (rewrittenUrl) { 17364 this.$$parse(rewrittenUrl); 17365 } 17366 return !!rewrittenUrl; 17367 }; 17368} 17369 17370 17371/** 17372 * LocationHashbangUrl represents url 17373 * This object is exposed as $location service when developer doesn't opt into html5 mode. 17374 * It also serves as the base class for html5 mode fallback on legacy browsers. 17375 * 17376 * @constructor 17377 * @param {string} appBase application base URL 17378 * @param {string} appBaseNoFile application base URL stripped of any filename 17379 * @param {string} hashPrefix hashbang prefix 17380 */ 17381function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 17382 17383 parseAbsoluteUrl(appBase, this); 17384 17385 17386 /** 17387 * Parse given hashbang url into properties 17388 * @param {string} url Hashbang url 17389 * @private 17390 */ 17391 this.$$parse = function(url) { 17392 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 17393 var withoutHashUrl; 17394 17395 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 17396 17397 // The rest of the url starts with a hash so we have 17398 // got either a hashbang path or a plain hash fragment 17399 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 17400 if (isUndefined(withoutHashUrl)) { 17401 // There was no hashbang prefix so we just have a hash fragment 17402 withoutHashUrl = withoutBaseUrl; 17403 } 17404 17405 } else { 17406 // There was no hashbang path nor hash fragment: 17407 // If we are in HTML5 mode we use what is left as the path; 17408 // Otherwise we ignore what is left 17409 if (this.$$html5) { 17410 withoutHashUrl = withoutBaseUrl; 17411 } else { 17412 withoutHashUrl = ''; 17413 if (isUndefined(withoutBaseUrl)) { 17414 appBase = url; 17415 this.replace(); 17416 } 17417 } 17418 } 17419 17420 parseAppUrl(withoutHashUrl, this); 17421 17422 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 17423 17424 this.$$compose(); 17425 17426 /* 17427 * In Windows, on an anchor node on documents loaded from 17428 * the filesystem, the browser will return a pathname 17429 * prefixed with the drive name ('/C:/path') when a 17430 * pathname without a drive is set: 17431 * * a.setAttribute('href', '/foo') 17432 * * a.pathname === '/C:/foo' //true 17433 * 17434 * Inside of Angular, we're always using pathnames that 17435 * do not include drive names for routing. 17436 */ 17437 function removeWindowsDriveName(path, url, base) { 17438 /* 17439 Matches paths for file protocol on windows, 17440 such as /C:/foo/bar, and captures only /foo/bar. 17441 */ 17442 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 17443 17444 var firstPathSegmentMatch; 17445 17446 //Get the relative path from the input URL. 17447 if (url.indexOf(base) === 0) { 17448 url = url.replace(base, ''); 17449 } 17450 17451 // The input URL intentionally contains a first path segment that ends with a colon. 17452 if (windowsFilePathExp.exec(url)) { 17453 return path; 17454 } 17455 17456 firstPathSegmentMatch = windowsFilePathExp.exec(path); 17457 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 17458 } 17459 }; 17460 17461 /** 17462 * Compose hashbang url and update `absUrl` property 17463 * @private 17464 */ 17465 this.$$compose = function() {
17466 var search = toKeyValue(this.$$search), 17467 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 17468 17469 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 17470 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 17471 }; 17472 17473 this.$$parseLinkUrl = function(url, relHref) { 17474 if (stripHash(appBase) == stripHash(url)) { 17475 this.$$parse(url); 17476 return true; 17477 } 17478 return false; 17479 }; 17480} 17481 17482 17483/** 17484 * LocationHashbangUrl represents url 17485 * This object is exposed as $location service when html5 history api is enabled but the browser 17486 * does not support it. 17487 * 17488 * @constructor 17489 * @param {string} appBase application base URL 17490 * @param {string} appBaseNoFile application base URL stripped of any filename 17491 * @param {string} hashPrefix hashbang prefix 17492 */ 17493function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 17494 this.$$html5 = true; 17495 LocationHashbangUrl.apply(this, arguments); 17496 17497 this.$$parseLinkUrl = function(url, relHref) { 17498 if (relHref && relHref[0] === '#') { 17499 // special case for links to hash fragments: 17500 // keep the old url and only replace the hash fragment 17501 this.hash(relHref.slice(1)); 17502 return true; 17503 } 17504 17505 var rewrittenUrl; 17506 var appUrl; 17507 17508 if (appBase == stripHash(url)) { 17509 rewrittenUrl = url; 17510 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 17511 rewrittenUrl = appBase + hashPrefix + appUrl; 17512 } else if (appBaseNoFile === url + '/') { 17513 rewrittenUrl = appBaseNoFile; 17514 } 17515 if (rewrittenUrl) { 17516 this.$$parse(rewrittenUrl); 17517 } 17518 return !!rewrittenUrl; 17519 }; 17520 17521 this.$$compose = function() { 17522 var search = toKeyValue(this.$$search), 17523 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 17524 17525 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 17526 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 17527 this.$$absUrl = appBase + hashPrefix + this.$$url; 17528 }; 17529 17530} 17531 17532 17533var locationPrototype = { 17534 17535 /** 17536 * Are we in html5 mode? 17537 * @private 17538 */ 17539 $$html5: false, 17540 17541 /** 17542 * Has any change been replacing? 17543 * @private 17544 */ 17545 $$replace: false, 17546 17547 /** 17548 * @ngdoc method 17549 * @name $location#absUrl 17550 * 17551 * @description 17552 * This method is getter only. 17553 * 17554 * Return full url representation with all segments encoded according to rules specified in 17555 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 17556 * 17557 * 17558 * ```js 17559 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 17560 * var absUrl = $location.absUrl(); 17561 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 17562 * ``` 17563 * 17564 * @return {string} full url 17565 */ 17566 absUrl: locationGetter('$$absUrl'), 17567 17568 /** 17569 * @ngdoc method 17570 * @name $location#url 17571 * 17572 * @description 17573 * This method is getter / setter. 17574 * 17575 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 17576 * 17577 * Change path, search and hash, when called with parameter and return `$location`. 17578 * 17579 * 17580 * ```js 17581 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 17582 * var url = $location.url(); 17583 * // => "/some/path?foo=bar&baz=xoxo" 17584 * ``` 17585 * 17586 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 17587 * @return {string} url 17588 */ 17589 url: function(url) { 17590 if (isUndefined(url)) { 17591 return this.$$url; 17592 } 17593 17594 var match = PATH_MATCH.exec(url); 17595 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 17596 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 17597 this.hash(match[5] || ''); 17598 17599 return this; 17600 }, 17601 17602 /** 17603 * @ngdoc method 17604 * @name $location#protocol 17605 * 17606 * @description 17607 * This method is getter only. 17608 * 17609 * Return protocol of current url. 17610 * 17611 * 17612 * ```js 17613 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 17614 * var protocol = $location.protocol(); 17615 * // => "http" 17616 * ``` 17617 * 17618 * @return {string} protocol of current url 17619 */ 17620 protocol: locationGetter('$$protocol'), 17621 17622 /** 17623 * @ngdoc method 17624 * @name $location#host 17625 * 17626 * @description 17627 * This method is getter only. 17628 * 17629 * Return host of current url. 17630 * 17631 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 17632 * 17633 * 17634 * ```js
17635 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 17636 * var host = $location.host(); 17637 * // => "example.com" 17638 * 17639 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 17640 * host = $location.host(); 17641 * // => "example.com" 17642 * host = location.host; 17643 * // => "example.com:8080" 17644 * ``` 17645 * 17646 * @return {string} host of current url. 17647 */ 17648 host: locationGetter('$$host'), 17649 17650 /** 17651 * @ngdoc method 17652 * @name $location#port 17653 * 17654 * @description 17655 * This method is getter only. 17656 * 17657 * Return port of current url. 17658 * 17659 * 17660 * ```js 17661 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 17662 * var port = $location.port(); 17663 * // => 80 17664 * ``` 17665 * 17666 * @return {Number} port 17667 */ 17668 port: locationGetter('$$port'), 17669 17670 /** 17671 * @ngdoc method 17672 * @name $location#path 17673 * 17674 * @description 17675 * This method is getter / setter. 17676 * 17677 * Return path of current url when called without any parameter. 17678 * 17679 * Change path when called with parameter and return `$location`. 17680 * 17681 * Note: Path should always begin with forward slash (/), this method will add the forward slash 17682 * if it is missing. 17683 * 17684 * 17685 * ```js 17686 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 17687 * var path = $location.path(); 17688 * // => "/some/path" 17689 * ``` 17690 * 17691 * @param {(string|number)=} path New path 17692 * @return {string} path 17693 */ 17694 path: locationGetterSetter('$$path', function(path) { 17695 path = path !== null ? path.toString() : ''; 17696 return path.charAt(0) == '/' ? path : '/' + path; 17697 }), 17698 17699 /** 17700 * @ngdoc method 17701 * @name $location#search 17702 * 17703 * @description 17704 * This method is getter / setter. 17705 * 17706 * Return search part (as object) of current url when called without any parameter. 17707 * 17708 * Change search part when called with parameter and return `$location`. 17709 * 17710 * 17711 * ```js 17712 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 17713 * var searchObject = $location.search(); 17714 * // => {foo: 'bar', baz: 'xoxo'} 17715 * 17716 * // set foo to 'yipee' 17717 * $location.search('foo', 'yipee'); 17718 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 17719 * ``` 17720 * 17721 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 17722 * hash object. 17723 * 17724 * When called with a single argument the method acts as a setter, setting the `search` component 17725 * of `$location` to the specified value. 17726 * 17727 * If the argument is a hash object containing an array of values, these values will be encoded 17728 * as duplicate search parameters in the url. 17729 * 17730 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 17731 * will override only a single search property. 17732 * 17733 * If `paramValue` is an array, it will override the property of the `search` component of 17734 * `$location` specified via the first argument. 17735 * 17736 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 17737 * 17738 * If `paramValue` is `true`, the property specified via the first argument will be added with no 17739 * value nor trailing equal sign. 17740 * 17741 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 17742 * one or more arguments returns `$location` object itself. 17743 */ 17744 search: function(search, paramValue) { 17745 switch (arguments.length) { 17746 case 0: 17747 return this.$$search; 17748 case 1: 17749 if (isString(search) || isNumber(search)) { 17750 search = search.toString(); 17751 this.$$search = parseKeyValue(search); 17752 } else if (isObject(search)) { 17753 search = copy(search, {}); 17754 // remove object undefined or null properties 17755 forEach(search, function(value, key) { 17756 if (value == null) delete search[key]; 17757 }); 17758 17759 this.$$search = search; 17760 } else { 17761 throw $locationMinErr('isrcharg', 17762 'The first argument of the `$location#search()` call must be a string or an object.'); 17763 } 17764 break; 17765 default: 17766 if (isUndefined(paramValue) || paramValue === null) { 17767 delete this.$$search[search]; 17768 } else { 17769 this.$$search[search] = paramValue; 17770 } 17771 } 17772 17773 this.$$compose(); 17774 return this; 17775 }, 17776 17777 /** 17778 * @ngdoc method 17779 * @name $location#hash 17780 * 17781 * @description 17782 * This method is getter / setter. 17783 * 17784 * Returns the hash fragment when called without any parameters. 17785 * 17786 * Changes the hash fragment when called with a parameter and returns `$location`. 17787 * 17788 * 17789 * ```js
17790 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 17791 * var hash = $location.hash(); 17792 * // => "hashValue" 17793 * ``` 17794 * 17795 * @param {(string|number)=} hash New hash fragment 17796 * @return {string} hash 17797 */ 17798 hash: locationGetterSetter('$$hash', function(hash) { 17799 return hash !== null ? hash.toString() : ''; 17800 }), 17801 17802 /** 17803 * @ngdoc method 17804 * @name $location#replace 17805 * 17806 * @description 17807 * If called, all changes to $location during the current `$digest` will replace the current history 17808 * record, instead of adding a new one. 17809 */ 17810 replace: function() { 17811 this.$$replace = true; 17812 return this; 17813 } 17814}; 17815 17816forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 17817 Location.prototype = Object.create(locationPrototype); 17818 17819 /** 17820 * @ngdoc method 17821 * @name $location#state 17822 * 17823 * @description 17824 * This method is getter / setter. 17825 * 17826 * Return the history state object when called without any parameter. 17827 * 17828 * Change the history state object when called with one parameter and return `$location`. 17829 * The state object is later passed to `pushState` or `replaceState`. 17830 * 17831 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 17832 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 17833 * older browsers (like IE9 or Android < 4.0), don't use this method. 17834 * 17835 * @param {object=} state State object for pushState or replaceState 17836 * @return {object} state 17837 */ 17838 Location.prototype.state = function(state) { 17839 if (!arguments.length) { 17840 return this.$$state; 17841 } 17842 17843 if (Location !== LocationHtml5Url || !this.$$html5) { 17844 throw $locationMinErr('nostate', 'History API state support is available only ' + 17845 'in HTML5 mode and only in browsers supporting HTML5 History API'); 17846 } 17847 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 17848 // but we're changing the $$state reference to $browser.state() during the $digest 17849 // so the modification window is narrow. 17850 this.$$state = isUndefined(state) ? null : state; 17851 17852 return this; 17853 }; 17854}); 17855 17856 17857function locationGetter(property) { 17858 return function() { 17859 return this[property]; 17860 }; 17861} 17862 17863 17864function locationGetterSetter(property, preprocess) { 17865 return function(value) { 17866 if (isUndefined(value)) { 17867 return this[property]; 17868 } 17869 17870 this[property] = preprocess(value); 17871 this.$$compose(); 17872 17873 return this; 17874 }; 17875} 17876 17877 17878/** 17879 * @ngdoc service 17880 * @name $location 17881 * 17882 * @requires $rootElement 17883 * 17884 * @description 17885 * The $location service parses the URL in the browser address bar (based on the 17886 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 17887 * available to your application. Changes to the URL in the address bar are reflected into 17888 * $location service and changes to $location are reflected into the browser address bar. 17889 * 17890 * **The $location service:** 17891 * 17892 * - Exposes the current URL in the browser address bar, so you can 17893 * - Watch and observe the URL. 17894 * - Change the URL. 17895 * - Synchronizes the URL with the browser when the user 17896 * - Changes the address bar. 17897 * - Clicks the back or forward button (or clicks a History link). 17898 * - Clicks on a link. 17899 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 17900 * 17901 * For more information see {@link guide/$location Developer Guide: Using $location} 17902 */ 17903 17904/** 17905 * @ngdoc provider 17906 * @name $locationProvider 17907 * @description 17908 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 17909 */ 17910function $LocationProvider() { 17911 var hashPrefix = '', 17912 html5Mode = { 17913 enabled: false, 17914 requireBase: true, 17915 rewriteLinks: true 17916 }; 17917 17918 /** 17919 * @ngdoc method 17920 * @name $locationProvider#hashPrefix 17921 * @description 17922 * @param {string=} prefix Prefix for hash part (containing path and search) 17923 * @returns {*} current value if used as getter or itself (chaining) if used as setter 17924 */ 17925 this.hashPrefix = function(prefix) { 17926 if (isDefined(prefix)) { 17927 hashPrefix = prefix; 17928 return this; 17929 } else { 17930 return hashPrefix; 17931 } 17932 }; 17933 17934 /** 17935 * @ngdoc method 17936 * @name $locationProvider#html5Mode 17937 * @description 17938 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 17939 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 17940 * properties: 17941 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 17942 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 17943 * support `pushState`. 17944 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 17945 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 17946 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 17947 * See the {@link guide/$location $location guide for more information} 17948 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 17949 * enables/disables url rewriting for relative links. 17950 * 17951 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 17952 */ 17953 this.html5Mode = function(mode) { 17954 if (isBoolean(mode)) { 17955 html5Mode.enabled = mode; 17956 return this; 17957 } else if (isObject(mode)) { 17958 17959 if (isBoolean(mode.enabled)) { 17960 html5Mode.enabled = mode.enabled; 17961 } 17962 17963 if (isBoolean(mode.requireBase)) { 17964 html5Mode.requireBase = mode.requireBase; 17965 } 17966 17967 if (isBoolean(mode.rewriteLinks)) { 17968 html5Mode.rewriteLinks = mode.rewriteLinks; 17969 } 17970 17971 return this; 17972 } else { 17973 return html5Mode; 17974 } 17975 }; 17976 17977 /** 17978 * @ngdoc event 17979 * @name $location#$locationChangeStart 17980 * @eventType broadcast on root scope 17981 * @description 17982 * Broadcasted before a URL will change. 17983 * 17984 * This change can be prevented by calling 17985 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 17986 * details about event object. Upon successful change 17987 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 17988 * 17989 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 17990 * the browser supports the HTML5 History API. 17991 * 17992 * @param {Object} angularEvent Synthetic event object. 17993 * @param {string} newUrl New URL 17994 * @param {string=} oldUrl URL that was before it was changed. 17995 * @param {string=} newState New history state object 17996 * @param {string=} oldState History state object that was before it was changed. 17997 */ 17998 17999 /** 18000 * @ngdoc event 18001 * @name $location#$locationChangeSuccess 18002 * @eventType broadcast on root scope 18003 * @description 18004 * Broadcasted after a URL was changed. 18005 * 18006 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 18007 * the browser supports the HTML5 History API. 18008 * 18009 * @param {Object} angularEvent Synthetic event object. 18010 * @param {string} newUrl New URL 18011 * @param {string=} oldUrl URL that was before it was changed. 18012 * @param {string=} newState New history state object 18013 * @param {string=} oldState History state object that was before it was changed. 18014 */ 18015 18016 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 18017 function($rootScope, $browser, $sniffer, $rootElement, $window) { 18018 var $location, 18019 LocationMode, 18020 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 18021 initialUrl = $browser.url(), 18022 appBase; 18023 18024 if (html5Mode.enabled) { 18025 if (!baseHref && html5Mode.requireBase) { 18026 throw $locationMinErr('nobase', 18027 "$location in HTML5 mode requires a <base> tag to be present!"); 18028 } 18029 appBase = serverBase(initialUrl) + (baseHref || '/'); 18030 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 18031 } else { 18032 appBase = stripHash(initialUrl); 18033 LocationMode = LocationHashbangUrl; 18034 } 18035 var appBaseNoFile = stripFile(appBase); 18036
18037 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 18038 $location.$$parseLinkUrl(initialUrl, initialUrl); 18039 18040 $location.$$state = $browser.state(); 18041 18042 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 18043 18044 function setBrowserUrlWithFallback(url, replace, state) { 18045 var oldUrl = $location.url(); 18046 var oldState = $location.$$state; 18047 try { 18048 $browser.url(url, replace, state); 18049 18050 // Make sure $location.state() returns referentially identical (not just deeply equal) 18051 // state object; this makes possible quick checking if the state changed in the digest 18052 // loop. Checking deep equality would be too expensive. 18053 $location.$$state = $browser.state(); 18054 } catch (e) { 18055 // Restore old values if pushState fails 18056 $location.url(oldUrl); 18057 $location.$$state = oldState; 18058 18059 throw e; 18060 } 18061 } 18062 18063 $rootElement.on('click', function(event) { 18064 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 18065 // currently we open nice url link and redirect then 18066 18067 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 18068 18069 var elm = jqLite(event.target); 18070 18071 // traverse the DOM up to find first A tag 18072 while (nodeName_(elm[0]) !== 'a') { 18073 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 18074 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 18075 } 18076 18077 var absHref = elm.prop('href'); 18078 // get the actual href attribute - see 18079 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 18080 var relHref = elm.attr('href') || elm.attr('xlink:href'); 18081 18082 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 18083 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 18084 // an animation. 18085 absHref = urlResolve(absHref.animVal).href; 18086 } 18087 18088 // Ignore when url is started with javascript: or mailto: 18089 if (IGNORE_URI_REGEXP.test(absHref)) return; 18090 18091 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 18092 if ($location.$$parseLinkUrl(absHref, relHref)) { 18093 // We do a preventDefault for all urls that are part of the angular application, 18094 // in html5mode and also without, so that we are able to abort navigation without 18095 // getting double entries in the location history. 18096 event.preventDefault(); 18097 // update location manually 18098 if ($location.absUrl() != $browser.url()) { 18099 $rootScope.$apply(); 18100 // hack to work around FF6 bug 684208 when scenario runner clicks on links 18101 $window.angular['ff-684208-preventDefault'] = true; 18102 } 18103 } 18104 } 18105 }); 18106 18107 18108 // rewrite hashbang url <> html5 url 18109 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 18110 $browser.url($location.absUrl(), true); 18111 } 18112 18113 var initializing = true; 18114 18115 // update $location when $browser url changes 18116 $browser.onUrlChange(function(newUrl, newState) { 18117 18118 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { 18119 // If we are navigating outside of the app then force a reload 18120 $window.location.href = newUrl; 18121 return; 18122 } 18123 18124 $rootScope.$evalAsync(function() { 18125 var oldUrl = $location.absUrl(); 18126 var oldState = $location.$$state; 18127 var defaultPrevented; 18128 newUrl = trimEmptyHash(newUrl); 18129 $location.$$parse(newUrl); 18130 $location.$$state = newState; 18131 18132 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 18133 newState, oldState).defaultPrevented; 18134 18135 // if the location was changed by a `$locationChangeStart` handler then stop 18136 // processing this location change 18137 if ($location.absUrl() !== newUrl) return; 18138 18139 if (defaultPrevented) { 18140 $location.$$parse(oldUrl); 18141 $location.$$state = oldState; 18142 setBrowserUrlWithFallback(oldUrl, false, oldState); 18143 } else { 18144 initializing = false;
vendor: 4,210 bytes, lines 18145-18287
18145 afterLocationChange(oldUrl, oldState); 18146 } 18147 }); 18148 if (!$rootScope.$$phase) $rootScope.$digest(); 18149 }); 18150 18151 // update browser 18152 $rootScope.$watch(function $locationWatch() { 18153 var oldUrl = trimEmptyHash($browser.url()); 18154 var newUrl = trimEmptyHash($location.absUrl()); 18155 var oldState = $browser.state(); 18156 var currentReplace = $location.$$replace; 18157 var urlOrStateChanged = oldUrl !== newUrl || 18158 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 18159 18160 if (initializing || urlOrStateChanged) { 18161 initializing = false; 18162 18163 $rootScope.$evalAsync(function() { 18164 var newUrl = $location.absUrl(); 18165 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 18166 $location.$$state, oldState).defaultPrevented; 18167 18168 // if the location was changed by a `$locationChangeStart` handler then stop 18169 // processing this location change 18170 if ($location.absUrl() !== newUrl) return; 18171 18172 if (defaultPrevented) { 18173 $location.$$parse(oldUrl); 18174 $location.$$state = oldState; 18175 } else { 18176 if (urlOrStateChanged) { 18177 setBrowserUrlWithFallback(newUrl, currentReplace, 18178 oldState === $location.$$state ? null : $location.$$state); 18179 } 18180 afterLocationChange(oldUrl, oldState); 18181 } 18182 }); 18183 } 18184 18185 $location.$$replace = false; 18186 18187 // we don't need to return anything because $evalAsync will make the digest loop dirty when 18188 // there is a change 18189 }); 18190 18191 return $location; 18192 18193 function afterLocationChange(oldUrl, oldState) { 18194 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 18195 $location.$$state, oldState); 18196 } 18197}]; 18198} 18199 18200/** 18201 * @ngdoc service 18202 * @name $log 18203 * @requires $window 18204 * 18205 * @description 18206 * Simple service for logging. Default implementation safely writes the message 18207 * into the browser's console (if present). 18208 * 18209 * The main purpose of this service is to simplify debugging and troubleshooting. 18210 * 18211 * The default is to log `debug` messages. You can use 18212 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 18213 * 18214 * @example 18215 <example module="logExample"> 18216 <file name="script.js"> 18217 angular.module('logExample', []) 18218 .controller('LogController', ['$scope', '$log', function($scope, $log) { 18219 $scope.$log = $log; 18220 $scope.message = 'Hello World!'; 18221 }]); 18222 </file> 18223 <file name="index.html"> 18224 <div ng-controller="LogController"> 18225 <p>Reload this page with open console, enter text and hit the log button...</p> 18226 <label>Message: 18227 <input type="text" ng-model="message" /></label> 18228 <button ng-click="$log.log(message)">log</button> 18229 <button ng-click="$log.warn(message)">warn</button> 18230 <button ng-click="$log.info(message)">info</button> 18231 <button ng-click="$log.error(message)">error</button> 18232 <button ng-click="$log.debug(message)">debug</button> 18233 </div> 18234 </file> 18235 </example> 18236 */ 18237 18238/** 18239 * @ngdoc provider 18240 * @name $logProvider 18241 * @description 18242 * Use the `$logProvider` to configure how the application logs messages 18243 */ 18244function $LogProvider() { 18245 var debug = true, 18246 self = this; 18247 18248 /** 18249 * @ngdoc method 18250 * @name $logProvider#debugEnabled 18251 * @description 18252 * @param {boolean=} flag enable or disable debug level messages 18253 * @returns {*} current value if used as getter or itself (chaining) if used as setter 18254 */ 18255 this.debugEnabled = function(flag) { 18256 if (isDefined(flag)) { 18257 debug = flag; 18258 return this; 18259 } else { 18260 return debug; 18261 } 18262 }; 18263 18264 this.$get = ['$window', function($window) { 18265 return { 18266 /** 18267 * @ngdoc method 18268 * @name $log#log 18269 * 18270 * @description 18271 * Write a log message 18272 */ 18273 log: consoleLog('log'), 18274 18275 /** 18276 * @ngdoc method 18277 * @name $log#info 18278 * 18279 * @description 18280 * Write an information message 18281 */ 18282 info: consoleLog('info'), 18283 18284 /** 18285 * @ngdoc method 18286 * @name $log#warn 18287 *
18288 * @description 18289 * Write a warning message 18290 */ 18291 warn: consoleLog('warn'), 18292 18293 /** 18294 * @ngdoc method 18295 * @name $log#error 18296 * 18297 * @description 18298 * Write an error message 18299 */ 18300 error: consoleLog('error'), 18301 18302 /** 18303 * @ngdoc method 18304 * @name $log#debug 18305 * 18306 * @description 18307 * Write a debug message 18308 */ 18309 debug: (function() { 18310 var fn = consoleLog('debug'); 18311 18312 return function() { 18313 if (debug) { 18314 fn.apply(self, arguments); 18315 } 18316 }; 18317 }()) 18318 }; 18319 18320 function formatError(arg) { 18321 if (arg instanceof Error) { 18322 if (arg.stack) { 18323 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 18324 ? 'Error: ' + arg.message + '\n' + arg.stack 18325 : arg.stack; 18326 } else if (arg.sourceURL) { 18327 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 18328 } 18329 } 18330 return arg; 18331 } 18332 18333 function consoleLog(type) { 18334 var console = $window.console || {}, 18335 logFn = console[type] || console.log || noop, 18336 hasApply = false; 18337 18338 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 18339 // The reason behind this is that console.log has type "object" in IE8... 18340 try { 18341 hasApply = !!logFn.apply; 18342 } catch (e) {} 18343 18344 if (hasApply) { 18345 return function() { 18346 var args = []; 18347 forEach(arguments, function(arg) { 18348 args.push(formatError(arg)); 18349 }); 18350 return logFn.apply(console, args); 18351 }; 18352 } 18353 18354 // we are IE which either doesn't have window.console => this is noop and we do nothing, 18355 // or we are IE where console.log doesn't have apply so we log at least first 2 args 18356 return function(arg1, arg2) { 18357 logFn(arg1, arg2 == null ? '' : arg2); 18358 }; 18359 } 18360 }]; 18361} 18362 18363/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 18364 * Any commits to this file should be reviewed with security in mind. * 18365 * Changes to this file can potentially create security vulnerabilities. * 18366 * An approval from 2 Core members with history of modifying * 18367 * this file is required. * 18368 * * 18369 * Does the change somehow allow for arbitrary javascript to be executed? * 18370 * Or allows for someone to change the prototype of built-in objects? * 18371 * Or gives undesired access to variables likes document or window? * 18372 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 18373 18374var $parseMinErr = minErr('$parse'); 18375 18376// Sandboxing Angular Expressions 18377// ------------------------------ 18378// Angular expressions are generally considered safe because these expressions only have direct 18379// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 18380// obtaining a reference to native JS functions such as the Function constructor. 18381// 18382// As an example, consider the following Angular expression: 18383// 18384// {}.toString.constructor('alert("evil JS code")') 18385// 18386// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 18387// against the expression language, but not to prevent exploits that were enabled by exposing 18388// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 18389// practice and therefore we are not even trying to protect against interaction with an object 18390// explicitly exposed in this way. 18391// 18392// In general, it is not possible to access a Window object from an angular expression unless a 18393// window or some DOM object that has a reference to window is published onto a Scope. 18394// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 18395// native objects. 18396// 18397// See https://docs.angularjs.org/guide/security 18398 18399 18400function ensureSafeMemberName(name, fullExpression) { 18401 if (name === "__defineGetter__" || name === "__defineSetter__" 18402 || name === "__lookupGetter__" || name === "__lookupSetter__" 18403 || name === "__proto__") { 18404 throw $parseMinErr('isecfld', 18405 'Attempting to access a disallowed field in Angular expressions! ' 18406 + 'Expression: {0}', fullExpression); 18407 } 18408 return name; 18409} 18410 18411function getStringValue(name) { 18412 // Property names must be strings. This means that non-string objects cannot be used 18413 // as keys in an object. Any non-string object, including a number, is typecasted 18414 // into a string via the toString method. 18415 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names 18416 // 18417 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it 18418 // to a string. It's not always possible. If `name` is an object and its `toString` method is 18419 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: 18420 // 18421 // TypeError: Cannot convert object to primitive value 18422 // 18423 // For performance reasons, we don't catch this error here and allow it to propagate up the call 18424 // stack. Note that you'll get the same error in JavaScript if you try to access a property using 18425 // such a 'broken' object as a key. 18426 return name + ''; 18427} 18428 18429function ensureSafeObject(obj, fullExpression) { 18430 // nifty check if obj is Function that is fast and works across iframes and other contexts 18431 if (obj) { 18432 if (obj.constructor === obj) { 18433 throw $parseMinErr('isecfn', 18434 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 18435 fullExpression); 18436 } else if (// isWindow(obj) 18437 obj.window === obj) { 18438 throw $parseMinErr('isecwindow', 18439 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 18440 fullExpression); 18441 } else if (// isElement(obj) 18442 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 18443 throw $parseMinErr('isecdom', 18444 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 18445 fullExpression); 18446 } else if (// block Object so that we can't get hold of dangerous Object.* methods 18447 obj === Object) { 18448 throw $parseMinErr('isecobj', 18449 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 18450 fullExpression); 18451 } 18452 } 18453 return obj; 18454} 18455 18456var CALL = Function.prototype.call; 18457var APPLY = Function.prototype.apply; 18458var BIND = Function.prototype.bind; 18459 18460function ensureSafeFunction(obj, fullExpression) { 18461 if (obj) { 18462 if (obj.constructor === obj) { 18463 throw $parseMinErr('isecfn', 18464 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 18465 fullExpression); 18466 } else if (obj === CALL || obj === APPLY || obj === BIND) { 18467 throw $parseMinErr('isecff', 18468 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 18469 fullExpression); 18470 } 18471 } 18472} 18473 18474function ensureSafeAssignContext(obj, fullExpression) { 18475 if (obj) { 18476 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor || 18477 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) { 18478 throw $parseMinErr('isecaf', 18479 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression); 18480 } 18481 } 18482} 18483 18484var OPERATORS = createMap(); 18485forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 18486var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 18487 18488
vendor: 48,343 bytes, lines 18489-20012
18489///////////////////////////////////////// 18490 18491 18492/** 18493 * @constructor 18494 */ 18495var Lexer = function(options) { 18496 this.options = options; 18497}; 18498 18499Lexer.prototype = { 18500 constructor: Lexer, 18501 18502 lex: function(text) { 18503 this.text = text; 18504 this.index = 0; 18505 this.tokens = []; 18506 18507 while (this.index < this.text.length) { 18508 var ch = this.text.charAt(this.index); 18509 if (ch === '"' || ch === "'") { 18510 this.readString(ch); 18511 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 18512 this.readNumber(); 18513 } else if (this.isIdent(ch)) { 18514 this.readIdent(); 18515 } else if (this.is(ch, '(){}[].,;:?')) { 18516 this.tokens.push({index: this.index, text: ch}); 18517 this.index++; 18518 } else if (this.isWhitespace(ch)) { 18519 this.index++; 18520 } else { 18521 var ch2 = ch + this.peek(); 18522 var ch3 = ch2 + this.peek(2); 18523 var op1 = OPERATORS[ch]; 18524 var op2 = OPERATORS[ch2]; 18525 var op3 = OPERATORS[ch3]; 18526 if (op1 || op2 || op3) { 18527 var token = op3 ? ch3 : (op2 ? ch2 : ch); 18528 this.tokens.push({index: this.index, text: token, operator: true}); 18529 this.index += token.length; 18530 } else { 18531 this.throwError('Unexpected next character ', this.index, this.index + 1); 18532 } 18533 } 18534 } 18535 return this.tokens; 18536 }, 18537 18538 is: function(ch, chars) { 18539 return chars.indexOf(ch) !== -1; 18540 }, 18541 18542 peek: function(i) { 18543 var num = i || 1; 18544 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 18545 }, 18546 18547 isNumber: function(ch) { 18548 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 18549 }, 18550 18551 isWhitespace: function(ch) { 18552 // IE treats non-breaking space as \u00A0 18553 return (ch === ' ' || ch === '\r' || ch === '\t' || 18554 ch === '\n' || ch === '\v' || ch === '\u00A0'); 18555 }, 18556 18557 isIdent: function(ch) { 18558 return ('a' <= ch && ch <= 'z' || 18559 'A' <= ch && ch <= 'Z' || 18560 '_' === ch || ch === '$'); 18561 }, 18562 18563 isExpOperator: function(ch) { 18564 return (ch === '-' || ch === '+' || this.isNumber(ch)); 18565 }, 18566 18567 throwError: function(error, start, end) { 18568 end = end || this.index; 18569 var colStr = (isDefined(start) 18570 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 18571 : ' ' + end); 18572 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 18573 error, colStr, this.text); 18574 }, 18575 18576 readNumber: function() { 18577 var number = ''; 18578 var start = this.index; 18579 while (this.index < this.text.length) { 18580 var ch = lowercase(this.text.charAt(this.index)); 18581 if (ch == '.' || this.isNumber(ch)) { 18582 number += ch; 18583 } else { 18584 var peekCh = this.peek(); 18585 if (ch == 'e' && this.isExpOperator(peekCh)) { 18586 number += ch; 18587 } else if (this.isExpOperator(ch) && 18588 peekCh && this.isNumber(peekCh) && 18589 number.charAt(number.length - 1) == 'e') { 18590 number += ch; 18591 } else if (this.isExpOperator(ch) && 18592 (!peekCh || !this.isNumber(peekCh)) && 18593 number.charAt(number.length - 1) == 'e') { 18594 this.throwError('Invalid exponent'); 18595 } else { 18596 break; 18597 } 18598 } 18599 this.index++; 18600 } 18601 this.tokens.push({ 18602 index: start, 18603 text: number, 18604 constant: true, 18605 value: Number(number) 18606 }); 18607 }, 18608 18609 readIdent: function() { 18610 var start = this.index; 18611 while (this.index < this.text.length) { 18612 var ch = this.text.charAt(this.index); 18613 if (!(this.isIdent(ch) || this.isNumber(ch))) { 18614 break; 18615 } 18616 this.index++; 18617 } 18618 this.tokens.push({ 18619 index: start, 18620 text: this.text.slice(start, this.index), 18621 identifier: true 18622 }); 18623 }, 18624 18625 readString: function(quote) { 18626 var start = this.index; 18627 this.index++; 18628 var string = ''; 18629 var rawString = quote; 18630 var escape = false; 18631 while (this.index < this.text.length) { 18632 var ch = this.text.charAt(this.index); 18633 rawString += ch; 18634 if (escape) { 18635 if (ch === 'u') { 18636 var hex = this.text.substring(this.index + 1, this.index + 5); 18637 if (!hex.match(/[\da-f]{4}/i)) { 18638 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 18639 } 18640 this.index += 4; 18641 string += String.fromCharCode(parseInt(hex, 16)); 18642 } else { 18643 var rep = ESCAPE[ch]; 18644 string = string + (rep || ch); 18645 } 18646 escape = false; 18647 } else if (ch === '\\') { 18648 escape = true; 18649 } else if (ch === quote) { 18650 this.index++; 18651 this.tokens.push({ 18652 index: start, 18653 text: rawString, 18654 constant: true, 18655 value: string 18656 }); 18657 return; 18658 } else { 18659 string += ch; 18660 } 18661 this.index++; 18662 } 18663 this.throwError('Unterminated quote', start); 18664 } 18665}; 18666 18667var AST = function(lexer, options) { 18668 this.lexer = lexer; 18669 this.options = options; 18670}; 18671 18672AST.Program = 'Program'; 18673AST.ExpressionStatement = 'ExpressionStatement'; 18674AST.AssignmentExpression = 'AssignmentExpression'; 18675AST.ConditionalExpression = 'ConditionalExpression'; 18676AST.LogicalExpression = 'LogicalExpression'; 18677AST.BinaryExpression = 'BinaryExpression'; 18678AST.UnaryExpression = 'UnaryExpression'; 18679AST.CallExpression = 'CallExpression'; 18680AST.MemberExpression = 'MemberExpression'; 18681AST.Identifier = 'Identifier'; 18682AST.Literal = 'Literal'; 18683AST.ArrayExpression = 'ArrayExpression'; 18684AST.Property = 'Property'; 18685AST.ObjectExpression = 'ObjectExpression'; 18686AST.ThisExpression = 'ThisExpression'; 18687AST.LocalsExpression = 'LocalsExpression'; 18688 18689// Internal use only 18690AST.NGValueParameter = 'NGValueParameter'; 18691 18692AST.prototype = { 18693 ast: function(text) { 18694 this.text = text; 18695 this.tokens = this.lexer.lex(text); 18696 18697 var value = this.program(); 18698 18699 if (this.tokens.length !== 0) { 18700 this.throwError('is an unexpected token', this.tokens[0]); 18701 } 18702 18703 return value; 18704 }, 18705 18706 program: function() { 18707 var body = []; 18708 while (true) { 18709 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 18710 body.push(this.expressionStatement()); 18711 if (!this.expect(';')) { 18712 return { type: AST.Program, body: body}; 18713 } 18714 } 18715 }, 18716 18717 expressionStatement: function() { 18718 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 18719 }, 18720 18721 filterChain: function() { 18722 var left = this.expression(); 18723 var token; 18724 while ((token = this.expect('|'))) { 18725 left = this.filter(left); 18726 } 18727 return left; 18728 }, 18729 18730 expression: function() { 18731 return this.assignment(); 18732 }, 18733 18734 assignment: function() { 18735 var result = this.ternary(); 18736 if (this.expect('=')) { 18737 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 18738 } 18739 return result; 18740 }, 18741 18742 ternary: function() { 18743 var test = this.logicalOR(); 18744 var alternate; 18745 var consequent; 18746 if (this.expect('?')) { 18747 alternate = this.expression(); 18748 if (this.consume(':')) { 18749 consequent = this.expression(); 18750 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 18751 } 18752 } 18753 return test; 18754 }, 18755 18756 logicalOR: function() { 18757 var left = this.logicalAND(); 18758 while (this.expect('||')) { 18759 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 18760 } 18761 return left; 18762 }, 18763 18764 logicalAND: function() { 18765 var left = this.equality(); 18766 while (this.expect('&&')) { 18767 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 18768 } 18769 return left; 18770 }, 18771 18772 equality: function() { 18773 var left = this.relational(); 18774 var token; 18775 while ((token = this.expect('==','!=','===','!=='))) { 18776 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 18777 } 18778 return left; 18779 }, 18780 18781 relational: function() { 18782 var left = this.additive(); 18783 var token; 18784 while ((token = this.expect('<', '>', '<=', '>='))) { 18785 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 18786 } 18787 return left; 18788 }, 18789 18790 additive: function() { 18791 var left = this.multiplicative(); 18792 var token; 18793 while ((token = this.expect('+','-'))) { 18794 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 18795 } 18796 return left; 18797 }, 18798 18799 multiplicative: function() { 18800 var left = this.unary(); 18801 var token; 18802 while ((token = this.expect('*','/','%'))) { 18803 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 18804 } 18805 return left; 18806 }, 18807 18808 unary: function() { 18809 var token; 18810 if ((token = this.expect('+', '-', '!'))) { 18811 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 18812 } else { 18813 return this.primary(); 18814 } 18815 }, 18816 18817 primary: function() { 18818 var primary; 18819 if (this.expect('(')) { 18820 primary = this.filterChain(); 18821 this.consume(')'); 18822 } else if (this.expect('[')) { 18823 primary = this.arrayDeclaration(); 18824 } else if (this.expect('{')) { 18825 primary = this.object(); 18826 } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { 18827 primary = copy(this.selfReferential[this.consume().text]); 18828 } else if (this.options.literals.hasOwnProperty(this.peek().text)) { 18829 primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; 18830 } else if (this.peek().identifier) { 18831 primary = this.identifier(); 18832 } else if (this.peek().constant) { 18833 primary = this.constant(); 18834 } else { 18835 this.throwError('not a primary expression', this.peek()); 18836 } 18837 18838 var next; 18839 while ((next = this.expect('(', '[', '.'))) { 18840 if (next.text === '(') { 18841 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 18842 this.consume(')'); 18843 } else if (next.text === '[') { 18844 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 18845 this.consume(']'); 18846 } else if (next.text === '.') { 18847 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 18848 } else { 18849 this.throwError('IMPOSSIBLE'); 18850 } 18851 } 18852 return primary; 18853 }, 18854 18855 filter: function(baseExpression) { 18856 var args = [baseExpression]; 18857 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 18858 18859 while (this.expect(':')) { 18860 args.push(this.expression()); 18861 } 18862 18863 return result; 18864 }, 18865 18866 parseArguments: function() { 18867 var args = []; 18868 if (this.peekToken().text !== ')') { 18869 do { 18870 args.push(this.expression()); 18871 } while (this.expect(',')); 18872 } 18873 return args; 18874 }, 18875 18876 identifier: function() { 18877 var token = this.consume(); 18878 if (!token.identifier) { 18879 this.throwError('is not a valid identifier', token); 18880 } 18881 return { type: AST.Identifier, name: token.text }; 18882 }, 18883 18884 constant: function() { 18885 // TODO check that it is a constant 18886 return { type: AST.Literal, value: this.consume().value }; 18887 }, 18888 18889 arrayDeclaration: function() { 18890 var elements = []; 18891 if (this.peekToken().text !== ']') { 18892 do { 18893 if (this.peek(']')) { 18894 // Support trailing commas per ES5.1. 18895 break; 18896 } 18897 elements.push(this.expression()); 18898 } while (this.expect(',')); 18899 } 18900 this.consume(']'); 18901 18902 return { type: AST.ArrayExpression, elements: elements }; 18903 }, 18904 18905 object: function() { 18906 var properties = [], property; 18907 if (this.peekToken().text !== '}') { 18908 do { 18909 if (this.peek('}')) { 18910 // Support trailing commas per ES5.1. 18911 break; 18912 } 18913 property = {type: AST.Property, kind: 'init'}; 18914 if (this.peek().constant) { 18915 property.key = this.constant(); 18916 } else if (this.peek().identifier) { 18917 property.key = this.identifier(); 18918 } else { 18919 this.throwError("invalid key", this.peek()); 18920 } 18921 this.consume(':'); 18922 property.value = this.expression(); 18923 properties.push(property); 18924 } while (this.expect(',')); 18925 } 18926 this.consume('}'); 18927 18928 return {type: AST.ObjectExpression, properties: properties }; 18929 }, 18930 18931 throwError: function(msg, token) { 18932 throw $parseMinErr('syntax', 18933 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 18934 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 18935 }, 18936 18937 consume: function(e1) { 18938 if (this.tokens.length === 0) { 18939 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 18940 } 18941 18942 var token = this.expect(e1); 18943 if (!token) { 18944 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 18945 } 18946 return token; 18947 }, 18948 18949 peekToken: function() { 18950 if (this.tokens.length === 0) { 18951 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 18952 } 18953 return this.tokens[0]; 18954 }, 18955 18956 peek: function(e1, e2, e3, e4) { 18957 return this.peekAhead(0, e1, e2, e3, e4); 18958 }, 18959 18960 peekAhead: function(i, e1, e2, e3, e4) { 18961 if (this.tokens.length > i) { 18962 var token = this.tokens[i]; 18963 var t = token.text; 18964 if (t === e1 || t === e2 || t === e3 || t === e4 || 18965 (!e1 && !e2 && !e3 && !e4)) { 18966 return token; 18967 } 18968 } 18969 return false; 18970 }, 18971 18972 expect: function(e1, e2, e3, e4) { 18973 var token = this.peek(e1, e2, e3, e4); 18974 if (token) { 18975 this.tokens.shift(); 18976 return token; 18977 } 18978 return false; 18979 }, 18980 18981 selfReferential: { 18982 'this': {type: AST.ThisExpression }, 18983 '$locals': {type: AST.LocalsExpression } 18984 } 18985}; 18986 18987function ifDefined(v, d) { 18988 return typeof v !== 'undefined' ? v : d; 18989} 18990 18991function plusFn(l, r) { 18992 if (typeof l === 'undefined') return r; 18993 if (typeof r === 'undefined') return l; 18994 return l + r; 18995} 18996 18997function isStateless($filter, filterName) { 18998 var fn = $filter(filterName); 18999 return !fn.$stateful; 19000} 19001 19002function findConstantAndWatchExpressions(ast, $filter) { 19003 var allConstants; 19004 var argsToWatch; 19005 switch (ast.type) { 19006 case AST.Program: 19007 allConstants = true; 19008 forEach(ast.body, function(expr) { 19009 findConstantAndWatchExpressions(expr.expression, $filter); 19010 allConstants = allConstants && expr.expression.constant; 19011 }); 19012 ast.constant = allConstants; 19013 break; 19014 case AST.Literal: 19015 ast.constant = true; 19016 ast.toWatch = []; 19017 break; 19018 case AST.UnaryExpression: 19019 findConstantAndWatchExpressions(ast.argument, $filter); 19020 ast.constant = ast.argument.constant; 19021 ast.toWatch = ast.argument.toWatch; 19022 break; 19023 case AST.BinaryExpression: 19024 findConstantAndWatchExpressions(ast.left, $filter); 19025 findConstantAndWatchExpressions(ast.right, $filter); 19026 ast.constant = ast.left.constant && ast.right.constant; 19027 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 19028 break; 19029 case AST.LogicalExpression: 19030 findConstantAndWatchExpressions(ast.left, $filter); 19031 findConstantAndWatchExpressions(ast.right, $filter); 19032 ast.constant = ast.left.constant && ast.right.constant; 19033 ast.toWatch = ast.constant ? [] : [ast]; 19034 break; 19035 case AST.ConditionalExpression: 19036 findConstantAndWatchExpressions(ast.test, $filter); 19037 findConstantAndWatchExpressions(ast.alternate, $filter); 19038 findConstantAndWatchExpressions(ast.consequent, $filter); 19039 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 19040 ast.toWatch = ast.constant ? [] : [ast]; 19041 break; 19042 case AST.Identifier: 19043 ast.constant = false; 19044 ast.toWatch = [ast]; 19045 break; 19046 case AST.MemberExpression: 19047 findConstantAndWatchExpressions(ast.object, $filter); 19048 if (ast.computed) { 19049 findConstantAndWatchExpressions(ast.property, $filter); 19050 } 19051 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 19052 ast.toWatch = [ast]; 19053 break; 19054 case AST.CallExpression: 19055 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 19056 argsToWatch = []; 19057 forEach(ast.arguments, function(expr) { 19058 findConstantAndWatchExpressions(expr, $filter); 19059 allConstants = allConstants && expr.constant; 19060 if (!expr.constant) { 19061 argsToWatch.push.apply(argsToWatch, expr.toWatch); 19062 } 19063 }); 19064 ast.constant = allConstants; 19065 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 19066 break; 19067 case AST.AssignmentExpression: 19068 findConstantAndWatchExpressions(ast.left, $filter); 19069 findConstantAndWatchExpressions(ast.right, $filter); 19070 ast.constant = ast.left.constant && ast.right.constant; 19071 ast.toWatch = [ast]; 19072 break; 19073 case AST.ArrayExpression: 19074 allConstants = true; 19075 argsToWatch = []; 19076 forEach(ast.elements, function(expr) { 19077 findConstantAndWatchExpressions(expr, $filter); 19078 allConstants = allConstants && expr.constant; 19079 if (!expr.constant) { 19080 argsToWatch.push.apply(argsToWatch, expr.toWatch); 19081 } 19082 }); 19083 ast.constant = allConstants; 19084 ast.toWatch = argsToWatch; 19085 break; 19086 case AST.ObjectExpression: 19087 allConstants = true; 19088 argsToWatch = []; 19089 forEach(ast.properties, function(property) { 19090 findConstantAndWatchExpressions(property.value, $filter); 19091 allConstants = allConstants && property.value.constant; 19092 if (!property.value.constant) { 19093 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 19094 } 19095 }); 19096 ast.constant = allConstants; 19097 ast.toWatch = argsToWatch; 19098 break; 19099 case AST.ThisExpression: 19100 ast.constant = false; 19101 ast.toWatch = []; 19102 break; 19103 case AST.LocalsExpression: 19104 ast.constant = false; 19105 ast.toWatch = []; 19106 break; 19107 } 19108} 19109 19110function getInputs(body) { 19111 if (body.length != 1) return; 19112 var lastExpression = body[0].expression; 19113 var candidate = lastExpression.toWatch; 19114 if (candidate.length !== 1) return candidate; 19115 return candidate[0] !== lastExpression ? candidate : undefined; 19116} 19117 19118function isAssignable(ast) { 19119 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 19120} 19121 19122function assignableAST(ast) { 19123 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 19124 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 19125 } 19126} 19127 19128function isLiteral(ast) { 19129 return ast.body.length === 0 || 19130 ast.body.length === 1 && ( 19131 ast.body[0].expression.type === AST.Literal || 19132 ast.body[0].expression.type === AST.ArrayExpression || 19133 ast.body[0].expression.type === AST.ObjectExpression); 19134} 19135 19136function isConstant(ast) { 19137 return ast.constant; 19138} 19139 19140function ASTCompiler(astBuilder, $filter) { 19141 this.astBuilder = astBuilder; 19142 this.$filter = $filter; 19143} 19144 19145ASTCompiler.prototype = { 19146 compile: function(expression, expensiveChecks) { 19147 var self = this; 19148 var ast = this.astBuilder.ast(expression); 19149 this.state = { 19150 nextId: 0, 19151 filters: {}, 19152 expensiveChecks: expensiveChecks, 19153 fn: {vars: [], body: [], own: {}}, 19154 assign: {vars: [], body: [], own: {}}, 19155 inputs: [] 19156 }; 19157 findConstantAndWatchExpressions(ast, self.$filter); 19158 var extra = ''; 19159 var assignable; 19160 this.stage = 'assign'; 19161 if ((assignable = assignableAST(ast))) { 19162 this.state.computing = 'assign'; 19163 var result = this.nextId(); 19164 this.recurse(assignable, result); 19165 this.return_(result); 19166 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 19167 } 19168 var toWatch = getInputs(ast.body); 19169 self.stage = 'inputs'; 19170 forEach(toWatch, function(watch, key) { 19171 var fnKey = 'fn' + key; 19172 self.state[fnKey] = {vars: [], body: [], own: {}}; 19173 self.state.computing = fnKey; 19174 var intoId = self.nextId(); 19175 self.recurse(watch, intoId); 19176 self.return_(intoId); 19177 self.state.inputs.push(fnKey); 19178 watch.watchId = key; 19179 }); 19180 this.state.computing = 'fn'; 19181 this.stage = 'main'; 19182 this.recurse(ast); 19183 var fnString = 19184 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 19185 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 19186 '"' + this.USE + ' ' + this.STRICT + '";\n' + 19187 this.filterPrefix() + 19188 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 19189 extra + 19190 this.watchFns() + 19191 'return fn;'; 19192 19193 /* jshint -W054 */ 19194 var fn = (new Function('$filter', 19195 'ensureSafeMemberName', 19196 'ensureSafeObject', 19197 'ensureSafeFunction', 19198 'getStringValue', 19199 'ensureSafeAssignContext', 19200 'ifDefined', 19201 'plus', 19202 'text', 19203 fnString))( 19204 this.$filter, 19205 ensureSafeMemberName, 19206 ensureSafeObject, 19207 ensureSafeFunction, 19208 getStringValue, 19209 ensureSafeAssignContext, 19210 ifDefined, 19211 plusFn, 19212 expression); 19213 /* jshint +W054 */ 19214 this.state = this.stage = undefined; 19215 fn.literal = isLiteral(ast); 19216 fn.constant = isConstant(ast); 19217 return fn; 19218 }, 19219 19220 USE: 'use', 19221 19222 STRICT: 'strict', 19223 19224 watchFns: function() { 19225 var result = []; 19226 var fns = this.state.inputs; 19227 var self = this; 19228 forEach(fns, function(name) { 19229 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 19230 }); 19231 if (fns.length) { 19232 result.push('fn.inputs=[' + fns.join(',') + '];'); 19233 } 19234 return result.join(''); 19235 }, 19236 19237 generateFunction: function(name, params) { 19238 return 'function(' + params + '){' + 19239 this.varsPrefix(name) + 19240 this.body(name) + 19241 '};'; 19242 }, 19243 19244 filterPrefix: function() { 19245 var parts = []; 19246 var self = this; 19247 forEach(this.state.filters, function(id, filter) { 19248 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 19249 }); 19250 if (parts.length) return 'var ' + parts.join(',') + ';'; 19251 return ''; 19252 }, 19253 19254 varsPrefix: function(section) { 19255 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 19256 }, 19257 19258 body: function(section) { 19259 return this.state[section].body.join(''); 19260 }, 19261 19262 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 19263 var left, right, self = this, args, expression; 19264 recursionFn = recursionFn || noop; 19265 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 19266 intoId = intoId || this.nextId(); 19267 this.if_('i', 19268 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 19269 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 19270 ); 19271 return; 19272 } 19273 switch (ast.type) { 19274 case AST.Program: 19275 forEach(ast.body, function(expression, pos) { 19276 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 19277 if (pos !== ast.body.length - 1) { 19278 self.current().body.push(right, ';'); 19279 } else { 19280 self.return_(right); 19281 } 19282 }); 19283 break; 19284 case AST.Literal: 19285 expression = this.escape(ast.value); 19286 this.assign(intoId, expression); 19287 recursionFn(expression); 19288 break; 19289 case AST.UnaryExpression: 19290 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 19291 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 19292 this.assign(intoId, expression); 19293 recursionFn(expression); 19294 break; 19295 case AST.BinaryExpression: 19296 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 19297 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 19298 if (ast.operator === '+') { 19299 expression = this.plus(left, right); 19300 } else if (ast.operator === '-') { 19301 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 19302 } else { 19303 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 19304 } 19305 this.assign(intoId, expression); 19306 recursionFn(expression); 19307 break; 19308 case AST.LogicalExpression: 19309 intoId = intoId || this.nextId(); 19310 self.recurse(ast.left, intoId); 19311 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 19312 recursionFn(intoId); 19313 break; 19314 case AST.ConditionalExpression: 19315 intoId = intoId || this.nextId(); 19316 self.recurse(ast.test, intoId); 19317 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 19318 recursionFn(intoId); 19319 break; 19320 case AST.Identifier: 19321 intoId = intoId || this.nextId(); 19322 if (nameId) { 19323 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 19324 nameId.computed = false; 19325 nameId.name = ast.name; 19326 } 19327 ensureSafeMemberName(ast.name); 19328 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 19329 function() { 19330 self.if_(self.stage === 'inputs' || 's', function() { 19331 if (create && create !== 1) { 19332 self.if_( 19333 self.not(self.nonComputedMember('s', ast.name)), 19334 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 19335 } 19336 self.assign(intoId, self.nonComputedMember('s', ast.name)); 19337 }); 19338 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 19339 ); 19340 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 19341 self.addEnsureSafeObject(intoId); 19342 } 19343 recursionFn(intoId); 19344 break; 19345 case AST.MemberExpression: 19346 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 19347 intoId = intoId || this.nextId(); 19348 self.recurse(ast.object, left, undefined, function() { 19349 self.if_(self.notNull(left), function() { 19350 if (create && create !== 1) { 19351 self.addEnsureSafeAssignContext(left); 19352 } 19353 if (ast.computed) { 19354 right = self.nextId(); 19355 self.recurse(ast.property, right); 19356 self.getStringValue(right); 19357 self.addEnsureSafeMemberName(right); 19358 if (create && create !== 1) { 19359 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 19360 } 19361 expression = self.ensureSafeObject(self.computedMember(left, right)); 19362 self.assign(intoId, expression); 19363 if (nameId) { 19364 nameId.computed = true; 19365 nameId.name = right; 19366 } 19367 } else { 19368 ensureSafeMemberName(ast.property.name); 19369 if (create && create !== 1) { 19370 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 19371 } 19372 expression = self.nonComputedMember(left, ast.property.name); 19373 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 19374 expression = self.ensureSafeObject(expression); 19375 } 19376 self.assign(intoId, expression); 19377 if (nameId) { 19378 nameId.computed = false; 19379 nameId.name = ast.property.name; 19380 } 19381 } 19382 }, function() { 19383 self.assign(intoId, 'undefined'); 19384 }); 19385 recursionFn(intoId); 19386 }, !!create); 19387 break; 19388 case AST.CallExpression: 19389 intoId = intoId || this.nextId(); 19390 if (ast.filter) { 19391 right = self.filter(ast.callee.name); 19392 args = []; 19393 forEach(ast.arguments, function(expr) { 19394 var argument = self.nextId(); 19395 self.recurse(expr, argument); 19396 args.push(argument); 19397 }); 19398 expression = right + '(' + args.join(',') + ')'; 19399 self.assign(intoId, expression); 19400 recursionFn(intoId); 19401 } else { 19402 right = self.nextId(); 19403 left = {}; 19404 args = []; 19405 self.recurse(ast.callee, right, left, function() { 19406 self.if_(self.notNull(right), function() { 19407 self.addEnsureSafeFunction(right); 19408 forEach(ast.arguments, function(expr) { 19409 self.recurse(expr, self.nextId(), undefined, function(argument) { 19410 args.push(self.ensureSafeObject(argument)); 19411 }); 19412 }); 19413 if (left.name) { 19414 if (!self.state.expensiveChecks) { 19415 self.addEnsureSafeObject(left.context); 19416 } 19417 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 19418 } else { 19419 expression = right + '(' + args.join(',') + ')'; 19420 } 19421 expression = self.ensureSafeObject(expression); 19422 self.assign(intoId, expression); 19423 }, function() { 19424 self.assign(intoId, 'undefined'); 19425 }); 19426 recursionFn(intoId); 19427 }); 19428 } 19429 break; 19430 case AST.AssignmentExpression: 19431 right = this.nextId(); 19432 left = {}; 19433 if (!isAssignable(ast.left)) { 19434 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); 19435 } 19436 this.recurse(ast.left, undefined, left, function() { 19437 self.if_(self.notNull(left.context), function() { 19438 self.recurse(ast.right, right); 19439 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 19440 self.addEnsureSafeAssignContext(left.context); 19441 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 19442 self.assign(intoId, expression); 19443 recursionFn(intoId || expression); 19444 }); 19445 }, 1); 19446 break; 19447 case AST.ArrayExpression: 19448 args = []; 19449 forEach(ast.elements, function(expr) { 19450 self.recurse(expr, self.nextId(), undefined, function(argument) { 19451 args.push(argument); 19452 }); 19453 }); 19454 expression = '[' + args.join(',') + ']'; 19455 this.assign(intoId, expression); 19456 recursionFn(expression); 19457 break; 19458 case AST.ObjectExpression: 19459 args = []; 19460 forEach(ast.properties, function(property) { 19461 self.recurse(property.value, self.nextId(), undefined, function(expr) { 19462 args.push(self.escape( 19463 property.key.type === AST.Identifier ? property.key.name : 19464 ('' + property.key.value)) + 19465 ':' + expr); 19466 }); 19467 }); 19468 expression = '{' + args.join(',') + '}'; 19469 this.assign(intoId, expression); 19470 recursionFn(expression); 19471 break; 19472 case AST.ThisExpression: 19473 this.assign(intoId, 's'); 19474 recursionFn('s'); 19475 break; 19476 case AST.LocalsExpression: 19477 this.assign(intoId, 'l'); 19478 recursionFn('l'); 19479 break; 19480 case AST.NGValueParameter: 19481 this.assign(intoId, 'v'); 19482 recursionFn('v'); 19483 break; 19484 } 19485 }, 19486 19487 getHasOwnProperty: function(element, property) { 19488 var key = element + '.' + property; 19489 var own = this.current().own; 19490 if (!own.hasOwnProperty(key)) { 19491 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 19492 } 19493 return own[key]; 19494 }, 19495 19496 assign: function(id, value) { 19497 if (!id) return; 19498 this.current().body.push(id, '=', value, ';'); 19499 return id; 19500 }, 19501 19502 filter: function(filterName) { 19503 if (!this.state.filters.hasOwnProperty(filterName)) { 19504 this.state.filters[filterName] = this.nextId(true); 19505 } 19506 return this.state.filters[filterName]; 19507 }, 19508 19509 ifDefined: function(id, defaultValue) { 19510 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 19511 }, 19512 19513 plus: function(left, right) { 19514 return 'plus(' + left + ',' + right + ')'; 19515 }, 19516 19517 return_: function(id) { 19518 this.current().body.push('return ', id, ';'); 19519 }, 19520 19521 if_: function(test, alternate, consequent) { 19522 if (test === true) { 19523 alternate(); 19524 } else { 19525 var body = this.current().body; 19526 body.push('if(', test, '){'); 19527 alternate(); 19528 body.push('}'); 19529 if (consequent) { 19530 body.push('else{'); 19531 consequent(); 19532 body.push('}'); 19533 } 19534 } 19535 }, 19536 19537 not: function(expression) { 19538 return '!(' + expression + ')'; 19539 }, 19540 19541 notNull: function(expression) { 19542 return expression + '!=null'; 19543 }, 19544 19545 nonComputedMember: function(left, right) { 19546 return left + '.' + right; 19547 }, 19548 19549 computedMember: function(left, right) { 19550 return left + '[' + right + ']'; 19551 }, 19552 19553 member: function(left, right, computed) { 19554 if (computed) return this.computedMember(left, right); 19555 return this.nonComputedMember(left, right); 19556 }, 19557 19558 addEnsureSafeObject: function(item) { 19559 this.current().body.push(this.ensureSafeObject(item), ';'); 19560 }, 19561 19562 addEnsureSafeMemberName: function(item) { 19563 this.current().body.push(this.ensureSafeMemberName(item), ';'); 19564 }, 19565 19566 addEnsureSafeFunction: function(item) { 19567 this.current().body.push(this.ensureSafeFunction(item), ';'); 19568 }, 19569 19570 addEnsureSafeAssignContext: function(item) { 19571 this.current().body.push(this.ensureSafeAssignContext(item), ';'); 19572 }, 19573 19574 ensureSafeObject: function(item) { 19575 return 'ensureSafeObject(' + item + ',text)'; 19576 }, 19577 19578 ensureSafeMemberName: function(item) { 19579 return 'ensureSafeMemberName(' + item + ',text)'; 19580 }, 19581 19582 ensureSafeFunction: function(item) { 19583 return 'ensureSafeFunction(' + item + ',text)'; 19584 }, 19585 19586 getStringValue: function(item) { 19587 this.assign(item, 'getStringValue(' + item + ')'); 19588 }, 19589 19590 ensureSafeAssignContext: function(item) { 19591 return 'ensureSafeAssignContext(' + item + ',text)'; 19592 }, 19593 19594 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 19595 var self = this; 19596 return function() { 19597 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 19598 }; 19599 }, 19600 19601 lazyAssign: function(id, value) { 19602 var self = this; 19603 return function() { 19604 self.assign(id, value); 19605 }; 19606 }, 19607 19608 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 19609 19610 stringEscapeFn: function(c) { 19611 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 19612 }, 19613 19614 escape: function(value) { 19615 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 19616 if (isNumber(value)) return value.toString(); 19617 if (value === true) return 'true'; 19618 if (value === false) return 'false'; 19619 if (value === null) return 'null'; 19620 if (typeof value === 'undefined') return 'undefined'; 19621 19622 throw $parseMinErr('esc', 'IMPOSSIBLE'); 19623 }, 19624 19625 nextId: function(skip, init) { 19626 var id = 'v' + (this.state.nextId++); 19627 if (!skip) { 19628 this.current().vars.push(id + (init ? '=' + init : '')); 19629 } 19630 return id; 19631 }, 19632 19633 current: function() { 19634 return this.state[this.state.computing]; 19635 } 19636}; 19637 19638 19639function ASTInterpreter(astBuilder, $filter) { 19640 this.astBuilder = astBuilder; 19641 this.$filter = $filter; 19642} 19643 19644ASTInterpreter.prototype = { 19645 compile: function(expression, expensiveChecks) { 19646 var self = this; 19647 var ast = this.astBuilder.ast(expression); 19648 this.expression = expression; 19649 this.expensiveChecks = expensiveChecks; 19650 findConstantAndWatchExpressions(ast, self.$filter); 19651 var assignable; 19652 var assign; 19653 if ((assignable = assignableAST(ast))) { 19654 assign = this.recurse(assignable); 19655 } 19656 var toWatch = getInputs(ast.body); 19657 var inputs; 19658 if (toWatch) { 19659 inputs = []; 19660 forEach(toWatch, function(watch, key) { 19661 var input = self.recurse(watch); 19662 watch.input = input; 19663 inputs.push(input); 19664 watch.watchId = key; 19665 }); 19666 } 19667 var expressions = []; 19668 forEach(ast.body, function(expression) { 19669 expressions.push(self.recurse(expression.expression)); 19670 }); 19671 var fn = ast.body.length === 0 ? noop : 19672 ast.body.length === 1 ? expressions[0] : 19673 function(scope, locals) { 19674 var lastValue; 19675 forEach(expressions, function(exp) { 19676 lastValue = exp(scope, locals); 19677 }); 19678 return lastValue; 19679 }; 19680 if (assign) { 19681 fn.assign = function(scope, value, locals) { 19682 return assign(scope, locals, value); 19683 }; 19684 } 19685 if (inputs) { 19686 fn.inputs = inputs; 19687 } 19688 fn.literal = isLiteral(ast); 19689 fn.constant = isConstant(ast); 19690 return fn; 19691 }, 19692 19693 recurse: function(ast, context, create) { 19694 var left, right, self = this, args, expression; 19695 if (ast.input) { 19696 return this.inputs(ast.input, ast.watchId); 19697 } 19698 switch (ast.type) { 19699 case AST.Literal: 19700 return this.value(ast.value, context); 19701 case AST.UnaryExpression: 19702 right = this.recurse(ast.argument); 19703 return this['unary' + ast.operator](right, context); 19704 case AST.BinaryExpression: 19705 left = this.recurse(ast.left); 19706 right = this.recurse(ast.right); 19707 return this['binary' + ast.operator](left, right, context); 19708 case AST.LogicalExpression: 19709 left = this.recurse(ast.left); 19710 right = this.recurse(ast.right); 19711 return this['binary' + ast.operator](left, right, context); 19712 case AST.ConditionalExpression: 19713 return this['ternary?:']( 19714 this.recurse(ast.test), 19715 this.recurse(ast.alternate), 19716 this.recurse(ast.consequent), 19717 context 19718 ); 19719 case AST.Identifier: 19720 ensureSafeMemberName(ast.name, self.expression); 19721 return self.identifier(ast.name, 19722 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 19723 context, create, self.expression); 19724 case AST.MemberExpression: 19725 left = this.recurse(ast.object, false, !!create); 19726 if (!ast.computed) { 19727 ensureSafeMemberName(ast.property.name, self.expression); 19728 right = ast.property.name; 19729 } 19730 if (ast.computed) right = this.recurse(ast.property); 19731 return ast.computed ? 19732 this.computedMember(left, right, context, create, self.expression) : 19733 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 19734 case AST.CallExpression: 19735 args = []; 19736 forEach(ast.arguments, function(expr) { 19737 args.push(self.recurse(expr)); 19738 }); 19739 if (ast.filter) right = this.$filter(ast.callee.name); 19740 if (!ast.filter) right = this.recurse(ast.callee, true); 19741 return ast.filter ? 19742 function(scope, locals, assign, inputs) { 19743 var values = []; 19744 for (var i = 0; i < args.length; ++i) { 19745 values.push(args[i](scope, locals, assign, inputs)); 19746 } 19747 var value = right.apply(undefined, values, inputs); 19748 return context ? {context: undefined, name: undefined, value: value} : value; 19749 } : 19750 function(scope, locals, assign, inputs) { 19751 var rhs = right(scope, locals, assign, inputs); 19752 var value; 19753 if (rhs.value != null) { 19754 ensureSafeObject(rhs.context, self.expression); 19755 ensureSafeFunction(rhs.value, self.expression); 19756 var values = []; 19757 for (var i = 0; i < args.length; ++i) { 19758 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 19759 } 19760 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 19761 } 19762 return context ? {value: value} : value; 19763 }; 19764 case AST.AssignmentExpression: 19765 left = this.recurse(ast.left, true, 1); 19766 right = this.recurse(ast.right); 19767 return function(scope, locals, assign, inputs) { 19768 var lhs = left(scope, locals, assign, inputs); 19769 var rhs = right(scope, locals, assign, inputs); 19770 ensureSafeObject(lhs.value, self.expression); 19771 ensureSafeAssignContext(lhs.context); 19772 lhs.context[lhs.name] = rhs; 19773 return context ? {value: rhs} : rhs; 19774 }; 19775 case AST.ArrayExpression: 19776 args = []; 19777 forEach(ast.elements, function(expr) { 19778 args.push(self.recurse(expr)); 19779 }); 19780 return function(scope, locals, assign, inputs) { 19781 var value = []; 19782 for (var i = 0; i < args.length; ++i) { 19783 value.push(args[i](scope, locals, assign, inputs)); 19784 } 19785 return context ? {value: value} : value; 19786 }; 19787 case AST.ObjectExpression: 19788 args = []; 19789 forEach(ast.properties, function(property) { 19790 args.push({key: property.key.type === AST.Identifier ? 19791 property.key.name : 19792 ('' + property.key.value), 19793 value: self.recurse(property.value) 19794 }); 19795 }); 19796 return function(scope, locals, assign, inputs) { 19797 var value = {}; 19798 for (var i = 0; i < args.length; ++i) { 19799 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 19800 } 19801 return context ? {value: value} : value; 19802 }; 19803 case AST.ThisExpression: 19804 return function(scope) { 19805 return context ? {value: scope} : scope; 19806 }; 19807 case AST.LocalsExpression: 19808 return function(scope, locals) { 19809 return context ? {value: locals} : locals; 19810 }; 19811 case AST.NGValueParameter: 19812 return function(scope, locals, assign, inputs) { 19813 return context ? {value: assign} : assign; 19814 }; 19815 } 19816 }, 19817 19818 'unary+': function(argument, context) { 19819 return function(scope, locals, assign, inputs) { 19820 var arg = argument(scope, locals, assign, inputs); 19821 if (isDefined(arg)) { 19822 arg = +arg; 19823 } else { 19824 arg = 0; 19825 } 19826 return context ? {value: arg} : arg; 19827 }; 19828 }, 19829 'unary-': function(argument, context) { 19830 return function(scope, locals, assign, inputs) { 19831 var arg = argument(scope, locals, assign, inputs); 19832 if (isDefined(arg)) { 19833 arg = -arg; 19834 } else { 19835 arg = 0; 19836 } 19837 return context ? {value: arg} : arg; 19838 }; 19839 }, 19840 'unary!': function(argument, context) { 19841 return function(scope, locals, assign, inputs) { 19842 var arg = !argument(scope, locals, assign, inputs); 19843 return context ? {value: arg} : arg; 19844 }; 19845 }, 19846 'binary+': function(left, right, context) { 19847 return function(scope, locals, assign, inputs) { 19848 var lhs = left(scope, locals, assign, inputs); 19849 var rhs = right(scope, locals, assign, inputs); 19850 var arg = plusFn(lhs, rhs); 19851 return context ? {value: arg} : arg; 19852 }; 19853 }, 19854 'binary-': function(left, right, context) { 19855 return function(scope, locals, assign, inputs) { 19856 var lhs = left(scope, locals, assign, inputs); 19857 var rhs = right(scope, locals, assign, inputs); 19858 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 19859 return context ? {value: arg} : arg; 19860 }; 19861 }, 19862 'binary*': function(left, right, context) { 19863 return function(scope, locals, assign, inputs) { 19864 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 19865 return context ? {value: arg} : arg; 19866 }; 19867 }, 19868 'binary/': function(left, right, context) { 19869 return function(scope, locals, assign, inputs) { 19870 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 19871 return context ? {value: arg} : arg; 19872 }; 19873 }, 19874 'binary%': function(left, right, context) { 19875 return function(scope, locals, assign, inputs) { 19876 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 19877 return context ? {value: arg} : arg; 19878 }; 19879 }, 19880 'binary===': function(left, right, context) { 19881 return function(scope, locals, assign, inputs) { 19882 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 19883 return context ? {value: arg} : arg; 19884 }; 19885 }, 19886 'binary!==': function(left, right, context) { 19887 return function(scope, locals, assign, inputs) { 19888 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 19889 return context ? {value: arg} : arg; 19890 }; 19891 }, 19892 'binary==': function(left, right, context) { 19893 return function(scope, locals, assign, inputs) { 19894 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 19895 return context ? {value: arg} : arg; 19896 }; 19897 }, 19898 'binary!=': function(left, right, context) { 19899 return function(scope, locals, assign, inputs) { 19900 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 19901 return context ? {value: arg} : arg; 19902 }; 19903 }, 19904 'binary<': function(left, right, context) { 19905 return function(scope, locals, assign, inputs) { 19906 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 19907 return context ? {value: arg} : arg; 19908 }; 19909 }, 19910 'binary>': function(left, right, context) { 19911 return function(scope, locals, assign, inputs) { 19912 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 19913 return context ? {value: arg} : arg; 19914 }; 19915 }, 19916 'binary<=': function(left, right, context) { 19917 return function(scope, locals, assign, inputs) { 19918 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 19919 return context ? {value: arg} : arg; 19920 }; 19921 }, 19922 'binary>=': function(left, right, context) { 19923 return function(scope, locals, assign, inputs) { 19924 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 19925 return context ? {value: arg} : arg; 19926 }; 19927 }, 19928 'binary&&': function(left, right, context) { 19929 return function(scope, locals, assign, inputs) { 19930 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 19931 return context ? {value: arg} : arg; 19932 }; 19933 }, 19934 'binary||': function(left, right, context) { 19935 return function(scope, locals, assign, inputs) { 19936 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 19937 return context ? {value: arg} : arg; 19938 }; 19939 }, 19940 'ternary?:': function(test, alternate, consequent, context) { 19941 return function(scope, locals, assign, inputs) { 19942 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 19943 return context ? {value: arg} : arg; 19944 }; 19945 }, 19946 value: function(value, context) { 19947 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 19948 }, 19949 identifier: function(name, expensiveChecks, context, create, expression) { 19950 return function(scope, locals, assign, inputs) { 19951 var base = locals && (name in locals) ? locals : scope; 19952 if (create && create !== 1 && base && !(base[name])) { 19953 base[name] = {}; 19954 } 19955 var value = base ? base[name] : undefined; 19956 if (expensiveChecks) { 19957 ensureSafeObject(value, expression); 19958 } 19959 if (context) { 19960 return {context: base, name: name, value: value}; 19961 } else { 19962 return value; 19963 } 19964 }; 19965 }, 19966 computedMember: function(left, right, context, create, expression) { 19967 return function(scope, locals, assign, inputs) { 19968 var lhs = left(scope, locals, assign, inputs); 19969 var rhs; 19970 var value; 19971 if (lhs != null) { 19972 rhs = right(scope, locals, assign, inputs); 19973 rhs = getStringValue(rhs); 19974 ensureSafeMemberName(rhs, expression); 19975 if (create && create !== 1) { 19976 ensureSafeAssignContext(lhs); 19977 if (lhs && !(lhs[rhs])) { 19978 lhs[rhs] = {}; 19979 } 19980 } 19981 value = lhs[rhs]; 19982 ensureSafeObject(value, expression); 19983 } 19984 if (context) { 19985 return {context: lhs, name: rhs, value: value}; 19986 } else { 19987 return value; 19988 } 19989 }; 19990 }, 19991 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 19992 return function(scope, locals, assign, inputs) { 19993 var lhs = left(scope, locals, assign, inputs); 19994 if (create && create !== 1) { 19995 ensureSafeAssignContext(lhs); 19996 if (lhs && !(lhs[right])) { 19997 lhs[right] = {}; 19998 } 19999 } 20000 var value = lhs != null ? lhs[right] : undefined; 20001 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 20002 ensureSafeObject(value, expression); 20003 } 20004 if (context) { 20005 return {context: lhs, name: right, value: value}; 20006 } else { 20007 return value; 20008 } 20009 }; 20010 }, 20011 inputs: function(input, watchId) { 20012 return function(scope, value, locals, inputs) {
20013 if (inputs) return inputs[watchId]; 20014 return input(scope, value, locals); 20015 }; 20016 } 20017}; 20018 20019/** 20020 * @constructor 20021 */ 20022var Parser = function(lexer, $filter, options) { 20023 this.lexer = lexer; 20024 this.$filter = $filter; 20025 this.options = options; 20026 this.ast = new AST(lexer, options); 20027 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 20028 new ASTCompiler(this.ast, $filter); 20029}; 20030 20031Parser.prototype = { 20032 constructor: Parser, 20033 20034 parse: function(text) { 20035 return this.astCompiler.compile(text, this.options.expensiveChecks); 20036 } 20037}; 20038 20039function isPossiblyDangerousMemberName(name) { 20040 return name == 'constructor'; 20041} 20042 20043var objectValueOf = Object.prototype.valueOf; 20044 20045function getValueOf(value) { 20046 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 20047} 20048 20049/////////////////////////////////// 20050 20051/** 20052 * @ngdoc service 20053 * @name $parse 20054 * @kind function 20055 * 20056 * @description 20057 * 20058 * Converts Angular {@link guide/expression expression} into a function. 20059 * 20060 * ```js 20061 * var getter = $parse('user.name'); 20062 * var setter = getter.assign; 20063 * var context = {user:{name:'angular'}}; 20064 * var locals = {user:{name:'local'}}; 20065 * 20066 * expect(getter(context)).toEqual('angular'); 20067 * setter(context, 'newValue'); 20068 * expect(context.user.name).toEqual('newValue'); 20069 * expect(getter(context, locals)).toEqual('local'); 20070 * ``` 20071 * 20072 * 20073 * @param {string} expression String expression to compile. 20074 * @returns {function(context, locals)} a function which represents the compiled expression: 20075 * 20076 * * `context` â `{object}` â an object against which any expressions embedded in the strings 20077 * are evaluated against (typically a scope object). 20078 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 20079 * `context`. 20080 * 20081 * The returned function also has the following properties: 20082 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 20083 * literal. 20084 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 20085 * constant literals. 20086 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 20087 * set to a function to change its value on the given context. 20088 * 20089 */ 20090 20091 20092/** 20093 * @ngdoc provider 20094 * @name $parseProvider 20095 * 20096 * @description 20097 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 20098 * service. 20099 */ 20100function $ParseProvider() { 20101 var cacheDefault = createMap(); 20102 var cacheExpensive = createMap(); 20103 var literals = { 20104 'true': true, 20105 'false': false, 20106 'null': null, 20107 'undefined': undefined 20108 }; 20109 20110 /** 20111 * @ngdoc method 20112 * @name $parseProvider#addLiteral 20113 * @description 20114 * 20115 * Configure $parse service to add literal values that will be present as literal at expressions. 20116 * 20117 * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. 20118 * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. 20119 * 20120 **/ 20121 this.addLiteral = function(literalName, literalValue) { 20122 literals[literalName] = literalValue; 20123 }; 20124 20125 this.$get = ['$filter', function($filter) { 20126 var noUnsafeEval = csp().noUnsafeEval; 20127 var $parseOptions = { 20128 csp: noUnsafeEval, 20129 expensiveChecks: false, 20130 literals: copy(literals) 20131 }, 20132 $parseOptionsExpensive = { 20133 csp: noUnsafeEval, 20134 expensiveChecks: true, 20135 literals: copy(literals) 20136 }; 20137 var runningChecksEnabled = false; 20138 20139 $parse.$$runningExpensiveChecks = function() { 20140 return runningChecksEnabled; 20141 }; 20142 20143 return $parse; 20144 20145 function $parse(exp, interceptorFn, expensiveChecks) { 20146 var parsedExpression, oneTime, cacheKey; 20147 20148 expensiveChecks = expensiveChecks || runningChecksEnabled; 20149 20150 switch (typeof exp) { 20151 case 'string': 20152 exp = exp.trim(); 20153 cacheKey = exp; 20154 20155 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 20156 parsedExpression = cache[cacheKey]; 20157 20158 if (!parsedExpression) { 20159 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 20160 oneTime = true; 20161 exp = exp.substring(2); 20162 } 20163 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 20164 var lexer = new Lexer(parseOptions); 20165 var parser = new Parser(lexer, $filter, parseOptions); 20166 parsedExpression = parser.parse(exp); 20167 if (parsedExpression.constant) { 20168 parsedExpression.$$watchDelegate = constantWatchDelegate; 20169 } else if (oneTime) { 20170 parsedExpression.$$watchDelegate = parsedExpression.literal ? 20171 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 20172 } else if (parsedExpression.inputs) { 20173 parsedExpression.$$watchDelegate = inputsWatchDelegate; 20174 } 20175 if (expensiveChecks) { 20176 parsedExpression = expensiveChecksInterceptor(parsedExpression); 20177 } 20178 cache[cacheKey] = parsedExpression; 20179 } 20180 return addInterceptor(parsedExpression, interceptorFn); 20181 20182 case 'function': 20183 return addInterceptor(exp, interceptorFn); 20184 20185 default: 20186 return addInterceptor(noop, interceptorFn); 20187 } 20188 } 20189 20190 function expensiveChecksInterceptor(fn) { 20191 if (!fn) return fn; 20192 expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate; 20193 expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign); 20194 expensiveCheckFn.constant = fn.constant; 20195 expensiveCheckFn.literal = fn.literal; 20196 for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) { 20197 fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]); 20198 } 20199 expensiveCheckFn.inputs = fn.inputs; 20200 20201 return expensiveCheckFn; 20202 20203 function expensiveCheckFn(scope, locals, assign, inputs) { 20204 var expensiveCheckOldValue = runningChecksEnabled; 20205 runningChecksEnabled = true; 20206 try { 20207 return fn(scope, locals, assign, inputs); 20208 } finally { 20209 runningChecksEnabled = expensiveCheckOldValue; 20210 } 20211 } 20212 } 20213 20214 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 20215 20216 if (newValue == null || oldValueOfValue == null) { // null/undefined 20217 return newValue === oldValueOfValue; 20218 } 20219 20220 if (typeof newValue === 'object') { 20221 20222 // attempt to convert the value to a primitive type 20223 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 20224 // be cheaply dirty-checked 20225 newValue = getValueOf(newValue); 20226 20227 if (typeof newValue === 'object') { 20228 // objects/arrays are not supported - deep-watching them would be too expensive 20229 return false;
20230 } 20231 20232 // fall-through to the primitive equality check 20233 } 20234 20235 //Primitive or NaN 20236 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 20237 } 20238 20239 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 20240 var inputExpressions = parsedExpression.inputs; 20241 var lastResult; 20242 20243 if (inputExpressions.length === 1) { 20244 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 20245 inputExpressions = inputExpressions[0]; 20246 return scope.$watch(function expressionInputWatch(scope) { 20247 var newInputValue = inputExpressions(scope); 20248 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 20249 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 20250 oldInputValueOf = newInputValue && getValueOf(newInputValue); 20251 } 20252 return lastResult; 20253 }, listener, objectEquality, prettyPrintExpression); 20254 } 20255 20256 var oldInputValueOfValues = []; 20257 var oldInputValues = []; 20258 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 20259 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 20260 oldInputValues[i] = null; 20261 } 20262 20263 return scope.$watch(function expressionInputsWatch(scope) { 20264 var changed = false; 20265 20266 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 20267 var newInputValue = inputExpressions[i](scope); 20268 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 20269 oldInputValues[i] = newInputValue; 20270 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 20271 } 20272 } 20273 20274 if (changed) { 20275 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 20276 } 20277 20278 return lastResult; 20279 }, listener, objectEquality, prettyPrintExpression); 20280 } 20281 20282 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 20283 var unwatch, lastValue; 20284 return unwatch = scope.$watch(function oneTimeWatch(scope) { 20285 return parsedExpression(scope); 20286 }, function oneTimeListener(value, old, scope) { 20287 lastValue = value; 20288 if (isFunction(listener)) { 20289 listener.apply(this, arguments); 20290 } 20291 if (isDefined(value)) { 20292 scope.$$postDigest(function() { 20293 if (isDefined(lastValue)) { 20294 unwatch(); 20295 } 20296 }); 20297 } 20298 }, objectEquality); 20299 } 20300 20301 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 20302 var unwatch, lastValue; 20303 return unwatch = scope.$watch(function oneTimeWatch(scope) { 20304 return parsedExpression(scope); 20305 }, function oneTimeListener(value, old, scope) { 20306 lastValue = value; 20307 if (isFunction(listener)) { 20308 listener.call(this, value, old, scope); 20309 } 20310 if (isAllDefined(value)) { 20311 scope.$$postDigest(function() { 20312 if (isAllDefined(lastValue)) unwatch(); 20313 }); 20314 } 20315 }, objectEquality); 20316 20317 function isAllDefined(value) { 20318 var allDefined = true; 20319 forEach(value, function(val) { 20320 if (!isDefined(val)) allDefined = false; 20321 }); 20322 return allDefined; 20323 } 20324 } 20325 20326 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 20327 var unwatch; 20328 return unwatch = scope.$watch(function constantWatch(scope) { 20329 unwatch(); 20330 return parsedExpression(scope); 20331 }, listener, objectEquality); 20332 } 20333 20334 function addInterceptor(parsedExpression, interceptorFn) { 20335 if (!interceptorFn) return parsedExpression; 20336 var watchDelegate = parsedExpression.$$watchDelegate; 20337 var useInputs = false; 20338 20339 var regularWatch = 20340 watchDelegate !== oneTimeLiteralWatchDelegate && 20341 watchDelegate !== oneTimeWatchDelegate; 20342 20343 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
20344 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); 20345 return interceptorFn(value, scope, locals); 20346 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 20347 var value = parsedExpression(scope, locals, assign, inputs); 20348 var result = interceptorFn(value, scope, locals); 20349 // we only return the interceptor's result if the 20350 // initial value is defined (for bind-once) 20351 return isDefined(value) ? result : value; 20352 }; 20353 20354 // Propagate $$watchDelegates other then inputsWatchDelegate 20355 if (parsedExpression.$$watchDelegate && 20356 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 20357 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 20358 } else if (!interceptorFn.$stateful) { 20359 // If there is an interceptor, but no watchDelegate then treat the interceptor like 20360 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 20361 fn.$$watchDelegate = inputsWatchDelegate; 20362 useInputs = !parsedExpression.inputs; 20363 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 20364 } 20365 20366 return fn; 20367 } 20368 }]; 20369} 20370 20371/** 20372 * @ngdoc service 20373 * @name $q 20374 * @requires $rootScope 20375 * 20376 * @description 20377 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 20378 * when they are done processing. 20379 * 20380 * This is an implementation of promises/deferred objects inspired by 20381 * [Kris Kowal's Q](https://github.com/kriskowal/q). 20382 * 20383 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 20384 * implementations, and the other which resembles ES6 promises to some degree. 20385 * 20386 * # $q constructor 20387 * 20388 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 20389 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, 20390 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 20391 * 20392 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are 20393 * available yet. 20394 * 20395 * It can be used like so: 20396 * 20397 * ```js 20398 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 20399 * // are available in the current lexical scope (they could have been injected or passed in). 20400 * 20401 * function asyncGreet(name) { 20402 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 20403 * return $q(function(resolve, reject) { 20404 * setTimeout(function() { 20405 * if (okToGreet(name)) { 20406 * resolve('Hello, ' + name + '!'); 20407 * } else { 20408 * reject('Greeting ' + name + ' is not allowed.'); 20409 * } 20410 * }, 1000); 20411 * }); 20412 * } 20413 * 20414 * var promise = asyncGreet('Robin Hood'); 20415 * promise.then(function(greeting) { 20416 * alert('Success: ' + greeting); 20417 * }, function(reason) { 20418 * alert('Failed: ' + reason); 20419 * }); 20420 * ``` 20421 * 20422 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 20423 * 20424 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. 20425 * 20426 * However, the more traditional CommonJS-style usage is still available, and documented below. 20427 * 20428 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 20429 * interface for interacting with an object that represents the result of an action that is 20430 * performed asynchronously, and may or may not be finished at any given point in time. 20431 * 20432 * From the perspective of dealing with error handling, deferred and promise APIs are to 20433 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 20434 * 20435 * ```js 20436 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 20437 * // are available in the current lexical scope (they could have been injected or passed in). 20438 * 20439 * function asyncGreet(name) { 20440 * var deferred = $q.defer(); 20441 * 20442 * setTimeout(function() { 20443 * deferred.notify('About to greet ' + name + '.'); 20444 * 20445 * if (okToGreet(name)) { 20446 * deferred.resolve('Hello, ' + name + '!'); 20447 * } else { 20448 * deferred.reject('Greeting ' + name + ' is not allowed.'); 20449 * } 20450 * }, 1000); 20451 * 20452 * return deferred.promise; 20453 * } 20454 * 20455 * var promise = asyncGreet('Robin Hood'); 20456 * promise.then(function(greeting) { 20457 * alert('Success: ' + greeting); 20458 * }, function(reason) { 20459 * alert('Failed: ' + reason); 20460 * }, function(update) { 20461 * alert('Got notification: ' + update); 20462 * }); 20463 * ``` 20464 * 20465 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 20466 * comes in the way of guarantees that promise and deferred APIs make, see 20467 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 20468 *
20469 * Additionally the promise api allows for composition that is very hard to do with the 20470 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 20471 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 20472 * section on serial or parallel joining of promises. 20473 * 20474 * # The Deferred API 20475 * 20476 * A new instance of deferred is constructed by calling `$q.defer()`. 20477 * 20478 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 20479 * that can be used for signaling the successful or unsuccessful completion, as well as the status 20480 * of the task. 20481 * 20482 * **Methods** 20483 * 20484 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 20485 * constructed via `$q.reject`, the promise will be rejected instead. 20486 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 20487 * resolving it with a rejection constructed via `$q.reject`. 20488 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 20489 * multiple times before the promise is either resolved or rejected. 20490 * 20491 * **Properties** 20492 * 20493 * - promise â `{Promise}` â promise object associated with this deferred. 20494 * 20495 * 20496 * # The Promise API 20497 * 20498 * A new promise instance is created when a deferred instance is created and can be retrieved by 20499 * calling `deferred.promise`. 20500 * 20501 * The purpose of the promise object is to allow for interested parties to get access to the result 20502 * of the deferred task when it completes. 20503 * 20504 * **Methods** 20505 * 20506 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 20507 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 20508 * as soon as the result is available. The callbacks are called with a single argument: the result 20509 * or rejection reason. Additionally, the notify callback may be called zero or more times to 20510 * provide a progress indication, before the promise is resolved or rejected. 20511 * 20512 * This method *returns a new promise* which is resolved or rejected via the return value of the 20513 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
20514 * with the value which is resolved in that promise using 20515 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 20516 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 20517 * resolved or rejected from the notifyCallback method. 20518 * 20519 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 20520 * 20521 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 20522 * but to do so without modifying the final value. This is useful to release resources or do some 20523 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 20524 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 20525 * more information. 20526 * 20527 * # Chaining promises 20528 * 20529 * Because calling the `then` method of a promise returns a new derived promise, it is easily 20530 * possible to create a chain of promises: 20531 * 20532 * ```js 20533 * promiseB = promiseA.then(function(result) { 20534 * return result + 1; 20535 * }); 20536 * 20537 * // promiseB will be resolved immediately after promiseA is resolved and its value 20538 * // will be the result of promiseA incremented by 1 20539 * ``` 20540 * 20541 * It is possible to create chains of any length and since a promise can be resolved with another 20542 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 20543 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 20544 * $http's response interceptors. 20545 * 20546 * 20547 * # Differences between Kris Kowal's Q and $q 20548 * 20549 * There are two main differences: 20550 * 20551 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 20552 * mechanism in angular, which means faster propagation of resolution or rejection into your 20553 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 20554 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
20555 * all the important functionality needed for common async tasks. 20556 * 20557 * # Testing 20558 * 20559 * ```js 20560 * it('should simulate promise', inject(function($q, $rootScope) { 20561 * var deferred = $q.defer(); 20562 * var promise = deferred.promise; 20563 * var resolvedValue; 20564 * 20565 * promise.then(function(value) { resolvedValue = value; }); 20566 * expect(resolvedValue).toBeUndefined(); 20567 * 20568 * // Simulate resolving of promise 20569 * deferred.resolve(123); 20570 * // Note that the 'then' function does not get called synchronously. 20571 * // This is because we want the promise API to always be async, whether or not 20572 * // it got called synchronously or asynchronously. 20573 * expect(resolvedValue).toBeUndefined(); 20574 * 20575 * // Propagate promise resolution to 'then' functions using $apply(). 20576 * $rootScope.$apply(); 20577 * expect(resolvedValue).toEqual(123); 20578 * })); 20579 * ``` 20580 * 20581 * @param {function(function, function)} resolver Function which is responsible for resolving or 20582 * rejecting the newly created promise. The first parameter is a function which resolves the 20583 * promise, the second parameter is a function which rejects the promise. 20584 * 20585 * @returns {Promise} The newly created promise. 20586 */ 20587function $QProvider() { 20588 20589 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 20590 return qFactory(function(callback) { 20591 $rootScope.$evalAsync(callback); 20592 }, $exceptionHandler); 20593 }]; 20594} 20595 20596function $$QProvider() { 20597 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 20598 return qFactory(function(callback) { 20599 $browser.defer(callback); 20600 }, $exceptionHandler); 20601 }]; 20602} 20603 20604/** 20605 * Constructs a promise manager. 20606 * 20607 * @param {function(function)} nextTick Function for executing functions in the next turn. 20608 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 20609 * debugging purposes. 20610 * @returns {object} Promise manager. 20611 */ 20612function qFactory(nextTick, exceptionHandler) { 20613 var $qMinErr = minErr('$q', TypeError); 20614 20615 /** 20616 * @ngdoc method 20617 * @name ng.$q#defer 20618 * @kind function 20619 * 20620 * @description 20621 * Creates a `Deferred` object which represents a task which will finish in the future. 20622 * 20623 * @returns {Deferred} Returns a new instance of deferred. 20624 */ 20625 var defer = function() { 20626 var d = new Deferred(); 20627 //Necessary to support unbound execution :/ 20628 d.resolve = simpleBind(d, d.resolve); 20629 d.reject = simpleBind(d, d.reject); 20630 d.notify = simpleBind(d, d.notify); 20631 return d; 20632 }; 20633 20634 function Promise() { 20635 this.$$state = { status: 0 }; 20636 } 20637 20638 extend(Promise.prototype, { 20639 then: function(onFulfilled, onRejected, progressBack) { 20640 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 20641 return this; 20642 } 20643 var result = new Deferred(); 20644 20645 this.$$state.pending = this.$$state.pending || []; 20646 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 20647 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 20648 20649 return result.promise; 20650 }, 20651 20652 "catch": function(callback) { 20653 return this.then(null, callback); 20654 }, 20655 20656 "finally": function(callback, progressBack) { 20657 return this.then(function(value) { 20658 return handleCallback(value, true, callback); 20659 }, function(error) { 20660 return handleCallback(error, false, callback); 20661 }, progressBack); 20662 } 20663 }); 20664 20665 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 20666 function simpleBind(context, fn) { 20667 return function(value) { 20668 fn.call(context, value); 20669 }; 20670 } 20671 20672 function processQueue(state) { 20673 var fn, deferred, pending; 20674 20675 pending = state.pending; 20676 state.processScheduled = false; 20677 state.pending = undefined; 20678 for (var i = 0, ii = pending.length; i < ii; ++i) { 20679 deferred = pending[i][0]; 20680 fn = pending[i][state.status]; 20681 try { 20682 if (isFunction(fn)) { 20683 deferred.resolve(fn(state.value)); 20684 } else if (state.status === 1) { 20685 deferred.resolve(state.value); 20686 } else { 20687 deferred.reject(state.value); 20688 } 20689 } catch (e) { 20690 deferred.reject(e); 20691 exceptionHandler(e); 20692 } 20693 } 20694 } 20695 20696 function scheduleProcessQueue(state) { 20697 if (state.processScheduled || !state.pending) return; 20698 state.processScheduled = true; 20699 nextTick(function() { processQueue(state); }); 20700 } 20701 20702 function Deferred() { 20703 this.promise = new Promise(); 20704 } 20705 20706 extend(Deferred.prototype, { 20707 resolve: function(val) { 20708 if (this.promise.$$state.status) return; 20709 if (val === this.promise) { 20710 this.$$reject($qMinErr( 20711 'qcycle', 20712 "Expected promise to be resolved with value other than
20712itself '{0}'", 20713 val)); 20714 } else { 20715 this.$$resolve(val); 20716 } 20717 20718 }, 20719 20720 $$resolve: function(val) { 20721 var then; 20722 var that = this; 20723 var done = false; 20724 try { 20725 if ((isObject(val) || isFunction(val))) then = val && val.then; 20726 if (isFunction(then)) { 20727 this.promise.$$state.status = -1; 20728 then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify)); 20729 } else { 20730 this.promise.$$state.value = val; 20731 this.promise.$$state.status = 1; 20732 scheduleProcessQueue(this.promise.$$state); 20733 } 20734 } catch (e) { 20735 rejectPromise(e); 20736 exceptionHandler(e); 20737 } 20738 20739 function resolvePromise(val) { 20740 if (done) return; 20741 done = true; 20742 that.$$resolve(val); 20743 } 20744 function rejectPromise(val) { 20745 if (done) return; 20746 done = true; 20747 that.$$reject(val); 20748 } 20749 }, 20750 20751 reject: function(reason) { 20752 if (this.promise.$$state.status) return; 20753 this.$$reject(reason); 20754 }, 20755 20756 $$reject: function(reason) { 20757 this.promise.$$state.value = reason; 20758 this.promise.$$state.status = 2; 20759 scheduleProcessQueue(this.promise.$$state); 20760 }, 20761 20762 notify: function(progress) { 20763 var callbacks = this.promise.$$state.pending; 20764 20765 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 20766 nextTick(function() { 20767 var callback, result; 20768 for (var i = 0, ii = callbacks.length; i < ii; i++) { 20769 result = callbacks[i][0]; 20770 callback = callbacks[i][3]; 20771 try { 20772 result.notify(isFunction(callback) ? callback(progress) : progress); 20773 } catch (e) { 20774 exceptionHandler(e); 20775 } 20776 } 20777 }); 20778 } 20779 } 20780 }); 20781 20782 /** 20783 * @ngdoc method 20784 * @name $q#reject 20785 * @kind function 20786 * 20787 * @description 20788 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 20789 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 20790 * a promise chain, you don't need to worry about it. 20791 * 20792 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 20793 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 20794 * a promise error callback and you want to forward the error to the promise derived from the 20795 * current promise, you have to "rethrow" the error by returning a rejection constructed via 20796 * `reject`. 20797 * 20798 * ```js 20799 * promiseB = promiseA.then(function(result) { 20800 * // success: do something and resolve promiseB 20801 * // with the old or a new result 20802 * return result; 20803 * }, function(reason) { 20804 * // error: handle the error if possible and 20805 * // resolve promiseB with newPromiseOrValue, 20806 * // otherwise forward the rejection to promiseB 20807 * if (canHandle(reason)) { 20808 * // handle the error and recover 20809 * return newPromiseOrValue; 20810 * } 20811 * return $q.reject(reason); 20812 * }); 20813 * ``` 20814 * 20815 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 20816 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 20817 */ 20818 var reject = function(reason) { 20819 var result = new Deferred(); 20820 result.reject(reason); 20821 return result.promise; 20822 }; 20823 20824 var makePromise = function makePromise(value, resolved) { 20825 var result = new Deferred(); 20826 if (resolved) { 20827 result.resolve(value); 20828 } else { 20829 result.reject(value); 20830 } 20831 return result.promise; 20832 }; 20833 20834 var handleCallback = function handleCallback(value, isResolved, callback) { 20835 var callbackOutput = null; 20836 try { 20837 if (isFunction(callback)) callbackOutput = callback(); 20838 } catch (e) { 20839 return makePromise(e, false); 20840 } 20841 if (isPromiseLike(callbackOutput)) { 20842 return callbackOutput.then(function() { 20843 return makePromise(value, isResolved); 20844 }, function(error) { 20845 return makePromise(error, false); 20846 }); 20847 } else { 20848 return makePromise(value, isResolved); 20849 } 20850 }; 20851 20852 /** 20853 * @ngdoc method 20854 * @name $q#when 20855 * @kind function 20856 * 20857 * @description 20858 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 20859 * This is useful when you are dealing with an object that might or might not be a promise, or if 20860 * the promise comes from a source that can't be trusted. 20861 * 20862 * @param {*} value Value or a promise 20863 * @param {Function=} successCallback 20864 * @param {Function=} errorCallback 20865 * @param {Function=} progressCallback 20866 * @returns {Promise} Returns a promise of the passed value or promise 20867 */ 20868 20869 20870 var when = function(value, callback, errback, progressBack) { 20871 var result = new Deferred(); 20872 result.resolve(value); 20873 return result.promise.then(callback, errback, progressBack); 20874 }; 20875 20876 /** 20877 * @ngdoc method 20878 * @name $q#resolve 20879 * @kind function 20880 * 20881 * @description 20882 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 20883 * 20884 * @param {*} value Value or a promise 20885 * @param {Function=} successCallback 20886 * @param {Function=} errorCallback 20887 * @param {Function=} progressCallback 20888 * @returns {Promise} Returns a promise of the passed value or promise 20889 */ 20890 var resolve = when; 20891 20892 /** 20893 * @ngdoc method 20894 * @name $q#all 20895 * @kind function 20896 * 20897 * @description 20898 * Combines multiple promises into a single promise that is resolved when all of the input 20899 * promises are resolved. 20900 * 20901 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 20902 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 20903 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 20904 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 20905 * with the same rejection value. 20906 */ 20907 20908 function all(promises) { 20909 var deferred = new Deferred(), 20910 counter = 0, 20911 results = isArray(promises) ? [] : {}; 20912
20913 forEach(promises, function(promise, key) { 20914 counter++; 20915 when(promise).then(function(value) { 20916 if (results.hasOwnProperty(key)) return; 20917 results[key] = value; 20918 if (!(--counter)) deferred.resolve(results); 20919 }, function(reason) { 20920 if (results.hasOwnProperty(key)) return; 20921 deferred.reject(reason); 20922 }); 20923 }); 20924 20925 if (counter === 0) { 20926 deferred.resolve(results); 20927 } 20928 20929 return deferred.promise; 20930 } 20931 20932 var $Q = function Q(resolver) { 20933 if (!isFunction(resolver)) { 20934 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 20935 } 20936 20937 var deferred = new Deferred(); 20938 20939 function resolveFn(value) { 20940 deferred.resolve(value); 20941 } 20942 20943 function rejectFn(reason) { 20944 deferred.reject(reason); 20945 } 20946 20947 resolver(resolveFn, rejectFn); 20948 20949 return deferred.promise; 20950 }; 20951 20952 // Let's make the instanceof operator work for promises, so that 20953 // `new $q(fn) instanceof $q` would evaluate to true. 20954 $Q.prototype = Promise.prototype; 20955 20956 $Q.defer = defer; 20957 $Q.reject = reject; 20958 $Q.when = when; 20959 $Q.resolve = resolve; 20960 $Q.all = all; 20961 20962 return $Q; 20963} 20964 20965function $$RAFProvider() { //rAF 20966 this.$get = ['$window', '$timeout', function($window, $timeout) { 20967 var requestAnimationFrame = $window.requestAnimationFrame || 20968 $window.webkitRequestAnimationFrame; 20969 20970 var cancelAnimationFrame = $window.cancelAnimationFrame || 20971 $window.webkitCancelAnimationFrame || 20972 $window.webkitCancelRequestAnimationFrame; 20973 20974 var rafSupported = !!requestAnimationFrame; 20975 var raf = rafSupported 20976 ? function(fn) { 20977 var id = requestAnimationFrame(fn); 20978 return function() { 20979 cancelAnimationFrame(id); 20980 }; 20981 } 20982 : function(fn) { 20983 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 20984 return function() { 20985 $timeout.cancel(timer); 20986 }; 20987 }; 20988 20989 raf.supported = rafSupported; 20990 20991 return raf; 20992 }]; 20993} 20994 20995/** 20996 * DESIGN NOTES 20997 * 20998 * The design decisions behind the scope are heavily favored for speed and memory consumption. 20999 * 21000 * The typical use of scope is to watch the expressions, which most of the time return the same 21001 * value as last time so we optimize the operation. 21002 * 21003 * Closures construction is expensive in terms of speed as well as memory: 21004 * - No closures, instead use prototypical inheritance for API 21005 * - Internal state needs to be stored on scope directly, which means that private state is 21006 * exposed as $$____ properties 21007 * 21008 * Loop operations are optimized by using while(count--) { ... } 21009 * - This means that in order to keep the same order of execution as addition we have to add 21010 * items to the array at the beginning (unshift) instead of at the end (push) 21011 * 21012 * Child scopes are created and removed often 21013 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists 21014 * 21015 * There are fewer watches than observers. This is why you don't want the observer to be implemented 21016 * in the same way as watch. Watch requires return of the initialization function which is expensive 21017 * to construct. 21018 */ 21019 21020 21021/** 21022 * @ngdoc provider 21023 * @name $rootScopeProvider 21024 * @description 21025 * 21026 * Provider for the $rootScope service. 21027 */ 21028 21029/** 21030 * @ngdoc method 21031 * @name $rootScopeProvider#digestTtl 21032 * @description 21033 * 21034 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 21035 * assuming that the model is unstable. 21036 * 21037 * The current default is 10 iterations. 21038 * 21039 * In complex applications it's possible that the dependencies between `$watch`s will result in 21040 * several digest iterations. However if an application needs more than the default 10 digest 21041 * iterations for its model to stabilize then you should investigate what is causing the model to 21042 * continuously change during the digest. 21043 * 21044 * Increasing the TTL could have performance implications, so you should not change it without 21045 * proper justification. 21046 * 21047 * @param {number} limit The number of digest iterations. 21048 */ 21049 21050 21051/** 21052 * @ngdoc service 21053 * @name $rootScope 21054 * @description 21055 * 21056 * Every application has a single root {@link ng.$rootScope.Scope scope}. 21057 * All other scopes are descendant scopes of the root scope. Scopes provide separation 21058 * between the model and the view, via a mechanism for watching the model for changes. 21059 * They also provide event emission/broadcast and subscription facility. See the 21060 * {@link guide/scope developer guide on scopes}. 21061 */ 21062function $RootScopeProvider() { 21063 var TTL = 10; 21064 var $rootScopeMinErr = minErr('$rootScope'); 21065 var lastDirtyWatch = null; 21066 var applyAsyncId = null; 21067 21068 this.digestTtl = function(value) { 21069 if (arguments.length) { 21070 TTL = value; 21071 } 21072 return TTL; 21073 }; 21074 21075 function createChildScopeClass(parent) { 21076 function ChildScope() {
21077 this.$$watchers = this.$$nextSibling = 21078 this.$$childHead = this.$$childTail = null; 21079 this.$$listeners = {}; 21080 this.$$listenerCount = {}; 21081 this.$$watchersCount = 0; 21082 this.$id = nextUid(); 21083 this.$$ChildScope = null; 21084 } 21085 ChildScope.prototype = parent; 21086 return ChildScope; 21087 } 21088 21089 this.$get = ['$exceptionHandler', '$parse', '$browser', 21090 function($exceptionHandler, $parse, $browser) { 21091 21092 function destroyChildScope($event) { 21093 $event.currentScope.$$destroyed = true; 21094 } 21095 21096 function cleanUpScope($scope) { 21097 21098 if (msie === 9) { 21099 // There is a memory leak in IE9 if all child scopes are not disconnected 21100 // completely when a scope is destroyed. So this code will recurse up through 21101 // all this scopes children 21102 // 21103 // See issue https://github.com/angular/angular.js/issues/10706 21104 $scope.$$childHead && cleanUpScope($scope.$$childHead); 21105 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling); 21106 } 21107 21108 // The code below works around IE9 and V8's memory leaks 21109 // 21110 // See: 21111 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 21112 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 21113 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 21114 21115 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = 21116 $scope.$$childTail = $scope.$root = $scope.$$watchers = null; 21117 } 21118 21119 /** 21120 * @ngdoc type 21121 * @name $rootScope.Scope 21122 * 21123 * @description 21124 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 21125 * {@link auto.$injector $injector}. Child scopes are created using the 21126 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 21127 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 21128 * an in-depth introduction and usage examples. 21129 * 21130 * 21131 * # Inheritance 21132 * A scope can inherit from a parent scope, as in this example: 21133 * ```js 21134 var parent = $rootScope; 21135 var child = parent.$new(); 21136 21137 parent.salutation = "Hello"; 21138 expect(child.salutation).toEqual('Hello'); 21139 21140 child.salutation = "Welcome"; 21141 expect(child.salutation).toEqual('Welcome'); 21142 expect(parent.salutation).toEqual('Hello'); 21143 * ``` 21144 * 21145 * When interacting with `Scope` in tests, additional helper methods are available on the 21146 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 21147 * details. 21148 * 21149 * 21150 * @param {Object.<string, function()>=} providers Map of service factory which need to be 21151 * provided for the current scope. Defaults to {@link ng}. 21152 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 21153 * append/override services provided by `providers`. This is handy 21154 * when unit-testing and having the need to override a default 21155 * service. 21156 * @returns {Object} Newly created scope. 21157 * 21158 */ 21159 function Scope() { 21160 this.$id = nextUid(); 21161 this.$$phase = this.$parent = this.$$watchers = 21162 this.$$nextSibling = this.$$prevSibling = 21163 this.$$childHead = this.$$childTail = null; 21164 this.$root = this; 21165 this.$$destroyed = false; 21166 this.$$listeners = {}; 21167 this.$$listenerCount = {}; 21168 this.$$watchersCount = 0; 21169 this.$$isolateBindings = null; 21170 } 21171 21172 /** 21173 * @ngdoc property 21174 * @name $rootScope.Scope#$id 21175 * 21176 * @description 21177 * Unique scope ID (monotonically increasing) useful for debugging. 21178 */ 21179 21180 /** 21181 * @ngdoc property 21182 * @name $rootScope.Scope#$parent 21183 * 21184 * @description 21185 * Reference to the parent scope. 21186 */ 21187 21188 /** 21189 * @ngdoc property 21190 * @name $rootScope.Scope#$root 21191 *
21192 * @description 21193 * Reference to the root scope. 21194 */ 21195 21196 Scope.prototype = { 21197 constructor: Scope, 21198 /** 21199 * @ngdoc method 21200 * @name $rootScope.Scope#$new 21201 * @kind function 21202 * 21203 * @description 21204 * Creates a new child {@link ng.$rootScope.Scope scope}. 21205 * 21206 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 21207 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 21208 * 21209 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 21210 * desired for the scope and its child scopes to be permanently detached from the parent and 21211 * thus stop participating in model change detection and listener notification by invoking. 21212 * 21213 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 21214 * parent scope. The scope is isolated, as it can not see parent scope properties. 21215 * When creating widgets, it is useful for the widget to not accidentally read parent 21216 * state. 21217 * 21218 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 21219 * of the newly created scope. Defaults to `this` scope if not provided. 21220 * This is used when creating a transclude scope to correctly place it 21221 * in the scope hierarchy while maintaining the correct prototypical 21222 * inheritance. 21223 * 21224 * @returns {Object} The newly created child scope. 21225 * 21226 */ 21227 $new: function(isolate, parent) { 21228 var child; 21229 21230 parent = parent || this; 21231 21232 if (isolate) { 21233 child = new Scope(); 21234 child.$root = this.$root; 21235 } else { 21236 // Only create a child scope class if somebody asks for one, 21237 // but cache it to allow the VM to optimize lookups. 21238 if (!this.$$ChildScope) { 21239 this.$$ChildScope = createChildScopeClass(this); 21240 } 21241 child = new this.$$ChildScope(); 21242 } 21243 child.$parent = parent; 21244 child.$$prevSibling = parent.$$childTail; 21245 if (parent.$$childHead) { 21246 parent.$$childTail.$$nextSibling = child; 21247 parent.$$childTail = child; 21248 } else { 21249 parent.$$childHead = parent.$$childTail = child; 21250 } 21251 21252 // When the new scope is not isolated or we inherit from `this`, and 21253 // the parent scope is destroyed, the property `$$destroyed` is inherited 21254 // prototypically. In all other cases, this property needs to be set 21255 // when the parent scope is destroyed. 21256 // The listener needs to be added after the parent is set 21257 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 21258 21259 return child; 21260 }, 21261 21262 /** 21263 * @ngdoc method 21264 * @name $rootScope.Scope#$watch 21265 * @kind function 21266 * 21267 * @description 21268 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 21269 * 21270 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 21271 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 21272 * its value when executed multiple times with the same input because it may be executed multiple 21273 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 21274 * [idempotent](http://en.wikipedia.org/wiki/Idempotence). 21275 * - The `listener` is called only when the value from the current `watchExpression` and the 21276 * previous call to `watchExpression` are not equal (with the exception of the initial run, 21277 * see below). Inequality is determined according to reference inequality, 21278 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 21279 * via the `!==` Javascript operator, unless `objectEquality == true` 21280 * (see next point) 21281 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 21282 * according to the {@link angular.equals} function. To save the value of the object for 21283 * later comparison, the {@link angular.copy} function is used. This therefore means that 21284 * watching complex objects will have adverse memory and performance implications. 21285 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 21286 * This is achieved by rerunning the watchers until no changes are detected. The rerun 21287 * iteration limit is 10 to prevent an infinite loop deadlock. 21288 * 21289 * 21290 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 21291 * you can register a `watchExpression` function with no `listener`. (Be prepared for 21292 * multiple calls to your `watchExpression` because it will execute multiple times in a 21293 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 21294 * 21295 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 21296 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 21297 * watcher. In rare cases, this is undesirable because the listener is called when the result 21298 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 21299 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 21300 * listener was called due to initialization. 21301 * 21302 * 21303 * 21304 * # Example 21305 * ```js 21306 // let's assume that scope was dependency injected as the $rootScope 21307 var scope = $rootScope; 21308 scope.name = 'misko'; 21309 scope.counter = 0; 21310 21311 expect(scope.counter).toEqual(0); 21312 scope.$watch('name', function(newValue, oldValue) { 21313 scope.counter = scope.counter + 1; 21314 }); 21315 expect(scope.counter).toEqual(0); 21316 21317 scope.$digest(); 21318 // the listener is always called during the first $digest loop after it was registered 21319 expect(scope.counter).toEqual(1); 21320 21321 scope.$digest(); 21322 // but now it will not be called unless the value changes 21323 expect(scope.counter).toEqual(1); 21324 21325 scope.name = 'adam'; 21326 scope.$digest(); 21327 expect(scope.counter).toEqual(2); 21328 21329 21330
21331 // Using a function as a watchExpression 21332 var food; 21333 scope.foodCounter = 0; 21334 expect(scope.foodCounter).toEqual(0); 21335 scope.$watch( 21336 // This function returns the value being watched. It is called for each turn of the $digest loop 21337 function() { return food; }, 21338 // This is the change listener, called when the value returned from the above function changes 21339 function(newValue, oldValue) { 21340 if ( newValue !== oldValue ) { 21341 // Only increment the counter if the value changed 21342 scope.foodCounter = scope.foodCounter + 1; 21343 } 21344 } 21345 ); 21346 // No digest has been run so the counter will be zero 21347 expect(scope.foodCounter).toEqual(0); 21348 21349 // Run the digest but since food has not changed count will still be zero 21350 scope.$digest(); 21351 expect(scope.foodCounter).toEqual(0); 21352 21353 // Update food and run digest. Now the counter will increment 21354 food = 'cheeseburger'; 21355 scope.$digest(); 21356 expect(scope.foodCounter).toEqual(1); 21357 21358 * ``` 21359 * 21360 * 21361 * 21362 * @param {(function()|string)} watchExpression Expression that is evaluated on each 21363 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 21364 * a call to the `listener`. 21365 * 21366 * - `string`: Evaluated as {@link guide/expression expression} 21367 * - `function(scope)`: called with current `scope` as a parameter. 21368 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 21369 * of `watchExpression` changes. 21370 * 21371 * - `newVal` contains the current value of the `watchExpression` 21372 * - `oldVal` contains the previous value of the `watchExpression` 21373 * - `scope` refers to the current scope 21374 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of 21375 * comparing for reference equality. 21376 * @returns {function()} Returns a deregistration function for this listener. 21377 */ 21378 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 21379 var get = $parse(watchExp); 21380 21381 if (get.$$watchDelegate) { 21382 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 21383 } 21384 var scope = this, 21385 array = scope.$$watchers, 21386 watcher = { 21387 fn: listener, 21388 last: initWatchVal, 21389 get: get, 21390 exp: prettyPrintExpression || watchExp, 21391 eq: !!objectEquality 21392 }; 21393 21394 lastDirtyWatch = null; 21395 21396 if (!isFunction(listener)) { 21397 watcher.fn = noop; 21398 } 21399 21400 if (!array) { 21401 array = scope.$$watchers = []; 21402 } 21403 // we use unshift since we use a while loop in $digest for speed. 21404 // the while loop reads in reverse order. 21405 array.unshift(watcher); 21406 incrementWatchersCount(this, 1); 21407 21408 return function deregisterWatch() { 21409 if (arrayRemove(array, watcher) >= 0) { 21410 incrementWatchersCount(scope, -1); 21411 } 21412 lastDirtyWatch = null; 21413 }; 21414 }, 21415 21416 /** 21417 * @ngdoc method 21418 * @name $rootScope.Scope#$watchGroup 21419 * @kind function 21420 * 21421 * @description 21422 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 21423 * If any one expression in the collection changes the `listener` is executed. 21424 * 21425 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 21426 * call to $digest() to see if any items changes. 21427 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 21428 * 21429 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 21430 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 21431 * 21432 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 21433 * expression in `watchExpressions` changes 21434 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 21435 * those of `watchExpression` 21436 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 21437 * those of `watchExpression` 21438 * The `scope` refers to the current scope. 21439 * @returns {function()} Returns a de-registration function for all listeners. 21440 */ 21441 $watchGroup: function(watchExpressions, listener) { 21442 var oldValues = new Array(watchExpressions.length); 21443 var newValues = new Array(watchExpressions.length); 21444 var deregisterFns = []; 21445 var self = this; 21446 var changeReactionScheduled = false;
vendor: 10,192 bytes, lines 21447-21709
21447 var firstRun = true; 21448 21449 if (!watchExpressions.length) { 21450 // No expressions means we call the listener ASAP 21451 var shouldCall = true; 21452 self.$evalAsync(function() { 21453 if (shouldCall) listener(newValues, newValues, self); 21454 }); 21455 return function deregisterWatchGroup() { 21456 shouldCall = false; 21457 }; 21458 } 21459 21460 if (watchExpressions.length === 1) { 21461 // Special case size of one 21462 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 21463 newValues[0] = value; 21464 oldValues[0] = oldValue; 21465 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 21466 }); 21467 } 21468 21469 forEach(watchExpressions, function(expr, i) { 21470 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 21471 newValues[i] = value; 21472 oldValues[i] = oldValue; 21473 if (!changeReactionScheduled) { 21474 changeReactionScheduled = true; 21475 self.$evalAsync(watchGroupAction); 21476 } 21477 }); 21478 deregisterFns.push(unwatchFn); 21479 }); 21480 21481 function watchGroupAction() { 21482 changeReactionScheduled = false; 21483 21484 if (firstRun) { 21485 firstRun = false; 21486 listener(newValues, newValues, self); 21487 } else { 21488 listener(newValues, oldValues, self); 21489 } 21490 } 21491 21492 return function deregisterWatchGroup() { 21493 while (deregisterFns.length) { 21494 deregisterFns.shift()(); 21495 } 21496 }; 21497 }, 21498 21499 21500 /** 21501 * @ngdoc method 21502 * @name $rootScope.Scope#$watchCollection 21503 * @kind function 21504 * 21505 * @description 21506 * Shallow watches the properties of an object and fires whenever any of the properties change 21507 * (for arrays, this implies watching the array items; for object maps, this implies watching 21508 * the properties). If a change is detected, the `listener` callback is fired. 21509 * 21510 * - The `obj` collection is observed via standard $watch operation and is examined on every 21511 * call to $digest() to see if any items have been added, removed, or moved. 21512 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 21513 * adding, removing, and moving items belonging to an object or array. 21514 * 21515 * 21516 * # Example 21517 * ```js 21518 $scope.names = ['igor', 'matias', 'misko', 'james']; 21519 $scope.dataCount = 4; 21520 21521 $scope.$watchCollection('names', function(newNames, oldNames) { 21522 $scope.dataCount = newNames.length; 21523 }); 21524 21525 expect($scope.dataCount).toEqual(4); 21526 $scope.$digest(); 21527 21528 //still at 4 ... no changes 21529 expect($scope.dataCount).toEqual(4); 21530 21531 $scope.names.pop(); 21532 $scope.$digest(); 21533 21534 //now there's been a change 21535 expect($scope.dataCount).toEqual(3); 21536 * ``` 21537 * 21538 * 21539 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 21540 * expression value should evaluate to an object or an array which is observed on each 21541 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 21542 * collection will trigger a call to the `listener`. 21543 * 21544 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 21545 * when a change is detected. 21546 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 21547 * - The `oldCollection` object is a copy of the former collection data. 21548 * Due to performance considerations, the`oldCollection` value is computed only if the 21549 * `listener` function declares two or more arguments. 21550 * - The `scope` argument refers to the current scope. 21551 * 21552 * @returns {function()} Returns a de-registration function for this listener. When the 21553 * de-registration function is executed, the internal watch operation is terminated. 21554 */ 21555 $watchCollection: function(obj, listener) { 21556 $watchCollectionInterceptor.$stateful = true; 21557 21558 var self = this; 21559 // the current value, updated on each dirty-check run 21560 var newValue; 21561 // a shallow copy of the newValue from the last dirty-check run, 21562 // updated to match newValue during dirty-check run 21563 var oldValue; 21564 // a shallow copy of the newValue from when the last change happened 21565 var veryOldValue; 21566 // only track veryOldValue if the listener is asking for it 21567 var trackVeryOldValue = (listener.length > 1); 21568 var changeDetected = 0; 21569 var changeDetector = $parse(obj, $watchCollectionInterceptor); 21570 var internalArray = []; 21571 var internalObject = {}; 21572 var initRun = true; 21573 var oldLength = 0; 21574 21575 function $watchCollectionInterceptor(_value) { 21576 newValue = _value; 21577 var newLength, key, bothNaN, newItem, oldItem; 21578 21579 // If the new value is undefined, then return undefined as the watch may be a one-time watch 21580 if (isUndefined(newValue)) return; 21581 21582 if (!isObject(newValue)) { // if primitive 21583 if (oldValue !== newValue) { 21584 oldValue = newValue; 21585 changeDetected++; 21586 } 21587 } else if (isArrayLike(newValue)) { 21588 if (oldValue !== internalArray) { 21589 // we are transitioning from something which was not an array into array. 21590 oldValue = internalArray; 21591 oldLength = oldValue.length = 0; 21592 changeDetected++; 21593 } 21594 21595 newLength = newValue.length; 21596 21597 if (oldLength !== newLength) { 21598 // if lengths do not match we need to trigger change notification 21599 changeDetected++; 21600 oldValue.length = oldLength = newLength; 21601 } 21602 // copy the items to oldValue and look for changes. 21603 for (var i = 0; i < newLength; i++) { 21604 oldItem = oldValue[i]; 21605 newItem = newValue[i]; 21606 21607 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 21608 if (!bothNaN && (oldItem !== newItem)) { 21609 changeDetected++; 21610 oldValue[i] = newItem; 21611 } 21612 } 21613 } else { 21614 if (oldValue !== internalObject) { 21615 // we are transitioning from something which was not an object into object. 21616 oldValue = internalObject = {}; 21617 oldLength = 0; 21618 changeDetected++; 21619 } 21620 // copy the items to oldValue and look for changes. 21621 newLength = 0; 21622 for (key in newValue) { 21623 if (hasOwnProperty.call(newValue, key)) { 21624 newLength++; 21625 newItem = newValue[key]; 21626 oldItem = oldValue[key]; 21627 21628 if (key in oldValue) { 21629 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 21630 if (!bothNaN && (oldItem !== newItem)) { 21631 changeDetected++; 21632 oldValue[key] = newItem; 21633 } 21634 } else { 21635 oldLength++; 21636 oldValue[key] = newItem; 21637 changeDetected++; 21638 } 21639 } 21640 } 21641 if (oldLength > newLength) { 21642 // we used to have more keys, need to find them and destroy them. 21643 changeDetected++; 21644 for (key in oldValue) { 21645 if (!hasOwnProperty.call(newValue, key)) { 21646 oldLength--; 21647 delete oldValue[key]; 21648 } 21649 } 21650 } 21651 } 21652 return changeDetected; 21653 } 21654 21655 function $watchCollectionAction() { 21656 if (initRun) { 21657 initRun = false; 21658 listener(newValue, newValue, self); 21659 } else { 21660 listener(newValue, veryOldValue, self); 21661 } 21662 21663 // make a copy for the next time a collection is changed 21664 if (trackVeryOldValue) { 21665 if (!isObject(newValue)) { 21666 //primitive 21667 veryOldValue = newValue; 21668 } else if (isArrayLike(newValue)) { 21669 veryOldValue = new Array(newValue.length); 21670 for (var i = 0; i < newValue.length; i++) { 21671 veryOldValue[i] = newValue[i]; 21672 } 21673 } else { // if object 21674 veryOldValue = {}; 21675 for (var key in newValue) { 21676 if (hasOwnProperty.call(newValue, key)) { 21677 veryOldValue[key] = newValue[key]; 21678 } 21679 } 21680 } 21681 } 21682 } 21683 21684 return this.$watch(changeDetector, $watchCollectionAction); 21685 }, 21686 21687 /** 21688 * @ngdoc method 21689 * @name $rootScope.Scope#$digest 21690 * @kind function 21691 * 21692 * @description 21693 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 21694 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 21695 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 21696 * until no more listeners are firing. This means that it is possible to get into an infinite 21697 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 21698 * iterations exceeds 10. 21699 * 21700 * Usually, you don't call `$digest()` directly in 21701 * {@link ng.directive:ngController controllers} or in 21702 * {@link ng.$compileProvider#directive directives}. 21703 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 21704 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 21705 * 21706 * If you want to be notified whenever `$digest()` is called, 21707 * you can register a `watchExpression` function with 21708 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 21709 *
21710 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 21711 * 21712 * # Example 21713 * ```js 21714 var scope = ...; 21715 scope.name = 'misko'; 21716 scope.counter = 0; 21717 21718 expect(scope.counter).toEqual(0); 21719 scope.$watch('name', function(newValue, oldValue) { 21720 scope.counter = scope.counter + 1; 21721 }); 21722 expect(scope.counter).toEqual(0); 21723 21724 scope.$digest(); 21725 // the listener is always called during the first $digest loop after it was registered 21726 expect(scope.counter).toEqual(1); 21727 21728 scope.$digest(); 21729 // but now it will not be called unless the value changes 21730 expect(scope.counter).toEqual(1); 21731 21732 scope.name = 'adam'; 21733 scope.$digest(); 21734 expect(scope.counter).toEqual(2); 21735 * ``` 21736 * 21737 */ 21738 $digest: function() { 21739 var watch, value, last, fn, get, 21740 watchers, 21741 length, 21742 dirty, ttl = TTL, 21743 next, current, target = this, 21744 watchLog = [], 21745 logIdx, logMsg, asyncTask; 21746 21747 beginPhase('$digest'); 21748 // Check for changes to browser url that happened in sync before the call to $digest 21749 $browser.$$checkUrlChange(); 21750 21751 if (this === $rootScope && applyAsyncId !== null) { 21752 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 21753 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 21754 $browser.defer.cancel(applyAsyncId); 21755 flushApplyAsync(); 21756 } 21757 21758 lastDirtyWatch = null; 21759 21760 do { // "while dirty" loop 21761 dirty = false; 21762 current = target; 21763 21764 while (asyncQueue.length) { 21765 try { 21766 asyncTask = asyncQueue.shift(); 21767 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 21768 } catch (e) { 21769 $exceptionHandler(e); 21770 } 21771 lastDirtyWatch = null; 21772 } 21773 21774 traverseScopesLoop: 21775 do { // "traverse the scopes" loop 21776 if ((watchers = current.$$watchers)) { 21777 // process our watches 21778 length = watchers.length; 21779 while (length--) { 21780 try { 21781 watch = watchers[length]; 21782 // Most common watches are on primitives, in which case we can short 21783 // circuit it with === operator, only when === fails do we use .equals 21784 if (watch) { 21785 get = watch.get; 21786 if ((value = get(current)) !== (last = watch.last) && 21787 !(watch.eq 21788 ? equals(value, last) 21789 : (typeof value === 'number' && typeof last === 'number' 21790 && isNaN(value) && isNaN(last)))) { 21791 dirty = true; 21792 lastDirtyWatch = watch; 21793 watch.last = watch.eq ? copy(value, null) : value; 21794 fn = watch.fn; 21795 fn(value, ((last === initWatchVal) ? value : last), current); 21796 if (ttl < 5) { 21797 logIdx = 4 - ttl; 21798 if (!watchLog[logIdx]) watchLog[logIdx] = []; 21799 watchLog[logIdx].push({ 21800 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 21801 newVal: value, 21802 oldVal: last 21803 }); 21804 } 21805 } else if (watch === lastDirtyWatch) { 21806 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 21807 // have already been tested. 21808 dirty = false; 21809 break traverseScopesLoop; 21810 } 21811 } 21812 } catch (e) { 21813 $exceptionHandler(e); 21814 } 21815 } 21816 } 21817 21818 // Insanity Warning: scope depth-first traversal 21819 // yes, this code is a bit crazy, but it works and we have tests to prove it! 21820 // this piece should be kept in sync with the traversal in $broadcast 21821 if (!(next = ((current.$$watchersCount && current.$$childHead) || 21822 (current !== target && current.$$nextSibling)))) { 21823 while (current !== target && !(next = current.$$nextSibling)) { 21824 current = current.$parent; 21825 } 21826 } 21827 } while ((current = next)); 21828 21829 // `break traverseScopesLoop;` takes us to here 21830 21831 if ((dirty || asyncQueue.length) && !(ttl--)) { 21832 clearPhase(); 21833 throw $rootScopeMinErr('infdig', 21834 '{0} $digest() iterations reached. Aborting!\n' + 21835 'Watchers fired in the last 5 iterations: {1}', 21836 TTL, watchLog); 21837 } 21838 21839 }
vendor: 4,632 bytes, lines 21839-21958
21839 while (dirty || asyncQueue.length); 21840 21841 clearPhase(); 21842 21843 while (postDigestQueue.length) { 21844 try { 21845 postDigestQueue.shift()(); 21846 } catch (e) { 21847 $exceptionHandler(e); 21848 } 21849 } 21850 }, 21851 21852 21853 /** 21854 * @ngdoc event 21855 * @name $rootScope.Scope#$destroy 21856 * @eventType broadcast on scope being destroyed 21857 * 21858 * @description 21859 * Broadcasted when a scope and its children are being destroyed. 21860 * 21861 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 21862 * clean up DOM bindings before an element is removed from the DOM. 21863 */ 21864 21865 /** 21866 * @ngdoc method 21867 * @name $rootScope.Scope#$destroy 21868 * @kind function 21869 * 21870 * @description 21871 * Removes the current scope (and all of its children) from the parent scope. Removal implies 21872 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 21873 * propagate to the current scope and its children. Removal also implies that the current 21874 * scope is eligible for garbage collection. 21875 * 21876 * The `$destroy()` is usually used by directives such as 21877 * {@link ng.directive:ngRepeat ngRepeat} for managing the 21878 * unrolling of the loop. 21879 * 21880 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 21881 * Application code can register a `$destroy` event handler that will give it a chance to 21882 * perform any necessary cleanup. 21883 * 21884 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 21885 * clean up DOM bindings before an element is removed from the DOM. 21886 */ 21887 $destroy: function() { 21888 // We can't destroy a scope that has been already destroyed. 21889 if (this.$$destroyed) return; 21890 var parent = this.$parent; 21891 21892 this.$broadcast('$destroy'); 21893 this.$$destroyed = true; 21894 21895 if (this === $rootScope) { 21896 //Remove handlers attached to window when $rootScope is removed 21897 $browser.$$applicationDestroyed(); 21898 } 21899 21900 incrementWatchersCount(this, -this.$$watchersCount); 21901 for (var eventName in this.$$listenerCount) { 21902 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 21903 } 21904 21905 // sever all the references to parent scopes (after this cleanup, the current scope should 21906 // not be retained by any of our references and should be eligible for garbage collection) 21907 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 21908 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 21909 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 21910 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 21911 21912 // Disable listeners, watchers and apply/digest methods 21913 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 21914 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 21915 this.$$listeners = {}; 21916 21917 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too 21918 this.$$nextSibling = null; 21919 cleanUpScope(this); 21920 }, 21921 21922 /** 21923 * @ngdoc method 21924 * @name $rootScope.Scope#$eval 21925 * @kind function 21926 * 21927 * @description 21928 * Executes the `expression` on the current scope and returns the result. Any exceptions in 21929 * the expression are propagated (uncaught). This is useful when evaluating Angular 21930 * expressions. 21931 * 21932 * # Example 21933 * ```js 21934 var scope = ng.$rootScope.Scope(); 21935 scope.a = 1; 21936 scope.b = 2; 21937 21938 expect(scope.$eval('a+b')).toEqual(3); 21939 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 21940 * ``` 21941 * 21942 * @param {(string|function())=} expression An angular expression to be executed. 21943 * 21944 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 21945 * - `function(scope)`: execute the function with the current `scope` parameter. 21946 * 21947 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 21948 * @returns {*} The result of evaluating the expression. 21949 */ 21950 $eval: function(expr, locals) { 21951 return $parse(expr)(this, locals); 21952 }, 21953 21954 /** 21955 * @ngdoc method 21956 * @name $rootScope.Scope#$evalAsync 21957 * @kind function 21958 *
21959 * @description 21960 * Executes the expression on the current scope at a later point in time. 21961 * 21962 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 21963 * that: 21964 * 21965 * - it will execute after the function that scheduled the evaluation (preferably before DOM 21966 * rendering). 21967 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 21968 * `expression` execution. 21969 * 21970 * Any exceptions from the execution of the expression are forwarded to the 21971 * {@link ng.$exceptionHandler $exceptionHandler} service. 21972 * 21973 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 21974 * will be scheduled. However, it is encouraged to always call code that changes the model 21975 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 21976 * 21977 * @param {(string|function())=} expression An angular expression to be executed. 21978 * 21979 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 21980 * - `function(scope)`: execute the function with the current `scope` parameter. 21981 * 21982 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 21983 */ 21984 $evalAsync: function(expr, locals) { 21985 // if we are outside of an $digest loop and this is the first time we are scheduling async 21986 // task also schedule async auto-flush 21987 if (!$rootScope.$$phase && !asyncQueue.length) { 21988 $browser.defer(function() { 21989 if (asyncQueue.length) { 21990 $rootScope.$digest(); 21991 } 21992 }); 21993 } 21994 21995 asyncQueue.push({scope: this, expression: $parse(expr), locals: locals}); 21996 }, 21997 21998 $$postDigest: function(fn) { 21999 postDigestQueue.push(fn); 22000 }, 22001 22002 /** 22003 * @ngdoc method 22004 * @name $rootScope.Scope#$apply 22005 * @kind function 22006 * 22007 * @description 22008 * `$apply()` is used to execute an expression in angular from outside of the angular 22009 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 22010 * Because we are calling into the angular framework we need to perform proper scope life 22011 * cycle of {@link ng.$exceptionHandler exception handling}, 22012 * {@link ng.$rootScope.Scope#$digest executing watches}. 22013 * 22014 * ## Life cycle 22015 * 22016 * # Pseudo-Code of `$apply()` 22017 * ```js 22018 function $apply(expr) { 22019 try { 22020 return $eval(expr); 22021 } catch (e) { 22022 $exceptionHandler(e); 22023 } finally { 22024 $root.$digest(); 22025 } 22026 } 22027 * ``` 22028 * 22029 * 22030 * Scope's `$apply()` method transitions through the following stages: 22031 * 22032 * 1. The {@link guide/expression expression} is executed using the 22033 * {@link ng.$rootScope.Scope#$eval $eval()} method. 22034 * 2. Any exceptions from the execution of the expression are forwarded to the 22035 * {@link ng.$exceptionHandler $exceptionHandler} service. 22036 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 22037 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 22038 * 22039 * 22040 * @param {(string|function())=} exp An angular expression to be executed. 22041 * 22042 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 22043 * - `function(scope)`: execute the function with current `scope` parameter. 22044 * 22045 * @returns {*} The result of evaluating the expression. 22046 */ 22047 $apply: function(expr) { 22048 try { 22049 beginPhase('$apply'); 22050 try { 22051 return this.$eval(expr); 22052 } finally { 22053 clearPhase(); 22054 } 22055 } catch (e) { 22056 $exceptionHandler(e); 22057 } finally { 22058 try { 22059 $rootScope.$digest(); 22060 } catch (e) { 22061 $exceptionHandler(e); 22062 throw e; 22063 } 22064 } 22065 }, 22066 22067 /** 22068 * @ngdoc method 22069 * @name $rootScope.Scope#$applyAsync 22070 * @kind function 22071 *
22072 * @description 22073 * Schedule the invocation of $apply to occur at a later time. The actual time difference 22074 * varies across browsers, but is typically around ~10 milliseconds. 22075 * 22076 * This can be used to queue up multiple expressions which need to be evaluated in the same 22077 * digest. 22078 * 22079 * @param {(string|function())=} exp An angular expression to be executed. 22080 * 22081 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 22082 * - `function(scope)`: execute the function with current `scope` parameter. 22083 */ 22084 $applyAsync: function(expr) { 22085 var scope = this; 22086 expr && applyAsyncQueue.push($applyAsyncExpression); 22087 expr = $parse(expr); 22088 scheduleApplyAsync(); 22089 22090 function $applyAsyncExpression() { 22091 scope.$eval(expr); 22092 } 22093 }, 22094 22095 /** 22096 * @ngdoc method 22097 * @name $rootScope.Scope#$on 22098 * @kind function 22099 * 22100 * @description 22101 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 22102 * discussion of event life cycle. 22103 * 22104 * The event listener function format is: `function(event, args...)`. The `event` object 22105 * passed into the listener has the following attributes: 22106 * 22107 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 22108 * `$broadcast`-ed. 22109 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 22110 * event propagates through the scope hierarchy, this property is set to null. 22111 * - `name` - `{string}`: name of the event. 22112 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 22113 * further event propagation (available only for events that were `$emit`-ed). 22114 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 22115 * to true. 22116 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 22117 * 22118 * @param {string} name Event name to listen on. 22119 * @param {function(event, ...args)} listener Function to call when the event is emitted. 22120 * @returns {function()} Returns a deregistration function for this listener. 22121 */ 22122 $on: function(name, listener) { 22123 var namedListeners = this.$$listeners[name]; 22124 if (!namedListeners) { 22125 this.$$listeners[name] = namedListeners = []; 22126 } 22127 namedListeners.push(listener); 22128 22129 var current = this; 22130 do { 22131 if (!current.$$listenerCount[name]) { 22132 current.$$listenerCount[name] = 0; 22133 } 22134 current.$$listenerCount[name]++; 22135 } while ((current = current.$parent)); 22136 22137 var self = this; 22138 return function() { 22139 var indexOfListener = namedListeners.indexOf(listener); 22140 if (indexOfListener !== -1) { 22141 namedListeners[indexOfListener] = null; 22142 decrementListenerCount(self, 1, name); 22143 } 22144 }; 22145 }, 22146 22147 22148 /** 22149 * @ngdoc method 22150 * @name $rootScope.Scope#$emit 22151 * @kind function 22152 * 22153 * @description 22154 * Dispatches an event `name` upwards through the scope hierarchy notifying the 22155 * registered {@link ng.$rootScope.Scope#$on} listeners. 22156 * 22157 * The event life cycle starts at the scope on which `$emit` was called. All 22158 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 22159 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 22160 * registered listeners along the way. The event will stop propagating if one of the listeners 22161 * cancels it. 22162 * 22163 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 22164 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 22165 * 22166 * @param {string} name Event name to emit. 22167 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 22168 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 22169 */ 22170 $emit: function(name, args) { 22171 var empty = [], 22172 namedListeners, 22173 scope = this, 22174 stopPropagation = false, 22175 event = { 22176 name: name, 22177 targetScope: scope, 22178 stopPropagation: function() {stopPropagation = true;}, 22179 preventDefault: function() { 22180 event.defaultPrevented = true; 22181 }, 22182 defaultPrevented: false 22183 }, 22184 listenerArgs = concat([event], arguments, 1), 22185 i, length; 22186 22187 do { 22188 namedListeners = scope.$$listeners[name] || empty; 22189 event.currentScope = scope; 22190 for (i = 0, length = namedListeners.length; i < length; i++) { 22191 22192 // if listeners were deregistered, defragment the array 22193 if (!namedListeners[i]) { 22194 namedListeners.splice(i, 1); 22195 i--; 22196 length--; 22197 continue; 22198 } 22199 try { 22200 //allow all listeners attached to the current scope to run 22201 namedListeners[i].apply(null, listenerArgs); 22202 } catch (e) { 22203 $exceptionHandler(e); 22204 } 22205 } 22206 //if any listener on the current scope stops propagation, prevent bubbling 22207 if (stopPropagation) { 22208 event.currentScope = null; 22209 return event; 22210 } 22211 //traverse upwards 22212 scope = scope.$parent; 22213 } while (scope); 22214 22215 event.currentScope = null; 22216 22217 return event; 22218 }, 22219 22220 22221 /** 22222 * @ngdoc method 22223 * @name $rootScope.Scope#$broadcast 22224 * @kind function 22225 *
22226 * @description 22227 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 22228 * registered {@link ng.$rootScope.Scope#$on} listeners. 22229 * 22230 * The event life cycle starts at the scope on which `$broadcast` was called. All 22231 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 22232 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 22233 * scope and calls all registered listeners along the way. The event cannot be canceled. 22234 * 22235 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 22236 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 22237 * 22238 * @param {string} name Event name to broadcast. 22239 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 22240 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 22241 */ 22242 $broadcast: function(name, args) { 22243 var target = this, 22244 current = target, 22245 next = target, 22246 event = { 22247 name: name, 22248 targetScope: target, 22249 preventDefault: function() { 22250 event.defaultPrevented = true; 22251 }, 22252 defaultPrevented: false 22253 }; 22254 22255 if (!target.$$listenerCount[name]) return event; 22256 22257 var listenerArgs = concat([event], arguments, 1), 22258 listeners, i, length; 22259 22260 //down while you can, then up and next sibling or up and next sibling until back at root 22261 while ((current = next)) { 22262 event.currentScope = current; 22263 listeners = current.$$listeners[name] || []; 22264 for (i = 0, length = listeners.length; i < length; i++) { 22265 // if listeners were deregistered, defragment the array 22266 if (!listeners[i]) { 22267 listeners.splice(i, 1); 22268 i--; 22269 length--; 22270 continue; 22271 } 22272 22273 try { 22274 listeners[i].apply(null, listenerArgs); 22275 } catch (e) { 22276 $exceptionHandler(e); 22277 } 22278 } 22279 22280 // Insanity Warning: scope depth-first traversal 22281 // yes, this code is a bit crazy, but it works and we have tests to prove it! 22282 // this piece should be kept in sync with the traversal in $digest 22283 // (though it differs due to having the extra check for $$listenerCount) 22284 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 22285 (current !== target && current.$$nextSibling)))) { 22286 while (current !== target && !(next = current.$$nextSibling)) { 22287 current = current.$parent; 22288 } 22289 } 22290 } 22291 22292 event.currentScope = null; 22293 return event; 22294 } 22295 }; 22296 22297 var $rootScope = new Scope(); 22298 22299 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 22300 var asyncQueue = $rootScope.$$asyncQueue = []; 22301 var postDigestQueue = $rootScope.$$postDigestQueue = []; 22302 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 22303 22304 return $rootScope; 22305 22306 22307 function beginPhase(phase) { 22308 if ($rootScope.$$phase) { 22309 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 22310 } 22311 22312 $rootScope.$$phase = phase; 22313 } 22314 22315 function clearPhase() { 22316 $rootScope.$$phase = null; 22317 } 22318 22319 function incrementWatchersCount(current, count) { 22320 do { 22321 current.$$watchersCount += count; 22322 } while ((current = current.$parent)); 22323 } 22324 22325 function decrementListenerCount(current, count, name) { 22326 do { 22327 current.$$listenerCount[name] -= count; 22328 22329 if (current.$$listenerCount[name] === 0) { 22330 delete current.$$listenerCount[name]; 22331 } 22332 } while ((current = current.$parent)); 22333 } 22334 22335 /** 22336 * function used as an initial value for watchers. 22337 * because it's unique we can easily tell it apart from other values 22338 */ 22339 function initWatchVal() {} 22340 22341 function flushApplyAsync() {
vendor: 5,358 bytes, lines 22342-22484
22342 while (applyAsyncQueue.length) { 22343 try { 22344 applyAsyncQueue.shift()(); 22345 } catch (e) { 22346 $exceptionHandler(e); 22347 } 22348 } 22349 applyAsyncId = null; 22350 } 22351 22352 function scheduleApplyAsync() { 22353 if (applyAsyncId === null) { 22354 applyAsyncId = $browser.defer(function() { 22355 $rootScope.$apply(flushApplyAsync); 22356 }); 22357 } 22358 } 22359 }]; 22360} 22361 22362/** 22363 * @ngdoc service 22364 * @name $rootElement 22365 * 22366 * @description 22367 * The root element of Angular application. This is either the element where {@link 22368 * ng.directive:ngApp ngApp} was declared or the element passed into 22369 * {@link angular.bootstrap}. The element represents the root element of application. It is also the 22370 * location where the application's {@link auto.$injector $injector} service gets 22371 * published, and can be retrieved using `$rootElement.injector()`. 22372 */ 22373 22374 22375// the implementation is in angular.bootstrap 22376 22377/** 22378 * @description 22379 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 22380 */ 22381function $$SanitizeUriProvider() { 22382 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 22383 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 22384 22385 /** 22386 * @description 22387 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 22388 * urls during a[href] sanitization. 22389 * 22390 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 22391 * 22392 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 22393 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 22394 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 22395 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 22396 * 22397 * @param {RegExp=} regexp New regexp to whitelist urls with. 22398 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 22399 * chaining otherwise. 22400 */ 22401 this.aHrefSanitizationWhitelist = function(regexp) { 22402 if (isDefined(regexp)) { 22403 aHrefSanitizationWhitelist = regexp; 22404 return this; 22405 } 22406 return aHrefSanitizationWhitelist; 22407 }; 22408 22409 22410 /** 22411 * @description 22412 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 22413 * urls during img[src] sanitization. 22414 * 22415 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 22416 * 22417 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 22418 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 22419 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 22420 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 22421 * 22422 * @param {RegExp=} regexp New regexp to whitelist urls with. 22423 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 22424 * chaining otherwise. 22425 */ 22426 this.imgSrcSanitizationWhitelist = function(regexp) { 22427 if (isDefined(regexp)) { 22428 imgSrcSanitizationWhitelist = regexp; 22429 return this; 22430 } 22431 return imgSrcSanitizationWhitelist; 22432 }; 22433 22434 this.$get = function() { 22435 return function sanitizeUri(uri, isImage) { 22436 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 22437 var normalizedVal; 22438 normalizedVal = urlResolve(uri).href; 22439 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 22440 return 'unsafe:' + normalizedVal; 22441 } 22442 return uri; 22443 }; 22444 }; 22445} 22446 22447/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 22448 * Any commits to this file should be reviewed with security in mind. * 22449 * Changes to this file can potentially create security vulnerabilities. * 22450 * An approval from 2 Core members with history of modifying * 22451 * this file is required. * 22452 * * 22453 * Does the change somehow allow for arbitrary javascript to be executed? * 22454 * Or allows for someone to change the prototype of built-in objects? * 22455 * Or gives undesired access to variables likes document or window? * 22456 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 22457 22458var $sceMinErr = minErr('$sce'); 22459 22460var SCE_CONTEXTS = { 22461 HTML: 'html', 22462 CSS: 'css', 22463 URL: 'url', 22464 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 22465 // url. (e.g. ng-include, script src, templateUrl) 22466 RESOURCE_URL: 'resourceUrl', 22467 JS: 'js' 22468}; 22469 22470// Helper functions follow. 22471 22472function adjustMatcher(matcher) { 22473 if (matcher === 'self') { 22474 return matcher; 22475 } else if (isString(matcher)) { 22476 // Strings match exactly except for 2 wildcards - '*' and '**'. 22477 // '*' matches any character except those from the set ':/.?&'. 22478 // '**' matches any character (like .* in a RegExp). 22479 // More than 2 *'s raises an error as it's ill defined. 22480 if (matcher.indexOf('***') > -1) { 22481 throw $sceMinErr('iwcard', 22482 'Illegal sequence *** in string matcher. String: {0}', matcher); 22483 } 22484 matcher = escapeForRegexp(matcher).
22485 replace('\\*\\*', '.*'). 22486 replace('\\*', '[^:/.?&;]*'); 22487 return new RegExp('^' + matcher + '$'); 22488 } else if (isRegExp(matcher)) { 22489 // The only other type of matcher allowed is a Regexp. 22490 // Match entire URL / disallow partial matches. 22491 // Flags are reset (i.e. no global, ignoreCase or multiline) 22492 return new RegExp('^' + matcher.source + '$'); 22493 } else { 22494 throw $sceMinErr('imatcher', 22495 'Matchers may only be "self", string patterns or RegExp objects'); 22496 } 22497} 22498 22499 22500function adjustMatchers(matchers) { 22501 var adjustedMatchers = []; 22502 if (isDefined(matchers)) { 22503 forEach(matchers, function(matcher) { 22504 adjustedMatchers.push(adjustMatcher(matcher)); 22505 }); 22506 } 22507 return adjustedMatchers; 22508} 22509 22510 22511/** 22512 * @ngdoc service 22513 * @name $sceDelegate 22514 * @kind function 22515 * 22516 * @description 22517 * 22518 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 22519 * Contextual Escaping (SCE)} services to AngularJS. 22520 * 22521 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 22522 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 22523 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 22524 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 22525 * work because `$sce` delegates to `$sceDelegate` for these operations. 22526 * 22527 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 22528 * 22529 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 22530 * can override it completely to change the behavior of `$sce`, the common case would 22531 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 22532 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 22533 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 22534 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 22535 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 22536 */ 22537 22538/** 22539 * @ngdoc provider 22540 * @name $sceDelegateProvider 22541 * @description 22542 * 22543 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 22544 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 22545 * that the URLs used for sourcing Angular templates are safe. Refer {@link 22546 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 22547 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 22548 * 22549 * For the general details about this service in Angular, read the main page for {@link ng.$sce 22550 * Strict Contextual Escaping (SCE)}. 22551 * 22552 * **Example**: Consider the following case. <a name="example"></a> 22553 * 22554 * - your app is hosted at url `http://myapp.example.com/` 22555 * - but some of your templates are hosted on other domains you control such as 22556 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 22557 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 22558 * 22559 * Here is what a secure configuration for this scenario might look like: 22560 * 22561 * ``` 22562 * angular.module('myApp', []).config(function($sceDelegateProvider) { 22563 * $sceDelegateProvider.resourceUrlWhitelist([ 22564 * // Allow same origin resource loads. 22565 * 'self', 22566 * // Allow loading from our assets domain. Notice the difference between * and **. 22567 * 'http://srv*.assets.example.com/**' 22568 * ]); 22569 * 22570 * // The blacklist overrides the whitelist so the open redirect here is blocked. 22571 * $sceDelegateProvider.resourceUrlBlacklist([ 22572 * 'http://myapp.example.com/clickThru**' 22573 * ]); 22574 * }); 22575 * ``` 22576 */ 22577 22578function $SceDelegateProvider() { 22579 this.SCE_CONTEXTS = SCE_CONTEXTS; 22580 22581 // Resource URLs can also be trusted by policy. 22582 var resourceUrlWhitelist = ['self'], 22583 resourceUrlBlacklist = []; 22584 22585 /** 22586 * @ngdoc method 22587 * @name $sceDelegateProvider#resourceUrlWhitelist 22588 * @kind function 22589 * 22590 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 22591 * provided. This must be an array or null. A snapshot of this array is used so further 22592 * changes to the array are ignored. 22593 * 22594 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 22595 * allowed in this array.
22596 * 22597 * <div class="alert alert-warning"> 22598 * **Note:** an empty whitelist array will block all URLs! 22599 * </div> 22600 * 22601 * @return {Array} the currently set whitelist array. 22602 * 22603 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 22604 * same origin resource requests. 22605 * 22606 * @description 22607 * Sets/Gets the whitelist of trusted resource URLs. 22608 */ 22609 this.resourceUrlWhitelist = function(value) { 22610 if (arguments.length) { 22611 resourceUrlWhitelist = adjustMatchers(value); 22612 } 22613 return resourceUrlWhitelist; 22614 }; 22615 22616 /** 22617 * @ngdoc method 22618 * @name $sceDelegateProvider#resourceUrlBlacklist 22619 * @kind function 22620 * 22621 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 22622 * provided. This must be an array or null. A snapshot of this array is used so further 22623 * changes to the array are ignored. 22624 * 22625 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 22626 * allowed in this array. 22627 * 22628 * The typical usage for the blacklist is to **block 22629 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 22630 * these would otherwise be trusted but actually return content from the redirected domain. 22631 * 22632 * Finally, **the blacklist overrides the whitelist** and has the final say. 22633 * 22634 * @return {Array} the currently set blacklist array. 22635 * 22636 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 22637 * is no blacklist.) 22638 * 22639 * @description 22640 * Sets/Gets the blacklist of trusted resource URLs. 22641 */ 22642 22643 this.resourceUrlBlacklist = function(value) { 22644 if (arguments.length) { 22645 resourceUrlBlacklist = adjustMatchers(value); 22646 } 22647 return resourceUrlBlacklist; 22648 }; 22649 22650 this.$get = ['$injector', function($injector) { 22651 22652 var htmlSanitizer = function htmlSanitizer(html) { 22653 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 22654 }; 22655 22656 if ($injector.has('$sanitize')) { 22657 htmlSanitizer = $injector.get('$sanitize'); 22658 } 22659 22660 22661 function matchUrl(matcher, parsedUrl) { 22662 if (matcher === 'self') { 22663 return urlIsSameOrigin(parsedUrl); 22664 } else { 22665 // definitely a regex. See adjustMatchers() 22666 return !!matcher.exec(parsedUrl.href); 22667 } 22668 } 22669 22670 function isResourceUrlAllowedByPolicy(url) { 22671 var parsedUrl = urlResolve(url.toString()); 22672 var i, n, allowed = false; 22673 // Ensure that at least one item from the whitelist allows this url. 22674 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 22675 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 22676 allowed = true; 22677 break; 22678 } 22679 } 22680 if (allowed) { 22681 // Ensure that no item from the blacklist blocked this url. 22682 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 22683 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 22684 allowed = false; 22685 break; 22686 } 22687 } 22688 } 22689 return allowed; 22690 } 22691 22692 function generateHolderType(Base) { 22693 var holderType = function TrustedValueHolderType(trustedValue) { 22694 this.$$unwrapTrustedValue = function() { 22695 return trustedValue; 22696 }; 22697 }; 22698 if (Base) { 22699 holderType.prototype = new Base(); 22700 } 22701 holderType.prototype.valueOf = function sceValueOf() { 22702 return this.$$unwrapTrustedValue(); 22703 }; 22704 holderType.prototype.toString = function sceToString() { 22705 return this.$$unwrapTrustedValue().toString(); 22706 }; 22707 return holderType; 22708 } 22709 22710 var trustedValueHolderBase = generateHolderType(), 22711 byType = {}; 22712 22713 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 22714 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 22715 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 22716 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 22717 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 22718 22719 /** 22720 * @ngdoc method 22721 * @name $sceDelegate#trustAs 22722 *
22723 * @description 22724 * Returns an object that is trusted by angular for use in specified strict 22725 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 22726 * attribute interpolation, any dom event binding attribute interpolation 22727 * such as for onclick, etc.) that uses the provided value. 22728 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 22729 * 22730 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 22731 * resourceUrl, html, js and css. 22732 * @param {*} value The value that that should be considered trusted/safe. 22733 * @returns {*} A value that can be used to stand in for the provided `value` in places 22734 * where Angular expects a $sce.trustAs() return value. 22735 */ 22736 function trustAs(type, trustedValue) { 22737 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 22738 if (!Constructor) { 22739 throw $sceMinErr('icontext', 22740 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 22741 type, trustedValue); 22742 } 22743 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 22744 return trustedValue; 22745 } 22746 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 22747 // mutable objects, we ensure here that the value passed in is actually a string. 22748 if (typeof trustedValue !== 'string') { 22749 throw $sceMinErr('itype', 22750 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 22751 type); 22752 } 22753 return new Constructor(trustedValue); 22754 } 22755 22756 /** 22757 * @ngdoc method 22758 * @name $sceDelegate#valueOf 22759 * 22760 * @description 22761 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 22762 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 22763 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 22764 * 22765 * If the passed parameter is not a value that had been returned by {@link 22766 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 22767 * 22768 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 22769 * call or anything else. 22770 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 22771 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 22772 * `value` unchanged. 22773 */ 22774 function valueOf(maybeTrusted) { 22775 if (maybeTrusted instanceof trustedValueHolderBase) { 22776 return maybeTrusted.$$unwrapTrustedValue(); 22777 } else { 22778 return maybeTrusted; 22779 } 22780 } 22781 22782 /** 22783 * @ngdoc method 22784 * @name $sceDelegate#getTrusted 22785 * 22786 * @description 22787 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 22788 * returns the originally supplied value if the queried context type is a supertype of the 22789 * created type. If this condition isn't satisfied, throws an exception. 22790 * 22791 * <div class="alert alert-danger"> 22792 * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting 22793 * (XSS) vulnerability in your application. 22794 * </div> 22795 * 22796 * @param {string} type The kind of context in which this value is to be used. 22797 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 22798 * `$sceDelegate.trustAs`} call. 22799 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 22800 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 22801 */ 22802 function getTrusted(type, maybeTrusted) { 22803 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 22804 return maybeTrusted; 22805 } 22806 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 22807 if (constructor && maybeTrusted instanceof constructor) { 22808 return maybeTrusted.$$unwrapTrustedValue(); 22809 } 22810 // If we get here, then we may only take one of two actions. 22811 // 1. sanitize the value for the requested type, or 22812 // 2. throw an exception. 22813 if (type === SCE_CONTEXTS.RESOURCE_URL) { 22814 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 22815 return maybeTrusted; 22816 } else { 22817 throw $sceMinErr('insecurl', 22818 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 22819 maybeTrusted.toString()); 22820 } 22821 } else if (type === SCE_CONTEXTS.HTML) { 22822 return htmlSanitizer(maybeTrusted); 22823 } 22824 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 22825 } 22826 22827 return { trustAs: trustAs, 22828 getTrusted: getTrusted,
22829 valueOf: valueOf }; 22830 }]; 22831} 22832 22833 22834/** 22835 * @ngdoc provider 22836 * @name $sceProvider 22837 * @description 22838 * 22839 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 22840 * - enable/disable Strict Contextual Escaping (SCE) in a module 22841 * - override the default implementation with a custom delegate 22842 * 22843 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 22844 */ 22845 22846/* jshint maxlen: false*/ 22847 22848/** 22849 * @ngdoc service 22850 * @name $sce 22851 * @kind function 22852 * 22853 * @description 22854 * 22855 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 22856 * 22857 * # Strict Contextual Escaping 22858 * 22859 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 22860 * contexts to result in a value that is marked as safe to use for that context. One example of 22861 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 22862 * to these contexts as privileged or SCE contexts. 22863 * 22864 * As of version 1.2, Angular ships with SCE enabled by default. 22865 * 22866 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 22867 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 22868 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
22869 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 22870 * to the top of your HTML document. 22871 * 22872 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 22873 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 22874 * 22875 * Here's an example of a binding in a privileged context: 22876 * 22877 * ``` 22878 * <input ng-model="userHtml" aria-label="User input"> 22879 * <div ng-bind-html="userHtml"></div> 22880 * ``` 22881 * 22882 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 22883 * disabled, this application allows the user to render arbitrary HTML into the DIV. 22884 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 22885 * bindings. (HTML is just one example of a context where rendering user controlled input creates 22886 * security vulnerabilities.) 22887 * 22888 * For the case of HTML, you might use a library, either on the client side, or on the server side, 22889 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 22890 * 22891 * How would you ensure that every place that used these types of bindings was bound to a value that 22892 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 22893 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 22894 * properties/fields and forgot to update the binding to the sanitized value? 22895 * 22896 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 22897 * determine that something explicitly says it's safe to use a value for binding in that
22898 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 22899 * for those values that you can easily tell are safe - because they were received from your server, 22900 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 22901 * allowing only the files in a specific directory to do this. Ensuring that the internal API 22902 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 22903 * 22904 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 22905 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 22906 * obtain values that will be accepted by SCE / privileged contexts. 22907 * 22908 * 22909 * ## How does it work? 22910 * 22911 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 22912 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 22913 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 22914 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 22915 * 22916 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 22917 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 22918 * simplified): 22919 * 22920 * ``` 22921 * var ngBindHtmlDirective = ['$sce', function($sce) { 22922 * return function(scope, element, attr) { 22923 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 22924 * element.html(value || ''); 22925 * }); 22926 * }; 22927 * }]; 22928 * ``` 22929 * 22930 * ## Impact on loading templates 22931 * 22932 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 22933 * `templateUrl`'s specified by {@link guide/directive directives}. 22934 * 22935 * By default, Angular only loads templates from the same domain and protocol as the application 22936 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 22937 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 22938 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 22939 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 22940 * 22941 * *Please note*: 22942 * The browser's 22943 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 22944 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 22945 * policy apply in addition to this and may further restrict whether the template is successfully 22946 * loaded. This means that without the right CORS policy, loading templates from a different domain 22947 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 22948 * browsers. 22949 * 22950 * ## This feels like too much overhead 22951 * 22952 * It's important to remember that SCE only applies to interpolation expressions. 22953 * 22954 * If your expressions are constant literals, they're automatically trusted and you don't need to 22955 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 22956 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 22957 * 22958 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 22959 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 22960 * 22961 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 22962 * templates in `ng-include` from your application's domain without having to even know about SCE. 22963 * It blocks loading templates from other domains or loading templates over http from an https 22964 * served document. You can change these by setting your own custom {@link 22965 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 22966 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 22967 * 22968 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 22969 * application that's secure and can be audited to verify that with much more ease than bolting 22970 * security onto an application later. 22971 * 22972 * <a name="contexts"></a> 22973 * ## What trusted context types are supported? 22974 * 22975 * | Context | Notes | 22976 * |---------------------|----------------| 22977 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | 22978 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 22979 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. | 22980 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | 22981 * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | 22982 * 22983 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 22984 *
22985 * Each element in these arrays must be one of the following: 22986 * 22987 * - **'self'** 22988 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 22989 * domain** as the application document using the **same protocol**. 22990 * - **String** (except the special value `'self'`) 22991 * - The string is matched against the full *normalized / absolute URL* of the resource 22992 * being tested (substring matches are not good enough.) 22993 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 22994 * match themselves. 22995 * - `*`: matches zero or more occurrences of any character other than one of the following 6 22996 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use 22997 * in a whitelist. 22998 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 22999 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 23000 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 23001 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 23002 * http://foo.example.com/templates/**). 23003 * - **RegExp** (*see caveat below*) 23004 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 23005 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 23006 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 23007 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 23008 * small number of cases. A `.` character in the regex used when matching the scheme or a 23009 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 23010 * is highly recommended to use the string patterns and only fall back to regular expressions 23011 * as a last resort. 23012 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 23013 * matched against the **entire** *normalized / absolute URL* of the resource being tested 23014 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 23015 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 23016 * - If you are generating your JavaScript from some other templating engine (not 23017 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 23018 * remember to escape your regular expression (and be aware that you might need more than 23019 * one level of escaping depending on your templating engine and the way you interpolated 23020 * the value.) Do make use of your platform's escaping mechanism as it might be good 23021 * enough before coding your own. E.g. Ruby has 23022 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 23023 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 23024 * Javascript lacks a similar built in function for escaping. Take a look at Google 23025 * Closure library's [goog.string.regExpEscape(s)]( 23026 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 23027 * 23028 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 23029 * 23030 * ## Show me an example using SCE. 23031 * 23032 * <example module="mySceApp" deps="angular-sanitize.js"> 23033 * <file name="index.html"> 23034 * <div ng-controller="AppController as myCtrl"> 23035 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 23036 * <b>User comments</b><br> 23037 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 23038 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 23039 * exploit. 23040 * <div class="well"> 23041 * <div ng-repeat="userComment in myCtrl.userComments"> 23042 * <b>{{userComment.name}}</b>: 23043 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 23044 * <br> 23045 * </div>
23046 * </div> 23047 * </div> 23048 * </file> 23049 * 23050 * <file name="script.js"> 23051 * angular.module('mySceApp', ['ngSanitize']) 23052 * .controller('AppController', ['$http', '$templateCache', '$sce', 23053 * function($http, $templateCache, $sce) { 23054 * var self = this; 23055 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 23056 * self.userComments = userComments; 23057 * }); 23058 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 23059 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 23060 * 'sanitization."">Hover over this text.</span>'); 23061 * }]); 23062 * </file> 23063 * 23064 * <file name="test_data.json"> 23065 * [ 23066 * { "name": "Alice", 23067 * "htmlComment": 23068 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 23069 * }, 23070 * { "name": "Bob", 23071 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 23072 * } 23073 * ] 23074 * </file> 23075 * 23076 * <file name="protractor.js" type="protractor"> 23077 * describe('SCE doc demo', function() { 23078 * it('should sanitize untrusted values', function() { 23079 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 23080 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 23081 * }); 23082 * 23083 * it('should NOT sanitize explicitly trusted values', function() { 23084 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 23085 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 23086 * 'sanitization."">Hover over this text.</span>'); 23087 * }); 23088 * }); 23089 * </file> 23090 * </example> 23091 * 23092 * 23093 * 23094 * ## Can I disable SCE completely? 23095 * 23096 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 23097 * for little coding overhead. It will be much harder to take an SCE disabled application and 23098 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 23099 * for cases where you have a lot of existing code that was written before SCE was introduced and 23100 * you're migrating them a module at a time. 23101 * 23102 * That said, here's how you can completely disable SCE: 23103 * 23104 * ``` 23105 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 23106 * // Completely disable SCE. For demonstration purposes only! 23107 * // Do not use in new projects. 23108 * $sceProvider.enabled(false); 23109 * }); 23110 * ``` 23111 * 23112 */ 23113/* jshint maxlen: 100 */ 23114 23115function $SceProvider() { 23116 var enabled = true; 23117 23118 /** 23119 * @ngdoc method 23120 * @name $sceProvider#enabled 23121 * @kind function 23122 * 23123 * @param {boolean=} value If provided, then enables/disables SCE. 23124 * @return {boolean} true if SCE is enabled, false otherwise. 23125 * 23126 * @description 23127 * Enables/disables SCE and returns the current value. 23128 */ 23129 this.enabled = function(value) { 23130 if (arguments.length) { 23131 enabled = !!value; 23132 } 23133 return enabled; 23134 }; 23135 23136 23137 /* Design notes on the default implementation for SCE. 23138 * 23139 * The API contract for the SCE delegate 23140 * ------------------------------------- 23141 * The SCE delegate object must provide the following 3 methods: 23142 * 23143 * - trustAs(contextEnum, value) 23144 * This method is used to tell the SCE service that the provided value is OK to use in the 23145 * contexts specified by contextEnum. It must return an object that will be accepted by 23146 * getTrusted() for a compatible contextEnum and return this value. 23147 * 23148 * - valueOf(value) 23149 * For values that were not produced by trustAs(), return them as is. For values that were 23150 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 23151 * trustAs is wrapping the given values into some type, this operation unwraps it when given 23152 * such a value. 23153 * 23154 * - getTrusted(contextEnum, value) 23155 * This function should return the a value that is safe to use in the context specified by 23156 * contextEnum or throw and exception otherwise. 23157 * 23158 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 23159 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 23160 * instance, an implementation could maintain a registry of all trusted objects by context. In 23161 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 23162 * return the same object passed in if it was found in the registry under a compatible context or 23163 * throw an exception otherwise. An implementation might only wrap values some of the time based 23164 * on some criteria. getTrusted() might return a value and not throw an exception for special 23165 * constants or objects even if not wrapped. All such implementations fulfill this contract. 23166 * 23167 * 23168 * A note on the inheritance model for SCE contexts 23169 * ------------------------------------------------ 23170 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 23171 * is purely an implementation details. 23172 * 23173 * The contract is simply this: 23174 * 23175 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 23176 * will also succeed. 23177 * 23178 * Inheritance happens to capture this in a natural way. In some future, we
23179 * may not use inheritance anymore. That is OK because no code outside of 23180 * sce.js and sceSpecs.js would need to be aware of this detail. 23181 */ 23182 23183 this.$get = ['$parse', '$sceDelegate', function( 23184 $parse, $sceDelegate) { 23185 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 23186 // the "expression(javascript expression)" syntax which is insecure. 23187 if (enabled && msie < 8) { 23188 throw $sceMinErr('iequirks', 23189 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 23190 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 23191 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 23192 } 23193 23194 var sce = shallowCopy(SCE_CONTEXTS); 23195 23196 /** 23197 * @ngdoc method 23198 * @name $sce#isEnabled 23199 * @kind function 23200 * 23201 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 23202 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 23203 * 23204 * @description 23205 * Returns a boolean indicating if SCE is enabled. 23206 */ 23207 sce.isEnabled = function() { 23208 return enabled; 23209 }; 23210 sce.trustAs = $sceDelegate.trustAs; 23211 sce.getTrusted = $sceDelegate.getTrusted; 23212 sce.valueOf = $sceDelegate.valueOf; 23213 23214 if (!enabled) { 23215 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 23216 sce.valueOf = identity; 23217 } 23218 23219 /** 23220 * @ngdoc method 23221 * @name $sce#parseAs 23222 * 23223 * @description 23224 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 23225 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 23226 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 23227 * *result*)} 23228 * 23229 * @param {string} type The kind of SCE context in which this result will be used. 23230 * @param {string} expression String expression to compile. 23231 * @returns {function(context, locals)} a function which represents the compiled expression: 23232 * 23233 * * `context` â `{object}` â an object against which any expressions embedded in the strings 23234 * are evaluated against (typically a scope object). 23235 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 23236 * `context`. 23237 */ 23238 sce.parseAs = function sceParseAs(type, expr) { 23239 var parsed = $parse(expr); 23240 if (parsed.literal && parsed.constant) { 23241 return parsed; 23242 } else { 23243 return $parse(expr, function(value) { 23244 return sce.getTrusted(type, value); 23245 }); 23246 } 23247 }; 23248 23249 /** 23250 * @ngdoc method 23251 * @name $sce#trustAs 23252 * 23253 * @description 23254 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 23255 * returns an object that is trusted by angular for use in specified strict contextual 23256 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 23257 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 23258 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 23259 * escaping. 23260 * 23261 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 23262 * resourceUrl, html, js and css. 23263 * @param {*} value The value that that should be considered trusted/safe. 23264 * @returns {*} A value that can be used to stand in for the provided `value` in places 23265 * where Angular expects a $sce.trustAs() return value. 23266 */ 23267 23268 /** 23269 * @ngdoc method 23270 * @name $sce#trustAsHtml 23271 * 23272 * @description 23273 * Shorthand method. `$sce.trustAsHtml(value)` â 23274 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 23275 * 23276 * @param {*} value The value to trustAs. 23277 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 23278 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 23279 * only accept expressions that are either literal constants or are the 23280 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 23281 */ 23282 23283 /** 23284 * @ngdoc method 23285 * @name $sce#trustAsUrl 23286 *
23287 * @description 23288 * Shorthand method. `$sce.trustAsUrl(value)` â 23289 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 23290 * 23291 * @param {*} value The value to trustAs. 23292 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 23293 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 23294 * only accept expressions that are either literal constants or are the 23295 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 23296 */ 23297 23298 /** 23299 * @ngdoc method 23300 * @name $sce#trustAsResourceUrl 23301 * 23302 * @description 23303 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 23304 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 23305 * 23306 * @param {*} value The value to trustAs. 23307 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 23308 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 23309 * only accept expressions that are either literal constants or are the return 23310 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 23311 */ 23312 23313 /** 23314 * @ngdoc method 23315 * @name $sce#trustAsJs 23316 * 23317 * @description 23318 * Shorthand method. `$sce.trustAsJs(value)` â 23319 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 23320 * 23321 * @param {*} value The value to trustAs. 23322 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 23323 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 23324 * only accept expressions that are either literal constants or are the 23325 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 23326 */ 23327 23328 /** 23329 * @ngdoc method 23330 * @name $sce#getTrusted 23331 * 23332 * @description 23333 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 23334 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 23335 * originally supplied value if the queried context type is a supertype of the created type. 23336 * If this condition isn't satisfied, throws an exception. 23337 * 23338 * @param {string} type The kind of context in which this value is to be used. 23339 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 23340 * call. 23341 * @returns {*} The value the was originally provided to 23342 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 23343 * Otherwise, throws an exception. 23344 */ 23345 23346 /** 23347 * @ngdoc method 23348 * @name $sce#getTrustedHtml 23349 * 23350 * @description 23351 * Shorthand method. `$sce.getTrustedHtml(value)` â 23352 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 23353 * 23354 * @param {*} value The value to pass to `$sce.getTrusted`. 23355 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 23356 */ 23357 23358 /** 23359 * @ngdoc method 23360 * @name $sce#getTrustedCss 23361 * 23362 * @description 23363 * Shorthand method. `$sce.getTrustedCss(value)` â 23364 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 23365 * 23366 * @param {*} value The value to pass to `$sce.getTrusted`. 23367 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 23368 */ 23369 23370 /** 23371 * @ngdoc method 23372 * @name $sce#getTrustedUrl 23373 * 23374 * @description 23375 * Shorthand method. `$sce.getTrustedUrl(value)` â 23376 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 23377 * 23378 * @param {*} value The value to pass to `$sce.getTrusted`. 23379 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 23380 */ 23381 23382 /** 23383 * @ngdoc method 23384 * @name $sce#getTrustedResourceUrl 23385 * 23386 * @description 23387 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 23388 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 23389 * 23390 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 23391 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 23392 */ 23393 23394 /** 23395 * @ngdoc method 23396 * @name $sce#getTrustedJs 23397 *
23398 * @description 23399 * Shorthand method. `$sce.getTrustedJs(value)` â 23400 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 23401 * 23402 * @param {*} value The value to pass to `$sce.getTrusted`. 23403 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 23404 */ 23405 23406 /** 23407 * @ngdoc method 23408 * @name $sce#parseAsHtml 23409 * 23410 * @description 23411 * Shorthand method. `$sce.parseAsHtml(expression string)` â 23412 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 23413 * 23414 * @param {string} expression String expression to compile. 23415 * @returns {function(context, locals)} a function which represents the compiled expression: 23416 * 23417 * * `context` â `{object}` â an object against which any expressions embedded in the strings 23418 * are evaluated against (typically a scope object). 23419 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 23420 * `context`. 23421 */ 23422 23423 /** 23424 * @ngdoc method 23425 * @name $sce#parseAsCss 23426 * 23427 * @description 23428 * Shorthand method. `$sce.parseAsCss(value)` â 23429 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 23430 * 23431 * @param {string} expression String expression to compile. 23432 * @returns {function(context, locals)} a function which represents the compiled expression: 23433 * 23434 * * `context` â `{object}` â an object against which any expressions embedded in the strings 23435 * are evaluated against (typically a scope object). 23436 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 23437 * `context`. 23438 */ 23439 23440 /** 23441 * @ngdoc method 23442 * @name $sce#parseAsUrl 23443 * 23444 * @description 23445 * Shorthand method. `$sce.parseAsUrl(value)` â 23446 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 23447 * 23448 * @param {string} expression String expression to compile. 23449 * @returns {function(context, locals)} a function which represents the compiled expression: 23450 * 23451 * * `context` â `{object}` â an object against which any expressions embedded in the strings 23452 * are evaluated against (typically a scope object). 23453 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 23454 * `context`. 23455 */ 23456 23457 /** 23458 * @ngdoc method 23459 * @name $sce#parseAsResourceUrl 23460 * 23461 * @description 23462 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 23463 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 23464 * 23465 * @param {string} expression String expression to compile. 23466 * @returns {function(context, locals)} a function which represents the compiled expression: 23467 * 23468 * * `context` â `{object}` â an object against which any expressions embedded in the strings 23469 * are evaluated against (typically a scope object). 23470 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 23471 * `context`. 23472 */ 23473 23474 /** 23475 * @ngdoc method 23476 * @name $sce#parseAsJs 23477 * 23478 * @description 23479 * Shorthand method. `$sce.parseAsJs(value)` â 23480 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 23481 * 23482 * @param {string} expression String expression to compile. 23483 * @returns {function(context, locals)} a function which represents the compiled expression: 23484 * 23485 * * `context` â `{object}` â an object against which any expressions embedded in the strings 23486 * are evaluated against (typically a scope object). 23487 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 23488 * `context`. 23489 */ 23490 23491 // Shorthand delegations. 23492 var parse = sce.parseAs, 23493 getTrusted = sce.getTrusted, 23494 trustAs = sce.trustAs; 23495 23496 forEach(SCE_CONTEXTS, function(enumValue, name) { 23497 var lName = lowercase(name); 23498 sce[camelCase("parse_as_" + lName)] = function(expr) { 23499 return parse(enumValue, expr); 23500 }; 23501 sce[camelCase("get_trusted_" + lName)] = function(value) { 23502 return getTrusted(enumValue, value); 23503 }; 23504 sce[camelCase("trust_as_" + lName)] = function(value) { 23505 return trustAs(enumValue, value); 23506 }; 23507 }); 23508 23509 return sce; 23510 }]; 23511} 23512 23513/** 23514 * !!! This is an undocumented "private" service !!! 23515 * 23516 * @name $sniffer 23517 * @requires $window 23518 * @requires $document 23519 * 23520 * @property {boolean} history Does the browser support html5 history api ? 23521 * @property {boolean} transitions Does the browser support CSS transition events ? 23522 * @property {boolean} animations Does the browser support CSS animation events ? 23523 * 23524 * @description 23525 * This is very simple implementation of testing browser's features. 23526 */ 23527function $SnifferProvider() { 23528 this.$get = ['$window', '$document', function($window, $document) { 23529 var eventSupport = {}, 23530 android = 23531 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 23532 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 23533 document = $document[0] || {}, 23534 vendorPrefix, 23535 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 23536 bodyStyle = document.body && document.body.style, 23537 transitions = false, 23538 animations = false, 23539 match; 23540 23541 if (bodyStyle) { 23542 for (var prop in bodyStyle) {
23543 if (match = vendorRegex.exec(prop)) { 23544 vendorPrefix = match[0]; 23545 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 23546 break; 23547 } 23548 } 23549 23550 if (!vendorPrefix) { 23551 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 23552 } 23553 23554 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 23555 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 23556 23557 if (android && (!transitions || !animations)) { 23558 transitions = isString(bodyStyle.webkitTransition); 23559 animations = isString(bodyStyle.webkitAnimation); 23560 } 23561 } 23562 23563 23564 return { 23565 // Android has history.pushState, but it does not update location correctly 23566 // so let's not use the history API at all. 23567 // http://code.google.com/p/android/issues/detail?id=17471 23568 // https://github.com/angular/angular.js/issues/904 23569 23570 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 23571 // so let's not use the history API also 23572 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 23573 // jshint -W018 23574 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 23575 // jshint +W018 23576 hasEvent: function(event) { 23577 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 23578 // it. In particular the event is not fired when backspace or delete key are pressed or 23579 // when cut operation is performed. 23580 // IE10+ implements 'input' event but it erroneously fires under various situations, 23581 // e.g. when placeholder changes, or a form is focused. 23582 if (event === 'input' && msie <= 11) return false; 23583 23584 if (isUndefined(eventSupport[event])) { 23585 var divElm = document.createElement('div'); 23586 eventSupport[event] = 'on' + event in divElm; 23587 } 23588 23589 return eventSupport[event]; 23590 }, 23591 csp: csp(), 23592 vendorPrefix: vendorPrefix, 23593 transitions: transitions, 23594 animations: animations, 23595 android: android 23596 }; 23597 }]; 23598} 23599 23600var $compileMinErr = minErr('$compile'); 23601 23602/** 23603 * @ngdoc provider 23604 * @name $templateRequestProvider 23605 * @description 23606 * Used to configure the options passed to the {@link $http} service when making a template request. 23607 * 23608 * For example, it can be used for specifying the "Accept" header that is sent to the server, when 23609 * requesting a template. 23610 */ 23611function $TemplateRequestProvider() { 23612 23613 var httpOptions; 23614 23615 /** 23616 * @ngdoc method 23617 * @name $templateRequestProvider#httpOptions 23618 * @description 23619 * The options to be passed to the {@link $http} service when making the request. 23620 * You can use this to override options such as the "Accept" header for template requests. 23621 * 23622 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the 23623 * options if not overridden here. 23624 * 23625 * @param {string=} value new value for the {@link $http} options. 23626 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. 23627 */ 23628 this.httpOptions = function(val) { 23629 if (val) { 23630 httpOptions = val; 23631 return this; 23632 } 23633 return httpOptions; 23634 }; 23635 23636 /** 23637 * @ngdoc service 23638 * @name $templateRequest 23639 * 23640 * @description 23641 * The `$templateRequest` service runs security checks then downloads the provided template using 23642 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 23643 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 23644 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 23645 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 23646 * when `tpl` is of type string and `$templateCache` has the matching entry. 23647 * 23648 * If you want to pass custom options to the `$http` service, such as setting the Accept header you 23649 * can configure this via {@link $templateRequestProvider#httpOptions}. 23650 * 23651 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 23652 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 23653 * 23654 * @return {Promise} a promise for the HTTP response data of the given URL. 23655 * 23656 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
23657 */ 23658 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { 23659 23660 function handleRequestFn(tpl, ignoreRequestError) { 23661 handleRequestFn.totalPendingRequests++; 23662 23663 // We consider the template cache holds only trusted templates, so 23664 // there's no need to go through whitelisting again for keys that already 23665 // are included in there. This also makes Angular accept any script 23666 // directive, no matter its name. However, we still need to unwrap trusted 23667 // types. 23668 if (!isString(tpl) || !$templateCache.get(tpl)) { 23669 tpl = $sce.getTrustedResourceUrl(tpl); 23670 } 23671 23672 var transformResponse = $http.defaults && $http.defaults.transformResponse; 23673 23674 if (isArray(transformResponse)) { 23675 transformResponse = transformResponse.filter(function(transformer) { 23676 return transformer !== defaultHttpResponseTransform; 23677 }); 23678 } else if (transformResponse === defaultHttpResponseTransform) { 23679 transformResponse = null; 23680 } 23681 23682 return $http.get(tpl, extend({ 23683 cache: $templateCache, 23684 transformResponse: transformResponse 23685 }, httpOptions)) 23686 ['finally'](function() { 23687 handleRequestFn.totalPendingRequests--; 23688 }) 23689 .then(function(response) { 23690 $templateCache.put(tpl, response.data); 23691 return response.data; 23692 }, handleError); 23693 23694 function handleError(resp) { 23695 if (!ignoreRequestError) { 23696 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 23697 tpl, resp.status, resp.statusText); 23698 } 23699 return $q.reject(resp); 23700 } 23701 } 23702 23703 handleRequestFn.totalPendingRequests = 0; 23704 23705 return handleRequestFn; 23706 }]; 23707} 23708 23709function $$TestabilityProvider() { 23710 this.$get = ['$rootScope', '$browser', '$location', 23711 function($rootScope, $browser, $location) { 23712 23713 /** 23714 * @name $testability 23715 * 23716 * @description 23717 * The private $$testability service provides a collection of methods for use when debugging 23718 * or by automated test and debugging tools. 23719 */ 23720 var testability = {}; 23721 23722 /** 23723 * @name $$testability#findBindings 23724 * 23725 * @description 23726 * Returns an array of elements that are bound (via ng-bind or {{}}) 23727 * to expressions matching the input. 23728 * 23729 * @param {Element} element The element root to search from. 23730 * @param {string} expression The binding expression to match. 23731 * @param {boolean} opt_exactMatch If true, only returns exact matches 23732 * for the expression. Filters and whitespace are ignored. 23733 */ 23734 testability.findBindings = function(element, expression, opt_exactMatch) { 23735 var bindings = element.getElementsByClassName('ng-binding'); 23736 var matches = []; 23737 forEach(bindings, function(binding) { 23738 var dataBinding = angular.element(binding).data('$binding'); 23739 if (dataBinding) { 23740 forEach(dataBinding, function(bindingName) { 23741 if (opt_exactMatch) { 23742 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 23743 if (matcher.test(bindingName)) { 23744 matches.push(binding); 23745 } 23746 } else { 23747 if (bindingName.indexOf(expression) != -1) { 23748 matches.push(binding); 23749 } 23750 } 23751 }); 23752 } 23753 }); 23754 return matches; 23755 }; 23756 23757 /** 23758 * @name $$testability#findModels 23759 * 23760 * @description 23761 * Returns an array of elements that are two-way found via ng-model to 23762 * expressions matching the input. 23763 * 23764 * @param {Element} element The element root to search from. 23765 * @param {string} expression The model expression to match. 23766 * @param {boolean} opt_exactMatch If true, only returns exact matches 23767 * for the expression. 23768 */ 23769 testability.findModels = function(element, expression, opt_exactMatch) { 23770 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 23771 for (var p = 0; p < prefixes.length; ++p) { 23772 var attributeEquals = opt_exactMatch ? '=' : '*='; 23773 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 23774 var elements = element.querySelectorAll(selector); 23775 if (elements.length) { 23776 return elements; 23777 } 23778 } 23779 }; 23780 23781 /** 23782 * @name $$testability#getLocation 23783 *
23784 * @description 23785 * Shortcut for getting the location in a browser agnostic way. Returns 23786 * the path, search, and hash. (e.g. /path?a=b#hash) 23787 */ 23788 testability.getLocation = function() { 23789 return $location.url(); 23790 }; 23791 23792 /** 23793 * @name $$testability#setLocation 23794 * 23795 * @description 23796 * Shortcut for navigating to a location without doing a full page reload. 23797 * 23798 * @param {string} url The location url (path, search and hash, 23799 * e.g. /path?a=b#hash) to go to. 23800 */ 23801 testability.setLocation = function(url) { 23802 if (url !== $location.url()) { 23803 $location.url(url); 23804 $rootScope.$digest(); 23805 } 23806 }; 23807 23808 /** 23809 * @name $$testability#whenStable 23810 * 23811 * @description 23812 * Calls the callback when $timeout and $http requests are completed. 23813 * 23814 * @param {function} callback 23815 */ 23816 testability.whenStable = function(callback) { 23817 $browser.notifyWhenNoOutstandingRequests(callback); 23818 }; 23819 23820 return testability; 23821 }]; 23822} 23823 23824function $TimeoutProvider() { 23825 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 23826 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 23827 23828 var deferreds = {}; 23829 23830 23831 /** 23832 * @ngdoc service 23833 * @name $timeout 23834 * 23835 * @description 23836 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 23837 * block and delegates any exceptions to 23838 * {@link ng.$exceptionHandler $exceptionHandler} service. 23839 * 23840 * The return value of calling `$timeout` is a promise, which will be resolved when 23841 * the delay has passed and the timeout function, if provided, is executed. 23842 * 23843 * To cancel a timeout request, call `$timeout.cancel(promise)`. 23844 * 23845 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 23846 * synchronously flush the queue of deferred functions. 23847 * 23848 * If you only want a promise that will be resolved after some specified delay 23849 * then you can call `$timeout` without the `fn` function. 23850 * 23851 * @param {function()=} fn A function, whose execution should be delayed. 23852 * @param {number=} [delay=0] Delay in milliseconds. 23853 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 23854 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 23855 * @param {...*=} Pass additional parameters to the executed function. 23856 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise 23857 * will be resolved with the return value of the `fn` function. 23858 * 23859 */ 23860 function timeout(fn, delay, invokeApply) { 23861 if (!isFunction(fn)) { 23862 invokeApply = delay; 23863 delay = fn; 23864 fn = noop; 23865 } 23866 23867 var args = sliceArgs(arguments, 3), 23868 skipApply = (isDefined(invokeApply) && !invokeApply), 23869 deferred = (skipApply ? $$q : $q).defer(), 23870 promise = deferred.promise, 23871 timeoutId; 23872 23873 timeoutId = $browser.defer(function() { 23874 try { 23875 deferred.resolve(fn.apply(null, args)); 23876 } catch (e) { 23877 deferred.reject(e); 23878 $exceptionHandler(e); 23879 } 23880 finally { 23881 delete deferreds[promise.$$timeoutId]; 23882 } 23883 23884 if (!skipApply) $rootScope.$apply(); 23885 }, delay); 23886 23887 promise.$$timeoutId = timeoutId; 23888 deferreds[timeoutId] = deferred; 23889 23890 return promise; 23891 } 23892 23893 23894 /** 23895 * @ngdoc method 23896 * @name $timeout#cancel 23897 * 23898 * @description 23899 * Cancels a task associated with the `promise`. As a result of this, the promise will be 23900 * resolved with a rejection. 23901 * 23902 * @param {Promise=} promise Promise returned by the `$timeout` function. 23903 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 23904 * canceled. 23905 */ 23906 timeout.cancel = function(promise) { 23907 if (promise && promise.$$timeoutId in deferreds) { 23908 deferreds[promise.$$timeoutId].reject('canceled'); 23909 delete deferreds[promise.$$timeoutId]; 23910 return $browser.defer.cancel(promise.$$timeoutId); 23911 } 23912 return false;
vendor: 5,335 bytes, lines 23913-24034
23913 }; 23914 23915 return timeout; 23916 }]; 23917} 23918 23919// NOTE: The usage of window and document instead of $window and $document here is 23920// deliberate. This service depends on the specific behavior of anchor nodes created by the 23921// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 23922// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 23923// doesn't know about mocked locations and resolves URLs to the real document - which is 23924// exactly the behavior needed here. There is little value is mocking these out for this 23925// service. 23926var urlParsingNode = document.createElement("a"); 23927var originUrl = urlResolve(window.location.href); 23928 23929 23930/** 23931 * 23932 * Implementation Notes for non-IE browsers 23933 * ---------------------------------------- 23934 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 23935 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 23936 * URL will be resolved into an absolute URL in the context of the application document. 23937 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 23938 * properties are all populated to reflect the normalized URL. This approach has wide 23939 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 23940 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 23941 * 23942 * Implementation Notes for IE 23943 * --------------------------- 23944 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 23945 * browsers. However, the parsed components will not be set if the URL assigned did not specify 23946 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 23947 * work around that by performing the parsing in a 2nd step by taking a previously normalized 23948 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 23949 * properties such as protocol, hostname, port, etc. 23950 * 23951 * References: 23952 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 23953 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 23954 * http://url.spec.whatwg.org/#urlutils 23955 * https://github.com/angular/angular.js/pull/2902 23956 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 23957 * 23958 * @kind function 23959 * @param {string} url The URL to be parsed. 23960 * @description Normalizes and parses a URL. 23961 * @returns {object} Returns the normalized URL as a dictionary. 23962 * 23963 * | member name | Description | 23964 * |---------------|----------------| 23965 * | href | A normalized version of the provided URL if it was not an absolute URL | 23966 * | protocol | The protocol including the trailing colon | 23967 * | host | The host and port (if the port is non-default) of the normalizedUrl | 23968 * | search | The search params, minus the question mark | 23969 * | hash | The hash string, minus the hash symbol 23970 * | hostname | The hostname 23971 * | port | The port, without ":" 23972 * | pathname | The pathname, beginning with "/" 23973 * 23974 */ 23975function urlResolve(url) { 23976 var href = url; 23977 23978 if (msie) { 23979 // Normalize before parse. Refer Implementation Notes on why this is 23980 // done in two steps on IE. 23981 urlParsingNode.setAttribute("href", href); 23982 href = urlParsingNode.href; 23983 } 23984 23985 urlParsingNode.setAttribute('href', href); 23986 23987 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 23988 return { 23989 href: urlParsingNode.href, 23990 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 23991 host: urlParsingNode.host, 23992 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 23993 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 23994 hostname: urlParsingNode.hostname, 23995 port: urlParsingNode.port, 23996 pathname: (urlParsingNode.pathname.charAt(0) === '/') 23997 ? urlParsingNode.pathname 23998 : '/' + urlParsingNode.pathname 23999 }; 24000} 24001 24002/** 24003 * Parse a request URL and determine whether this is a same-origin request as the application document. 24004 * 24005 * @param {string|object} requestUrl The url of the request as a string that will be resolved 24006 * or a parsed URL object. 24007 * @returns {boolean} Whether the request is for the same origin as the application document. 24008 */ 24009function urlIsSameOrigin(requestUrl) { 24010 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 24011 return (parsed.protocol === originUrl.protocol && 24012 parsed.host === originUrl.host); 24013} 24014 24015/** 24016 * @ngdoc service 24017 * @name $window 24018 * 24019 * @description 24020 * A reference to the browser's `window` object. While `window` 24021 * is globally available in JavaScript, it causes testability problems, because 24022 * it is a global variable. In angular we always refer to it through the 24023 * `$window` service, so it may be overridden, removed or mocked for testing. 24024 * 24025 * Expressions, like the one defined for the `ngClick` directive in the example 24026 * below, are evaluated with respect to the current scope. Therefore, there is 24027 * no risk of inadvertently coding in a dependency on a global value in such an 24028 * expression. 24029 * 24030 * @example 24031 <example module="windowExample"> 24032 <file name="index.html"> 24033 <script> 24034 angular.module('windowExample', [])
24035 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 24036 $scope.greeting = 'Hello, World!'; 24037 $scope.doGreeting = function(greeting) { 24038 $window.alert(greeting); 24039 }; 24040 }]); 24041 </script> 24042 <div ng-controller="ExampleController"> 24043 <input type="text" ng-model="greeting" aria-label="greeting" /> 24044 <button ng-click="doGreeting(greeting)">ALERT</button> 24045 </div> 24046 </file> 24047 <file name="protractor.js" type="protractor"> 24048 it('should display the greeting in the input box', function() { 24049 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 24050 // If we click the button it will block the test runner 24051 // element(':button').click(); 24052 }); 24053 </file> 24054 </example> 24055 */ 24056function $WindowProvider() { 24057 this.$get = valueFn(window); 24058} 24059 24060/** 24061 * @name $$cookieReader 24062 * @requires $document 24063 * 24064 * @description 24065 * This is a private service for reading cookies used by $http and ngCookies 24066 * 24067 * @return {Object} a key/value map of the current cookies 24068 */ 24069function $$CookieReader($document) { 24070 var rawDocument = $document[0] || {}; 24071 var lastCookies = {}; 24072 var lastCookieString = ''; 24073 24074 function safeDecodeURIComponent(str) { 24075 try { 24076 return decodeURIComponent(str); 24077 } catch (e) { 24078 return str; 24079 } 24080 } 24081 24082 return function() { 24083 var cookieArray, cookie, i, index, name; 24084 var currentCookieString = rawDocument.cookie || ''; 24085 24086 if (currentCookieString !== lastCookieString) { 24087 lastCookieString = currentCookieString; 24088 cookieArray = lastCookieString.split('; '); 24089 lastCookies = {}; 24090 24091 for (i = 0; i < cookieArray.length; i++) { 24092 cookie = cookieArray[i]; 24093 index = cookie.indexOf('='); 24094 if (index > 0) { //ignore nameless cookies 24095 name = safeDecodeURIComponent(cookie.substring(0, index)); 24096 // the first value that is seen for a cookie is the most 24097 // specific one. values for the same cookie name that 24098 // follow are for less specific paths. 24099 if (isUndefined(lastCookies[name])) { 24100 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 24101 } 24102 } 24103 } 24104 } 24105 return lastCookies; 24106 }; 24107} 24108 24109$$CookieReader.$inject = ['$document']; 24110 24111function $$CookieReaderProvider() { 24112 this.$get = $$CookieReader; 24113} 24114 24115/* global currencyFilter: true, 24116 dateFilter: true, 24117 filterFilter: true, 24118 jsonFilter: true, 24119 limitToFilter: true, 24120 lowercaseFilter: true, 24121 numberFilter: true, 24122 orderByFilter: true, 24123 uppercaseFilter: true, 24124 */ 24125 24126/** 24127 * @ngdoc provider 24128 * @name $filterProvider 24129 * @description 24130 * 24131 * Filters are just functions which transform input to an output. However filters need to be 24132 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 24133 * annotated with dependencies and is responsible for creating a filter function. 24134 * 24135 * <div class="alert alert-warning"> 24136 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 24137 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 24138 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 24139 * (`myapp_subsection_filterx`). 24140 * </div> 24141 * 24142 * ```js 24143 * // Filter registration 24144 * function MyModule($provide, $filterProvider) { 24145 * // create a service to demonstrate injection (not always needed) 24146 * $provide.value('greet', function(name){ 24147 * return 'Hello ' + name + '!'; 24148 * }); 24149 * 24150 * // register a filter factory which uses the 24151 * // greet service to demonstrate DI. 24152 * $filterProvider.register('greet', function(greet){ 24153 * // return the filter function which uses the greet service 24154 * // to generate salutation 24155 * return function(text) { 24156 * // filters need to be forgiving so check input validity 24157 * return text && greet(text) || text; 24158 * }; 24159 * }); 24160 * } 24161 * ``` 24162 * 24163 * The filter function is registered with the `$injector` under the filter name suffix with 24164 * `Filter`. 24165 * 24166 * ```js 24167 * it('should be the same instance', inject( 24168 * function($filterProvider) { 24169 * $filterProvider.register('reverse', function(){ 24170 * return ...; 24171 * }); 24172 * }, 24173 * function($filter, reverseFilter) { 24174 * expect($filter('reverse')).toBe(reverseFilter); 24175 * }); 24176 * ``` 24177 * 24178 * 24179 * For more information about how angular filters work, and how to create your own filters, see 24180 * {@link guide/filter Filters} in the Angular Developer Guide. 24181 */ 24182 24183/** 24184 * @ngdoc service 24185 * @name $filter 24186 * @kind function 24187 * @description 24188 * Filters are used for formatting data displayed to the user. 24189 * 24190 * The general syntax in templates is as follows: 24191 * 24192 * {{ expression [| filter_name[:parameter_value] ... ] }} 24193 * 24194 * @param {String} name Name of the filter function to retrieve 24195 * @return {Function} the filter function 24196 * @example 24197 <example name="$filter" module="filterExample"> 24198 <file name="index.html"> 24199 <div ng-controller="MainCtrl"> 24200 <h3>{{ originalText }}</h3> 24201 <h3>{{ filteredText }}</h3> 24202 </div> 24203 </file> 24204 24205 <file name="script.js"> 24206 angular.module('filterExample', []) 24207 .controller('MainCtrl', function($scope, $filter) { 24208 $scope.originalText = 'hello'; 24209 $scope.filteredText = $filter('uppercase')($scope.originalText); 24210 }); 24211 </file> 24212 </example> 24213 */ 24214$FilterProvider.$inject = ['$provide']; 24215function $FilterProvider($provide) { 24216 var suffix = 'Filter'; 24217 24218 /** 24219 * @ngdoc method 24220 * @name $filterProvider#register 24221 * @param {string|Object} name Name of the filter function, or an object map of filters where 24222 * the keys are the filter names and the values are the filter factories. 24223 * 24224 * <div class="alert alert-warning"> 24225 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 24226 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 24227 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 24228 * (`myapp_subsection_filterx`). 24229 * </div> 24230 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 24231 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 24232 * of the registered filter instances. 24233 */ 24234 function register(name, factory) { 24235 if (isObject(name)) { 24236 var filters = {};
24237 forEach(name, function(filter, key) { 24238 filters[key] = register(key, filter); 24239 }); 24240 return filters; 24241 } else { 24242 return $provide.factory(name + suffix, factory); 24243 } 24244 } 24245 this.register = register; 24246 24247 this.$get = ['$injector', function($injector) { 24248 return function(name) { 24249 return $injector.get(name + suffix); 24250 }; 24251 }]; 24252 24253 //////////////////////////////////////// 24254 24255 /* global 24256 currencyFilter: false, 24257 dateFilter: false, 24258 filterFilter: false, 24259 jsonFilter: false, 24260 limitToFilter: false, 24261 lowercaseFilter: false, 24262 numberFilter: false, 24263 orderByFilter: false, 24264 uppercaseFilter: false, 24265 */ 24266 24267 register('currency', currencyFilter); 24268 register('date', dateFilter); 24269 register('filter', filterFilter); 24270 register('json', jsonFilter); 24271 register('limitTo', limitToFilter); 24272 register('lowercase', lowercaseFilter); 24273 register('number', numberFilter); 24274 register('orderBy', orderByFilter); 24275 register('uppercase', uppercaseFilter); 24276} 24277 24278/** 24279 * @ngdoc filter 24280 * @name filter 24281 * @kind function 24282 * 24283 * @description 24284 * Selects a subset of items from `array` and returns it as a new array. 24285 * 24286 * @param {Array} array The source array. 24287 * @param {string|Object|function()} expression The predicate to be used for selecting items from 24288 * `array`. 24289 * 24290 * Can be one of: 24291 * 24292 * - `string`: The string is used for matching against the contents of the `array`. All strings or 24293 * objects with string properties in `array` that match this string will be returned. This also 24294 * applies to nested object properties. 24295 * The predicate can be negated by prefixing the string with `!`. 24296 * 24297 * - `Object`: A pattern object can be used to filter specific properties on objects contained 24298 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 24299 * which have property `name` containing "M" and property `phone` containing "1". A special 24300 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 24301 * property of the object or its nested object properties. That's equivalent to the simple 24302 * substring match with a `string` as described above. The predicate can be negated by prefixing 24303 * the string with `!`. 24304 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 24305 * not containing "M". 24306 * 24307 * Note that a named property will match properties on the same level only, while the special 24308 * `$` property will match properties on the same level or deeper. E.g. an array item like 24309 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 24310 * **will** be matched by `{$: 'John'}`. 24311 * 24312 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 24313 * The function is called for each element of the array, with the element, its index, and 24314 * the entire array itself as arguments. 24315 * 24316 * The final result is an array of those elements that the predicate returned true for. 24317 * 24318 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 24319 * determining if the expected value (from the filter expression) and actual value (from 24320 * the object in the array) should be considered a match. 24321 * 24322 * Can be one of: 24323 * 24324 * - `function(actual, expected)`: 24325 * The function will be given the object value and the predicate value to compare and 24326 * should return true if both values should be considered equal. 24327 * 24328 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 24329 * This is essentially strict comparison of expected and actual. 24330 * 24331 * - `false|undefined`: A short hand for a function which will look for a substring match in case 24332 * insensitive way. 24333 * 24334 * Primitive values are converted to strings. Objects are not compared against primitives, 24335 * unless they have a custom `toString` method (e.g. `Date` objects). 24336 * 24337 * @example 24338 <example> 24339 <file name="index.html"> 24340 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 24341 {name:'Mary', phone:'800-BIG-MARY'}, 24342 {name:'Mike', phone:'555-4321'}, 24343 {name:'Adam', phone:'555-5678'}, 24344 {name:'Julie', phone:'555-8765'}, 24345 {name:'Juliette', phone:'555-5678'}]"></div> 24346 24347 <label>Search: <input ng-model="searchText"></label> 24348 <table id="searchTextResults"> 24349 <tr><th>
24349Name</th><th>Phone</th></tr> 24350 <tr ng-repeat="friend in friends | filter:searchText"> 24351 <td>{{friend.name}}</td> 24352 <td>{{friend.phone}}</td> 24353 </tr> 24354 </table> 24355 <hr> 24356 <label>Any: <input ng-model="search.$"></label> <br> 24357 <label>Name only <input ng-model="search.name"></label><br> 24358 <label>Phone only <input ng-model="search.phone"></label><br> 24359 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 24360 <table id="searchObjResults"> 24361 <tr><th>Name</th><th>Phone</th></tr> 24362 <tr ng-repeat="friendObj in friends | filter:search:strict"> 24363 <td>{{friendObj.name}}</td> 24364 <td>{{friendObj.phone}}</td> 24365 </tr> 24366 </table> 24367 </file> 24368 <file name="protractor.js" type="protractor"> 24369 var expectFriendNames = function(expectedNames, key) { 24370 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 24371 arr.forEach(function(wd, i) { 24372 expect(wd.getText()).toMatch(expectedNames[i]); 24373 }); 24374 }); 24375 }; 24376 24377 it('should search across all fields when filtering with a string', function() { 24378 var searchText = element(by.model('searchText')); 24379 searchText.clear(); 24380 searchText.sendKeys('m'); 24381 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 24382 24383 searchText.clear(); 24384 searchText.sendKeys('76'); 24385 expectFriendNames(['John', 'Julie'], 'friend'); 24386 }); 24387 24388 it('should search in specific fields when filtering with a predicate object', function() { 24389 var searchAny = element(by.model('search.$')); 24390 searchAny.clear(); 24391 searchAny.sendKeys('i'); 24392 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 24393 }); 24394 it('should use a equal comparison when comparator is true', function() { 24395 var searchName = element(by.model('search.name')); 24396 var strict = element(by.model('strict')); 24397 searchName.clear(); 24398 searchName.sendKeys('Julie'); 24399 strict.click(); 24400 expectFriendNames(['Julie'], 'friendObj'); 24401 }); 24402 </file> 24403 </example> 24404 */ 24405function filterFilter() { 24406 return function(array, expression, comparator) { 24407 if (!isArrayLike(array)) { 24408 if (array == null) { 24409 return array; 24410 } else { 24411 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 24412 } 24413 } 24414 24415 var expressionType = getTypeForFilter(expression); 24416 var predicateFn; 24417 var matchAgainstAnyProp; 24418 24419 switch (expressionType) { 24420 case 'function': 24421 predicateFn = expression; 24422 break; 24423 case 'boolean': 24424 case 'null': 24425 case 'number': 24426 case 'string': 24427 matchAgainstAnyProp = true; 24428 //jshint -W086 24429 case 'object': 24430 //jshint +W086 24431 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 24432 break; 24433 default: 24434 return array; 24435 } 24436 24437 return Array.prototype.filter.call(array, predicateFn); 24438 }; 24439} 24440 24441// Helper functions for `filterFilter` 24442function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 24443 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 24444 var predicateFn; 24445 24446 if (comparator === true) { 24447 comparator = equals; 24448 } else if (!isFunction(comparator)) { 24449 comparator = function(actual, expected) { 24450 if (isUndefined(actual)) { 24451 // No substring matching against `undefined` 24452 return false; 24453 } 24454 if ((actual === null) || (expected === null)) { 24455 // No substring matching against `null`; only match against `null` 24456 return actual === expected; 24457 } 24458 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 24459 // Should not compare primitives against objects, unless they have custom `toString` method 24460 return false; 24461 } 24462 24463 actual = lowercase('' + actual); 24464 expected = lowercase('' + expected); 24465 return actual.indexOf(expected) !== -1; 24466 }; 24467 } 24468 24469 predicateFn = function(item) { 24470 if (shouldMatchPrimitives && !isObject(item)) { 24471 return deepCompare(item, expression.$, comparator, false); 24472 } 24473 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 24474 }; 24475 24476 return predicateFn; 24477} 24478 24479function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 24480 var actualType = getTypeForFilter(actual); 24481 var expectedType = getTypeForFilter(expected); 24482 24483 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 24484 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 24485 } else if (isArray(actual)) { 24486 // In case `actual` is an array, consider it a match 24487 // if ANY of it's items matches `expected` 24488 return actual.some(function(item) { 24489 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 24490 }); 24491 } 24492 24493 switch (actualType) { 24494 case 'object': 24495 var key; 24496 if (matchAgainstAnyProp) { 24497 for (key in actual) { 24498 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 24499 return true; 24500 } 24501 } 24502 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 24503 } else if (expectedType === 'object') { 24504 for (key in expected) { 24505 var expectedVal = expected[key]; 24506 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 24507 continue; 24508 } 24509 24510 var matchAnyProperty = key === '$'; 24511 var actualVal = matchAnyProperty ? actual : actual[key]; 24512 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 24513 return false;
24514 } 24515 } 24516 return true; 24517 } else { 24518 return comparator(actual, expected); 24519 } 24520 break; 24521 case 'function': 24522 return false; 24523 default: 24524 return comparator(actual, expected); 24525 } 24526} 24527 24528// Used for easily differentiating between `null` and actual `object` 24529function getTypeForFilter(val) { 24530 return (val === null) ? 'null' : typeof val; 24531} 24532 24533var MAX_DIGITS = 22; 24534var DECIMAL_SEP = '.'; 24535var ZERO_CHAR = '0'; 24536 24537/** 24538 * @ngdoc filter 24539 * @name currency 24540 * @kind function 24541 * 24542 * @description 24543 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 24544 * symbol for current locale is used. 24545 * 24546 * @param {number} amount Input to filter. 24547 * @param {string=} symbol Currency symbol or identifier to be displayed. 24548 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 24549 * @returns {string} Formatted number. 24550 * 24551 * 24552 * @example 24553 <example module="currencyExample"> 24554 <file name="index.html"> 24555 <script> 24556 angular.module('currencyExample', []) 24557 .controller('ExampleController', ['$scope', function($scope) { 24558 $scope.amount = 1234.56; 24559 }]); 24560 </script>
24561 <div ng-controller="ExampleController"> 24562 <input type="number" ng-model="amount" aria-label="amount"> <br> 24563 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 24564 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 24565 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 24566 </div> 24567 </file> 24568 <file name="protractor.js" type="protractor"> 24569 it('should init with 1234.56', function() { 24570 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 24571 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 24572 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 24573 }); 24574 it('should update', function() { 24575 if (browser.params.browser == 'safari') { 24576 // Safari does not understand the minus key. See 24577 // https://github.com/angular/protractor/issues/481 24578 return; 24579 } 24580 element(by.model('amount')).clear(); 24581 element(by.model('amount')).sendKeys('-1234'); 24582 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 24583 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 24584 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 24585 }); 24586 </file> 24587 </example> 24588 */ 24589currencyFilter.$inject = ['$locale']; 24590function currencyFilter($locale) { 24591 var formats = $locale.NUMBER_FORMATS; 24592 return function(amount, currencySymbol, fractionSize) { 24593 if (isUndefined(currencySymbol)) { 24594 currencySymbol = formats.CURRENCY_SYM; 24595 } 24596 24597 if (isUndefined(fractionSize)) { 24598 fractionSize = formats.PATTERNS[1].maxFrac; 24599 } 24600 24601 // if null or undefined pass it through 24602 return (amount == null) 24603 ? amount 24604 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 24605 replace(/\u00A4/g, currencySymbol); 24606 }; 24607} 24608 24609/** 24610 * @ngdoc filter 24611 * @name number 24612 * @kind function 24613 * 24614 * @description 24615 * Formats a number as text. 24616 * 24617 * If the input is null or undefined, it will just be returned. 24618 * If the input is infinite (Infinity or -Infinity), the Infinity symbol 'â' or '-â' is returned, respectively. 24619 * If the input is not a number an empty string is returned. 24620 * 24621 * 24622 * @param {number|string} number Number to format. 24623 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 24624 * If this is not provided then the fraction size is computed from the current locale's number 24625 * formatting pattern. In the case of the default locale, it will be 3. 24626 * @returns {string} Number rounded to fractionSize and places a â,â after each third digit. 24627 * 24628 * @example 24629 <example module="numberFilterExample
vendor: 3,034 bytes, lines 24629-24708
24629"> 24630 <file name="index.html"> 24631 <script> 24632 angular.module('numberFilterExample', []) 24633 .controller('ExampleController', ['$scope', function($scope) { 24634 $scope.val = 1234.56789; 24635 }]); 24636 </script> 24637 <div ng-controller="ExampleController"> 24638 <label>Enter number: <input ng-model='val'></label><br> 24639 Default formatting: <span id='number-default'>{{val | number}}</span><br> 24640 No fractions: <span>{{val | number:0}}</span><br> 24641 Negative number: <span>{{-val | number:4}}</span> 24642 </div> 24643 </file> 24644 <file name="protractor.js" type="protractor"> 24645 it('should format numbers', function() { 24646 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 24647 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 24648 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 24649 }); 24650 24651 it('should update', function() { 24652 element(by.model('val')).clear(); 24653 element(by.model('val')).sendKeys('3374.333'); 24654 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 24655 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 24656 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 24657 }); 24658 </file> 24659 </example> 24660 */ 24661numberFilter.$inject = ['$locale']; 24662function numberFilter($locale) { 24663 var formats = $locale.NUMBER_FORMATS; 24664 return function(number, fractionSize) { 24665 24666 // if null or undefined pass it through 24667 return (number == null) 24668 ? number 24669 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 24670 fractionSize); 24671 }; 24672} 24673 24674/** 24675 * Parse a number (as a string) into three components that can be used 24676 * for formatting the number. 24677 * 24678 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) 24679 * 24680 * @param {string} numStr The number to parse 24681 * @return {object} An object describing this number, containing the following keys: 24682 * - d : an array of digits containing leading zeros as necessary 24683 * - i : the number of the digits in `d` that are to the left of the decimal point 24684 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` 24685 * 24686 */ 24687function parse(numStr) { 24688 var exponent = 0, digits, numberOfIntegerDigits; 24689 var i, j, zeros; 24690 24691 // Decimal point? 24692 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { 24693 numStr = numStr.replace(DECIMAL_SEP, ''); 24694 } 24695 24696 // Exponential form? 24697 if ((i = numStr.search(/e/i)) > 0) { 24698 // Work out the exponent. 24699 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; 24700 numberOfIntegerDigits += +numStr.slice(i + 1); 24701 numStr = numStr.substring(0, i); 24702 } else if (numberOfIntegerDigits < 0) { 24703 // There was no decimal point or exponent so it is an integer. 24704 numberOfIntegerDigits = numStr.length; 24705 } 24706 24707 // Count the number of leading zeros. 24708 for (i = 0; numStr.charAt(i) ==
vendor: 13,215 bytes, lines 24708-25079
24708 ZERO_CHAR; i++) {/* jshint noempty: false */} 24709 24710 if (i == (zeros = numStr.length)) { 24711 // The digits are all zero. 24712 digits = [0]; 24713 numberOfIntegerDigits = 1; 24714 } else { 24715 // Count the number of trailing zeros 24716 zeros--; 24717 while (numStr.charAt(zeros) == ZERO_CHAR) zeros--; 24718 24719 // Trailing zeros are insignificant so ignore them 24720 numberOfIntegerDigits -= i; 24721 digits = []; 24722 // Convert string to array of digits without leading/trailing zeros. 24723 for (j = 0; i <= zeros; i++, j++) { 24724 digits[j] = +numStr.charAt(i); 24725 } 24726 } 24727 24728 // If the number overflows the maximum allowed digits then use an exponent. 24729 if (numberOfIntegerDigits > MAX_DIGITS) { 24730 digits = digits.splice(0, MAX_DIGITS - 1); 24731 exponent = numberOfIntegerDigits - 1; 24732 numberOfIntegerDigits = 1; 24733 } 24734 24735 return { d: digits, e: exponent, i: numberOfIntegerDigits }; 24736} 24737 24738/** 24739 * Round the parsed number to the specified number of decimal places 24740 * This function changed the parsedNumber in-place 24741 */ 24742function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { 24743 var digits = parsedNumber.d; 24744 var fractionLen = digits.length - parsedNumber.i; 24745 24746 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number 24747 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; 24748 24749 // The index of the digit to where rounding is to occur 24750 var roundAt = fractionSize + parsedNumber.i; 24751 var digit = digits[roundAt]; 24752 24753 if (roundAt > 0) { 24754 // Drop fractional digits beyond `roundAt` 24755 digits.splice(Math.max(parsedNumber.i, roundAt)); 24756 24757 // Set non-fractional digits beyond `roundAt` to 0 24758 for (var j = roundAt; j < digits.length; j++) { 24759 digits[j] = 0; 24760 } 24761 } else { 24762 // We rounded to zero so reset the parsedNumber 24763 fractionLen = Math.max(0, fractionLen); 24764 parsedNumber.i = 1; 24765 digits.length = Math.max(1, roundAt = fractionSize + 1); 24766 digits[0] = 0; 24767 for (var i = 1; i < roundAt; i++) digits[i] = 0; 24768 } 24769 24770 if (digit >= 5) { 24771 if (roundAt - 1 < 0) { 24772 for (var k = 0; k > roundAt; k--) { 24773 digits.unshift(0); 24774 parsedNumber.i++; 24775 } 24776 digits.unshift(1); 24777 parsedNumber.i++; 24778 } else { 24779 digits[roundAt - 1]++; 24780 } 24781 } 24782 24783 // Pad out with zeros to get the required fraction length 24784 for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); 24785 24786 24787 // Do any carrying, e.g. a digit was rounded up to 10 24788 var carry = digits.reduceRight(function(carry, d, i, digits) { 24789 d = d + carry; 24790 digits[i] = d % 10; 24791 return Math.floor(d / 10); 24792 }, 0); 24793 if (carry) { 24794 digits.unshift(carry); 24795 parsedNumber.i++; 24796 } 24797} 24798 24799/** 24800 * Format a number into a string 24801 * @param {number} number The number to format 24802 * @param {{ 24803 * minFrac, // the minimum number of digits required in the fraction part of the number 24804 * maxFrac, // the maximum number of digits required in the fraction part of the number 24805 * gSize, // number of digits in each group of separated digits 24806 * lgSize, // number of digits in the last group of digits before the decimal separator 24807 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) 24808 * posPre, // the string to go in front of a positive number 24809 * negSuf, // the string to go after a negative number (e.g. `)`) 24810 * posSuf // the string to go after a positive number 24811 * }} pattern 24812 * @param {string} groupSep The string to separate groups of number (e.g. `,`) 24813 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) 24814 * @param {[type]} fractionSize The size of the fractional part of the number 24815 * @return {string} The number formatted as a string 24816 */ 24817function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 24818 24819 if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; 24820 24821 var isInfinity = !isFinite(number); 24822 var isZero = false; 24823 var numStr = Math.abs(number) + '', 24824 formattedText = '', 24825 parsedNumber; 24826 24827 if (isInfinity) { 24828 formattedText = '\u221e'; 24829 } else { 24830 parsedNumber = parse(numStr); 24831 24832 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); 24833 24834 var digits = parsedNumber.d; 24835 var integerLen = parsedNumber.i; 24836 var exponent = parsedNumber.e; 24837 var decimals = []; 24838 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); 24839 24840 // pad zeros for small numbers 24841 while (integerLen < 0) { 24842 digits.unshift(0); 24843 integerLen++; 24844 } 24845 24846 // extract decimals digits 24847 if (integerLen > 0) { 24848 decimals = digits.splice(integerLen); 24849 } else { 24850 decimals = digits; 24851 digits = [0]; 24852 } 24853 24854 // format the integer digits with grouping separators 24855 var groups = []; 24856 if (digits.length > pattern.lgSize) { 24857 groups.unshift(digits.splice(-pattern.lgSize).join('')); 24858 } 24859 while (digits.length > pattern.gSize) { 24860 groups.unshift(digits.splice(-pattern.gSize).join('')); 24861 } 24862 if (digits.length) { 24863 groups.unshift(digits.join('')); 24864 } 24865 formattedText = groups.join(groupSep); 24866 24867 // append the decimal digits 24868 if (decimals.length) { 24869 formattedText += decimalSep + decimals.join(''); 24870 } 24871 24872 if (exponent) { 24873 formattedText += 'e+' + exponent; 24874 } 24875 } 24876 if (number < 0 && !isZero) { 24877 return pattern.negPre + formattedText + pattern.negSuf; 24878 } else { 24879 return pattern.posPre + formattedText + pattern.posSuf; 24880 } 24881} 24882 24883function padNumber(num, digits, trim, negWrap) { 24884 var neg = ''; 24885 if (num < 0 || (negWrap && num <= 0)) { 24886 if (negWrap) { 24887 num = -num + 1; 24888 } else { 24889 num = -num; 24890 neg = '-'; 24891 } 24892 } 24893 num = '' + num; 24894 while (num.length < digits) num = ZERO_CHAR + num; 24895 if (trim) { 24896 num = num.substr(num.length - digits); 24897 } 24898 return neg + num; 24899} 24900 24901 24902function dateGetter(name, size, offset, trim, negWrap) { 24903 offset = offset || 0; 24904 return function(date) { 24905 var value = date['get' + name](); 24906 if (offset > 0 || value > -offset) { 24907 value += offset; 24908 } 24909 if (value === 0 && offset == -12) value = 12; 24910 return padNumber(value, size, trim, negWrap); 24911 }; 24912} 24913 24914function dateStrGetter(name, shortForm, standAlone) { 24915 return function(date, formats) { 24916 var value = date['get' + name](); 24917 var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); 24918 var get = uppercase(propPrefix + name); 24919 24920 return formats[get][value]; 24921 }; 24922} 24923 24924function timeZoneGetter(date, formats, offset) { 24925 var zone = -1 * offset; 24926 var paddedZone = (zone >= 0) ? "+" : ""; 24927 24928 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 24929 padNumber(Math.abs(zone % 60), 2); 24930 24931 return paddedZone; 24932} 24933 24934function getFirstThursdayOfYear(year) { 24935 // 0 = index of January 24936 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 24937 // 4 = index of Thursday (+1 to account for 1st = 5) 24938 // 11 = index of *next* Thursday (+1 account for 1st = 12) 24939 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 24940} 24941 24942function getThursdayThisWeek(datetime) { 24943 return new Date(datetime.getFullYear(), datetime.getMonth(), 24944 // 4 = index of Thursday 24945 datetime.getDate() + (4 - datetime.getDay())); 24946} 24947 24948function weekGetter(size) { 24949 return function(date) { 24950 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 24951 thisThurs = getThursdayThisWeek(date); 24952 24953 var diff = +thisThurs - +firstThurs, 24954 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 24955 24956 return padNumber(result, size); 24957 }; 24958} 24959 24960function ampmGetter(date, formats) { 24961 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 24962} 24963 24964function eraGetter(date, formats) { 24965 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 24966} 24967 24968function longEraGetter(date, formats) { 24969 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 24970} 24971 24972var DATE_FORMATS = { 24973 yyyy: dateGetter('FullYear', 4, 0, false, true), 24974 yy: dateGetter('FullYear', 2, 0, true, true), 24975 y: dateGetter('FullYear', 1, 0, false, true), 24976 MMMM: dateStrGetter('Month'), 24977 MMM: dateStrGetter('Month', true), 24978 MM: dateGetter('Month', 2, 1), 24979 M: dateGetter('Month', 1, 1), 24980 LLLL: dateStrGetter('Month', false, true), 24981 dd: dateGetter('Date', 2), 24982 d: dateGetter('Date', 1), 24983 HH: dateGetter('Hours', 2), 24984 H: dateGetter('Hours', 1), 24985 hh: dateGetter('Hours', 2, -12), 24986 h: dateGetter('Hours', 1, -12), 24987 mm: dateGetter('Minutes', 2), 24988 m: dateGetter('Minutes', 1), 24989 ss: dateGetter('Seconds', 2), 24990 s: dateGetter('Seconds', 1), 24991 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 24992 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 24993 sss: dateGetter('Milliseconds', 3), 24994 EEEE: dateStrGetter('Day'), 24995 EEE: dateStrGetter('Day', true), 24996 a: ampmGetter, 24997 Z: timeZoneGetter, 24998 ww: weekGetter(2), 24999 w: weekGetter(1), 25000 G: eraGetter, 25001 GG: eraGetter, 25002 GGG: eraGetter, 25003 GGGG: longEraGetter 25004}; 25005 25006var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 25007 NUMBER_STRING = /^\-?\d+$/; 25008 25009/** 25010 * @ngdoc filter 25011 * @name date 25012 * @kind function 25013 * 25014 * @description 25015 * Formats `date` to a string based on the requested `format`. 25016 * 25017 * `format` string can be composed of the following elements: 25018 * 25019 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 25020 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 25021 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 25022 * * `'MMMM'`: Month in year (January-December) 25023 * * `'MMM'`: Month in year (Jan-Dec) 25024 * * `'MM'`: Month in year, padded (01-12) 25025 * * `'M'`: Month in year (1-12) 25026 * * `'LLLL'`: Stand-alone month in year (January-December) 25027 * * `'dd'`: Day in month, padded (01-31) 25028 * * `'d'`: Day in month (1-31) 25029 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 25030 * * `'EEE'`: Day in Week, (Sun-Sat) 25031 * * `'HH'`: Hour in day, padded (00-23) 25032 * * `'H'`: Hour in day (0-23) 25033 * * `'hh'`: Hour in AM/PM, padded (01-12) 25034 * * `'h'`: Hour in AM/PM, (1-12) 25035 * * `'mm'`: Minute in hour, padded (00-59) 25036 * * `'m'`: Minute in hour (0-59) 25037 * * `'ss'`: Second in minute, padded (00-59) 25038 * * `'s'`: Second in minute (0-59) 25039 * * `'sss'`: Millisecond in second, padded (000-999) 25040 * * `'a'`: AM/PM marker 25041 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 25042 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 25043 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 25044 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 25045 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 25046 * 25047 * `format` string can also be one of the following predefined 25048 * {@link guide/i18n localizable formats}: 25049 * 25050 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 25051 * (e.g. Sep 3, 2010 12:05:08 PM) 25052 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 25053 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 25054 * (e.g. Friday, September 3, 2010) 25055 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 25056 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 25057 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 25058 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 25059 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 25060 * 25061 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 25062 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 25063 * (e.g. `"h 'o''clock'"`). 25064 * 25065 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 25066 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 25067 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 25068 * specified in the string input, the time is considered to be in the local timezone. 25069 * @param {string=} format Formatting rules (see Description). If not specified, 25070 * `mediumDate` is used. 25071 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 25072 * continental US time zone abbreviations, but for general use, use a time zone offset, for 25073 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 25074 * If not specified, the timezone of the browser will be used. 25075 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 25076 * 25077 * @example 25078 <example> 25079 <file name="index.html">
25080 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 25081 <span>{{1288323623006 | date:'medium'}}</span><br> 25082 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 25083 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 25084 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 25085 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 25086 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 25087 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 25088 </file> 25089 <file name="protractor.js" type="protractor"> 25090 it('should format date', function() { 25091 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 25092 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 25093 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 25094 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 25095 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 25096 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 25097 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 25098 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 25099 }); 25100 </file> 25101 </example> 25102 */ 25103dateFilter.$inject = ['$locale']; 25104function dateFilter($locale) { 25105 25106 25107 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 25108 // 1 2 3 4 5 6 7 8 9 10 11 25109 function jsonStringToDate(string) { 25110 var match; 25111 if (match = string.match(R_ISO8601_STR)) { 25112 var date = new Date(0), 25113 tzHour = 0, 25114 tzMin = 0, 25115 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 25116 timeSetter = match[8] ? date.setUTCHours : date.setHours; 25117 25118 if (match[9]) { 25119 tzHour = toInt(match[9] + match[10]); 25120 tzMin = toInt(match[9] + match[11]); 25121 } 25122 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 25123 var h = toInt(match[4] || 0) - tzHour; 25124 var m = toInt(match[5] || 0) - tzMin; 25125 var s = toInt(match[6] || 0); 25126 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 25127 timeSetter.call(date, h, m, s, ms); 25128 return date; 25129 } 25130 return string; 25131 } 25132 25133 25134 return function(date, format, timezone) { 25135 var text = '', 25136 parts = [], 25137 fn, match; 25138 25139 format = format || 'mediumDate'; 25140 format = $locale.DATETIME_FORMATS[format] || format; 25141 if (isString(date)) { 25142 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 25143 } 25144 25145 if (isNumber(date)) { 25146 date = new Date(date); 25147 } 25148 25149 if (!isDate(date) || !isFinite(date.getTime())) { 25150 return date; 25151 } 25152 25153 while (format) { 25154 match = DATE_FORMATS_SPLIT.exec(format); 25155 if (match) { 25156 parts = concat(parts, match, 1); 25157 format = parts.pop(); 25158 } else { 25159 parts.push(format); 25160 format = null; 25161 } 25162 } 25163 25164 var dateTimezoneOffset = date.getTimezoneOffset(); 25165 if (timezone) { 25166 dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 25167 date = convertTimezoneToLocal(date, timezone, true); 25168 } 25169 forEach(parts, function(value) { 25170 fn = DATE_FORMATS[value]; 25171 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 25172 : value === "''" ? "'" : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 25173 }); 25174 25175 return text; 25176 }; 25177} 25178 25179 25180/** 25181 * @ngdoc filter 25182 * @name json 25183 * @kind function 25184 * 25185 * @description 25186 * Allows you to convert a JavaScript object into JSON string. 25187 * 25188 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 25189 * the binding is automatically converted to JSON. 25190 * 25191 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 25192 * @param {number=}
25192 spacing The number of spaces to use per indentation, defaults to 2. 25193 * @returns {string} JSON string. 25194 * 25195 * 25196 * @example 25197 <example> 25198 <file name="index.html"> 25199 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 25200 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 25201 </file> 25202 <file name="protractor.js" type="protractor"> 25203 it('should jsonify filtered objects', function() { 25204 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 25205 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 25206 }); 25207 </file> 25208 </example> 25209 * 25210 */ 25211function jsonFilter() { 25212 return function(object, spacing) { 25213 if (isUndefined(spacing)) { 25214 spacing = 2; 25215 } 25216 return toJson(object, spacing); 25217 }; 25218} 25219 25220 25221/** 25222 * @ngdoc filter 25223 * @name lowercase 25224 * @kind function 25225 * @description 25226 * Converts string to lowercase. 25227 * @see angular.lowercase 25228 */ 25229var lowercaseFilter = valueFn(lowercase); 25230 25231 25232/** 25233 * @ngdoc filter 25234 * @name uppercase 25235 * @kind function 25236 * @description 25237 * Converts string to uppercase. 25238 * @see angular.uppercase 25239 */ 25240var uppercaseFilter = valueFn(uppercase); 25241 25242/** 25243 * @ngdoc filter 25244 * @name limitTo 25245 * @kind function 25246 * 25247 * @description 25248 * Creates a new array or string containing only a specified number of elements. The elements 25249 * are taken from either the beginning or the end of the source array, string or number, as specified by 25250 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 25251 * converted to a string. 25252 * 25253 * @param {Array|string|number} input Source array, string or number to be limited. 25254 * @param {string|number} limit The length of the returned array or string. If the `limit` number 25255 * is positive, `limit` number of items from the beginning of the source array/string are copied. 25256 * If the number is negative, `limit` number of items from the end of the source array/string 25257 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 25258 * the input will be returned unchanged. 25259 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 25260 * indicates an offset from the end of `input`. Defaults to `0`. 25261 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 25262 * had less than `limit` elements. 25263 * 25264 * @example 25265 <example module="limitToExample"> 25266 <file name="index.html"> 25267 <script> 25268 angular.module('limitToExample', []) 25269 .controller('ExampleController', ['$scope', function($scope) { 25270 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 25271 $scope.letters = "abcdefghi"; 25272 $scope.longNumber = 2345432342; 25273 $scope.numLimit = 3; 25274 $scope.letterLimit = 3; 25275 $scope.longNumberLimit = 3; 25276 }]); 25277 </script>
25278 <div ng-controller="ExampleController"> 25279 <label> 25280 Limit {{numbers}} to: 25281 <input type="number" step="1" ng-model="numLimit"> 25282 </label> 25283 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 25284 <label> 25285 Limit {{letters}} to: 25286 <input type="number" step="1" ng-model="letterLimit"> 25287 </label> 25288 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 25289 <label> 25290 Limit {{longNumber}} to: 25291 <input type="number" step="1" ng-model="longNumberLimit"> 25292 </label> 25293 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 25294 </div> 25295 </file> 25296 <file name="protractor.js" type="protractor"> 25297 var numLimitInput = element(by.model('numLimit')); 25298 var letterLimitInput = element(by.model('letterLimit')); 25299 var longNumberLimitInput = element(by.model('longNumberLimit')); 25300 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 25301 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 25302 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 25303 25304 it('should limit the number array to first three items', function() { 25305 expect(numLimitInput.getAttribute('value')).toBe('3'); 25306 expect(letterLimitInput.getAttribute('value')).toBe('3'); 25307 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 25308 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 25309 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 25310 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 25311 }); 25312 25313 // There is a bug in safari and protractor that doesn't like the minus key 25314 // it('should update the output when -3 is entered', function() { 25315 // numLimitInput.clear(); 25316 // numLimitInput.sendKeys('-3'); 25317 // letterLimitInput.clear(); 25318 // letterLimitInput.sendKeys('-3'); 25319 // longNumberLimitInput.clear(); 25320 // longNumberLimitInput.sendKeys('-3'); 25321 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 25322 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 25323 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 25324 // }); 25325 25326 it('should not exceed the maximum size of input array', function() { 25327 numLimitInput.clear(); 25328 numLimitInput.sendKeys('100'); 25329 letterLimitInput.clear(); 25330 letterLimitInput.sendKeys('100'); 25331 longNumberLimitInput.clear(); 25332 longNumberLimitInput.sendKeys('100'); 25333 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 25334 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 25335 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 25336 }); 25337 </file> 25338 </example> 25339*/ 25340function limitToFilter() { 25341 return function(input, limit, begin) { 25342 if (Math.abs(Number(limit)) === Infinity) { 25343 limit = Number(limit); 25344 } else { 25345 limit = toInt(limit); 25346 } 25347 if (isNaN(limit)) return input; 25348 25349 if (isNumber(input)) input = input.toString(); 25350 if (!isArray(input) && !isString(input)) return input; 25351 25352 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 25353 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; 25354 25355 if (limit >= 0) { 25356 return input.slice(begin, begin + limit); 25357 } else { 25358 if (begin === 0) { 25359 return input.slice(limit, input.length); 25360 } else { 25361 return input.slice(Math.max(0, begin + limit), begin); 25362 } 25363 } 25364 }; 25365} 25366 25367/** 25368 * @ngdoc filter 25369 * @name orderBy 25370 * @kind function 25371 * 25372 * @description 25373 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 25374 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 25375 * as expected, make sure they are actually being saved as numbers and not strings. 25376 * Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported. 25377 * 25378 * @param {Array} array The array (or array-like object) to sort. 25379 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 25380 * used by the comparator to determine the order of elements. 25381 * 25382 * Can be one of: 25383 * 25384 * - `function`: Getter function. The result of this function will be sorted using the 25385 * `<`, `===`, `>` operator. 25386 * - `string`: An Angular expression. The result of this expression is used to compare elements 25387 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 25388 * 3 first characters of a property called `name`). The result of a constant expression 25389 * is interpreted as a property name to be used in comparisons (for example `"special name"` 25390 * to sort object by the value of their `special name` property). An expression can be 25391 * optionally prefixed with `+` or `-` to control ascending or descending sort order 25392 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 25393 * element itself is used to compare where sorting. 25394 * - `Array`: An array of function or string predicates. The first predicate in the array 25395 * is used for sorting, but when two items are equivalent, the next predicate is used. 25396 * 25397 * If the predicate is missing or empty then it defaults to `'+'`. 25398 * 25399 * @param {boolean=} reverse Reverse the order of the array. 25400 * @returns {Array} Sorted copy of the source array. 25401 * 25402 * 25403 * @example 25404 * The example below demonstrates a simple ngRepeat, where the data is sorted 25405 * by age in descending order (predicate is set to `'-age'`). 25406 * `reverse` is not set, which means it defaults to `false`. 25407 <example module="orderByExample"> 25408 <file name="index.html">
25409 <div ng-controller="ExampleController"> 25410 <table class="friend"> 25411 <tr> 25412 <th>Name</th> 25413 <th>Phone Number</th> 25414 <th>Age</th> 25415 </tr> 25416 <tr ng-repeat="friend in friends | orderBy:'-age'"> 25417 <td>{{friend.name}}</td> 25418 <td>{{friend.phone}}</td> 25419 <td>{{friend.age}}</td> 25420 </tr> 25421 </table> 25422 </div> 25423 </file> 25424 <file name="script.js"> 25425 angular.module('orderByExample', []) 25426 .controller('ExampleController', ['$scope', function($scope) { 25427 $scope.friends = 25428 [{name:'John', phone:'555-1212', age:10}, 25429 {name:'Mary', phone:'555-9876', age:19}, 25430 {name:'Mike', phone:'555-4321', age:21}, 25431 {name:'Adam', phone:'555-5678', age:35}, 25432 {name:'Julie', phone:'555-8765', age:29}]; 25433 }]); 25434 </file> 25435 </example> 25436 * 25437 * The predicate and reverse parameters can be controlled dynamically through scope properties, 25438 * as shown in the next example. 25439 * @example 25440 <example module="orderByExample"> 25441 <file name="index.html">
25442 <div ng-controller="ExampleController"> 25443 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 25444 <hr/> 25445 <button ng-click="predicate=''">Set to unsorted</button> 25446 <table class="friend"> 25447 <tr> 25448 <th> 25449 <button ng-click="order('name')">Name</button> 25450 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 25451 </th> 25452 <th> 25453 <button ng-click="order('phone')">Phone Number</button> 25454 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 25455 </th> 25456 <th> 25457 <button ng-click="order('age')">Age</button> 25458 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 25459 </th> 25460 </tr> 25461 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 25462 <td>{{friend.name}}</td> 25463 <td>{{friend.phone}}</td> 25464 <td>{{friend.age}}</td> 25465 </tr> 25466 </table> 25467 </div> 25468 </file> 25469 <file name="script.js"> 25470 angular.module('orderByExample', []) 25471 .controller('ExampleController', ['$scope', function($scope) { 25472 $scope.friends = 25473 [{name:'John', phone:'555-1212', age:10}, 25474 {name:'Mary', phone:'555-9876', age:19}, 25475 {name:'Mike', phone:'555-4321', age:21}, 25476 {name:'Adam', phone:'555-5678', age:35}, 25477 {name:'Julie', phone:'555-8765', age:29}]; 25478 $scope.predicate = 'age'; 25479 $scope.reverse = true; 25480 $scope.order = function(predicate) { 25481 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 25482 $scope.predicate = predicate; 25483 }; 25484 }]); 25485 </file> 25486 <file name="style.css"> 25487 .sortorder:after { 25488 content: '\25b2'; 25489 } 25490 .sortorder.reverse:after { 25491 content: '\25bc'; 25492 } 25493 </file> 25494 </example> 25495 * 25496 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 25497 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 25498 * desired parameters. 25499 * 25500 * Example: 25501 * 25502 * @example 25503 <example module="orderByExample"> 25504 <file name="index.html">
25505 <div ng-controller="ExampleController"> 25506 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 25507 <table class="friend"> 25508 <tr> 25509 <th> 25510 <button ng-click="order('name')">Name</button> 25511 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 25512 </th> 25513 <th> 25514 <button ng-click="order('phone')">Phone Number</button> 25515 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 25516 </th> 25517 <th> 25518 <button ng-click="order('age')">Age</button> 25519 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 25520 </th> 25521 </tr> 25522 <tr ng-repeat="friend in friends"> 25523 <td>{{friend.name}}</td> 25524 <td>{{friend.phone}}</td> 25525 <td>{{friend.age}}</td> 25526 </tr> 25527 </table> 25528 </div> 25529 </file> 25530 25531 <file name="script.js"> 25532 angular.module('orderByExample', []) 25533 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 25534 var orderBy = $filter('orderBy'); 25535 $scope.friends = [ 25536 { name: 'John', phone: '555-1212', age: 10 }, 25537 { name: 'Mary', phone: '555-9876', age: 19 }, 25538 { name: 'Mike', phone: '555-4321', age: 21 }, 25539 { name: 'Adam', phone: '555-5678', age: 35 }, 25540 { name: 'Julie', phone: '555-8765', age: 29 } 25541 ]; 25542 $scope.order = function(predicate) { 25543 $scope.predicate = predicate; 25544 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 25545 $scope.friends = orderBy($scope.friends, predicate, $scope.reverse); 25546 }; 25547 $scope.order('age', true); 25548 }]); 25549 </file> 25550 25551 <file name="style.css"> 25552 .sortorder:after { 25553 content: '\25b2'; 25554 } 25555 .sortorder.reverse:after { 25556 content: '\25bc'; 25557 } 25558 </file> 25559</example> 25560 */ 25561orderByFilter.$inject = ['$parse']; 25562function orderByFilter($parse) { 25563 return function(array, sortPredicate, reverseOrder) { 25564 25565 if (array == null) return array; 25566 if (!isArrayLike(array)) { 25567 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); 25568 } 25569 25570 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 25571 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 25572 25573 var predicates = processPredicates(sortPredicate, reverseOrder); 25574 // Add a predicate at the end that evaluates to the element index. This makes the 25575 // sort stable as it works as a tie-breaker when all the input predicates cannot 25576 // distinguish between two elements. 25577 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1}); 25578 25579 // The next three lines are a version of a Swartzian Transform idiom from Perl 25580 // (sometimes called the Decorate-Sort-Undecorate idiom) 25581 // See https://en.wikipedia.org/wiki/Schwartzian_transform 25582 var compareValues = Array.prototype.map.call(array, getComparisonObject); 25583 compareValues.sort(doComparison); 25584 array = compareValues.map(function(item) { return item.value; }); 25585 25586 return array; 25587 25588 function getComparisonObject(value, index) { 25589 return { 25590 value: value, 25591 predicateValues: predicates.map(function(predicate) { 25592 return getPredicateValue(predicate.get(value), index); 25593 }) 25594 }; 25595 } 25596 25597 function doComparison(v1, v2) { 25598 var result = 0; 25599 for (var index=0, length = predicates.length; index < length; ++index) { 25600 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; 25601 if (result) break; 25602 } 25603 return result; 25604 } 25605 }; 25606 25607 function processPredicates(sortPredicate, reverseOrder) { 25608 reverseOrder = reverseOrder ? -1 : 1; 25609 return sortPredicate.map(function(predicate) { 25610 var descending = 1, get = identity; 25611 25612 if (isFunction(predicate)) { 25613 get = predicate; 25614 } else if (isString(predicate)) { 25615 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 25616 descending = predicate.charAt(0) == '-' ? -1 : 1; 25617 predicate = predicate.substring(1); 25618 } 25619 if (predicate !== '') { 25620 get = $parse(predicate); 25621 if (get.constant) { 25622 var key = get(); 25623 get = function(value) { return value[key]; }; 25624 } 25625 } 25626 }
25627 return { get: get, descending: descending * reverseOrder }; 25628 }); 25629 } 25630 25631 function isPrimitive(value) { 25632 switch (typeof value) { 25633 case 'number': /* falls through */ 25634 case 'boolean': /* falls through */ 25635 case 'string': 25636 return true; 25637 default: 25638 return false; 25639 } 25640 } 25641 25642 function objectValue(value, index) { 25643 // If `valueOf` is a valid function use that 25644 if (typeof value.valueOf === 'function') { 25645 value = value.valueOf(); 25646 if (isPrimitive(value)) return value; 25647 } 25648 // If `toString` is a valid function and not the one from `Object.prototype` use that 25649 if (hasCustomToString(value)) { 25650 value = value.toString(); 25651 if (isPrimitive(value)) return value; 25652 } 25653 // We have a basic object so we use the position of the object in the collection 25654 return index; 25655 } 25656 25657 function getPredicateValue(value, index) { 25658 var type = typeof value; 25659 if (value === null) { 25660 type = 'string'; 25661 value = 'null'; 25662 } else if (type === 'string') { 25663 value = value.toLowerCase(); 25664 } else if (type === 'object') { 25665 value = objectValue(value, index); 25666 } 25667 return { value: value, type: type }; 25668 } 25669 25670 function compare(v1, v2) { 25671 var result = 0; 25672 if (v1.type === v2.type) { 25673 if (v1.value !== v2.value) { 25674 result = v1.value < v2.value ? -1 : 1; 25675 } 25676 } else { 25677 result = v1.type < v2.type ? -1 : 1; 25678 } 25679 return result; 25680 } 25681} 25682 25683function ngDirective(directive) { 25684 if (isFunction(directive)) { 25685 directive = { 25686 link: directive 25687 }; 25688 } 25689 directive.restrict = directive.restrict || 'AC'; 25690 return valueFn(directive); 25691} 25692 25693/** 25694 * @ngdoc directive 25695 * @name a 25696 * @restrict E 25697 * 25698 * @description 25699 * Modifies the default behavior of the html A tag so that the default action is prevented when 25700 * the href attribute is empty. 25701 * 25702 * This change permits the easy creation of action links with the `ngClick` directive 25703 * without changing the location or causing page reloads, e.g.: 25704 * `<a href="" ng-click="list.addItem()">Add Item</a>` 25705 */ 25706var htmlAnchorDirective = valueFn({ 25707 restrict: 'E', 25708 compile: function(element, attr) { 25709 if (!attr.href && !attr.xlinkHref) { 25710 return function(scope, element) { 25711 // If the linked element is not an anchor tag anymore, do nothing 25712 if (element[0].nodeName.toLowerCase() !== 'a') return; 25713 25714 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 25715 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 25716 'xlink:href' : 'href'; 25717 element.on('click', function(event) { 25718 // if we have no href url, then don't navigate anywhere. 25719 if (!element.attr(href)) { 25720 event.preventDefault(); 25721 } 25722 }); 25723 }; 25724 } 25725 } 25726}); 25727 25728/** 25729 * @ngdoc directive 25730 * @name ngHref 25731 * @restrict A 25732 * @priority 99 25733 * 25734 * @description 25735 * Using Angular markup like `{{hash}}` in an href attribute will 25736 * make the link go to the wrong URL if the user clicks it before 25737 * Angular has a chance to replace the `{{hash}}` markup with its 25738 * value. Until Angular replaces the markup the link will be broken 25739 * and will most likely return a 404 error. The `ngHref` directive 25740 * solves this problem. 25741 * 25742 * The wrong way to write it: 25743 * ```html 25744 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 25745 * ``` 25746 * 25747 * The correct way to write it: 25748 * ```html 25749 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 25750 * ``` 25751 * 25752 * @element A 25753 * @param {template} ngHref any string which can contain `{{}}` markup. 25754 * 25755 * @example 25756 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 25757 * in links and their different behaviors: 25758 <example> 25759 <file name="index.html"> 25760 <input ng-model="value" /><br /> 25761 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 25762 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 25763 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 25764 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 25765 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 25766 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 25767 </file> 25768 <file name="protractor.js" type="protractor"> 25769 it('should execute ng-click but not reload when href without value', function() {
25770 element(by.id('link-1')).click(); 25771 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 25772 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 25773 }); 25774 25775 it('should execute ng-click but not reload when href empty string', function() { 25776 element(by.id('link-2')).click(); 25777 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 25778 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 25779 }); 25780 25781 it('should execute ng-click and change url when ng-href specified', function() { 25782 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 25783 25784 element(by.id('link-3')).click(); 25785 25786 // At this point, we navigate away from an Angular page, so we need 25787 // to use browser.driver to get the base webdriver. 25788 25789 browser.wait(function() { 25790 return browser.driver.getCurrentUrl().then(function(url) { 25791 return url.match(/\/123$/); 25792 }); 25793 }, 5000, 'page should navigate to /123'); 25794 }); 25795 25796 it('should execute ng-click but not reload when href empty string and name specified', function() { 25797 element(by.id('link-4')).click(); 25798 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 25799 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 25800 }); 25801 25802 it('should execute ng-click but not reload when no href but name specified', function() { 25803 element(by.id('link-5')).click(); 25804 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 25805 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 25806 }); 25807 25808 it('should only change url when only ng-href', function() { 25809 element(by.model('value')).clear(); 25810 element(by.model('value')).sendKeys('6'); 25811 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 25812 25813 element(by.id('link-6')).click(); 25814 25815 // At this point, we navigate away from an Angular page, so we need 25816 // to use browser.driver to get the base webdriver. 25817 browser.wait(function() { 25818 return browser.driver.getCurrentUrl().then(function(url) { 25819 return url.match(/\/6$/); 25820 }); 25821 }, 5000, 'page should navigate to /6'); 25822 }); 25823 </file> 25824 </example> 25825 */ 25826 25827/** 25828 * @ngdoc directive 25829 * @name ngSrc 25830 * @restrict A 25831 * @priority 99 25832 * 25833 * @description 25834 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 25835 * work right: The browser will fetch from the URL with the literal 25836 * text `{{hash}}` until Angular replaces the expression inside 25837 * `{{hash}}`. The `ngSrc` directive solves this problem. 25838 * 25839 * The buggy way to write it: 25840 * ```html 25841 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 25842 * ``` 25843 * 25844 * The correct way to write it: 25845 * ```html 25846 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 25847 * ``` 25848 * 25849 * @element IMG 25850 * @param {template} ngSrc any string which can contain `{{}}` markup. 25851 */ 25852 25853/** 25854 * @ngdoc directive 25855 * @name ngSrcset 25856 * @restrict A 25857 * @priority 99 25858 * 25859 * @description 25860 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 25861 * work right: The browser will fetch from the URL with the literal 25862 * text `{{hash}}` until Angular replaces the expression inside 25863 * `{{hash}}`. The `ngSrcset` directive solves this problem. 25864 * 25865 * The buggy way to write it: 25866 * ```html 25867 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 25868 * ``` 25869 * 25870 * The correct way to write it: 25871 * ```html 25872 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 25873 * ``` 25874 * 25875 * @element IMG 25876 * @param {template} ngSrcset any string which can contain `{{}}` markup. 25877 */ 25878 25879/** 25880 * @ngdoc directive 25881 * @name ngDisabled 25882 * @restrict A 25883 * @priority 100 25884 * 25885 * @description 25886 * 25887 * This directive sets the `disabled` attribute on the element if the 25888 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 25889 * 25890 * A special directive is necessary because we cannot use interpolation inside the `disabled` 25891 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 25892 * 25893 * @example 25894 <example> 25895 <file name="index.html"> 25896 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 25897 <button ng-model="button" ng-disabled="checked">Button</button> 25898 </file> 25899 <file name="protractor.js" type="protractor"> 25900 it('should toggle button', function() { 25901 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
25902 element(by.model('checked')).click(); 25903 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 25904 }); 25905 </file> 25906 </example> 25907 * 25908 * @element INPUT 25909 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 25910 * then the `disabled` attribute will be set on the element 25911 */ 25912 25913 25914/** 25915 * @ngdoc directive 25916 * @name ngChecked 25917 * @restrict A 25918 * @priority 100 25919 * 25920 * @description 25921 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 25922 * 25923 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 25924 * as this can lead to unexpected behavior. 25925 * 25926 * A special directive is necessary because we cannot use interpolation inside the `checked` 25927 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 25928 * 25929 * @example 25930 <example> 25931 <file name="index.html"> 25932 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 25933 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 25934 </file> 25935 <file name="protractor.js" type="protractor"> 25936 it('should check both checkBoxes', function() { 25937 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 25938 element(by.model('master')).click(); 25939 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 25940 }); 25941 </file> 25942 </example> 25943 * 25944 * @element INPUT 25945 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 25946 * then the `checked` attribute will be set on the element 25947 */ 25948 25949 25950/** 25951 * @ngdoc directive 25952 * @name ngReadonly 25953 * @restrict A 25954 * @priority 100 25955 * 25956 * @description 25957 * 25958 * Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy. 25959 * 25960 * A special directive is necessary because we cannot use interpolation inside the `readOnly` 25961 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 25962 * 25963 * @example 25964 <example> 25965 <file name="index.html"> 25966 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 25967 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 25968 </file> 25969 <file name="protractor.js" type="protractor"> 25970 it('should toggle readonly attr', function() { 25971 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 25972 element(by.model('checked')).click(); 25973 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 25974 }); 25975 </file> 25976 </example> 25977 * 25978 * @element INPUT 25979 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 25980 * then special attribute "readonly" will be set on the element 25981 */ 25982 25983 25984/** 25985 * @ngdoc directive 25986 * @name ngSelected 25987 * @restrict A 25988 * @priority 100 25989 * 25990 * @description 25991 * 25992 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. 25993 * 25994 * A special directive is necessary because we cannot use interpolation inside the `selected` 25995 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 25996 * 25997 * @example 25998 <example> 25999 <file name="index.html"> 26000 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 26001 <select aria-label="ngSelected demo"> 26002 <option>Hello!</option> 26003 <option id="greet" ng-selected="selected">Greetings!</option> 26004 </select> 26005 </file> 26006 <file name="protractor.js" type="protractor"> 26007 it('should select Greetings!', function() { 26008 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 26009 element(by.model('selected')).click(); 26010 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 26011 }); 26012 </file> 26013 </example> 26014 * 26015 * @element OPTION 26016 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 26017 * then special attribute "selected" will be set on the element 26018 */ 26019 26020/** 26021 * @ngdoc directive 26022 * @name ngOpen 26023 * @restrict A 26024 * @priority 100 26025 * 26026 * @description 26027 * 26028 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. 26029 * 26030 * A special directive is necessary because we cannot use interpolation inside the `open` 26031 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 26032 * 26033 * @example 26034 <example> 26035 <file name="index.html"> 26036 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 26037 <details id="details" ng-open="open"> 26038 <summary>Show/Hide me</summary> 26039 </details> 26040 </file> 26041 <file name="protractor.js" type="protractor"> 26042 it('should toggle open', function() { 26043 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
26044 element(by.model('open')).click(); 26045 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 26046 }); 26047 </file> 26048 </example> 26049 * 26050 * @element DETAILS 26051 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 26052 * then special attribute "open" will be set on the element 26053 */ 26054 26055var ngAttributeAliasDirectives = {}; 26056 26057// boolean attrs are evaluated 26058forEach(BOOLEAN_ATTR, function(propName, attrName) { 26059 // binding to multiple is not supported 26060 if (propName == "multiple") return; 26061 26062 function defaultLinkFn(scope, element, attr) { 26063 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 26064 attr.$set(attrName, !!value); 26065 }); 26066 } 26067 26068 var normalized = directiveNormalize('ng-' + attrName); 26069 var linkFn = defaultLinkFn; 26070 26071 if (propName === 'checked') { 26072 linkFn = function(scope, element, attr) { 26073 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 26074 if (attr.ngModel !== attr[normalized]) { 26075 defaultLinkFn(scope, element, attr); 26076 } 26077 }; 26078 } 26079 26080 ngAttributeAliasDirectives[normalized] = function() { 26081 return { 26082 restrict: 'A', 26083 priority: 100, 26084 link: linkFn 26085 }; 26086 }; 26087}); 26088 26089// aliased input attrs are evaluated 26090forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 26091 ngAttributeAliasDirectives[ngAttr] = function() { 26092 return { 26093 priority: 100, 26094 link: function(scope, element, attr) { 26095 //special case ngPattern when a literal regular expression value 26096 //is used as the expression (this way we don't have to watch anything). 26097 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 26098 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 26099 if (match) { 26100 attr.$set("ngPattern", new RegExp(match[1], match[2])); 26101 return; 26102 } 26103 } 26104 26105 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 26106 attr.$set(ngAttr, value); 26107 }); 26108 } 26109 }; 26110 }; 26111}); 26112 26113// ng-src, ng-srcset, ng-href are interpolated 26114forEach(['src', 'srcset', 'href'], function(attrName) { 26115 var normalized = directiveNormalize('ng-' + attrName); 26116 ngAttributeAliasDirectives[normalized] = function() { 26117 return { 26118 priority: 99, // it needs to run after the attributes are interpolated 26119 link: function(scope, element, attr) { 26120 var propName = attrName, 26121 name = attrName; 26122 26123 if (attrName === 'href' && 26124 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 26125 name = 'xlinkHref'; 26126 attr.$attr[name] = 'xlink:href'; 26127 propName = null; 26128 } 26129 26130 attr.$observe(normalized, function(value) { 26131 if (!value) { 26132 if (attrName === 'href') { 26133 attr.$set(name, null); 26134 } 26135 return; 26136 } 26137 26138 attr.$set(name, value); 26139 26140 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 26141 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 26142 // to set the property as well to achieve the desired effect. 26143 // we use attr[attrName] value since $set can sanitize the url. 26144 if (msie && propName) element.prop(propName, attr[name]); 26145 }); 26146 } 26147 }; 26148 }; 26149}); 26150 26151/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 26152 */ 26153var nullFormCtrl = { 26154 $addControl: noop, 26155 $$renameControl: nullFormRenameControl, 26156 $removeControl: noop, 26157 $setValidity: noop, 26158 $setDirty: noop, 26159 $setPristine: noop, 26160 $setSubmitted: noop 26161}, 26162SUBMITTED_CLASS = 'ng-submitted'; 26163 26164function nullFormRenameControl(control, name) { 26165 control.$name = name; 26166} 26167 26168/** 26169 * @ngdoc type 26170 * @name form.FormController 26171 * 26172 * @property {boolean} $pristine True if user has not interacted with the form yet. 26173 * @property {boolean} $dirty True if user has already interacted with the form. 26174 * @property {boolean} $valid True if all of the containing forms and controls are valid. 26175 * @property {boolean} $invalid True if at least one containing control or form is invali
26175d. 26176 * @property {boolean} $pending True if at least one containing control or form is pending. 26177 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 26178 * 26179 * @property {Object} $error Is an object hash, containing references to controls or 26180 * forms with failing validators, where: 26181 * 26182 * - keys are validation tokens (error names), 26183 * - values are arrays of controls or forms that have a failing validator for given error name. 26184 * 26185 * Built-in validation tokens: 26186 * 26187 * - `email` 26188 * - `max` 26189 * - `maxlength` 26190 * - `min` 26191 * - `minlength` 26192 * - `number` 26193 * - `pattern` 26194 * - `required` 26195 * - `url` 26196 * - `date` 26197 * - `datetimelocal` 26198 * - `time` 26199 * - `week` 26200 * - `month` 26201 * 26202 * @description 26203 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 26204 * such as being valid/invalid or dirty/pristine. 26205 * 26206 * Each {@link ng.directive:form form} directive creates an instance 26207 * of `FormController`. 26208 * 26209 */ 26210//asks for $scope to fool the BC controller module 26211FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 26212function FormController(element, attrs, $scope, $animate, $interpolate) { 26213 var form = this, 26214 controls = []; 26215 26216 // init state 26217 form.$error = {}; 26218 form.$$success = {}; 26219 form.$pending = undefined; 26220 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 26221 form.$dirty = false; 26222 form.$pristine = true; 26223 form.$valid = true; 26224 form.$invalid = false; 26225 form.$submitted = false; 26226 form.$$parentForm = nullFormCtrl; 26227 26228 /** 26229 * @ngdoc method 26230 * @name form.FormController#$rollbackViewValue 26231 * 26232 * @description 26233 * Rollback all form controls pending updates to the `$modelValue`. 26234 * 26235 * Updates may be pending by a debounced event or because the input is waiting for a some future 26236 * event defined in `ng-model-options`. This method is typically needed by the reset button of 26237 * a form that uses `ng-model-options` to pend updates. 26238 */ 26239 form.$rollbackViewValue = function() { 26240 forEach(controls, function(control) { 26241 control.$rollbackViewValue(); 26242 }); 26243 }; 26244 26245 /** 26246 * @ngdoc method 26247 * @name form.FormController#$commitViewValue 26248 * 26249 * @description 26250 * Commit all form controls pending updates to the `$modelValue`. 26251 * 26252 * Updates may be pending by a debounced event or because the input is waiting for a some future 26253 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 26254 * usually handles calling this in response to input events. 26255 */ 26256 form.$commitViewValue = function() { 26257 forEach(controls, function(control) { 26258 control.$commitViewValue(); 26259 }); 26260 }; 26261 26262 /** 26263 * @ngdoc method 26264 * @name form.FormController#$addControl 26265 * @param {object} control control object, either a {@link form.FormController} or an 26266 * {@link ngModel.NgModelController} 26267 * 26268 * @description 26269 * Register a control with the form. Input elements using ngModelController do this automatically 26270 * when they are linked. 26271 * 26272 * Note that the current state of the control will not be reflected on the new parent form. This 26273 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 26274 * state. 26275 * 26276 * However, if the method is used programmatically, for example by adding dynamically created controls, 26277 * or controls that have been previously removed without destroying their corresponding DOM element, 26278 * it's the developers responsibility to make sure the current state propagates to the parent form. 26279 * 26280 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 26281 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 26282 */ 26283 form.$addControl = function(control) { 26284 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 26285 // and not added to the scope. Now we throw an error. 26286 assertNotHasOwnProperty(control.$name, 'input'); 26287 controls.push(control); 26288 26289 if (control.$name) { 26290 form[control.$name] = control; 26291 } 26292 26293 control.$$parentForm = form; 26294 }; 26295 26296 // Private API: rename a form control 26297 form.$$renameControl = function(control, newName) { 26298 var oldName = control.$name; 26299 26300 if (form[oldName] === control) { 26301 delete form[oldName]; 26302 } 26303 form[newName] = control; 26304 control.$name = newName; 26305 }; 26306 26307 /** 26308 * @ngdoc method
26309 * @name form.FormController#$removeControl 26310 * @param {object} control control object, either a {@link form.FormController} or an 26311 * {@link ngModel.NgModelController} 26312 * 26313 * @description 26314 * Deregister a control from the form. 26315 * 26316 * Input elements using ngModelController do this automatically when they are destroyed. 26317 * 26318 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 26319 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 26320 * different from case to case. For example, removing the only `$dirty` control from a form may or 26321 * may not mean that the form is still `$dirty`. 26322 */ 26323 form.$removeControl = function(control) { 26324 if (control.$name && form[control.$name] === control) { 26325 delete form[control.$name]; 26326 } 26327 forEach(form.$pending, function(value, name) { 26328 form.$setValidity(name, null, control); 26329 }); 26330 forEach(form.$error, function(value, name) { 26331 form.$setValidity(name, null, control); 26332 }); 26333 forEach(form.$$success, function(value, name) { 26334 form.$setValidity(name, null, control); 26335 }); 26336 26337 arrayRemove(controls, control); 26338 control.$$parentForm = nullFormCtrl; 26339 }; 26340 26341 26342 /** 26343 * @ngdoc method 26344 * @name form.FormController#$setValidity 26345 * 26346 * @description 26347 * Sets the validity of a form control. 26348 * 26349 * This method will also propagate to parent forms. 26350 */ 26351 addSetValidityMethod({ 26352 ctrl: this, 26353 $element: element, 26354 set: function(object, property, controller) { 26355 var list = object[property]; 26356 if (!list) { 26357 object[property] = [controller]; 26358 } else { 26359 var index = list.indexOf(controller); 26360 if (index === -1) { 26361 list.push(controller); 26362 } 26363 } 26364 }, 26365 unset: function(object, property, controller) { 26366 var list = object[property]; 26367 if (!list) { 26368 return; 26369 } 26370 arrayRemove(list, controller); 26371 if (list.length === 0) { 26372 delete object[property]; 26373 } 26374 }, 26375 $animate: $animate 26376 }); 26377 26378 /** 26379 * @ngdoc method 26380 * @name form.FormController#$setDirty 26381 * 26382 * @description 26383 * Sets the form to a dirty state. 26384 * 26385 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 26386 * state (ng-dirty class). This method will also propagate to parent forms. 26387 */ 26388 form.$setDirty = function() { 26389 $animate.removeClass(element, PRISTINE_CLASS); 26390 $animate.addClass(element, DIRTY_CLASS); 26391 form.$dirty = true; 26392 form.$pristine = false; 26393 form.$$parentForm.$setDirty(); 26394 }; 26395 26396 /** 26397 * @ngdoc method 26398 * @name form.FormController#$setPristine 26399 * 26400 * @description 26401 * Sets the form to its pristine state. 26402 * 26403 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 26404 * state (ng-pristine class). This method will also propagate to all the controls contained 26405 * in this form. 26406 * 26407 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 26408 * saving or resetting it. 26409 */ 26410 form.$setPristine = function() { 26411 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 26412 form.$dirty = false; 26413 form.$pristine = true; 26414 form.$submitted = false; 26415 forEach(controls, function(control) { 26416 control.$setPristine(); 26417 }); 26418 }; 26419 26420 /** 26421 * @ngdoc method 26422 * @name form.FormController#$setUntouched 26423 * 26424 * @description 26425 * Sets the form to its untouched state. 26426 * 26427 * This method can be called to remove the 'ng-touched' class and set the form controls to their 26428 * untouched state (ng-untouched class). 26429 * 26430 * Setting a form controls back to their untouched state is often useful when setting the form 26431 * back to its pristine state. 26432 */ 26433 form.$setUntouched = function() { 26434 forEach(controls, function(control) { 26435 control.$setUntouched(); 26436 }); 26437 }; 26438 26439 /** 26440 * @ngdoc method 26441 * @name form.FormController#$setSubmitted 26442 * 26443 * @description 26444 * Sets the form to its submitted state. 26445 */ 26446 form.$setSubmitted = function() {
26447 $animate.addClass(element, SUBMITTED_CLASS); 26448 form.$submitted = true; 26449 form.$$parentForm.$setSubmitted(); 26450 }; 26451} 26452 26453/** 26454 * @ngdoc directive 26455 * @name ngForm 26456 * @restrict EAC 26457 * 26458 * @description 26459 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 26460 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 26461 * sub-group of controls needs to be determined. 26462 * 26463 * Note: the purpose of `ngForm` is to group controls, 26464 * but not to be a replacement for the `<form>` tag with all of its capabilities 26465 * (e.g. posting to the server, ...). 26466 * 26467 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 26468 * related scope, under this name. 26469 * 26470 */ 26471 26472 /** 26473 * @ngdoc directive 26474 * @name form 26475 * @restrict E 26476 * 26477 * @description 26478 * Directive that instantiates 26479 * {@link form.FormController FormController}. 26480 * 26481 * If the `name` attribute is specified, the form controller is published onto the current scope under 26482 * this name. 26483 * 26484 * # Alias: {@link ng.directive:ngForm `ngForm`} 26485 * 26486 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 26487 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 26488 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to 26489 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group 26490 * of controls needs to be determined. 26491 * 26492 * # CSS classes 26493 * - `ng-valid` is set if the form is valid. 26494 * - `ng-invalid` is set if the form is invalid. 26495 * - `ng-pending` is set if the form is pending. 26496 * - `ng-pristine` is set if the form is pristine. 26497 * - `ng-dirty` is set if the form is dirty. 26498 * - `ng-submitted` is set if the form was submitted. 26499 * 26500 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 26501 * 26502 * 26503 * # Submitting a form and preventing the default action 26504 * 26505 * Since the role of forms in client-side Angular applications is different than in classical 26506 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 26507 * page reload that sends the data to the server. Instead some javascript logic should be triggered 26508 * to handle the form submission in an application-specific way. 26509 * 26510 * For this reason, Angular prevents the default action (form submission to the server) unless the 26511 * `<form>` element has an `action` attribute specified. 26512 * 26513 * You can use one of the following two ways to specify what javascript method should be called when 26514 * a form is submitted: 26515 * 26516 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 26517 * - {@link ng.directive:ngClick ngClick} directive on the first 26518 * button or input field of type submit (input[type=submit]) 26519 * 26520 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 26521 * or {@link ng.directive:ngClick ngClick} directives. 26522 * This is because of the following form submission rules in the HTML specification: 26523 * 26524 * - If a form has only one input field then hitting enter in this field triggers form submit 26525 * (`ngSubmit`) 26526 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 26527 * doesn't trigger submit 26528 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 26529 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 26530 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 26531 * 26532 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 26533 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 26534 * to have access to the updated model. 26535 * 26536 * ## Animation Hooks 26537 * 26538 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 26539 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 26540 * other validations that are performed within the form. Animations in ngForm are similar to how 26541 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 26542 * as JS animations. 26543 * 26544 * The following example shows a simple way to utilize CSS transitions to style a form element
26545 * that has been rendered as invalid after it has been validated: 26546 * 26547 * <pre> 26548 * //be sure to include ngAnimate as a module to hook into more 26549 * //advanced animations 26550 * .my-form { 26551 * transition:0.5s linear all; 26552 * background: white; 26553 * } 26554 * .my-form.ng-invalid { 26555 * background: red; 26556 * color:white; 26557 * } 26558 * </pre> 26559 * 26560 * @example 26561 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 26562 <file name="index.html"> 26563 <script> 26564 angular.module('formExample', []) 26565 .controller('FormController', ['$scope', function($scope) { 26566 $scope.userType = 'guest'; 26567 }]); 26568 </script> 26569 <style> 26570 .my-form { 26571 transition:all linear 0.5s; 26572 background: transparent; 26573 } 26574 .my-form.ng-invalid { 26575 background: red; 26576 } 26577 </style> 26578 <form name="myForm" ng-controller="FormController" class="my-form"> 26579 userType: <input name="input" ng-model="userType" required> 26580 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 26581 <code>userType = {{userType}}</code><br> 26582 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 26583 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 26584 <code>myForm.$valid = {{myForm.$valid}}</code><br> 26585 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 26586 </form> 26587 </file> 26588 <file name="protractor.js" type="protractor"> 26589 it('should initialize to model', function() { 26590 var userType = element(by.binding('userType')); 26591 var valid = element(by.binding('myForm.input.$valid')); 26592 26593 expect(userType.getText()).toContain('guest'); 26594 expect(valid.getText()).toContain('true'); 26595 }); 26596 26597 it('should be invalid if empty', function() { 26598 var userType = element(by.binding('userType')); 26599 var valid = element(by.binding('myForm.input.$valid')); 26600 var userInput = element(by.model('userType')); 26601 26602 userInput.clear(); 26603 userInput.sendKeys(''); 26604 26605 expect(userType.getText()).toEqual('userType ='); 26606 expect(valid.getText()).toContain('false'); 26607 }); 26608 </file> 26609 </example> 26610 * 26611 * @param {string=} name Name of the form. If specified, the form controller will be published into 26612 * related scope, under this name. 26613 */ 26614var formDirectiveFactory = function(isNgForm) { 26615 return ['$timeout', '$parse', function($timeout, $parse) { 26616 var formDirective = { 26617 name: 'form', 26618 restrict: isNgForm ? 'EAC' : 'E', 26619 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 26620 controller: FormController, 26621 compile: function ngFormCompile(formElement, attr) { 26622 // Setup initial state of the control 26623 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 26624 26625 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 26626 26627 return { 26628 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 26629 var controller = ctrls[0]; 26630 26631 // if `action` attr is not present on the form, prevent the default action (submission) 26632 if (!('action' in attr)) { 26633 // we can't use jq events because if a form is destroyed during submission the default 26634 // action is not prevented. see #1238 26635 // 26636 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 26637 // page reload if the form was destroyed by submission of the form via a click handler 26638 // on a button in the form. Looks like an IE9 specific bug. 26639 var handleFormSubmission = function(event) { 26640 scope.$apply(function() { 26641 controller.$commitViewValue(); 26642 controller.$setSubmitted(); 26643 }); 26644 26645 event.preventDefault(); 26646 }; 26647 26648 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 26649 26650 // unregister the preventDefault listener so that we don't not leak memory but in a 26651 // way that will achieve the prevention of the default action. 26652 formElement.on('$destroy', function() { 26653 $timeout(function() { 26654 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 26655 }, 0, false); 26656 }); 26657 } 26658 26659 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 26660 parentFormCtrl.$addControl(controller); 26661 26662 var setter = nameAttr ? getSetter(controller.$name) : noop; 26663 26664 if (nameAttr) { 26665 setter(scope, controller); 26666 attr.$observe(nameAttr, function(newValue) { 26667 if (controller.$name === newValue) return; 26668 setter(scope, undefined); 26669 controller.$$parentForm.$$renameControl(controller, newValue); 26670 setter = getSetter(controller.$name); 26671 setter(scope, controller); 26672 }); 26673 } 26674 formElement.on('$destroy', function() { 26675 controller.$$parentForm.$removeControl(controller); 26676 setter(scope, undefined); 26677 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 26678 }); 26679 } 26680 }; 26681 } 26682 }; 26683 26684 return formDirective; 26685 26686 function getSetter(expression) { 26687 if (expression === '') { 26688 //create an assignable expression, so forms with an empty name can be renamed later 26689 return $parse('this[""]').assign; 26690 } 26691 return $parse(expression).assign || noop; 26692 } 26693 }]; 26694}; 26695 26696var formDirective = formDirectiveFactory(); 26697var ngFormDirective = formDirectiveFactory(true); 26698 26699/* global VALID_CLASS: false, 26700 INVALID_CLASS: false, 26701 PRISTINE_CLASS: false, 26702 DIRTY_CLASS: false, 26703 UNTOUCHED_CLASS: false, 26704 TOUCHED_CLASS: false, 26705 ngModelMinErr: false, 26706*/ 26707 26708// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 26709var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/;
26710// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) 26711// Note: We are being more lenient, because browsers are too. 26712// 1. Scheme 26713// 2. Slashes 26714// 3. Username 26715// 4. Password 26716// 5. Hostname 26717// 6. Port 26718// 7. Path 26719// 8. Query 26720// 9. Fragment 26721// 1111111111111111 222 333333 44444 555555555555555555555555 666 77777777 8888888 999 26722var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; 26723var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 26724var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 26725var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; 26726var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 26727var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; 26728var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; 26729var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 26730 26731var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; 26732var PARTIAL_VALIDATION_TYPES = createMap(); 26733forEach('date,datetime-local,month,time,week'.split(','), function(type) { 26734 PARTIAL_VALIDATION_TYPES[type] = true; 26735}); 26736 26737var inputType = { 26738 26739 /** 26740 * @ngdoc input 26741 * @name input[text] 26742 * 26743 * @description 26744 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 26745 * 26746 * 26747 * @param {string} ngModel Assignable angular expression to data-bind to. 26748 * @param {string=} name Property name of the form under which the control is published. 26749 * @param {string=} required Adds `required` validation error key if the value is not entered. 26750 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26751 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26752 * `required` when you want to data-bind to the `required` attribute. 26753 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26754 * minlength. 26755 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26756 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 26757 * any length. 26758 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 26759 * that contains the regular expression body that will be converted to a regular expression 26760 * as in the ngPattern directive. 26761 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26762 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 26763 * If the expression evaluates to a RegExp object, then this is used directly. 26764 * If the expression evaluates to a string, then it will be converted to a RegExp 26765 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26766 * `new RegExp('^abc$')`.<br /> 26767 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26768 * start at the index of the last search's match, thus not taking the whole input value into 26769 * account. 26770 * @param {string=} ngChange Angular expression to be executed when input changes due to user 26771 * interaction with the input element. 26772 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 26773 * This parameter is ignored for input[type=password] controls, which will never trim the 26774 * input. 26775 * 26776 * @example 26777 <example name="text-input-directive" module="textInputExample"> 26778 <file name="index.html"> 26779 <script> 26780 angular.module('textInputExample', []) 26781 .controller('ExampleController', ['$scope', function($scope) { 26782 $scope.example = { 26783 text: 'guest', 26784 word: /^\s*\w*\s*$/ 26785 }; 26786 }]); 26787 </script>
26788 <form name="myForm" ng-controller="ExampleController"> 26789 <label>Single word: 26790 <input type="text" name="input" ng-model="example.text" 26791 ng-pattern="example.word" required ng-trim="false"> 26792 </label> 26793 <div role="alert"> 26794 <span class="error" ng-show="myForm.input.$error.required"> 26795 Required!</span> 26796 <span class="error" ng-show="myForm.input.$error.pattern"> 26797 Single word only!</span> 26798 </div> 26799 <tt>text = {{example.text}}</tt><br/> 26800 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26801 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 26802 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26803 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26804 </form> 26805 </file> 26806 <file name="protractor.js" type="protractor"> 26807 var text = element(by.binding('example.text')); 26808 var valid = element(by.binding('myForm.input.$valid')); 26809 var input = element(by.model('example.text')); 26810 26811 it('should initialize to model', function() { 26812 expect(text.getText()).toContain('guest'); 26813 expect(valid.getText()).toContain('true'); 26814 }); 26815 26816 it('should be invalid if empty', function() { 26817 input.clear(); 26818 input.sendKeys(''); 26819 26820 expect(text.getText()).toEqual('text ='); 26821 expect(valid.getText()).toContain('false'); 26822 }); 26823 26824 it('should be invalid if multi word', function() { 26825 input.clear(); 26826 input.sendKeys('hello world'); 26827 26828 expect(valid.getText()).toContain('false'); 26829 }); 26830 </file> 26831 </example> 26832 */ 26833 'text': textInputType, 26834 26835 /** 26836 * @ngdoc input 26837 * @name input[date] 26838 * 26839 * @description 26840 * Input with date validation and transformation. In browsers that do not yet support 26841 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 26842 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 26843 * modern browsers do not yet support this input type, it is important to provide cues to users on the 26844 * expected input format via a placeholder or label. 26845 * 26846 * The model must always be a Date object, otherwise Angular will throw an error. 26847 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 26848 * 26849 * The timezone to be used to read/write the `Date` instance in the model can be defined using 26850 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 26851 * 26852 * @param {string} ngModel Assignable angular expression to data-bind to. 26853 * @param {string=} name Property name of the form under which the control is published. 26854 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 26855 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 26856 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 26857 * constraint validation. 26858 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 26859 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 26860 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 26861 * constraint validation. 26862 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 26863 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 26864 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 26865 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 26866 * @param {string=} required Sets `required` validation error key if the value is not entered. 26867 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26868 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26869 * `required` when you want to data-bind to the `required` attribute. 26870 * @param {string=} ngChange Angular expression to be executed when input changes due to user 26871 * interaction with the input element. 26872 * 26873 * @example 26874 <example name="date-input-directive" module="dateInputExample"> 26875 <file name="index.html"> 26876 <script> 26877 angular.module('dateInputExample', []) 26878 .controller('DateController', ['$scope', function($scope) { 26879 $scope.example = { 26880 value: new Date(2013, 9, 22) 26881 }; 26882 }]); 26883 </script> 26884 <form name="myForm" ng-controller="DateController as dateCtrl"> 26885 <label for="exampleInput">Pick a date in 2013:</label> 26886 <input type="date" id="exampleInput" name="input" ng-model="example.value" 26887 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 26888 <div role="alert">
26889 <span class="error" ng-show="myForm.input.$error.required"> 26890 Required!</span> 26891 <span class="error" ng-show="myForm.input.$error.date"> 26892 Not a valid date!</span> 26893 </div> 26894 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 26895 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26896 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 26897 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26898 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26899 </form> 26900 </file> 26901 <file name="protractor.js" type="protractor"> 26902 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 26903 var valid = element(by.binding('myForm.input.$valid')); 26904 var input = element(by.model('example.value')); 26905 26906 // currently protractor/webdriver does not support 26907 // sending keys to all known HTML5 input controls 26908 // for various browsers (see https://github.com/angular/protractor/issues/562). 26909 function setInput(val) { 26910 // set the value of the element and force validation. 26911 var scr = "var ipt = document.getElementById('exampleInput'); " + 26912 "ipt.value = '" + val + "';" + 26913 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 26914 browser.executeScript(scr); 26915 } 26916 26917 it('should initialize to model', function() { 26918 expect(value.getText()).toContain('2013-10-22'); 26919 expect(valid.getText()).toContain('myForm.input.$valid = true'); 26920 }); 26921 26922 it('should be invalid if empty', function() { 26923 setInput(''); 26924 expect(value.getText()).toEqual('value ='); 26925 expect(valid.getText()).toContain('myForm.input.$valid = false'); 26926 }); 26927 26928 it('should be invalid if over max', function() { 26929 setInput('2015-01-01'); 26930 expect(value.getText()).toContain(''); 26931 expect(valid.getText()).toContain('myForm.input.$valid = false'); 26932 }); 26933 </file> 26934 </example> 26935 */ 26936 'date': createDateInputType('date', DATE_REGEXP, 26937 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 26938 'yyyy-MM-dd'), 26939 26940 /** 26941 * @ngdoc input 26942 * @name input[datetime-local] 26943 * 26944 * @description 26945 * Input with datetime validation and transformation. In browsers that do not yet support 26946 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 26947 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 26948 * 26949 * The model must always be a Date object, otherwise Angular will throw an error. 26950 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 26951 * 26952 * The timezone to be used to read/write the `Date` instance in the model can be defined using 26953 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 26954 * 26955 * @param {string} ngModel Assignable angular expression to data-bind to. 26956 * @param {string=} name Property name of the form under which the control is published. 26957 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 26958 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 26959 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 26960 * Note that `min` will also add native HTML5 constraint validation. 26961 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 26962 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 26963 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 26964 * Note that `max` will also add native HTML5 constraint validation. 26965 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 26966 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 26967 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 26968 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 26969 * @param {string=} required Sets `required` validation error key if the value is not entered. 26970 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26971 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26972 * `required` when you want to data-bind to the `required` attribute. 26973 * @param {string=} ngChange Angular expression to be executed when input changes due to user 26974 * interaction with the input element. 26975 * 26976 * @example 26977 <example name="datetimelocal-input-directive" module="dateExample"> 26978 <file name="index.html"> 26979 <script> 26980 angular.module('dateExample', []) 26981 .controller('DateController', ['$scope', function($scope) { 26982 $scope.example = { 26983 value: new Date(2010, 11, 28, 14, 57) 26984 }; 26985 }]); 26986 </script> 26987 <form name="myForm" ng-controller="DateController as dateCtrl"> 26988 <label for="exampleInput">Pick a date between in 2013:</label> 26989 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 26990 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 26991 <div role="alert">
26992 <span class="error" ng-show="myForm.input.$error.required"> 26993 Required!</span> 26994 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 26995 Not a valid date!</span> 26996 </div> 26997 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 26998 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 26999 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 27000 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27001 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27002 </form> 27003 </file> 27004 <file name="protractor.js" type="protractor"> 27005 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 27006 var valid = element(by.binding('myForm.input.$valid')); 27007 var input = element(by.model('example.value')); 27008 27009 // currently protractor/webdriver does not support 27010 // sending keys to all known HTML5 input controls 27011 // for various browsers (https://github.com/angular/protractor/issues/562). 27012 function setInput(val) { 27013 // set the value of the element and force validation. 27014 var scr = "var ipt = document.getElementById('exampleInput'); " + 27015 "ipt.value = '" + val + "';" + 27016 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 27017 browser.executeScript(scr); 27018 } 27019 27020 it('should initialize to model', function() { 27021 expect(value.getText()).toContain('2010-12-28T14:57:00'); 27022 expect(valid.getText()).toContain('myForm.input.$valid = true'); 27023 }); 27024 27025 it('should be invalid if empty', function() { 27026 setInput(''); 27027 expect(value.getText()).toEqual('value ='); 27028 expect(valid.getText()).toContain('myForm.input.$valid = false'); 27029 }); 27030 27031 it('should be invalid if over max', function() { 27032 setInput('2015-01-01T23:59:00'); 27033 expect(value.getText()).toContain(''); 27034 expect(valid.getText()).toContain('myForm.input.$valid = false'); 27035 }); 27036 </file> 27037 </example> 27038 */ 27039 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 27040 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 27041 'yyyy-MM-ddTHH:mm:ss.sss'), 27042 27043 /** 27044 * @ngdoc input 27045 * @name input[time] 27046 * 27047 * @description 27048 * Input with time validation and transformation. In browsers that do not yet support 27049 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 27050 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 27051 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 27052 * 27053 * The model must always be a Date object, otherwise Angular will throw an error. 27054 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 27055 * 27056 * The timezone to be used to read/write the `Date` instance in the model can be defined using 27057 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 27058 * 27059 * @param {string} ngModel Assignable angular expression to data-bind to. 27060 * @param {string=} name Property name of the form under which the control is published. 27061 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 27062 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 27063 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 27064 * native HTML5 constraint validation. 27065 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 27066 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 27067 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 27068 * native HTML5 constraint validation. 27069 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 27070 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 27071 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 27072 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 27073 * @param {string=} required Sets `required` validation error key if the value is not entered. 27074 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27075 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27076 * `required` when you want to data-bind to the `required` attribute. 27077 * @param {string=} ngChange Angular expression to be executed when input changes due to user 27078 * interaction with the input element. 27079 * 27080 * @example 27081 <example name="time-input-directive" module="timeExample"> 27082 <file name="index.html"> 27083 <script> 27084 angular.module('timeExample', []) 27085 .controller('DateController', ['$scope', function($scope) { 27086 $scope.example = { 27087 value: new Date(1970, 0, 1, 14, 57, 0) 27088 }; 27089 }]); 27090 </script> 27091 <form name="myForm" ng-controller="DateController as dateCtrl"> 27092 <label for="exampleInput">Pick a between 8am and 5pm:</label> 27093 <input type="time" id="exampleInput" name="input" ng-model="example.value" 27094 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 27095 <div role="alert">
27096 <span class="error" ng-show="myForm.input.$error.required"> 27097 Required!</span> 27098 <span class="error" ng-show="myForm.input.$error.time"> 27099 Not a valid date!</span> 27100 </div> 27101 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 27102 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 27103 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 27104 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27105 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27106 </form> 27107 </file> 27108 <file name="protractor.js" type="protractor"> 27109 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 27110 var valid = element(by.binding('myForm.input.$valid')); 27111 var input = element(by.model('example.value')); 27112 27113 // currently protractor/webdriver does not support 27114 // sending keys to all known HTML5 input controls 27115 // for various browsers (https://github.com/angular/protractor/issues/562). 27116 function setInput(val) { 27117 // set the value of the element and force validation. 27118 var scr = "var ipt = document.getElementById('exampleInput'); " + 27119 "ipt.value = '" + val + "';" + 27120 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 27121 browser.executeScript(scr); 27122 } 27123 27124 it('should initialize to model', function() { 27125 expect(value.getText()).toContain('14:57:00'); 27126 expect(valid.getText()).toContain('myForm.input.$valid = true'); 27127 }); 27128 27129 it('should be invalid if empty', function() { 27130 setInput(''); 27131 expect(value.getText()).toEqual('value ='); 27132 expect(valid.getText()).toContain('myForm.input.$valid = false'); 27133 }); 27134 27135 it('should be invalid if over max', function() { 27136 setInput('23:59:00'); 27137 expect(value.getText()).toContain(''); 27138 expect(valid.getText()).toContain('myForm.input.$valid = false'); 27139 }); 27140 </file> 27141 </example> 27142 */ 27143 'time': createDateInputType('time', TIME_REGEXP, 27144 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 27145 'HH:mm:ss.sss'), 27146 27147 /** 27148 * @ngdoc input 27149 * @name input[week] 27150 * 27151 * @description 27152 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 27153 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 27154 * week format (yyyy-W##), for example: `2013-W02`. 27155 * 27156 * The model must always be a Date object, otherwise Angular will throw an error. 27157 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 27158 * 27159 * The timezone to be used to read/write the `Date` instance in the model can be defined using 27160 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 27161 * 27162 * @param {string} ngModel Assignable angular expression to data-bind to. 27163 * @param {string=} name Property name of the form under which the control is published. 27164 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 27165 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 27166 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 27167 * native HTML5 constraint validation. 27168 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 27169 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 27170 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 27171 * native HTML5 constraint validation. 27172 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 27173 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 27174 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 27175 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 27176 * @param {string=} required Sets `required` validation error key if the value is not entered. 27177 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27178 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27179 * `required` when you want to data-bind to the `required` attribute. 27180 * @param {string=} ngChange Angular expression to be executed when input changes due to user 27181 * interaction with the input element. 27182 * 27183 * @example 27184 <example name="week-input-directive" module="weekExample"> 27185 <file name="index.html"> 27186 <script> 27187 angular.module('weekExample', []) 27188 .controller('DateController', ['$scope', function($scope) { 27189 $scope.example = { 27190 value: new Date(2013, 0, 3) 27191 }; 27192 }]); 27193 </script> 27194 <form name="myForm" ng-controller="DateController as dateCtrl"> 27195 <label>Pick a date between in 2013: 27196 <input id="exampleInput" type="week" name="input" ng-model="example.value" 27197 placeholder="YYYY-W##" min="2012-W32" 27198 max="2013-W52" required /> 27199 </label> 27200 <div role="alert">
27201 <span class="error" ng-show="myForm.input.$error.required"> 27202 Required!</span> 27203 <span class="error" ng-show="myForm.input.$error.week"> 27204 Not a valid date!</span> 27205 </div> 27206 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 27207 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 27208 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 27209 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27210 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27211 </form> 27212 </file> 27213 <file name="protractor.js" type="protractor"> 27214 var value = element(by.binding('example.value | date: "yyyy-Www"')); 27215 var valid = element(by.binding('myForm.input.$valid')); 27216 var input = element(by.model('example.value')); 27217 27218 // currently protractor/webdriver does not support 27219 // sending keys to all known HTML5 input controls 27220 // for various browsers (https://github.com/angular/protractor/issues/562). 27221 function setInput(val) { 27222 // set the value of the element and force validation. 27223 var scr = "var ipt = document.getElementById('exampleInput'); " + 27224 "ipt.value = '" + val + "';" + 27225 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 27226 browser.executeScript(scr); 27227 } 27228 27229 it('should initialize to model', function() { 27230 expect(value.getText()).toContain('2013-W01'); 27231 expect(valid.getText()).toContain('myForm.input.$valid = true'); 27232 }); 27233 27234 it('should be invalid if empty', function() { 27235 setInput(''); 27236 expect(value.getText()).toEqual('value ='); 27237 expect(valid.getText()).toContain('myForm.input.$valid = false'); 27238 }); 27239 27240 it('should be invalid if over max', function() { 27241 setInput('2015-W01'); 27242 expect(value.getText()).toContain(''); 27243 expect(valid.getText()).toContain('myForm.input.$valid = false'); 27244 }); 27245 </file> 27246 </example> 27247 */ 27248 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 27249 27250 /** 27251 * @ngdoc input 27252 * @name input[month] 27253 * 27254 * @description 27255 * Input with month validation and transformation. In browsers that do not yet support 27256 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 27257 * month format (yyyy-MM), for example: `2009-01`. 27258 * 27259 * The model must always be a Date object, otherwise Angular will throw an error. 27260 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 27261 * If the model is not set to the first of the month, the next view to model update will set it 27262 * to the first of the month. 27263 * 27264 * The timezone to be used to read/write the `Date` instance in the model can be defined using 27265 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 27266 * 27267 * @param {string} ngModel Assignable angular expression to data-bind to. 27268 * @param {string=} name Property name of the form under which the control is published. 27269 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 27270 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 27271 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 27272 * native HTML5 constraint validation. 27273 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 27274 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 27275 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 27276 * native HTML5 constraint validation. 27277 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 27278 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 27279 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 27280 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 27281 27282 * @param {string=} required Sets `required` validation error key if the value is not entered. 27283 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27284 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27285 * `required` when you want to data-bind to the `required` attribute. 27286 * @param {string=} ngChange Angular expression to be executed when input changes due to user 27287 * interaction with the input element. 27288 * 27289 * @example 27290 <example name="month-input-directive" module="monthExample"> 27291 <file name="index.html"> 27292 <script> 27293 angular.module('monthExample', []) 27294 .controller('DateController', ['$scope', function($scope) { 27295 $scope.example = { 27296 value: new Date(2013, 9, 1) 27297 }; 27298 }]); 27299 </script> 27300 <form name="myForm" ng-controller="DateController as dateCtrl"> 27301 <label for="exampleInput">Pick a month in 2013:</label> 27302 <input id="exampleInput" type="month" name="input" ng-model="example.value" 27303 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 27304 <div role="alert">
27305 <span class="error" ng-show="myForm.input.$error.required"> 27306 Required!</span> 27307 <span class="error" ng-show="myForm.input.$error.month"> 27308 Not a valid month!</span> 27309 </div> 27310 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 27311 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 27312 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 27313 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27314 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27315 </form> 27316 </file> 27317 <file name="protractor.js" type="protractor"> 27318 var value = element(by.binding('example.value | date: "yyyy-MM"')); 27319 var valid = element(by.binding('myForm.input.$valid')); 27320 var input = element(by.model('example.value')); 27321 27322 // currently protractor/webdriver does not support 27323 // sending keys to all known HTML5 input controls 27324 // for various browsers (https://github.com/angular/protractor/issues/562). 27325 function setInput(val) { 27326 // set the value of the element and force validation. 27327 var scr = "var ipt = document.getElementById('exampleInput'); " + 27328 "ipt.value = '" + val + "';" + 27329 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 27330 browser.executeScript(scr); 27331 } 27332 27333 it('should initialize to model', function() { 27334 expect(value.getText()).toContain('2013-10'); 27335 expect(valid.getText()).toContain('myForm.input.$valid = true'); 27336 }); 27337 27338 it('should be invalid if empty', function() { 27339 setInput(''); 27340 expect(value.getText()).toEqual('value ='); 27341 expect(valid.getText()).toContain('myForm.input.$valid = false'); 27342 }); 27343 27344 it('should be invalid if over max', function() { 27345 setInput('2015-01'); 27346 expect(value.getText()).toContain(''); 27347 expect(valid.getText()).toContain('myForm.input.$valid = false'); 27348 }); 27349 </file> 27350 </example> 27351 */ 27352 'month': createDateInputType('month', MONTH_REGEXP, 27353 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 27354 'yyyy-MM'), 27355 27356 /** 27357 * @ngdoc input 27358 * @name input[number] 27359 * 27360 * @description 27361 * Text input with number validation and transformation. Sets the `number` validation 27362 * error if not a valid number. 27363 * 27364 * <div class="alert alert-warning"> 27365 * The model must always be of type `number` otherwise Angular will throw an error. 27366 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 27367 * error docs for more information and an example of how to convert your model if necessary. 27368 * </div> 27369 * 27370 * ## Issues with HTML5 constraint validation 27371 * 27372 * In browsers that follow the 27373 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 27374 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 27375 * If a non-number is entered in the input, the browser will report the value as an empty string, 27376 * which means the view / model values in `ngModel` and subsequently the scope value 27377 * will also be an empty string. 27378 * 27379 * 27380 * @param {string} ngModel Assignable angular expression to data-bind to. 27381 * @param {string=} name Property name of the form under which the control is published. 27382 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 27383 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 27384 * @param {string=} required Sets `required` validation error key if the value is not entered. 27385 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27386 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27387 * `required` when you want to data-bind to the `required` attribute. 27388 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 27389 * minlength. 27390 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 27391 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 27392 * any length. 27393 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 27394 * that contains the regular expression body that will be converted to a regular expression 27395 * as in the ngPattern directive. 27396 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 27397 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 27398 * If the expression evaluates to a RegExp object, then this is used directly. 27399 * If the expression evaluates to a string, then it will be converted to a RegExp 27400 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 27401 * `new RegExp('^abc$')`.<br /> 27402 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 27403 * start at the index of the last search's match, thus not taking the whole input value into 27404 * account. 27405 * @param {string=} ngChange Angular expression to be executed when input changes due to user 27406 * interaction with the input element. 27407 * 27408 * @example 27409 <example name="number-input-directive" module="numberExample"> 27410 <file name="index.html"> 27411 <script> 27412 angular.module('numberExample', [])
27413 .controller('ExampleController', ['$scope', function($scope) { 27414 $scope.example = { 27415 value: 12 27416 }; 27417 }]); 27418 </script> 27419 <form name="myForm" ng-controller="ExampleController"> 27420 <label>Number: 27421 <input type="number" name="input" ng-model="example.value" 27422 min="0" max="99" required> 27423 </label> 27424 <div role="alert"> 27425 <span class="error" ng-show="myForm.input.$error.required"> 27426 Required!</span> 27427 <span class="error" ng-show="myForm.input.$error.number"> 27428 Not valid number!</span> 27429 </div> 27430 <tt>value = {{example.value}}</tt><br/> 27431 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 27432 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 27433 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27434 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27435 </form> 27436 </file> 27437 <file name="protractor.js" type="protractor"> 27438 var value = element(by.binding('example.value')); 27439 var valid = element(by.binding('myForm.input.$valid')); 27440 var input = element(by.model('example.value')); 27441 27442 it('should initialize to model', function() { 27443 expect(value.getText()).toContain('12'); 27444 expect(valid.getText()).toContain('true'); 27445 }); 27446 27447 it('should be invalid if empty', function() { 27448 input.clear(); 27449 input.sendKeys(''); 27450 expect(value.getText()).toEqual('value ='); 27451 expect(valid.getText()).toContain('false'); 27452 }); 27453 27454 it('should be invalid if over max', function() { 27455 input.clear(); 27456 input.sendKeys('123'); 27457 expect(value.getText()).toEqual('value ='); 27458 expect(valid.getText()).toContain('false'); 27459 }); 27460 </file> 27461 </example> 27462 */ 27463 'number': numberInputType, 27464 27465 27466 /** 27467 * @ngdoc input 27468 * @name input[url] 27469 * 27470 * @description 27471 * Text input with URL validation. Sets the `url` validation error key if the content is not a 27472 * valid URL. 27473 * 27474 * <div class="alert alert-warning"> 27475 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 27476 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 27477 * the built-in validators (see the {@link guide/forms Forms guide}) 27478 * </div> 27479 * 27480 * @param {string} ngModel Assignable angular expression to data-bind to. 27481 * @param {string=} name Property name of the form under which the control is published. 27482 * @param {string=} required Sets `required` validation error key if the value is not entered. 27483 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27484 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27485 * `required` when you want to data-bind to the `required` attribute. 27486 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 27487 * minlength. 27488 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 27489 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 27490 * any length. 27491 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 27492 * that contains the regular expression body that will be converted to a regular expression 27493 * as in the ngPattern directive. 27494 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 27495 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 27496 * If the expression evaluates to a RegExp object, then this is used directly. 27497 * If the expression evaluates to a string, then it will be converted to a RegExp 27498 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 27499 * `new RegExp('^abc$')`.<br /> 27500 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 27501 * start at the index of the last search's match, thus not taking the whole input value into 27502 * account. 27503 * @param {string=} ngChange Angular expression to be executed when input changes due to user 27504 * interaction with the input element. 27505 * 27506 * @example 27507 <example name="url-input-directive" module="urlExample"> 27508 <file name="index.html"> 27509 <script> 27510 angular.module('urlExample', [])
27511 .controller('ExampleController', ['$scope', function($scope) { 27512 $scope.url = { 27513 text: 'http://google.com' 27514 }; 27515 }]); 27516 </script> 27517 <form name="myForm" ng-controller="ExampleController"> 27518 <label>URL: 27519 <input type="url" name="input" ng-model="url.text" required> 27520 <label> 27521 <div role="alert"> 27522 <span class="error" ng-show="myForm.input.$error.required"> 27523 Required!</span> 27524 <span class="error" ng-show="myForm.input.$error.url"> 27525 Not valid url!</span> 27526 </div> 27527 <tt>text = {{url.text}}</tt><br/> 27528 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 27529 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 27530 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27531 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27532 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 27533 </form> 27534 </file> 27535 <file name="protractor.js" type="protractor"> 27536 var text = element(by.binding('url.text')); 27537 var valid = element(by.binding('myForm.input.$valid')); 27538 var input = element(by.model('url.text')); 27539 27540 it('should initialize to model', function() { 27541 expect(text.getText()).toContain('http://google.com'); 27542 expect(valid.getText()).toContain('true'); 27543 }); 27544 27545 it('should be invalid if empty', function() { 27546 input.clear(); 27547 input.sendKeys(''); 27548 27549 expect(text.getText()).toEqual('text ='); 27550 expect(valid.getText()).toContain('false'); 27551 }); 27552 27553 it('should be invalid if not url', function() { 27554 input.clear(); 27555 input.sendKeys('box'); 27556 27557 expect(valid.getText()).toContain('false'); 27558 }); 27559 </file> 27560 </example> 27561 */ 27562 'url': urlInputType, 27563 27564 27565 /** 27566 * @ngdoc input 27567 * @name input[email] 27568 * 27569 * @description 27570 * Text input with email validation. Sets the `email` validation error key if not a valid email 27571 * address. 27572 * 27573 * <div class="alert alert-warning"> 27574 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 27575 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 27576 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 27577 * </div> 27578 * 27579 * @param {string} ngModel Assignable angular expression to data-bind to. 27580 * @param {string=} name Property name of the form under which the control is published. 27581 * @param {string=} required Sets `required` validation error key if the value is not entered. 27582 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 27583 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 27584 * `required` when you want to data-bind to the `required` attribute. 27585 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 27586 * minlength. 27587 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 27588 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 27589 * any length. 27590 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 27591 * that contains the regular expression body that will be converted to a regular expression 27592 * as in the ngPattern directive. 27593 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 27594 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 27595 * If the expression evaluates to a RegExp object, then this is used directly. 27596 * If the expression evaluates to a string, then it will be converted to a RegExp 27597 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 27598 * `new RegExp('^abc$')`.<br /> 27599 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 27600 * start at the index of the last search's match, thus not taking the whole input value into 27601 * account. 27602 * @param {string=} ngChange Angular expression to be executed when input changes due to user 27603 * interaction with the input element. 27604 * 27605 * @example 27606 <example name="email-input-directive" module="emailExample"> 27607 <file name="index.html"> 27608 <script> 27609 angular.module('emailExample', [])
27610 .controller('ExampleController', ['$scope', function($scope) { 27611 $scope.email = { 27612 text: '[email protected]' 27613 }; 27614 }]); 27615 </script> 27616 <form name="myForm" ng-controller="ExampleController"> 27617 <label>Email: 27618 <input type="email" name="input" ng-model="email.text" required> 27619 </label> 27620 <div role="alert"> 27621 <span class="error" ng-show="myForm.input.$error.required"> 27622 Required!</span> 27623 <span class="error" ng-show="myForm.input.$error.email"> 27624 Not valid email!</span> 27625 </div> 27626 <tt>text = {{email.text}}</tt><br/> 27627 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 27628 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 27629 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 27630 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 27631 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 27632 </form> 27633 </file> 27634 <file name="protractor.js" type="protractor"> 27635 var text = element(by.binding('email.text')); 27636 var valid = element(by.binding('myForm.input.$valid')); 27637 var input = element(by.model('email.text')); 27638 27639 it('should initialize to model', function() { 27640 expect(text.getText()).toContain('[email protected]'); 27641 expect(valid.getText()).toContain('true'); 27642 }); 27643 27644 it('should be invalid if empty', function() { 27645 input.clear(); 27646 input.sendKeys(''); 27647 expect(text.getText()).toEqual('text ='); 27648 expect(valid.getText()).toContain('false'); 27649 }); 27650 27651 it('should be invalid if not email', function() { 27652 input.clear(); 27653 input.sendKeys('xxx'); 27654 27655 expect(valid.getText()).toContain('false'); 27656 }); 27657 </file> 27658 </example> 27659 */ 27660 'email': emailInputType, 27661 27662 27663 /** 27664 * @ngdoc input 27665 * @name input[radio] 27666 * 27667 * @description 27668 * HTML radio button. 27669 * 27670 * @param {string} ngModel Assignable angular expression to data-bind to. 27671 * @param {string} value The value to which the `ngModel` expression should be set when selected. 27672 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 27673 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 27674 * @param {string=} name Property name of the form under which the control is published. 27675 * @param {string=} ngChange Angular expression to be executed when input changes due to user 27676 * interaction with the input element. 27677 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio 27678 * is selected. Should be used instead of the `value` attribute if you need 27679 * a non-string `ngModel` (`boolean`, `array`, ...). 27680 * 27681 * @example 27682 <example name="radio-input-directive" module="radioExample"> 27683 <file name="index.html"> 27684 <script> 27685 angular.module('radioExample', []) 27686 .controller('ExampleController', ['$scope', function($scope) { 27687 $scope.color = { 27688 name: 'blue' 27689 }; 27690 $scope.specialValue = { 27691 "id": "12345", 27692 "value": "green" 27693 }; 27694 }]); 27695 </script>
27696 <form name="myForm" ng-controller="ExampleController"> 27697 <label> 27698 <input type="radio" ng-model="color.name" value="red"> 27699 Red 27700 </label><br/> 27701 <label> 27702 <input type="radio" ng-model="color.name" ng-value="specialValue"> 27703 Green 27704 </label><br/> 27705 <label> 27706 <input type="radio" ng-model="color.name" value="blue"> 27707 Blue 27708 </label><br/> 27709 <tt>color = {{color.name | json}}</tt><br/> 27710 </form> 27711 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 27712 </file> 27713 <file name="protractor.js" type="protractor"> 27714 it('should change state', function() { 27715 var color = element(by.binding('color.name')); 27716 27717 expect(color.getText()).toContain('blue'); 27718 27719 element.all(by.model('color.name')).get(0).click(); 27720 27721 expect(color.getText()).toContain('red'); 27722 }); 27723 </file> 27724 </example> 27725 */ 27726 'radio': radioInputType, 27727 27728 27729 /** 27730 * @ngdoc input 27731 * @name input[checkbox] 27732 * 27733 * @description 27734 * HTML checkbox. 27735 * 27736 * @param {string} ngModel Assignable angular expression to data-bind to. 27737 * @param {string=} name Property name of the form under which the control is published. 27738 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 27739 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 27740 * @param {string=} ngChange Angular expression to be executed when input changes due to user 27741 * interaction with the input element. 27742 * 27743 * @example 27744 <example name="checkbox-input-directive" module="checkboxExample"> 27745 <file name="index.html"> 27746 <script> 27747 angular.module('checkboxExample', []) 27748 .controller('ExampleController', ['$scope', function($scope) {
27749 $scope.checkboxModel = { 27750 value1 : true, 27751 value2 : 'YES' 27752 }; 27753 }]); 27754 </script> 27755 <form name="myForm" ng-controller="ExampleController"> 27756 <label>Value1: 27757 <input type="checkbox" ng-model="checkboxModel.value1"> 27758 </label><br/> 27759 <label>Value2: 27760 <input type="checkbox" ng-model="checkboxModel.value2" 27761 ng-true-value="'YES'" ng-false-value="'NO'"> 27762 </label><br/> 27763 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 27764 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 27765 </form> 27766 </file> 27767 <file name="protractor.js" type="protractor"> 27768 it('should change state', function() { 27769 var value1 = element(by.binding('checkboxModel.value1')); 27770 var value2 = element(by.binding('checkboxModel.value2')); 27771 27772 expect(value1.getText()).toContain('true'); 27773 expect(value2.getText()).toContain('YES'); 27774 27775 element(by.model('checkboxModel.value1')).click(); 27776 element(by.model('checkboxModel.value2')).click(); 27777 27778 expect(value1.getText()).toContain('false'); 27779 expect(value2.getText()).toContain('NO'); 27780 }); 27781 </file> 27782 </example> 27783 */ 27784 'checkbox': checkboxInputType, 27785 27786 'hidden': noop, 27787 'button': noop, 27788 'submit': noop, 27789 'reset': noop, 27790 'file': noop 27791}; 27792 27793function stringBasedInputType(ctrl) { 27794 ctrl.$formatters.push(function(value) { 27795 return ctrl.$isEmpty(value) ? value : value.toString(); 27796 }); 27797} 27798 27799function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 27800 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 27801 stringBasedInputType(ctrl); 27802} 27803 27804function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 27805 var type = lowercase(element[0].type); 27806 27807 // In composition mode, users are still inputing intermediate text buffer, 27808 // hold the listener until composition is done. 27809 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 27810 if (!$sniffer.android) { 27811 var composing = false; 27812 27813 element.on('compositionstart', function(data) { 27814 composing = true; 27815 }); 27816 27817 element.on('compositionend', function() { 27818 composing = false; 27819 listener(); 27820 }); 27821 } 27822 27823 var timeout; 27824 27825 var listener = function(ev) { 27826 if (timeout) { 27827 $browser.defer.cancel(timeout); 27828 timeout = null; 27829 } 27830 if (composing) return; 27831 var value = element.val(), 27832 event = ev && ev.type; 27833 27834 // By default we will trim the value 27835 // If the attribute ng-trim exists we will avoid trimming 27836 // If input type is 'password', the value is never trimmed 27837 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 27838 value = trim(value); 27839 } 27840 27841 // If a control is suffering from bad input (due to native validators), browsers discard its 27842 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 27843 // control's value is the same empty value twice in a row. 27844 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 27845 ctrl.$setViewValue(value, event); 27846 } 27847 }; 27848 27849 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 27850 // input event on backspace, delete or cut 27851 if ($sniffer.hasEvent('input')) { 27852 element.on('input', listener); 27853 } else { 27854 var deferListener = function(ev, input, origValue) { 27855 if (!timeout) { 27856 timeout = $browser.defer(function() { 27857 timeout = null; 27858 if (!input || input.value !== origValue) { 27859 listener(ev); 27860 } 27861 }); 27862 } 27863 }; 27864 27865 element.on('keydown', function(event) { 27866 var key = event.keyCode; 27867 27868 // ignore 27869 // command modifiers arrows 27870 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 27871 27872 deferListener(event, this, this.value); 27873 }); 27874 27875 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 27876 if ($sniffer.hasEvent('paste')) { 27877 element.on('paste cut', deferListener); 27878 } 27879 } 27880 27881 // if user paste into input using mouse on older browser 27882 // or form autocomplete on newer browser, we need "change" event to catch it 27883 element.on('change', listener); 27884 27885 // Some native input types (date-family) have the ability to change validity without 27886 // firing any input/change events. 27887 // For these event types, when native validators are present and the browser supports the type, 27888 // check for validity changes on various DOM events. 27889 if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { 27890 element.on(PARTIAL_VALIDATION_EVENTS, function(ev) { 27891 if (!timeout) {
27892 var validity = this[VALIDITY_STATE_PROPERTY]; 27893 var origBadInput = validity.badInput; 27894 var origTypeMismatch = validity.typeMismatch; 27895 timeout = $browser.defer(function() { 27896 timeout = null; 27897 if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { 27898 listener(ev); 27899 } 27900 }); 27901 } 27902 }); 27903 } 27904 27905 ctrl.$render = function() { 27906 // Workaround for Firefox validation #12102. 27907 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 27908 if (element.val() !== value) { 27909 element.val(value); 27910 } 27911 }; 27912} 27913 27914function weekParser(isoWeek, existingDate) { 27915 if (isDate(isoWeek)) { 27916 return isoWeek; 27917 } 27918 27919 if (isString(isoWeek)) { 27920 WEEK_REGEXP.lastIndex = 0; 27921 var parts = WEEK_REGEXP.exec(isoWeek); 27922 if (parts) { 27923 var year = +parts[1], 27924 week = +parts[2], 27925 hours = 0, 27926 minutes = 0, 27927 seconds = 0, 27928 milliseconds = 0, 27929 firstThurs = getFirstThursdayOfYear(year), 27930 addDays = (week - 1) * 7; 27931 27932 if (existingDate) { 27933 hours = existingDate.getHours(); 27934 minutes = existingDate.getMinutes(); 27935 seconds = existingDate.getSeconds(); 27936 milliseconds = existingDate.getMilliseconds(); 27937 } 27938 27939 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 27940 } 27941 } 27942 27943 return NaN; 27944} 27945 27946function createDateParser(regexp, mapping) { 27947 return function(iso, date) { 27948 var parts, map; 27949 27950 if (isDate(iso)) { 27951 return iso; 27952 } 27953 27954 if (isString(iso)) { 27955 // When a date is JSON'ified to wraps itself inside of an extra 27956 // set of double quotes. This makes the date parsing code unable 27957 // to match the date string and parse it as a date. 27958 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 27959 iso = iso.substring(1, iso.length - 1); 27960 } 27961 if (ISO_DATE_REGEXP.test(iso)) { 27962 return new Date(iso); 27963 } 27964 regexp.lastIndex = 0; 27965 parts = regexp.exec(iso); 27966 27967 if (parts) { 27968 parts.shift(); 27969 if (date) { 27970 map = { 27971 yyyy: date.getFullYear(), 27972 MM: date.getMonth() + 1, 27973 dd: date.getDate(), 27974 HH: date.getHours(), 27975 mm: date.getMinutes(), 27976 ss: date.getSeconds(), 27977 sss: date.getMilliseconds() / 1000 27978 }; 27979 } else { 27980 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 27981 } 27982 27983 forEach(parts, function(part, index) { 27984 if (index < mapping.length) { 27985 map[mapping[index]] = +part; 27986 } 27987 }); 27988 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 27989 } 27990 } 27991 27992 return NaN; 27993 }; 27994} 27995 27996function createDateInputType(type, regexp, parseDate, format) { 27997 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 27998 badInputChecker(scope, element, attr, ctrl); 27999 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 28000 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 28001 var previousDate; 28002 28003 ctrl.$$parserName = type; 28004 ctrl.$parsers.push(function(value) { 28005 if (ctrl.$isEmpty(value)) return null; 28006 if (regexp.test(value)) { 28007 // Note: We cannot read ctrl.$modelValue, as there might be a different 28008 // parser/formatter in the processing chain so that the model 28009 // contains some different data format! 28010 var parsedDate = parseDate(value, previousDate); 28011 if (timezone) { 28012 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 28013 } 28014 return parsedDate; 28015 } 28016 return undefined; 28017 }); 28018 28019 ctrl.$formatters.push(function(value) { 28020 if (value && !isDate(value)) { 28021 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 28022 } 28023 if (isValidDate(value)) { 28024 previousDate = value; 28025 if (previousDate && timezone) { 28026 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 28027 } 28028 return $filter('date')(value, format, timezone); 28029 } else { 28030 previousDate = null; 28031 return ''; 28032 } 28033 }); 28034 28035 if (isDefined(attr.min) || attr.ngMin) { 28036 var minVal; 28037 ctrl.$validators.min = function(value) { 28038 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 28039 }; 28040 attr.$observe('min', function(val) { 28041 minVal = parseObservedDateValue(val); 28042 ctrl.$validate(); 28043 }); 28044 } 28045 28046 if (isDefined(attr.max) || attr.ngMax) { 28047 var maxVal; 28048 ctrl.$validators.max = function(value) { 28049 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 28050 }; 28051 attr.$observe('max', function(val) { 28052 maxVal = parseObservedDateValue(val); 28053 ctrl.$validate(); 28054 }); 28055 } 28056 28057 function isValidDate(value) { 28058 // Invalid Date: getTime() returns NaN 28059 return value && !(value.getTime && value.getTime() !== value.getTime()); 28060 } 28061 28062 function parseObservedDateValue(val) { 28063 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; 28064 } 28065 }; 28066} 28067 28068function badInputChecker(scope, element, attr, ctrl) { 28069 var node = element[0]; 28070 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 28071 if (nativeValidation) { 28072 ctrl.$parsers.push(function(value) {
28073 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 28074 return validity.badInput || validity.typeMismatch ? undefined : value; 28075 }); 28076 } 28077} 28078 28079function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 28080 badInputChecker(scope, element, attr, ctrl); 28081 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 28082 28083 ctrl.$$parserName = 'number'; 28084 ctrl.$parsers.push(function(value) { 28085 if (ctrl.$isEmpty(value)) return null; 28086 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 28087 return undefined; 28088 }); 28089 28090 ctrl.$formatters.push(function(value) { 28091 if (!ctrl.$isEmpty(value)) { 28092 if (!isNumber(value)) { 28093 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 28094 } 28095 value = value.toString(); 28096 } 28097 return value; 28098 }); 28099 28100 if (isDefined(attr.min) || attr.ngMin) { 28101 var minVal; 28102 ctrl.$validators.min = function(value) { 28103 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 28104 }; 28105 28106 attr.$observe('min', function(val) { 28107 if (isDefined(val) && !isNumber(val)) { 28108 val = parseFloat(val, 10); 28109 } 28110 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 28111 // TODO(matsko): implement validateLater to reduce number of validations 28112 ctrl.$validate(); 28113 }); 28114 } 28115 28116 if (isDefined(attr.max) || attr.ngMax) { 28117 var maxVal; 28118 ctrl.$validators.max = function(value) { 28119 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 28120 }; 28121 28122 attr.$observe('max', function(val) { 28123 if (isDefined(val) && !isNumber(val)) { 28124 val = parseFloat(val, 10); 28125 } 28126 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 28127 // TODO(matsko): implement validateLater to reduce number of validations 28128 ctrl.$validate(); 28129 }); 28130 } 28131} 28132 28133function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 28134 // Note: no badInputChecker here by purpose as `url` is only a validation 28135 // in browsers, i.e. we can always read out input.value even if it is not valid! 28136 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 28137 stringBasedInputType(ctrl); 28138 28139 ctrl.$$parserName = 'url'; 28140 ctrl.$validators.url = function(modelValue, viewValue) { 28141 var value = modelValue || viewValue; 28142 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 28143 }; 28144} 28145 28146function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 28147 // Note: no badInputChecker here by purpose as `url` is only a validation 28148 // in browsers, i.e. we can always read out input.value even if it is not valid! 28149 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 28150 stringBasedInputType(ctrl); 28151 28152 ctrl.$$parserName = 'email'; 28153 ctrl.$validators.email = function(modelValue, viewValue) { 28154 var value = modelValue || viewValue; 28155 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 28156 }; 28157} 28158 28159function radioInputType(scope, element, attr, ctrl) { 28160 // make the name unique, if not defined 28161 if (isUndefined(attr.name)) { 28162 element.attr('name', nextUid()); 28163 } 28164 28165 var listener = function(ev) { 28166 if (element[0].checked) { 28167 ctrl.$setViewValue(attr.value, ev && ev.type); 28168 } 28169 }; 28170 28171 element.on('click', listener); 28172 28173 ctrl.$render = function() { 28174 var value = attr.value; 28175 element[0].checked = (value == ctrl.$viewValue); 28176 }; 28177 28178 attr.$observe('value', ctrl.$render); 28179} 28180 28181function parseConstantExpr($parse, context, name, expression, fallback) { 28182 var parseFn; 28183 if (isDefined(expression)) { 28184 parseFn = $parse(expression); 28185 if (!parseFn.constant) { 28186 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 28187 '`{1}`.', name, expression); 28188 } 28189 return parseFn(context); 28190 } 28191 return fallback; 28192} 28193 28194function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 28195 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 28196 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 28197 28198 var listener = function(ev) { 28199 ctrl.$setViewValue(element[0].checked, ev && ev.type); 28200 }; 28201 28202 element.on('click', listener); 28203 28204 ctrl.$render = function() { 28205 element[0].checked = ctrl.$viewValue; 28206 }; 28207
28208 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 28209 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 28210 // it to a boolean. 28211 ctrl.$isEmpty = function(value) { 28212 return value === false; 28213 }; 28214 28215 ctrl.$formatters.push(function(value) { 28216 return equals(value, trueValue); 28217 }); 28218 28219 ctrl.$parsers.push(function(value) { 28220 return value ? trueValue : falseValue; 28221 }); 28222} 28223 28224 28225/** 28226 * @ngdoc directive 28227 * @name textarea 28228 * @restrict E 28229 * 28230 * @description 28231 * HTML textarea element control with angular data-binding. The data-binding and validation 28232 * properties of this element are exactly the same as those of the 28233 * {@link ng.directive:input input element}. 28234 * 28235 * @param {string} ngModel Assignable angular expression to data-bind to. 28236 * @param {string=} name Property name of the form under which the control is published. 28237 * @param {string=} required Sets `required` validation error key if the value is not entered. 28238 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 28239 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 28240 * `required` when you want to data-bind to the `required` attribute. 28241 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 28242 * minlength. 28243 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 28244 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 28245 * length. 28246 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 28247 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 28248 * If the expression evaluates to a RegExp object, then this is used directly. 28249 * If the expression evaluates to a string, then it will be converted to a RegExp 28250 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 28251 * `new RegExp('^abc$')`.<br /> 28252 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 28253 * start at the index of the last search's match, thus not taking the whole input value into 28254 * account. 28255 * @param {string=} ngChange Angular expression to be executed when input changes due to user 28256 * interaction with the input element. 28257 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 28258 */ 28259 28260 28261/** 28262 * @ngdoc directive 28263 * @name input 28264 * @restrict E 28265 * 28266 * @description 28267 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 28268 * input state control, and validation. 28269 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 28270 * 28271 * <div class="alert alert-warning"> 28272 * **Note:** Not every feature offered is available for all input types. 28273 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 28274 * </div> 28275 * 28276 * @param {string} ngModel Assignable angular expression to data-bind to. 28277 * @param {string=} name Property name of the form under which the control is published. 28278 * @param {string=} required Sets `required` validation error key if the value is not entered. 28279 * @param {boolean=} ngRequired Sets `required` attribute if set to true 28280 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 28281 * minlength. 28282 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 28283 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 28284 * length. 28285 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 28286 * value does not match a RegExp found by evaluating the Angular expression given in the attribute value. 28287 * If the expression evaluates to a RegExp object, then this is used directly. 28288 * If the expression evaluates to a string, then it will be converted to a RegExp 28289 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 28290 * `new RegExp('^abc$')`.<br /> 28291 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 28292 * start at the index of the last search's match, thus not taking the whole input value into 28293 * account. 28294 * @param {string=} ngChange Angular expression to be executed when input changes due to user 28295 * interaction with the input element. 28296 * @param {boolean=} [ngTrim=true] If set to false Angular
vendor: 4,842 bytes, lines 28296-28414
28296 will not automatically trim the input. 28297 * This parameter is ignored for input[type=password] controls, which will never trim the 28298 * input. 28299 * 28300 * @example 28301 <example name="input-directive" module="inputExample"> 28302 <file name="index.html"> 28303 <script> 28304 angular.module('inputExample', []) 28305 .controller('ExampleController', ['$scope', function($scope) { 28306 $scope.user = {name: 'guest', last: 'visitor'}; 28307 }]); 28308 </script> 28309 <div ng-controller="ExampleController"> 28310 <form name="myForm"> 28311 <label> 28312 User name: 28313 <input type="text" name="userName" ng-model="user.name" required> 28314 </label> 28315 <div role="alert"> 28316 <span class="error" ng-show="myForm.userName.$error.required"> 28317 Required!</span> 28318 </div> 28319 <label> 28320 Last name: 28321 <input type="text" name="lastName" ng-model="user.last" 28322 ng-minlength="3" ng-maxlength="10"> 28323 </label> 28324 <div role="alert"> 28325 <span class="error" ng-show="myForm.lastName.$error.minlength"> 28326 Too short!</span> 28327 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 28328 Too long!</span> 28329 </div> 28330 </form> 28331 <hr> 28332 <tt>user = {{user}}</tt><br/> 28333 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 28334 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 28335 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 28336 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 28337 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 28338 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 28339 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 28340 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 28341 </div> 28342 </file> 28343 <file name="protractor.js" type="protractor"> 28344 var user = element(by.exactBinding('user')); 28345 var userNameValid = element(by.binding('myForm.userName.$valid')); 28346 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 28347 var lastNameError = element(by.binding('myForm.lastName.$error')); 28348 var formValid = element(by.binding('myForm.$valid')); 28349 var userNameInput = element(by.model('user.name')); 28350 var userLastInput = element(by.model('user.last')); 28351 28352 it('should initialize to model', function() { 28353 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 28354 expect(userNameValid.getText()).toContain('true'); 28355 expect(formValid.getText()).toContain('true'); 28356 }); 28357 28358 it('should be invalid if empty when required', function() { 28359 userNameInput.clear(); 28360 userNameInput.sendKeys(''); 28361 28362 expect(user.getText()).toContain('{"last":"visitor"}'); 28363 expect(userNameValid.getText()).toContain('false'); 28364 expect(formValid.getText()).toContain('false'); 28365 }); 28366 28367 it('should be valid if empty when min length is set', function() { 28368 userLastInput.clear(); 28369 userLastInput.sendKeys(''); 28370 28371 expect(user.getText()).toContain('{"name":"guest","last":""}'); 28372 expect(lastNameValid.getText()).toContain('true'); 28373 expect(formValid.getText()).toContain('true'); 28374 }); 28375 28376 it('should be invalid if less than required min length', function() { 28377 userLastInput.clear(); 28378 userLastInput.sendKeys('xx'); 28379 28380 expect(user.getText()).toContain('{"name":"guest"}'); 28381 expect(lastNameValid.getText()).toContain('false'); 28382 expect(lastNameError.getText()).toContain('minlength'); 28383 expect(formValid.getText()).toContain('false'); 28384 }); 28385 28386 it('should be invalid if longer than max length', function() { 28387 userLastInput.clear(); 28388 userLastInput.sendKeys('some ridiculously long name'); 28389 28390 expect(user.getText()).toContain('{"name":"guest"}'); 28391 expect(lastNameValid.getText()).toContain('false'); 28392 expect(lastNameError.getText()).toContain('maxlength'); 28393 expect(formValid.getText()).toContain('false'); 28394 }); 28395 </file> 28396 </example> 28397 */ 28398var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 28399 function($browser, $sniffer, $filter, $parse) { 28400 return { 28401 restrict: 'E', 28402 require: ['?ngModel'], 28403 link: { 28404 pre: function(scope, element, attr, ctrls) { 28405 if (ctrls[0]) { 28406 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 28407 $browser, $filter, $parse); 28408 } 28409 } 28410 } 28411 }; 28412}]; 28413 28414
28415 28416var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 28417/** 28418 * @ngdoc directive 28419 * @name ngValue 28420 * 28421 * @description 28422 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 28423 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 28424 * the bound value. 28425 * 28426 * `ngValue` is useful when dynamically generating lists of radio buttons using 28427 * {@link ngRepeat `ngRepeat`}, as shown below. 28428 * 28429 * Likewise, `ngValue` can be used to generate `<option>` elements for 28430 * the {@link select `select`} element. In that case however, only strings are supported 28431 * for the `value `attribute, so the resulting `ngModel` will always be a string. 28432 * Support for `select` models with non-string values is available via `ngOptions`. 28433 * 28434 * @element input 28435 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 28436 * of the `input` element 28437 * 28438 * @example 28439 <example name="ngValue-directive" module="valueExample"> 28440 <file name="index.html"> 28441 <script> 28442 angular.module('valueExample', []) 28443 .controller('ExampleController', ['$scope', function($scope) { 28444 $scope.names = ['pizza', 'unicorns', 'robots']; 28445 $scope.my = { favorite: 'unicorns' }; 28446 }]); 28447 </script>
28448 <form ng-controller="ExampleController"> 28449 <h2>Which is your favorite?</h2> 28450 <label ng-repeat="name in names" for="{{name}}"> 28451 {{name}} 28452 <input type="radio" 28453 ng-model="my.favorite" 28454 ng-value="name" 28455 id="{{name}}" 28456 name="favorite"> 28457 </label> 28458 <div>You chose {{my.favorite}}</div> 28459 </form> 28460 </file> 28461 <file name="protractor.js" type="protractor"> 28462 var favorite = element(by.binding('my.favorite')); 28463 28464 it('should initialize to model', function() { 28465 expect(favorite.getText()).toContain('unicorns'); 28466 }); 28467 it('should bind the values to the inputs', function() { 28468 element.all(by.model('my.favorite')).get(0).click(); 28469 expect(favorite.getText()).toContain('pizza'); 28470 }); 28471 </file> 28472 </example> 28473 */ 28474var ngValueDirective = function() { 28475 return { 28476 restrict: 'A', 28477 priority: 100, 28478 compile: function(tpl, tplAttr) { 28479 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 28480 return function ngValueConstantLink(scope, elm, attr) { 28481 attr.$set('value', scope.$eval(attr.ngValue)); 28482 }; 28483 } else { 28484 return function ngValueLink(scope, elm, attr) { 28485 scope.$watch(attr.ngValue, function valueWatchAction(value) { 28486 attr.$set('value', value); 28487 }); 28488 }; 28489 } 28490 } 28491 }; 28492}; 28493 28494/** 28495 * @ngdoc directive 28496 * @name ngBind 28497 * @restrict AC 28498 * 28499 * @description 28500 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 28501 * with the value of a given expression, and to update the text content when the value of that 28502 * expression changes. 28503 * 28504 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 28505 * `{{ expression }}` which is similar but less verbose. 28506 * 28507 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 28508 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 28509 * element attribute, it makes the bindings invisible to the user while the page is loading. 28510 * 28511 * An alternative solution to this problem would be using the 28512 * {@link ng.directive:ngCloak ngCloak} directive. 28513 * 28514 * 28515 * @element ANY 28516 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 28517 * 28518 * @example 28519 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 28520 <example module="bindExample"> 28521 <file name="index.html"> 28522 <script> 28523 angular.module('bindExample', []) 28524 .controller('ExampleController', ['$scope', function($scope) { 28525 $scope.name = 'Whirled'; 28526 }]); 28527 </script>
28528 <div ng-controller="ExampleController"> 28529 <label>Enter name: <input type="text" ng-model="name"></label><br> 28530 Hello <span ng-bind="name"></span>! 28531 </div> 28532 </file> 28533 <file name="protractor.js" type="protractor"> 28534 it('should check ng-bind', function() { 28535 var nameInput = element(by.model('name')); 28536 28537 expect(element(by.binding('name')).getText()).toBe('Whirled'); 28538 nameInput.clear(); 28539 nameInput.sendKeys('world'); 28540 expect(element(by.binding('name')).getText()).toBe('world'); 28541 }); 28542 </file> 28543 </example> 28544 */ 28545var ngBindDirective = ['$compile', function($compile) { 28546 return { 28547 restrict: 'AC', 28548 compile: function ngBindCompile(templateElement) { 28549 $compile.$$addBindingClass(templateElement); 28550 return function ngBindLink(scope, element, attr) { 28551 $compile.$$addBindingInfo(element, attr.ngBind); 28552 element = element[0]; 28553 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 28554 element.textContent = isUndefined(value) ? '' : value; 28555 }); 28556 }; 28557 } 28558 }; 28559}]; 28560 28561 28562/** 28563 * @ngdoc directive 28564 * @name ngBindTemplate 28565 * 28566 * @description 28567 * The `ngBindTemplate` directive specifies that the element 28568 * text content should be replaced with the interpolation of the template 28569 * in the `ngBindTemplate` attribute. 28570 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 28571 * expressions. This directive is needed since some HTML elements 28572 * (such as TITLE and OPTION) cannot contain SPAN elements. 28573 * 28574 * @element ANY 28575 * @param {string} ngBindTemplate template of form 28576 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 28577 * 28578 * @example 28579 * Try it here: enter text in text box and watch the greeting change. 28580 <example module="bindExample"> 28581 <file name="index.html"> 28582 <script> 28583 angular.module('bindExample', []) 28584 .controller('ExampleController', ['$scope', function($scope) { 28585 $scope.salutation = 'Hello'; 28586 $scope.name = 'World'; 28587 }]); 28588 </script>
28589 <div ng-controller="ExampleController"> 28590 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 28591 <label>Name: <input type="text" ng-model="name"></label><br> 28592 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 28593 </div> 28594 </file> 28595 <file name="protractor.js" type="protractor"> 28596 it('should check ng-bind', function() { 28597 var salutationElem = element(by.binding('salutation')); 28598 var salutationInput = element(by.model('salutation')); 28599 var nameInput = element(by.model('name')); 28600 28601 expect(salutationElem.getText()).toBe('Hello World!'); 28602 28603 salutationInput.clear(); 28604 salutationInput.sendKeys('Greetings'); 28605 nameInput.clear(); 28606 nameInput.sendKeys('user'); 28607 28608 expect(salutationElem.getText()).toBe('Greetings user!'); 28609 }); 28610 </file> 28611 </example> 28612 */ 28613var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 28614 return { 28615 compile: function ngBindTemplateCompile(templateElement) { 28616 $compile.$$addBindingClass(templateElement); 28617 return function ngBindTemplateLink(scope, element, attr) { 28618 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 28619 $compile.$$addBindingInfo(element, interpolateFn.expressions); 28620 element = element[0]; 28621 attr.$observe('ngBindTemplate', function(value) { 28622 element.textContent = isUndefined(value) ? '' : value; 28623 }); 28624 }; 28625 } 28626 }; 28627}]; 28628 28629 28630/** 28631 * @ngdoc directive 28632 * @name ngBindHtml 28633 * 28634 * @description 28635 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 28636 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 28637 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 28638 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 28639 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 28640 * 28641 * You may also bypass sanitization for values you know are safe. To do so, bind to 28642 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 28643 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 28644 * 28645 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 28646 * will have an exception (instead of an exploit.) 28647 * 28648 * @element ANY 28649 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 28650 * 28651 * @example 28652 28653 <example module="bindHtmlExample" deps="angular-sanitize.js"> 28654 <file name="index.html">
28655 <div ng-controller="ExampleController"> 28656 <p ng-bind-html="myHTML"></p> 28657 </div> 28658 </file> 28659 28660 <file name="script.js"> 28661 angular.module('bindHtmlExample', ['ngSanitize']) 28662 .controller('ExampleController', ['$scope', function($scope) { 28663 $scope.myHTML = 28664 'I am an <code>HTML</code>string with ' + 28665 '<a href="#">links!</a> and other <em>stuff</em>'; 28666 }]); 28667 </file> 28668 28669 <file name="protractor.js" type="protractor"> 28670 it('should check ng-bind-html', function() { 28671 expect(element(by.binding('myHTML')).getText()).toBe( 28672 'I am an HTMLstring with links! and other stuff'); 28673 }); 28674 </file> 28675 </example> 28676 */ 28677var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 28678 return { 28679 restrict: 'A', 28680 compile: function ngBindHtmlCompile(tElement, tAttrs) { 28681 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 28682 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 28683 return (value || '').toString(); 28684 }); 28685 $compile.$$addBindingClass(tElement); 28686 28687 return function ngBindHtmlLink(scope, element, attr) { 28688 $compile.$$addBindingInfo(element, attr.ngBindHtml); 28689 28690 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 28691 // we re-evaluate the expr because we want a TrustedValueHolderType 28692 // for $sce, not a string 28693 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 28694 }); 28695 }; 28696 } 28697 }; 28698}]; 28699 28700/** 28701 * @ngdoc directive 28702 * @name ngChange 28703 * 28704 * @description 28705 * Evaluate the given expression when the user changes the input. 28706 * The expression is evaluated immediately, unlike the JavaScript onchange event 28707 * which only triggers at the end of a change (usually, when the user leaves the 28708 * form element or presses the return key). 28709 * 28710 * The `ngChange` expression is only evaluated when a change in the input value causes 28711 * a new value to be committed to the model. 28712 * 28713 * It will not be evaluated: 28714 * * if the value returned from the `$parsers` transformation pipeline has not changed 28715 * * if the input has continued to be invalid since the model will stay `null` 28716 * * if the model is changed programmatically and not by a change to the input value 28717 * 28718 * 28719 * Note, this directive requires `ngModel` to be present. 28720 * 28721 * @element input 28722 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 28723 * in input value. 28724 * 28725 * @example 28726 * <example name="ngChange-directive" module="changeExample"> 28727 * <file name="index.html"> 28728 * <script> 28729 * angular.module('changeExample', []) 28730 * .controller('ExampleController', ['$scope', function($scope) { 28731 * $scope.counter = 0; 28732 * $scope.change = function() { 28733 * $scope.counter++; 28734 * }; 28735 * }]); 28736 * </script>
28737 * <div ng-controller="ExampleController"> 28738 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 28739 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 28740 * <label for="ng-change-example2">Confirmed</label><br /> 28741 * <tt>debug = {{confirmed}}</tt><br/> 28742 * <tt>counter = {{counter}}</tt><br/> 28743 * </div> 28744 * </file> 28745 * <file name="protractor.js" type="protractor"> 28746 * var counter = element(by.binding('counter')); 28747 * var debug = element(by.binding('confirmed')); 28748 * 28749 * it('should evaluate the expression if changing from view', function() { 28750 * expect(counter.getText()).toContain('0'); 28751 * 28752 * element(by.id('ng-change-example1')).click(); 28753 * 28754 * expect(counter.getText()).toContain('1'); 28755 * expect(debug.getText()).toContain('true'); 28756 * }); 28757 * 28758 * it('should not evaluate the expression if changing from model', function() { 28759 * element(by.id('ng-change-example2')).click(); 28760 28761 * expect(counter.getText()).toContain('0'); 28762 * expect(debug.getText()).toContain('true'); 28763 * }); 28764 * </file> 28765 * </example> 28766 */ 28767var ngChangeDirective = valueFn({ 28768 restrict: 'A', 28769 require: 'ngModel', 28770 link: function(scope, element, attr, ctrl) { 28771 ctrl.$viewChangeListeners.push(function() { 28772 scope.$eval(attr.ngChange); 28773 }); 28774 } 28775}); 28776 28777function classDirective(name, selector) { 28778 name = 'ngClass' + name; 28779 return ['$animate', function($animate) { 28780 return { 28781 restrict: 'AC', 28782 link: function(scope, element, attr) { 28783 var oldVal; 28784 28785 scope.$watch(attr[name], ngClassWatchAction, true); 28786 28787 attr.$observe('class', function(value) { 28788 ngClassWatchAction(scope.$eval(attr[name])); 28789 }); 28790 28791 28792 if (name !== 'ngClass') { 28793 scope.$watch('$index', function($index, old$index) { 28794 // jshint bitwise: false 28795 var mod = $index & 1; 28796 if (mod !== (old$index & 1)) { 28797 var classes = arrayClasses(scope.$eval(attr[name])); 28798 mod === selector ? 28799 addClasses(classes) : 28800 removeClasses(classes); 28801 } 28802 }); 28803 } 28804 28805 function addClasses(classes) { 28806 var newClasses = digestClassCounts(classes, 1); 28807 attr.$addClass(newClasses); 28808 } 28809 28810 function removeClasses(classes) { 28811 var newClasses = digestClassCounts(classes, -1); 28812 attr.$removeClass(newClasses); 28813 } 28814 28815 function digestClassCounts(classes, count) { 28816 // Use createMap() to prevent class assumptions involving property 28817 // names in Object.prototype 28818 var classCounts = element.data('$classCounts') || createMap(); 28819 var classesToUpdate = []; 28820 forEach(classes, function(className) { 28821 if (count > 0 || classCounts[className]) { 28822 classCounts[className] = (classCounts[className] || 0) + count; 28823 if (classCounts[className] === +(count > 0)) { 28824 classesToUpdate.push(className); 28825 } 28826 } 28827 }); 28828 element.data('$classCounts', classCounts); 28829 return classesToUpdate.join(' '); 28830 } 28831 28832 function updateClasses(oldClasses, newClasses) { 28833 var toAdd = arrayDifference(newClasses, oldClasses); 28834 var toRemove = arrayDifference(oldClasses, newClasses); 28835 toAdd = digestClassCounts(toAdd, 1); 28836 toRemove = digestClassCounts(toRemove, -1); 28837 if (toAdd && toAdd.length) { 28838 $animate.addClass(element, toAdd); 28839 } 28840 if (toRemove && toRemove.length) { 28841 $animate.removeClass(element, toRemove); 28842 } 28843 } 28844 28845 function ngClassWatchAction(newVal) { 28846 if (selector === true || scope.$index % 2 === selector) { 28847 var newClasses = arrayClasses(newVal || []); 28848 if (!oldVal) { 28849 addClasses(newClasses); 28850 } else if (!equals(newVal,oldVal)) { 28851 var oldClasses = arrayClasses(oldVal);
28852 updateClasses(oldClasses, newClasses); 28853 } 28854 } 28855 oldVal = shallowCopy(newVal); 28856 } 28857 } 28858 }; 28859 28860 function arrayDifference(tokens1, tokens2) { 28861 var values = []; 28862 28863 outer: 28864 for (var i = 0; i < tokens1.length; i++) { 28865 var token = tokens1[i]; 28866 for (var j = 0; j < tokens2.length; j++) { 28867 if (token == tokens2[j]) continue outer; 28868 } 28869 values.push(token); 28870 } 28871 return values; 28872 } 28873 28874 function arrayClasses(classVal) { 28875 var classes = []; 28876 if (isArray(classVal)) { 28877 forEach(classVal, function(v) { 28878 classes = classes.concat(arrayClasses(v)); 28879 }); 28880 return classes; 28881 } else if (isString(classVal)) { 28882 return classVal.split(' '); 28883 } else if (isObject(classVal)) { 28884 forEach(classVal, function(v, k) { 28885 if (v) { 28886 classes = classes.concat(k.split(' ')); 28887 } 28888 }); 28889 return classes; 28890 } 28891 return classVal; 28892 } 28893 }]; 28894} 28895 28896/** 28897 * @ngdoc directive 28898 * @name ngClass 28899 * @restrict AC 28900 * 28901 * @description 28902 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 28903 * an expression that represents all classes to be added. 28904 * 28905 * The directive operates in three different ways, depending on which of three types the expression 28906 * evaluates to: 28907 * 28908 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 28909 * names. 28910 * 28911 * 2. If the expression evaluates to an object, then for each key-value pair of the 28912 * object with a truthy value the corresponding key is used as a class name. 28913 * 28914 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 28915 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 28916 * to give you more control over what CSS classes appear. See the code below for an example of this. 28917 * 28918 * 28919 * The directive won't add duplicate classes if a particular class was already set. 28920 * 28921 * When the expression changes, the previously added classes are removed and only then are the 28922 * new classes added. 28923 * 28924 * @animations 28925 * | Animation | Occurs | 28926 * |----------------------------------|-------------------------------------| 28927 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 28928 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 28929 * 28930 * @element ANY 28931 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 28932 * of the evaluation can be a string representing space delimited class 28933 * names, an array, or a map of class names to boolean values. In the case of a map, the 28934 * names of the properties whose values are truthy will be added as css classes to the 28935 * element. 28936 * 28937 * @example Example that demonstrates basic bindings via ngClass directive. 28938 <example> 28939 <file name="index.html"> 28940 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 28941 <label> 28942 <input type="checkbox" ng-model="deleted"> 28943 deleted (apply "strike" class) 28944 </label><br> 28945 <label> 28946 <input type="checkbox" ng-model="important"> 28947 important (apply "bold" class) 28948 </label><br> 28949 <label> 28950 <input type="checkbox" ng-model="error"> 28951 error (apply "has-error" class) 28952 </label> 28953 <hr> 28954 <p ng-class="style">Using String Syntax</p> 28955 <input type="text" ng-model="style" 28956 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 28957 <hr> 28958 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 28959 <input ng-model="style1" 28960 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 28961 <input ng-model="style2" 28962 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 28963 <input ng-model="style3" 28964 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 28965 <hr> 28966 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 28967 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 28968 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 28969 </file> 28970 <file name="style.css"> 28971 .strike { 28972 text-decoration: line-through; 28973 } 28974 .bold { 28975 font-weight: bold; 28976 } 28977 .red { 28978 color: red; 28979 } 28980 .has-error { 28981 color: red; 28982 background-color: yellow; 28983 } 28984 .orange { 28985 color: orange; 28986 } 28987 </file> 28988 <file name="protractor.js" type="protractor"> 28989 var ps = element.all(by.css('p')); 28990 28991 it('should let you toggle the class', function() { 28992 28993 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 28994 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 28995
28996 element(by.model('important')).click(); 28997 expect(ps.first().getAttribute('class')).toMatch(/bold/); 28998 28999 element(by.model('error')).click(); 29000 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 29001 }); 29002 29003 it('should let you toggle string example', function() { 29004 expect(ps.get(1).getAttribute('class')).toBe(''); 29005 element(by.model('style')).clear(); 29006 element(by.model('style')).sendKeys('red'); 29007 expect(ps.get(1).getAttribute('class')).toBe('red'); 29008 }); 29009 29010 it('array example should have 3 classes', function() { 29011 expect(ps.get(2).getAttribute('class')).toBe(''); 29012 element(by.model('style1')).sendKeys('bold'); 29013 element(by.model('style2')).sendKeys('strike'); 29014 element(by.model('style3')).sendKeys('red'); 29015 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 29016 }); 29017 29018 it('array with map example should have 2 classes', function() { 29019 expect(ps.last().getAttribute('class')).toBe(''); 29020 element(by.model('style4')).sendKeys('bold'); 29021 element(by.model('warning')).click(); 29022 expect(ps.last().getAttribute('class')).toBe('bold orange'); 29023 }); 29024 </file> 29025 </example> 29026 29027 ## Animations 29028 29029 The example below demonstrates how to perform animations using ngClass. 29030 29031 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 29032 <file name="index.html"> 29033 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 29034 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 29035 <br> 29036 <span class="base-class" ng-class="myVar">Sample Text</span> 29037 </file> 29038 <file name="style.css"> 29039 .base-class { 29040 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 29041 } 29042 29043 .base-class.my-class { 29044 color: red; 29045 font-size:3em; 29046 } 29047 </file> 29048 <file name="protractor.js" type="protractor"> 29049 it('should check ng-class', function() { 29050 expect(element(by.css('.base-class')).getAttribute('class')).not. 29051 toMatch(/my-class/); 29052 29053 element(by.id('setbtn')).click(); 29054 29055 expect(element(by.css('.base-class')).getAttribute('class')). 29056 toMatch(/my-class/); 29057 29058 element(by.id('clearbtn')).click(); 29059 29060 expect(element(by.css('.base-class')).getAttribute('class')).not. 29061 toMatch(/my-class/); 29062 }); 29063 </file> 29064 </example> 29065 29066 29067 ## ngClass and pre-existing CSS3 Transitions/Animations 29068 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 29069 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 29070 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 29071 to view the step by step details of {@link $animate#addClass $animate.addClass} and 29072 {@link $animate#removeClass $animate.removeClass}. 29073 */ 29074var ngClassDirective = classDirective('', true); 29075 29076/** 29077 * @ngdoc directive 29078 * @name ngClassOdd 29079 * @restrict AC 29080 * 29081 * @description 29082 * The `ngClassOdd` and `ngClassEven` directives work exactly as 29083 * {@link ng.directive:ngClass ngClass}, except they work in 29084 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 29085 * 29086 * This directive can be applied only within the scope of an 29087 * {@link ng.directive:ngRepeat ngRepeat}. 29088 * 29089 * @element ANY 29090 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 29091 * of the evaluation can be a string representing space delimited class names or an array. 29092 * 29093 * @example 29094 <example> 29095 <file name="index.html"> 29096 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 29097 <li ng-repeat="name in names"> 29098 <span ng-class-odd="'odd'" ng-class-even="'even'"> 29099 {{name}} 29100 </span> 29101 </li> 29102 </ol> 29103 </file> 29104 <file name="style.css"> 29105 .odd { 29106 color: red; 29107 } 29108 .even { 29109 color: blue; 29110 } 29111 </file> 29112 <file name="protractor.js" type="protractor"> 29113 it('should check ng-class-odd and ng-class-even', function() { 29114 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 29115 toMatch(/odd/); 29116 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 29117 toMatch(/even/); 29118 }); 29119 </file> 29120 </example> 29121 */ 29122var ngClassOddDirective = classDirective('Odd', 0); 29123 29124/** 29125 * @ngdoc directive 29126 * @name ngClassEven 29127 * @restrict AC 29128 * 29129 * @description 29130 * The `ngClassOdd` and `ngClassEven` directives work exactly as 29131 * {@link ng.directive:ngClass ngClass}, except they work in 29132 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 29133 * 29134 * This directive can be applied only within the scope of an 29135 * {@link ng.directive:ngRepeat ngRepeat}. 29136 * 29137 * @element ANY 29138 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 29139 * result of the evaluation can be a string representing space delimited class names or an array.
29140 * 29141 * @example 29142 <example> 29143 <file name="index.html"> 29144 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 29145 <li ng-repeat="name in names"> 29146 <span ng-class-odd="'odd'" ng-class-even="'even'"> 29147 {{name}} 29148 </span> 29149 </li> 29150 </ol> 29151 </file> 29152 <file name="style.css"> 29153 .odd { 29154 color: red; 29155 } 29156 .even { 29157 color: blue; 29158 } 29159 </file> 29160 <file name="protractor.js" type="protractor"> 29161 it('should check ng-class-odd and ng-class-even', function() { 29162 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 29163 toMatch(/odd/); 29164 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 29165 toMatch(/even/); 29166 }); 29167 </file> 29168 </example> 29169 */ 29170var ngClassEvenDirective = classDirective('Even', 1); 29171 29172/** 29173 * @ngdoc directive 29174 * @name ngCloak 29175 * @restrict AC 29176 * 29177 * @description 29178 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 29179 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 29180 * directive to avoid the undesirable flicker effect caused by the html template display. 29181 * 29182 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 29183 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 29184 * of the browser view. 29185 * 29186 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 29187 * `angular.min.js`. 29188 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 29189 * 29190 * ```css 29191 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 29192 * display: none !important; 29193 * } 29194 * ``` 29195 * 29196 * When this css rule is loaded by the browser, all html elements (including their children) that 29197 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 29198 * during the compilation of the template it deletes the `ngCloak` element attribute, making 29199 * the compiled element visible. 29200 * 29201 * For the best result, the `angular.js` script must be loaded in the head section of the html 29202 * document; alternatively, the css rule above must be included in the external stylesheet of the 29203 * application. 29204 * 29205 * @element ANY 29206 * 29207 * @example 29208 <example> 29209 <file name="index.html"> 29210 <div id="template1" ng-cloak>{{ 'hello' }}</div> 29211 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 29212 </file> 29213 <file name="protractor.js" type="protractor"> 29214 it('should remove the template directive and css class', function() { 29215 expect($('#template1').getAttribute('ng-cloak')). 29216 toBeNull(); 29217 expect($('#template2').getAttribute('ng-cloak')). 29218 toBeNull(); 29219 }); 29220 </file> 29221 </example> 29222 * 29223 */ 29224var ngCloakDirective = ngDirective({ 29225 compile: function(element, attr) { 29226 attr.$set('ngCloak', undefined); 29227 element.removeClass('ng-cloak'); 29228 } 29229}); 29230 29231/** 29232 * @ngdoc directive 29233 * @name ngController 29234 * 29235 * @description 29236 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 29237 * supports the principles behind the Model-View-Controller design pattern. 29238 * 29239 * MVC components in angular: 29240 * 29241 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 29242 * are accessed through bindings. 29243 * * View â The template (HTML with data bindings) that is rendered into the View. 29244 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 29245 * logic behind the application to decorate the scope with functions and values 29246 * 29247 * Note that you can also attach controllers to the DOM by declaring it in a route definition 29248 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 29249 * again using `ng-controller` in the template itself. This will cause the controller to be attached 29250 * and executed twice. 29251 * 29252 * @element ANY 29253 * @scope 29254 * @priority 500 29255 * @param {expression} ngController Name of a constructor function registered with the current 29256 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 29257 * that on the current scope evaluates to a constructor function. 29258 * 29259 * The controller instance can be published into a scope property by specifying 29260 * `ng-controller="as propertyName"`. 29261 * 29262 * If the current `$controllerProvider` is configured to use globals (via 29263 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 29264 * also be the name of a globally accessible constructor function (not recommended). 29265 * 29266 * @example 29267 * Here is a simple form for editing user contact information. Adding, remov
29267ing, clearing, and 29268 * greeting are methods declared on the controller (see source tab). These methods can 29269 * easily be called from the angular markup. Any changes to the data are automatically reflected 29270 * in the View without the need for a manual update. 29271 * 29272 * Two different declaration styles are included below: 29273 * 29274 * * one binds methods and properties directly onto the controller using `this`: 29275 * `ng-controller="SettingsController1 as settings"` 29276 * * one injects `$scope` into the controller: 29277 * `ng-controller="SettingsController2"` 29278 * 29279 * The second option is more common in the Angular community, and is generally used in boilerplates 29280 * and in this guide. However, there are advantages to binding properties directly to the controller 29281 * and avoiding scope. 29282 * 29283 * * Using `controller as` makes it obvious which controller you are accessing in the template when 29284 * multiple controllers apply to an element. 29285 * * If you are writing your controllers as classes you have easier access to the properties and 29286 * methods, which will appear on the scope, from inside the controller code. 29287 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 29288 * inheritance masking primitives. 29289 * 29290 * This example demonstrates the `controller as` syntax. 29291 * 29292 * <example name="ngControllerAs" module="controllerAsExample"> 29293 * <file name="index.html"> 29294 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 29295 * <label>Name: <input type="text" ng-model="settings.name"/></label> 29296 * <button ng-click="settings.greet()">greet</button><br/> 29297 * Contact: 29298 * <ul> 29299 * <li ng-repeat="contact in settings.contacts"> 29300 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 29301 * <option>phone</option> 29302 * <option>email</option> 29303 * </select> 29304 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 29305 * <button ng-click="settings.clearContact(contact)">clear</button> 29306 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 29307 * </li> 29308 * <li><button ng-click="settings.addContact()">add</button></li> 29309 * </ul> 29310 * </div> 29311 * </file> 29312 * <file name="app.js"> 29313 * angular.module('controllerAsExample', []) 29314 * .controller('SettingsController1', SettingsController1); 29315 * 29316 * function SettingsController1() { 29317 * this.name = "John Smith"; 29318 * this.contacts = [ 29319 * {type: 'phone', value: '408 555 1212'}, 29320 * {type: 'email', value: '[email protected]'} ]; 29321 * } 29322 * 29323 * SettingsController1.prototype.greet = function() { 29324 * alert(this.name); 29325 * }; 29326 * 29327 * SettingsController1.prototype.addContact = function() { 29328 * this.contacts.push({type: 'email', value: '[email protected]'}); 29329 * }; 29330 * 29331 * SettingsController1.prototype.removeContact = function(contactToRemove) { 29332 * var index = this.contacts.indexOf(contactToRemove); 29333 * this.contacts.splice(index, 1); 29334 * }; 29335 * 29336 * SettingsController1.prototype.clearContact = function(contact) { 29337 * contact.type = 'phone'; 29338 * contact.value = ''; 29339 * }; 29340 * </file> 29341 * <file name="protractor.js" type="protractor"> 29342 * it('should check controller as', function() { 29343 * var container = element(by.id('ctrl-as-exmpl')); 29344 * expect(container.element(by.model('settings.name')) 29345 * .getAttribute('value')).toBe('John Smith'); 29346 * 29347 * var firstRepeat = 29348 * container.element(by.repeater('contact in settings.contacts').row(0)); 29349 * var secondRepeat = 29350 * container.element(by.repeater('contact in settings.contacts').row(1)); 29351 * 29352 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 29353 * .toBe('408 555 1212'); 29354 * 29355 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 29356 * .toBe('[email protected]'); 29357 * 29358 * firstRepeat.element(by.buttonText('clear')).click(); 29359 * 29360 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 29361 * .toBe(''); 29362 * 29363 * container.element(by.buttonText('add')).click(); 29364 * 29365 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 29366 * .element(by.model('contact.value')) 29367 * .getAttribute('value')) 29368 * .toBe('[email protected]'); 29369 * }); 29370 * </file> 29371 * </example> 29372 * 29373 * This example demonstrates the "attach to `$scope`" style of controller. 29374 * 29375 * <example name="ngController" module="controllerExample"> 29376 * <file name="index.html"> 29377 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 29378 * <label>Name: <input type="text" ng-model="name"/></label> 29379 * <button ng-click="greet()">greet</button><br/> 29380 * Contact: 29381 * <ul> 29382 * <li ng-repeat="contact in contacts"> 29383 * <select ng-model="contact.type" id="select_{{$index}}"> 29384 * <option>phone</option> 29385 * <option>email</option> 29386 * </select> 29387 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 29388 * <button ng-click="clearContact(contact)">clear</button> 29389 * <button ng-click="removeContact(contact)">X</button> 29390 * </li> 29391 * <li>[ <button ng-click="addContact()">add</button> ]</li> 29392 * </ul> 29393 * </div> 29394 * </file> 29395 * <file name="app.js">
29396 * angular.module('controllerExample', []) 29397 * .controller('SettingsController2', ['$scope', SettingsController2]); 29398 * 29399 * function SettingsController2($scope) { 29400 * $scope.name = "John Smith"; 29401 * $scope.contacts = [ 29402 * {type:'phone', value:'408 555 1212'}, 29403 * {type:'email', value:'[email protected]'} ]; 29404 * 29405 * $scope.greet = function() { 29406 * alert($scope.name); 29407 * }; 29408 * 29409 * $scope.addContact = function() { 29410 * $scope.contacts.push({type:'email', value:'[email protected]'}); 29411 * }; 29412 * 29413 * $scope.removeContact = function(contactToRemove) { 29414 * var index = $scope.contacts.indexOf(contactToRemove); 29415 * $scope.contacts.splice(index, 1); 29416 * }; 29417 * 29418 * $scope.clearContact = function(contact) { 29419 * contact.type = 'phone'; 29420 * contact.value = ''; 29421 * }; 29422 * } 29423 * </file> 29424 * <file name="protractor.js" type="protractor"> 29425 * it('should check controller', function() { 29426 * var container = element(by.id('ctrl-exmpl')); 29427 * 29428 * expect(container.element(by.model('name')) 29429 * .getAttribute('value')).toBe('John Smith'); 29430 * 29431 * var firstRepeat = 29432 * container.element(by.repeater('contact in contacts').row(0)); 29433 * var secondRepeat = 29434 * container.element(by.repeater('contact in contacts').row(1)); 29435 * 29436 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 29437 * .toBe('408 555 1212'); 29438 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 29439 * .toBe('[email protected]'); 29440 * 29441 * firstRepeat.element(by.buttonText('clear')).click(); 29442 * 29443 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 29444 * .toBe(''); 29445 * 29446 * container.element(by.buttonText('add')).click(); 29447 * 29448 * expect(container.element(by.repeater('contact in contacts').row(2)) 29449 * .element(by.model('contact.value')) 29450 * .getAttribute('value')) 29451 * .toBe('[email protected]'); 29452 * }); 29453 * </file> 29454 *</example> 29455 29456 */ 29457var ngControllerDirective = [function() { 29458 return { 29459 restrict: 'A', 29460 scope: true, 29461 controller: '@', 29462 priority: 500 29463 }; 29464}]; 29465 29466/** 29467 * @ngdoc directive 29468 * @name ngCsp 29469 * 29470 * @element html 29471 * @description 29472 * 29473 * Angular has some features that can break certain 29474 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 29475 * 29476 * If you intend to implement these rules then you must tell Angular not to use these features. 29477 * 29478 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 29479 * 29480 * 29481 * The following rules affect Angular: 29482 * 29483 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions 29484 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30% 29485 * increase in the speed of evaluating Angular expressions. 29486 * 29487 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular 29488 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). 29489 * To make these directives work when a CSP rule is blocking inline styles, you must link to the 29490 * `angular-csp.css` in your HTML manually. 29491 * 29492 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval 29493 * and automatically deactivates this feature in the {@link $parse} service. This autodetection, 29494 * however, triggers a CSP error to be logged in the console: 29495 * 29496 * ``` 29497 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 29498 * script in the following Content Security Policy directive: "default-src 'self'". Note that 29499 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 29500 * ``` 29501 * 29502 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 29503 * directive on an element of the HTML document that appears before the `<script>` tag that loads 29504 * the `angular.js` file. 29505 * 29506 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 29507 * 29508 * You can specify which of the CSP related Angular features should be deactivated by providing 29509 * a value for the `ng-csp` attribute. The options are as follows: 29510 * 29511 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM 29512 *
29513 * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings 29514 * 29515 * You can use these values in the following combinations: 29516 * 29517 * 29518 * * No declaration means that Angular will assume that you can do inline styles, but it will do 29519 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions 29520 * of Angular. 29521 * 29522 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline 29523 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions 29524 * of Angular. 29525 * 29526 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject 29527 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 29528 * 29529 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can 29530 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 29531 * 29532 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject 29533 * styles nor use eval, which is the same as an empty: ng-csp. 29534 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 29535 * 29536 * @example 29537 * This example shows how to apply the `ngCsp` directive to the `html` tag. 29538 ```html 29539 <!doctype html> 29540 <html ng-app ng-csp> 29541 ... 29542 ... 29543 </html> 29544 ``` 29545 * @example 29546 // Note: the suffix `.csp` in the example name triggers 29547 // csp mode in our http server! 29548 <example name="example.csp" module="cspExample" ng-csp="true"> 29549 <file name="index.html"> 29550 <div ng-controller="MainController as ctrl"> 29551 <div> 29552 <button ng-click="ctrl.inc()" id="inc">Increment</button> 29553 <span id="counter"> 29554 {{ctrl.counter}} 29555 </span> 29556 </div> 29557 29558 <div> 29559 <button ng-click="ctrl.evil()" id="evil">Evil</button> 29560 <span id="evilError"> 29561 {{ctrl.evilError}} 29562 </span> 29563 </div> 29564 </div> 29565 </file> 29566 <file name="script.js"> 29567 angular.module('cspExample', []) 29568 .controller('MainController', function() { 29569 this.counter = 0; 29570 this.inc = function() { 29571 this.counter++; 29572 }; 29573 this.evil = function() { 29574 // jshint evil:true 29575 try { 29576 eval('1+2'); 29577 } catch (e) { 29578 this.evilError = e.message; 29579 } 29580 }; 29581 }); 29582 </file> 29583 <file name="protractor.js" type="protractor"> 29584 var util, webdriver; 29585 29586 var incBtn = element(by.id('inc')); 29587 var counter = element(by.id('counter')); 29588 var evilBtn = element(by.id('evil')); 29589 var evilError = element(by.id('evilError')); 29590 29591 function getAndClearSevereErrors() { 29592 return browser.manage().logs().get('browser').then(function(browserLog) { 29593 return browserLog.filter(function(logEntry) { 29594 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 29595 }); 29596 }); 29597 } 29598 29599 function clearErrors() { 29600 getAndClearSevereErrors(); 29601 } 29602 29603 function expectNoErrors() { 29604 getAndClearSevereErrors().then(function(filteredLog) { 29605 expect(filteredLog.length).toEqual(0); 29606 if (filteredLog.length) { 29607 console.log('browser console errors: ' + util.inspect(filteredLog)); 29608 } 29609 }); 29610 } 29611 29612 function expectError(regex) { 29613 getAndClearSevereErrors().then(function(filteredLog) { 29614 var found = false; 29615 filteredLog.forEach(function(log) { 29616 if (log.message.match(regex)) { 29617 found = true; 29618 } 29619 }); 29620 if (!found) { 29621 throw new Error('expected an error that matches ' + regex); 29622 } 29623 }); 29624 } 29625 29626 beforeEach(function() { 29627 util = require('util'); 29628 webdriver = require('protractor/node_modules/selenium-webdriver'); 29629 }); 29630 29631 // For now, we only test on Chrome, 29632 // as Safari does not load the page with Protractor's injected scripts, 29633 // and Firefox webdriver always disables content security policy (#6358) 29634 if (browser.params.browser !== 'chrome') { 29635 return; 29636 } 29637 29638 it('should not report errors when the page is loaded', function() { 29639 // clear errors so we are not dependent on previous tests 29640 clearErrors(); 29641 // Need to reload the page as the page is already loaded when 29642 // we come here 29643 browser.driver.getCurrentUrl().then(function(url) { 29644 browser.get(url); 29645 }); 29646 expectNoErrors(); 29647 }); 29648 29649 it('should evaluate expressions', function() { 29650 expect(counter.getText()).toEqual('0'); 29651 incBtn.click(); 29652 expect(counter.getText()).toEqual('1'); 29653 expectNoErrors(); 29654 }); 29655 29656 it('should throw and report an error when using "eval"', function() { 29657 evilBtn.click(); 29658 expect(evilError.getText()).toMatch(/Content Security Policy/); 29659 expectError(/Content Security Policy/); 29660 }); 29661 </file> 29662 </example> 29663 */ 29664 29665// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
29666// bootstrap the system (before $parse is instantiated), for this reason we just have 29667// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc 29668 29669/** 29670 * @ngdoc directive 29671 * @name ngClick 29672 * 29673 * @description 29674 * The ngClick directive allows you to specify custom behavior when 29675 * an element is clicked. 29676 * 29677 * @element ANY 29678 * @priority 0 29679 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 29680 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 29681 * 29682 * @example 29683 <example> 29684 <file name="index.html"> 29685 <button ng-click="count = count + 1" ng-init="count=0"> 29686 Increment 29687 </button> 29688 <span> 29689 count: {{count}} 29690 </span> 29691 </file> 29692 <file name="protractor.js" type="protractor"> 29693 it('should check ng-click', function() { 29694 expect(element(by.binding('count')).getText()).toMatch('0'); 29695 element(by.css('button')).click(); 29696 expect(element(by.binding('count')).getText()).toMatch('1'); 29697 }); 29698 </file> 29699 </example> 29700 */ 29701/* 29702 * A collection of directives that allows creation of custom event handlers that are defined as 29703 * angular expressions and are compiled and executed within the current scope. 29704 */ 29705var ngEventDirectives = {}; 29706 29707// For events that might fire synchronously during DOM manipulation 29708// we need to execute their event handlers asynchronously using $evalAsync, 29709// so that they are not executed in an inconsistent state. 29710var forceAsyncEvents = { 29711 'blur': true, 29712 'focus': true 29713}; 29714forEach( 29715 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 29716 function(eventName) { 29717 var directiveName = directiveNormalize('ng-' + eventName); 29718 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 29719 return { 29720 restrict: 'A', 29721 compile: function($element, attr) { 29722 // We expose the powerful $event object on the scope that provides access to the Window, 29723 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 29724 // checks at the cost of speed since event handler expressions are not executed as 29725 // frequently as regular change detection. 29726 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 29727 return function ngEventHandler(scope, element) { 29728 element.on(eventName, function(event) { 29729 var callback = function() { 29730 fn(scope, {$event:event}); 29731 }; 29732 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 29733 scope.$evalAsync(callback); 29734 } else { 29735 scope.$apply(callback); 29736 } 29737 }); 29738 }; 29739 } 29740 }; 29741 }]; 29742 } 29743); 29744 29745/** 29746 * @ngdoc directive 29747 * @name ngDblclick 29748 * 29749 * @description 29750 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 29751 * 29752 * @element ANY 29753 * @priority 0 29754 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 29755 * a dblclick. (The Event object is available as `$event`) 29756 * 29757 * @example 29758 <example> 29759 <file name="index.html"> 29760 <button ng-dblclick="count = count + 1" ng-init="count=0"> 29761 Increment (on double click) 29762 </button> 29763 count: {{count}} 29764 </file> 29765 </example> 29766 */ 29767 29768 29769/** 29770 * @ngdoc directive 29771 * @name ngMousedown 29772 * 29773 * @description 29774 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 29775 * 29776 * @element ANY 29777 * @priority 0 29778 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 29779 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 29780 * 29781 * @example 29782 <example> 29783 <file name="index.html"> 29784 <button ng-mousedown="count = count + 1" ng-init="count=0"> 29785 Increment (on mouse down) 29786 </button> 29787 count: {{count}} 29788 </file> 29789 </example> 29790 */ 29791 29792 29793/** 29794 * @ngdoc directive 29795 * @name ngMouseup 29796 * 29797 * @description 29798 * Specify custom behavior on mouseup event. 29799 * 29800 * @element ANY 29801 * @priority 0 29802 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 29803 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 29804 * 29805 * @example 29806 <example> 29807 <file name="index.html"> 29808 <button ng-mouseup="count = count + 1" ng-init="count=0"> 29809 Increment (on mouse up) 29810 </button> 29811 count: {{count}} 29812 </file> 29813 </example> 29814 */ 29815 29816/** 29817 * @ngdoc directive 29818 * @name ngMouseover 29819 * 29820 * @description 29821 * Specify custom behavior on mouseover event. 29822 * 29823 * @element ANY 29824 * @priority 0 29825 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 29826 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 29827 * 29828 * @example 29829 <example> 29830 <file name="index.html"> 29831 <button ng-mouseover="count = count + 1" ng-init="count=0"> 29832 Increment (when mouse is over) 29833 </button> 29834 count: {{count}} 29835 </file> 29836 </example> 29837 */ 29838 29839 29840/** 29841 * @ngdoc directive 29842 * @name ngMouseenter 29843 * 29844 * @description 29845 * Specify custom behavior on mouseenter event. 29846 * 29847 * @element ANY 29848 * @priority 0 29849 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 29850 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 29851 * 29852 * @example 29853 <example> 29854 <file name="index.html"> 29855 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 29856 Increment (when mouse enters) 29857 </button> 29858 count: {{count}} 29859 </file> 29860 </example> 29861 */ 29862 29863 29864/** 29865 * @ngdoc directive 29866 * @name ngMouseleave 29867 * 29868 * @description 29869 * Specify custom behavior on mouseleave event. 29870 * 29871 * @element ANY 29872 * @priority 0 29873 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 29874 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 29875 * 29876 * @example 29877 <example> 29878 <file name="index.html"> 29879 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 29880 Increment (when mouse leaves) 29881 </button> 29882 count: {{count}} 29883 </file> 29884 </example> 29885 */ 29886 29887 29888/** 29889 * @ngdoc directive 29890 * @name ngMousemove 29891 * 29892 * @description 29893 * Specify custom behavior on mousemove event. 29894 * 29895 * @element ANY 29896 * @priority 0 29897 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 29898 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 29899 * 29900 * @example 29901 <example> 29902 <file name="index.html"> 29903 <button ng-mousemove="count = count + 1" ng-init="count=0"> 29904 Increment (when mouse moves) 29905 </button> 29906 count: {{count}} 29907 </file> 29908 </example> 29909 */ 29910 29911 29912/** 29913 * @ngdoc directive 29914 * @name ngKeydown 29915 * 29916 * @description 29917 * Specify custom behavior on keydown event. 29918 * 29919 * @element ANY 29920 * @priority 0 29921 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 29922 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 29923 * 29924 * @example 29925 <example> 29926 <file name="index.html"> 29927 <input ng-keydown="count = count + 1" ng-init="count=0"> 29928 key down count: {{count}} 29929 </file> 29930 </example> 29931 */ 29932 29933 29934/** 29935 * @ngdoc directive 29936 * @name ngKeyup 29937 * 29938 * @description 29939 * Specify custom behavior on keyup event. 29940 * 29941 * @element ANY 29942 * @priority 0 29943 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 29944 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 29945 * 29946 * @example 29947 <example> 29948 <file name="index.html"> 29949 <p>Typing in the input box below updates the key count</p> 29950 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 29951 29952 <p>Typing in the input box below updates the keycode</p> 29953 <input ng-keyup="event=$event"> 29954 <p>event keyCode: {{ event.keyCode }}</p> 29955 <p>event altKey: {{ event.altKey }}</p> 29956 </file> 29957 </example> 29958 */ 29959 29960 29961/** 29962 * @ngdoc directive 29963 * @name ngKeypress 29964 * 29965 * @description 29966 * Specify custom behavior on keypress event. 29967 * 29968 * @element ANY 29969 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 29970 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 29971 * and can be interrogated for keyCode, altKey, etc.) 29972 * 29973 * @example 29974 <example> 29975 <file name="index.html"> 29976 <input ng-keypress="count = count + 1" ng-init="count=0"> 29977 key press count: {{count}} 29978 </file> 29979 </example> 29980 */ 29981 29982 29983/** 29984 * @ngdoc directive 29985 * @name ngSubmit 29986 * 29987 * @description 29988 * Enables binding angular expressions to onsubmit events. 29989 * 29990 * Additionally it prevents the default action (which for form means
29990sending the request to the 29991 * server and reloading the current page), but only if the form does not contain `action`, 29992 * `data-action`, or `x-action` attributes. 29993 * 29994 * <div class="alert alert-warning"> 29995 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 29996 * `ngSubmit` handlers together. See the 29997 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 29998 * for a detailed discussion of when `ngSubmit` may be triggered. 29999 * </div> 30000 * 30001 * @element form 30002 * @priority 0 30003 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 30004 * ({@link guide/expression#-event- Event object is available as `$event`}) 30005 * 30006 * @example 30007 <example module="submitExample"> 30008 <file name="index.html"> 30009 <script> 30010 angular.module('submitExample', []) 30011 .controller('ExampleController', ['$scope', function($scope) { 30012 $scope.list = []; 30013 $scope.text = 'hello'; 30014 $scope.submit = function() { 30015 if ($scope.text) { 30016 $scope.list.push(this.text); 30017 $scope.text = ''; 30018 } 30019 }; 30020 }]); 30021 </script>
30022 <form ng-submit="submit()" ng-controller="ExampleController"> 30023 Enter text and hit enter: 30024 <input type="text" ng-model="text" name="text" /> 30025 <input type="submit" id="submit" value="Submit" /> 30026 <pre>list={{list}}</pre> 30027 </form> 30028 </file> 30029 <file name="protractor.js" type="protractor"> 30030 it('should check ng-submit', function() { 30031 expect(element(by.binding('list')).getText()).toBe('list=[]'); 30032 element(by.css('#submit')).click(); 30033 expect(element(by.binding('list')).getText()).toContain('hello'); 30034 expect(element(by.model('text')).getAttribute('value')).toBe(''); 30035 }); 30036 it('should ignore empty strings', function() { 30037 expect(element(by.binding('list')).getText()).toBe('list=[]'); 30038 element(by.css('#submit')).click(); 30039 element(by.css('#submit')).click(); 30040 expect(element(by.binding('list')).getText()).toContain('hello'); 30041 }); 30042 </file> 30043 </example> 30044 */ 30045 30046/** 30047 * @ngdoc directive 30048 * @name ngFocus 30049 * 30050 * @description 30051 * Specify custom behavior on focus event. 30052 * 30053 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 30054 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 30055 * during an `$apply` to ensure a consistent state. 30056 * 30057 * @element window, input, select, textarea, a 30058 * @priority 0 30059 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 30060 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 30061 * 30062 * @example 30063 * See {@link ng.directive:ngClick ngClick} 30064 */ 30065 30066/** 30067 * @ngdoc directive 30068 * @name ngBlur 30069 * 30070 * @description 30071 * Specify custom behavior on blur event. 30072 * 30073 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 30074 * an element has lost focus. 30075 * 30076 * Note: As the `blur` event is executed synchronously also during DOM manipulations 30077 * (e.g. removing a focussed input), 30078 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 30079 * during an `$apply` to ensure a consistent state. 30080 * 30081 * @element window, input, select, textarea, a 30082 * @priority 0 30083 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 30084 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 30085 * 30086 * @example 30087 * See {@link ng.directive:ngClick ngClick} 30088 */ 30089 30090/** 30091 * @ngdoc directive 30092 * @name ngCopy 30093 * 30094 * @description 30095 * Specify custom behavior on copy event. 30096 * 30097 * @element window, input, select, textarea, a 30098 * @priority 0 30099 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 30100 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 30101 * 30102 * @example 30103 <example> 30104 <file name="index.html"> 30105 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 30106 copied: {{copied}} 30107 </file> 30108 </example> 30109 */ 30110 30111/** 30112 * @ngdoc directive 30113 * @name ngCut 30114 * 30115 * @description 30116 * Specify custom behavior on cut event. 30117 * 30118 * @element window, input, select, textarea, a 30119 * @priority 0 30120 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 30121 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 30122 * 30123 * @example 30124 <example> 30125 <file name="index.html"> 30126 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 30127 cut: {{cut}} 30128 </file> 30129 </example> 30130 */ 30131 30132/** 30133 * @ngdoc directive 30134 * @name ngPaste 30135 * 30136 * @description 30137 * Specify custom behavior on paste event. 30138 * 30139 * @element window, input, select, textarea, a 30140 * @priority 0 30141 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 30142 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 30143 * 30144 * @example 30145 <example> 30146 <file name="index.html"> 30147 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 30148 pasted: {{paste}} 30149 </file> 30150 </example> 30151 */ 30152 30153/** 30154 * @ngdoc directive 30155 * @name ngIf 30156 * @restrict A 30157 * @multiElement 30158 * 30159 * @description 30160 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 30161 * {expression}. If the expression assigned to `ngIf` evaluates to a false 30162 * value then the element is removed from the DOM, otherwise a clone of the 30163 * element is reinserted into the DOM. 30164 * 30165 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 30166 * element in the DOM rather than changing its visibility via the `display` css property. A common 30167 * case when this difference is significant is when using css selectors that rely on an element's 30168 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
30168s. 30169 * 30170 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 30171 * is created when the element is restored. The scope created within `ngIf` inherits from 30172 * its parent scope using 30173 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 30174 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 30175 * a javascript primitive defined in the parent scope. In this case any modifications made to the 30176 * variable within the child scope will override (hide) the value in the parent scope. 30177 * 30178 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 30179 * is if an element's class attribute is directly modified after it's compiled, using something like 30180 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 30181 * the added class will be lost because the original compiled state is used to regenerate the element. 30182 * 30183 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 30184 * and `leave` effects. 30185 * 30186 * @animations 30187 * | Animation | Occurs | 30188 * |----------------------------------|-------------------------------------| 30189 * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container | 30190 * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM | 30191 * 30192 * @element ANY 30193 * @scope 30194 * @priority 600 30195 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 30196 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 30197 * element is added to the DOM tree. 30198 * 30199 * @example 30200 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 30201 <file name="index.html"> 30202 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 30203 Show when checked: 30204 <span ng-if="checked" class="animate-if"> 30205 This is removed when the checkbox is unchecked. 30206 </span> 30207 </file> 30208 <file name="animations.css"> 30209 .animate-if { 30210 background:white; 30211 border:1px solid black; 30212 padding:10px; 30213 } 30214 30215 .animate-if.ng-enter, .animate-if.ng-leave { 30216 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 30217 } 30218 30219 .animate-if.ng-enter, 30220 .animate-if.ng-leave.ng-leave-active { 30221 opacity:0; 30222 } 30223 30224 .animate-if.ng-leave, 30225 .animate-if.ng-enter.ng-enter-active { 30226 opacity:1; 30227 } 30228 </file> 30229 </example> 30230 */ 30231var ngIfDirective = ['$animate', '$compile', function($animate, $compile) { 30232 return { 30233 multiElement: true, 30234 transclude: 'element', 30235 priority: 600, 30236 terminal: true, 30237 restrict: 'A', 30238 $$tlb: true, 30239 link: function($scope, $element, $attr, ctrl, $transclude) { 30240 var block, childScope, previousElements; 30241 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 30242 30243 if (value) { 30244 if (!childScope) { 30245 $transclude(function(clone, newScope) { 30246 childScope = newScope; 30247 clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf); 30248 // Note: We only need the first/last node of the cloned nodes. 30249 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 30250 // by a directive with templateUrl when its template arrives. 30251 block = { 30252 clone: clone 30253 }; 30254 $animate.enter(clone, $element.parent(), $element); 30255 }); 30256 } 30257 } else { 30258 if (previousElements) { 30259 previousElements.remove(); 30260 previousElements = null; 30261 } 30262 if (childScope) { 30263 childScope.$destroy(); 30264 childScope = null; 30265 } 30266 if (block) { 30267 previousElements = getBlockNodes(block.clone); 30268 $animate.leave(previousElements).then(function() { 30269 previousElements = null; 30270 }); 30271 block = null; 30272 } 30273 } 30274 }); 30275 } 30276 }; 30277}]; 30278 30279/** 30280 * @ngdoc directive 30281 * @name ngInclude 30282 * @restrict ECA 30283 * 30284 * @description 30285 * Fetches, compiles and includes an external HTML fragment. 30286 * 30287 * By default, the template URL is restricted to the same domain and protocol as the 30288 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 30289 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 30290 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 30291 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 30292 * ng.$sce Strict Contextual Escaping}. 30293 * 30294 * In addition, the browser's 30295 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 30296 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 30297 * policy may further restrict whether the template is successfully loaded. 30298 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 30299 * access on some browsers. 30300 * 30301 * @animations 30302 * | Animation | Occurs | 30303 * |----------------------------------|-------------------------------------| 30304 * | {@link ng.$animate#enter enter} | when the expression changes, on the new include | 30305 * | {@link ng.$animate#leave leave} | when the expression changes, on the old include | 30306 * 30307 * The enter and leave animation occur concurrently. 30308 * 30309 * @scope 30310 * @priority 400 30311 * 30312 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 30313 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 30314 * @param {string=} onload Expression to evaluate when a new partial is loaded. 30315 * <div class="alert alert-warning"> 30316 * **Note:** When using onload on SVG elements in IE11, the browser will try to call 30317 * a function with the name on the window element, which will usually throw a 30318 * "function is undefined" error. To fix this, you can instead use `data-onload` or a 30319 * different form that {@link guide/directive#normalization matches} `onload`. 30320 * </div> 30321 * 30322 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 30323 * $anchorScroll} to scroll the viewport after the content is loaded. 30324 * 30325 * - If the attribute is not set, disable scrolling. 30326 * - If the attribute is set without value, enable scrolling. 30327 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 30328 * 30329 * @example 30330 <example module="includeExample" deps="angular-animate.js" animations="true"> 30331 <file name="index.html">
30332 <div ng-controller="ExampleController"> 30333 <select ng-model="template" ng-options="t.name for t in templates"> 30334 <option value="">(blank)</option> 30335 </select> 30336 url of the template: <code>{{template.url}}</code> 30337 <hr/> 30338 <div class="slide-animate-container"> 30339 <div class="slide-animate" ng-include="template.url"></div> 30340 </div> 30341 </div> 30342 </file> 30343 <file name="script.js"> 30344 angular.module('includeExample', ['ngAnimate']) 30345 .controller('ExampleController', ['$scope', function($scope) { 30346 $scope.templates = 30347 [ { name: 'template1.html', url: 'template1.html'}, 30348 { name: 'template2.html', url: 'template2.html'} ]; 30349 $scope.template = $scope.templates[0]; 30350 }]); 30351 </file> 30352 <file name="template1.html"> 30353 Content of template1.html 30354 </file> 30355 <file name="template2.html"> 30356 Content of template2.html 30357 </file> 30358 <file name="animations.css"> 30359 .slide-animate-container { 30360 position:relative; 30361 background:white; 30362 border:1px solid black; 30363 height:40px; 30364 overflow:hidden; 30365 } 30366 30367 .slide-animate { 30368 padding:10px; 30369 } 30370 30371 .slide-animate.ng-enter, .slide-animate.ng-leave { 30372 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 30373 30374 position:absolute; 30375 top:0; 30376 left:0; 30377 right:0; 30378 bottom:0; 30379 display:block; 30380 padding:10px; 30381 } 30382 30383 .slide-animate.ng-enter { 30384 top:-50px; 30385 } 30386 .slide-animate.ng-enter.ng-enter-active { 30387 top:0; 30388 } 30389 30390 .slide-animate.ng-leave { 30391 top:0; 30392 } 30393 .slide-animate.ng-leave.ng-leave-active { 30394 top:50px; 30395 } 30396 </file> 30397 <file name="protractor.js" type="protractor"> 30398 var templateSelect = element(by.model('template')); 30399 var includeElem = element(by.css('[ng-include]')); 30400 30401 it('should load template1.html', function() { 30402 expect(includeElem.getText()).toMatch(/Content of template1.html/); 30403 }); 30404 30405 it('should load template2.html', function() { 30406 if (browser.params.browser == 'firefox') { 30407 // Firefox can't handle using selects 30408 // See https://github.com/angular/protractor/issues/480 30409 return; 30410 } 30411 templateSelect.click(); 30412 templateSelect.all(by.css('option')).get(2).click(); 30413 expect(includeElem.getText()).toMatch(/Content of template2.html/); 30414 }); 30415 30416 it('should change to blank', function() { 30417 if (browser.params.browser == 'firefox') { 30418 // Firefox can't handle using selects 30419 return; 30420 } 30421 templateSelect.click(); 30422 templateSelect.all(by.css('option')).get(0).click(); 30423 expect(includeElem.isPresent()).toBe(false); 30424 }); 30425 </file> 30426 </example> 30427 */ 30428 30429 30430/** 30431 * @ngdoc event 30432 * @name ngInclude#$includeContentRequested 30433 * @eventType emit on the scope ngInclude was declared in 30434 * @description 30435 * Emitted every time the ngInclude content is requested. 30436 * 30437 * @param {Object} angularEvent Synthetic event object. 30438 * @param {String} src URL of content to load. 30439 */ 30440 30441 30442/** 30443 * @ngdoc event 30444 * @name ngInclude#$includeContentLoaded 30445 * @eventType emit on the current ngInclude scope 30446 * @description 30447 * Emitted every time the ngInclude content is reloaded. 30448 * 30449 * @param {Object} angularEvent Synthetic event object. 30450 * @param {String} src URL of content to load. 30451 */ 30452 30453 30454/** 30455 * @ngdoc event 30456 * @name ngInclude#$includeContentError 30457 * @eventType emit on the scope ngInclude was declared in 30458 * @description 30459 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 30460 * 30461 * @param {Object} angularEvent Synthetic event object. 30462 * @param {String} src URL of content to load. 30463 */ 30464var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 30465 function($templateRequest, $anchorScroll, $animate) { 30466 return { 30467 restrict: 'ECA', 30468 priority: 400, 30469 terminal: true, 30470 transclude: 'element', 30471 controller: angular.noop, 30472 compile: function(element, attr) { 30473 var srcExp = attr.ngInclude || attr.src, 30474 onloadExp = attr.onload || '', 30475 autoScrollExp = attr.autoscroll; 30476 30477 return function(scope, $element, $attr, ctrl, $transclude) { 30478 var changeCounter = 0, 30479 currentScope, 30480 previousElement, 30481 currentElement; 30482 30483 var cleanupLastIncludeContent = function() { 30484 if (previousElement) { 30485 previousElement.remove(); 30486 previousElement = null; 30487 } 30488 if (currentScope) { 30489 currentScope.$destroy(); 30490 currentScope = null; 30491 } 30492 if (currentElement) { 30493 $animate.leave(currentElement).then(function() { 30494 previousElement = null; 30495 }); 30496 previousElement = currentElement; 30497 currentElement = null; 30498 } 30499 }; 30500 30501 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 30502 var afterAnimation = function() { 30503 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 30504 $anchorScroll(); 30505 } 30506 }; 30507 var thisChangeId = ++changeCounter; 30508 30509 if (src) { 30510 //set the 2nd param to true to ignore the template request error so that the inner 30511 //contents and scope can be cleaned up. 30512 $templateRequest(src, true).then(function(response) { 30513 if (scope.$$destroyed) return; 30514 30515 if (thisChangeId !== changeCounter) return; 30516 var newScope = scope.$new(); 30517 ctrl.template = response; 30518 30519 // Note: This will also link all children of ng-include that were contained in the original 30520 // html. If that content contains controllers, ... they could pollute/change the scope. 30521 // However, using ng-include on an element with additional content does not make sense... 30522 // Note: We can't remove them in the cloneAttchFn of $transclude as that 30523 // function is called before linking the content, which would apply child 30524 // directives to non existing elements. 30525 var clone = $transclude(newScope, function(clone) { 30526 cleanupLastIncludeContent(); 30527 $animate.enter(clone, null, $element).then(afterAnimation); 30528 }); 30529 30530 currentScope = newScope; 30531 currentElement = clone; 30532 30533 currentScope.$emit('$includeContentLoaded', src); 30534 scope.$eval(onloadExp); 30535 }, function() { 30536 if (scope.$$destroyed) return; 30537 30538 if (thisChangeId === changeCounter) { 30539 cleanupLastIncludeContent();
vendor: 2,835 bytes, lines 30540-30622
30540 scope.$emit('$includeContentError', src); 30541 } 30542 }); 30543 scope.$emit('$includeContentRequested', src); 30544 } else { 30545 cleanupLastIncludeContent(); 30546 ctrl.template = null; 30547 } 30548 }); 30549 }; 30550 } 30551 }; 30552}]; 30553 30554// This directive is called during the $transclude call of the first `ngInclude` directive. 30555// It will replace and compile the content of the element with the loaded template. 30556// We need this directive so that the element content is already filled when 30557// the link function of another directive on the same element as ngInclude 30558// is called. 30559var ngIncludeFillContentDirective = ['$compile', 30560 function($compile) { 30561 return { 30562 restrict: 'ECA', 30563 priority: -400, 30564 require: 'ngInclude', 30565 link: function(scope, $element, $attr, ctrl) { 30566 if (toString.call($element[0]).match(/SVG/)) { 30567 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 30568 // support innerHTML, so detect this here and try to generate the contents 30569 // specially. 30570 $element.empty(); 30571 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, 30572 function namespaceAdaptedClone(clone) { 30573 $element.append(clone); 30574 }, {futureParentElement: $element}); 30575 return; 30576 } 30577 30578 $element.html(ctrl.template); 30579 $compile($element.contents())(scope); 30580 } 30581 }; 30582 }]; 30583 30584/** 30585 * @ngdoc directive 30586 * @name ngInit 30587 * @restrict AC 30588 * 30589 * @description 30590 * The `ngInit` directive allows you to evaluate an expression in the 30591 * current scope. 30592 * 30593 * <div class="alert alert-danger"> 30594 * This directive can be abused to add unnecessary amounts of logic into your templates. 30595 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of 30596 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via 30597 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} 30598 * rather than `ngInit` to initialize values on a scope. 30599 * </div> 30600 * 30601 * <div class="alert alert-warning"> 30602 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 30603 * sure you have parentheses to ensure correct operator precedence: 30604 * <pre class="prettyprint"> 30605 * `<div ng-init="test1 = ($index | toString)"></div>` 30606 * </pre> 30607 * </div> 30608 * 30609 * @priority 450 30610 * 30611 * @element ANY 30612 * @param {expression} ngInit {@link guide/expression Expression} to eval. 30613 * 30614 * @example 30615 <example module="initExample"> 30616 <file name="index.html"> 30617 <script> 30618 angular.module('initExample', []) 30619 .controller('ExampleController', ['$scope', function($scope) { 30620 $scope.list = [['a', 'b'], ['c', 'd']]; 30621 }]); 30622 </script>
30623 <div ng-controller="ExampleController"> 30624 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 30625 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 30626 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 30627 </div> 30628 </div> 30629 </div> 30630 </file> 30631 <file name="protractor.js" type="protractor"> 30632 it('should alias index positions', function() { 30633 var elements = element.all(by.css('.example-init')); 30634 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 30635 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 30636 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 30637 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 30638 }); 30639 </file> 30640 </example> 30641 */ 30642var ngInitDirective = ngDirective({ 30643 priority: 450, 30644 compile: function() { 30645 return { 30646 pre: function(scope, element, attrs) { 30647 scope.$eval(attrs.ngInit); 30648 } 30649 }; 30650 } 30651}); 30652 30653/** 30654 * @ngdoc directive 30655 * @name ngList 30656 * 30657 * @description 30658 * Text input that converts between a delimited string and an array of strings. The default 30659 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 30660 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 30661 * 30662 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 30663 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 30664 * list item is respected. This implies that the user of the directive is responsible for 30665 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 30666 * tab or newline character. 30667 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 30668 * when joining the list items back together) and whitespace around each list item is stripped 30669 * before it is added to the model. 30670 * 30671 * ### Example with Validation 30672 * 30673 * <example name="ngList-directive" module="listExample"> 30674 * <file name="app.js"> 30675 * angular.module('listExample', []) 30676 * .controller('ExampleController', ['$scope', function($scope) { 30677 * $scope.names = ['morpheus', 'neo', 'trinity']; 30678 * }]); 30679 * </file> 30680 * <file name="index.html">
30681 * <form name="myForm" ng-controller="ExampleController"> 30682 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 30683 * <span role="alert"> 30684 * <span class="error" ng-show="myForm.namesInput.$error.required"> 30685 * Required!</span> 30686 * </span> 30687 * <br> 30688 * <tt>names = {{names}}</tt><br/> 30689 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 30690 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 30691 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 30692 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 30693 * </form> 30694 * </file> 30695 * <file name="protractor.js" type="protractor"> 30696 * var listInput = element(by.model('names')); 30697 * var names = element(by.exactBinding('names')); 30698 * var valid = element(by.binding('myForm.namesInput.$valid')); 30699 * var error = element(by.css('span.error')); 30700 * 30701 * it('should initialize to model', function() { 30702 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 30703 * expect(valid.getText()).toContain('true'); 30704 * expect(error.getCssValue('display')).toBe('none'); 30705 * }); 30706 * 30707 * it('should be invalid if empty', function() { 30708 * listInput.clear(); 30709 * listInput.sendKeys(''); 30710 * 30711 * expect(names.getText()).toContain(''); 30712 * expect(valid.getText()).toContain('false'); 30713 * expect(error.getCssValue('display')).not.toBe('none'); 30714 * }); 30715 * </file> 30716 * </example> 30717 * 30718 * ### Example - splitting on newline 30719 * <example name="ngList-directive-newlines"> 30720 * <file name="index.html"> 30721 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 30722 * <pre>{{ list | json }}</pre> 30723 * </file> 30724 * <file name="protractor.js" type="protractor"> 30725 * it("should split the text by newlines", function() { 30726 * var listInput = element(by.model('list')); 30727 * var output = element(by.binding('list | json')); 30728 * listInput.sendKeys('abc\ndef\nghi'); 30729 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 30730 * }); 30731 * </file> 30732 * </example> 30733 * 30734 * @element input 30735 * @param {string=} ngList optional delimiter that should be used to split the value. 30736 */ 30737var ngListDirective = function() { 30738 return { 30739 restrict: 'A', 30740 priority: 100, 30741 require: 'ngModel', 30742 link: function(scope, element, attr, ctrl) { 30743 // We want to control whitespace trimming so we use this convoluted approach 30744 // to access the ngList attribute, which doesn't pre-trim the attribute 30745 var ngList = element.attr(attr.$attr.ngList) || ', '; 30746 var trimValues = attr.ngTrim !== 'false'; 30747 var separator = trimValues ? trim(ngList) : ngList; 30748 30749 var parse = function(viewValue) { 30750 // If the viewValue is invalid (say required but empty) it will be `undefined` 30751 if (isUndefined(viewValue)) return; 30752 30753 var list = []; 30754 30755 if (viewValue) { 30756 forEach(viewValue.split(separator), function(value) { 30757 if (value) list.push(trimValues ? trim(value) : value); 30758 }); 30759 } 30760 30761 return list; 30762 }; 30763 30764 ctrl.$parsers.push(parse); 30765 ctrl.$formatters.push(function(value) { 30766 if (isArray(value)) { 30767 return value.join(ngList); 30768 } 30769 30770 return undefined; 30771 }); 30772 30773 // Override the standard $isEmpty because an empty array means the input is empty. 30774 ctrl.$isEmpty = function(value) { 30775 return !value || !value.length; 30776 }; 30777 } 30778 }; 30779}; 30780 30781/* global VALID_CLASS: true, 30782 INVALID_CLASS: true, 30783 PRISTINE_CLASS: true, 30784 DIRTY_CLASS: true, 30785 UNTOUCHED_CLASS: true, 30786 TOUCHED_CLASS: true, 30787*/ 30788 30789var VALID_CLASS = 'ng-valid', 30790 INVALID_CLASS = 'ng-invalid', 30791 PRISTINE_CLASS = 'ng-pristine', 30792 DIRTY_CLASS = 'ng-dirty', 30793 UNTOUCHED_CLASS = 'ng-untouched', 30794 TOUCHED_CLASS = 'ng-touched', 30795 PENDING_CLASS = 'ng-pending', 30796 EMPTY_CLASS = 'ng-empty', 30797 NOT_EMPTY_CLASS = 'ng-not-empty'; 30798 30799var ngModelMinErr = minErr('ngModel'); 30800 30801/** 30802 * @ngdoc type 30803 * @name ngModel.NgModelController 30804 * 30805 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 30806 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 30807 * is set. 30808 * @property {*} $modelValue The value in the model that the control is bound to. 30809 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 30810 the control reads value from the DOM. The functions are called in array order, each passing 30811 its return value through to the next. The last return value is forwarded to the 30812 {@link ngModel.NgModelController#$validators `$validators`} collection. 30813 30814Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 30815`$viewValue`}. 30816 30817Returning `undefined` from a parser means a parse error occurred. In that case, 30818no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 30819will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 30820is set to `true`. The parse error is stored in `ngModel.$error.parse`. 30821 30822 * 30823 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 30824 the model value changes. The functions are called in reverse array order, each passing the value through to the 30825 next. The last return value is used as the actual DOM value. 30826 Used to format / convert values for display in the control. 30827 * ```js 30828 * function formatter(value) { 30829 * if (value) { 30830 * return value.toUpperCase(); 30831 * } 30832 * } 30833 * ngModel.$formatters.push(formatter); 30834 * ``` 30835 * 30836 * @property {Object.<string, function>} $validators A collection of validators that are applied 30837 * whenever the model value changes. The key value within the object refers to the name of the 30838 * validator while the function refers to the validation operation. The validation operation is 30839 * provided with the model value as an argument and must return a true or false value depending 30840 * on the response of that validation. 30841 * 30842 * ```js 30843 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 30844 * var value = modelValue || viewValue; 30845 * return /[0-9]+/.test(value) && 30846 * /[a-z]+/.test(value) && 30847 * /[A-Z]+/.test(value) && 30848 * /\W+/.test(value); 30849 * }; 30850 * ``` 30851 * 30852 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 30853 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 30854 * is expected to return a promise when it is run during the model validation process. Once the promise 30855 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 30856 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 30857 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 30858 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 30859 * will only run once all synchronous validators have passed. 30860 * 30861 * Please note that if $http is used then it is important that the server returns a success HTTP response code 30862 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 30863 * 30864 * ```js 30865 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 30866 * var value = modelValue || viewValue; 30867 * 30868 * // Lookup user by username 30869 * return $http.get('/api/users/' + value). 30870 * then(function resolved() { 30871 * //username exists, this means validation fails 30872 * return $q.reject('exists'); 30873 * }, function rejected() { 30874 * //username does not exist, therefore this validation passes 30875 * return true; 30876 * }); 30877 * }; 30878 * ``` 30879 * 30880 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 30881 * view value has changed. It is called with no arguments, and its return value is ignored. 30882 * This can be used in place of additional $watches against the model value. 30883 * 30884 * @property {Object} $error An object hash with all failing validator ids as keys. 30885 * @property {Object} $pending An object hash with all pending validator ids as keys. 30886 * 30887 * @property {boolean} $untouched True if control has not lost focus yet. 30888 * @property {boolean} $touched True if control has lost focus. 30889 * @property {boolean} $pristine True if user has not interacted with the control yet. 30890 * @property {boolean} $dirty True if user has already interacted with the control. 30891 * @property {boolean} $valid True if there is no error. 30892 * @property {boolean}
30892 $invalid True if at least one error on the control. 30893 * @property {string} $name The name attribute of the control. 30894 * 30895 * @description 30896 * 30897 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 30898 * The controller contains services for data-binding, validation, CSS updates, and value formatting 30899 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 30900 * listening to DOM events. 30901 * Such DOM related logic should be provided by other directives which make use of 30902 * `NgModelController` for data-binding to control elements. 30903 * Angular provides this DOM logic for most {@link input `input`} elements. 30904 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 30905 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 30906 * 30907 * @example 30908 * ### Custom Control Example 30909 * This example shows how to use `NgModelController` with a custom control to achieve 30910 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 30911 * collaborate together to achieve the desired result. 30912 * 30913 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 30914 * contents be edited in place by the user. 30915 * 30916 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 30917 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 30918 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 30919 * that content using the `$sce` service. 30920 * 30921 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 30922 <file name="style.css"> 30923 [contenteditable] { 30924 border: 1px solid black; 30925 background-color: white; 30926 min-height: 20px; 30927 } 30928 30929 .ng-invalid { 30930 border: 1px solid red; 30931 } 30932 30933 </file> 30934 <file name="script.js"> 30935 angular.module('customControl', ['ngSanitize']). 30936 directive('contenteditable', ['$sce', function($sce) { 30937 return { 30938 restrict: 'A', // only activate on element attribute 30939 require: '?ngModel', // get a hold of NgModelController 30940 link: function(scope, element, attrs, ngModel) { 30941 if (!ngModel) return; // do nothing if no ng-model 30942 30943 // Specify how UI should be updated 30944 ngModel.$render = function() { 30945 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 30946 }; 30947 30948 // Listen for change events to enable binding 30949 element.on('blur keyup change', function() { 30950 scope.$evalAsync(read); 30951 }); 30952 read(); // initialize 30953 30954 // Write data to the model 30955 function read() { 30956 var html = element.html(); 30957 // When we clear the content editable the browser leaves a <br> behind 30958 // If strip-br attribute is provided then we strip this out 30959 if ( attrs.stripBr && html == '<br>' ) { 30960 html = ''; 30961 } 30962 ngModel.$setViewValue(html); 30963 } 30964 } 30965 }; 30966 }]); 30967 </file> 30968 <file name="index.html"> 30969 <form name="myForm"> 30970 <div contenteditable 30971 name="myWidget" ng-model="userContent" 30972 strip-br="true" 30973 required>Change me!</div> 30974 <span ng-show="myForm.myWidget.$error.required">Required!</span> 30975 <hr> 30976 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 30977 </form> 30978 </file> 30979 <file name="protractor.js" type="protractor"> 30980 it('should data-bind and become invalid', function() { 30981 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 30982 // SafariDriver can't handle contenteditable 30983 // and Firefox driver can't clear contenteditables very well 30984 return; 30985 } 30986 var contentEditable = element(by.css('[contenteditable]')); 30987 var content = 'Change me!'; 30988 30989 expect(contentEditable.getText()).toEqual(content); 30990 30991 contentEditable.clear(); 30992 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 30993 expect(contentEditable.getText()).toEqual(''); 30994 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 30995 }); 30996 </file> 30997 * </example> 30998 * 30999 * 31000 */ 31001var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 31002 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 31003 this.$viewValue = Number.NaN; 31004 this.$modelValue = Number.NaN; 31005 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 31006 this.$validators = {}; 31007 this.$asyncValidators = {}; 31008 this.$parsers = []; 31009 this.$formatters = []; 31010 this.$viewChangeListeners = []; 31011 this.$untouched = true; 31012 this.$touched = false;
31013 this.$pristine = true; 31014 this.$dirty = false; 31015 this.$valid = true; 31016 this.$invalid = false; 31017 this.$error = {}; // keep invalid keys here 31018 this.$$success = {}; // keep valid keys here 31019 this.$pending = undefined; // keep pending keys here 31020 this.$name = $interpolate($attr.name || '', false)($scope); 31021 this.$$parentForm = nullFormCtrl; 31022 31023 var parsedNgModel = $parse($attr.ngModel), 31024 parsedNgModelAssign = parsedNgModel.assign, 31025 ngModelGet = parsedNgModel, 31026 ngModelSet = parsedNgModelAssign, 31027 pendingDebounce = null, 31028 parserValid, 31029 ctrl = this; 31030 31031 this.$$setOptions = function(options) { 31032 ctrl.$options = options; 31033 if (options && options.getterSetter) { 31034 var invokeModelGetter = $parse($attr.ngModel + '()'), 31035 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 31036 31037 ngModelGet = function($scope) { 31038 var modelValue = parsedNgModel($scope); 31039 if (isFunction(modelValue)) { 31040 modelValue = invokeModelGetter($scope); 31041 } 31042 return modelValue; 31043 }; 31044 ngModelSet = function($scope, newValue) { 31045 if (isFunction(parsedNgModel($scope))) { 31046 invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); 31047 } else { 31048 parsedNgModelAssign($scope, ctrl.$modelValue); 31049 } 31050 }; 31051 } else if (!parsedNgModel.assign) { 31052 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 31053 $attr.ngModel, startingTag($element)); 31054 } 31055 }; 31056 31057 /** 31058 * @ngdoc method 31059 * @name ngModel.NgModelController#$render 31060 * 31061 * @description 31062 * Called when the view needs to be updated. It is expected that the user of the ng-model 31063 * directive will implement this method. 31064 * 31065 * The `$render()` method is invoked in the following situations: 31066 * 31067 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 31068 * committed value then `$render()` is called to update the input control. 31069 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 31070 * the `$viewValue` are different from last time. 31071 * 31072 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 31073 * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` 31074 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 31075 * invoked if you only change a property on the objects. 31076 */ 31077 this.$render = noop; 31078 31079 /** 31080 * @ngdoc method 31081 * @name ngModel.NgModelController#$isEmpty 31082 * 31083 * @description 31084 * This is called when we need to determine if the value of an input is empty. 31085 * 31086 * For instance, the required directive does this to work out if the input has data or not. 31087 * 31088 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 31089 * 31090 * You can override this for input directives whose concept of being empty is different from the 31091 * default. The `checkboxInputType` directive does this because in its case a value of `false` 31092 * implies empty. 31093 * 31094 * @param {*} value The value of the input to check for emptiness. 31095 * @returns {boolean} True if `value` is "empty". 31096 */ 31097 this.$isEmpty = function(value) { 31098 return isUndefined(value) || value === '' || value === null || value !== value; 31099 }; 31100 31101 this.$$updateEmptyClasses = function(value) { 31102 if (ctrl.$isEmpty(value)) { 31103 $animate.removeClass($element, NOT_EMPTY_CLASS); 31104 $animate.addClass($element, EMPTY_CLASS); 31105 } else { 31106 $animate.removeClass($element, EMPTY_CLASS); 31107 $animate.addClass($element, NOT_EMPTY_CLASS); 31108 } 31109 }; 31110 31111 31112 var currentValidationRunId = 0; 31113 31114 /** 31115 * @ngdoc method 31116 * @name ngModel.NgModelController#$setValidity 31117 * 31118 * @description 31119 * Change the validity state, and notify the form. 31120 * 31121 * This method can be called within $parsers/$formatters or a custom validation implementation. 31122 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 31123 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 31124 * 31125 * @param {string}
31125 validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 31126 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 31127 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. 31128 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 31129 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 31130 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 31131 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 31132 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 31133 * Skipped is used by Angular when validators do not run because of parse errors and 31134 * when `$asyncValidators` do not run because any of the `$validators` failed. 31135 */ 31136 addSetValidityMethod({ 31137 ctrl: this, 31138 $element: $element, 31139 set: function(object, property) { 31140 object[property] = true; 31141 }, 31142 unset: function(object, property) { 31143 delete object[property]; 31144 }, 31145 $animate: $animate 31146 }); 31147 31148 /** 31149 * @ngdoc method 31150 * @name ngModel.NgModelController#$setPristine 31151 * 31152 * @description 31153 * Sets the control to its pristine state. 31154 * 31155 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 31156 * state (`ng-pristine` class). A model is considered to be pristine when the control 31157 * has not been changed from when first compiled. 31158 */ 31159 this.$setPristine = function() { 31160 ctrl.$dirty = false; 31161 ctrl.$pristine = true; 31162 $animate.removeClass($element, DIRTY_CLASS); 31163 $animate.addClass($element, PRISTINE_CLASS); 31164 }; 31165 31166 /** 31167 * @ngdoc method 31168 * @name ngModel.NgModelController#$setDirty 31169 * 31170 * @description 31171 * Sets the control to its dirty state. 31172 * 31173 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 31174 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 31175 * from when first compiled. 31176 */ 31177 this.$setDirty = function() { 31178 ctrl.$dirty = true; 31179 ctrl.$pristine = false; 31180 $animate.removeClass($element, PRISTINE_CLASS); 31181 $animate.addClass($element, DIRTY_CLASS); 31182 ctrl.$$parentForm.$setDirty(); 31183 }; 31184 31185 /** 31186 * @ngdoc method 31187 * @name ngModel.NgModelController#$setUntouched 31188 * 31189 * @description 31190 * Sets the control to its untouched state. 31191 * 31192 * This method can be called to remove the `ng-touched` class and set the control to its 31193 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 31194 * by default, however this function can be used to restore that state if the model has 31195 * already been touched by the user. 31196 */ 31197 this.$setUntouched = function() { 31198 ctrl.$touched = false; 31199 ctrl.$untouched = true; 31200 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 31201 }; 31202 31203 /** 31204 * @ngdoc method 31205 * @name ngModel.NgModelController#$setTouched 31206 * 31207 * @description 31208 * Sets the control to its touched state. 31209 * 31210 * This method can be called to remove the `ng-untouched` class and set the control to its 31211 * touched state (`ng-touched` class). A model is considered to be touched when the user has 31212 * first focused the control element and then shifted focus away from the control (blur event). 31213 */ 31214 this.$setTouched = function() { 31215 ctrl.$touched = true; 31216 ctrl.$untouched = false; 31217 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 31218 }; 31219 31220 /** 31221 * @ngdoc method 31222 * @name ngModel.NgModelController#$rollbackViewValue 31223 * 31224 * @description 31225 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 31226 * which may be caused by a pending debounced event or because the input is waiting for a some 31227 * future event. 31228 * 31229 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that 31230 * depend on special events such as blur, you can have a situation where there is a period when 31231 * the `$viewValue` is out of sync with the ngModel's `$modelValue`. 31232 * 31233 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update 31234 * and reset the input to the last committed view value. 31235 * 31236 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` 31237 * programmatically before these debounced/future events have resolved/occurred, because Angular's 31238 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 31239 * 31240 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 31241 * input which may have such events pending. This is important in order to make sure that the 31242 * input field will be updated with the new model value and any pending operations are cancelled. 31243 * 31244 * <example name="ng-model-cancel-update" module="cancel-update-example"> 31245 * <file name="app.js">
31246 * angular.module('cancel-update-example', []) 31247 * 31248 * .controller('CancelUpdateController', ['$scope', function($scope) { 31249 * $scope.model = {}; 31250 * 31251 * $scope.setEmpty = function(e, value, rollback) { 31252 * if (e.keyCode == 27) { 31253 * e.preventDefault(); 31254 * if (rollback) { 31255 * $scope.myForm[value].$rollbackViewValue(); 31256 * } 31257 * $scope.model[value] = ''; 31258 * } 31259 * }; 31260 * }]); 31261 * </file> 31262 * <file name="index.html"> 31263 * <div ng-controller="CancelUpdateController"> 31264 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should 31265 * empty them. Follow these steps and observe the difference:</p> 31266 * <ol> 31267 * <li>Type something in the input. You will see that the model is not yet updated</li> 31268 * <li>Press the Escape key. 31269 * <ol> 31270 * <li> In the first example, nothing happens, because the model is already '', and no 31271 * update is detected. If you blur the input, the model will be set to the current view. 31272 * </li> 31273 * <li> In the second example, the pending update is cancelled, and the input is set back 31274 * to the last committed view value (''). Blurring the input does nothing. 31275 * </li> 31276 * </ol> 31277 * </li> 31278 * </ol> 31279 * 31280 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 31281 * <div> 31282 * <p id="inputDescription1">Without $rollbackViewValue():</p> 31283 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" 31284 * ng-keydown="setEmpty($event, 'value1')"> 31285 * value1: "{{ model.value1 }}" 31286 * </div> 31287 * 31288 * <div> 31289 * <p id="inputDescription2">With $rollbackViewValue():</p> 31290 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" 31291 * ng-keydown="setEmpty($event, 'value2', true)"> 31292 * value2: "{{ model.value2 }}" 31293 * </div> 31294 * </form> 31295 * </div> 31296 * </file> 31297 <file name="style.css"> 31298 div { 31299 display: table-cell; 31300 } 31301 div:nth-child(1) { 31302 padding-right: 30px; 31303 } 31304 31305 </file> 31306 * </example> 31307 */ 31308 this.$rollbackViewValue = function() { 31309 $timeout.cancel(pendingDebounce); 31310 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 31311 ctrl.$render(); 31312 }; 31313 31314 /** 31315 * @ngdoc method 31316 * @name ngModel.NgModelController#$validate 31317 * 31318 * @description 31319 * Runs each of the registered validators (first synchronous validators and then 31320 * asynchronous validators). 31321 * If the validity changes to invalid, the model will be set to `undefined`, 31322 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 31323 * If the validity changes to valid, it will set the model to the last available valid 31324 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 31325 */ 31326 this.$validate = function() { 31327 // ignore $validate before model is initialized 31328 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 31329 return; 31330 } 31331 31332 var viewValue = ctrl.$$lastCommittedViewValue; 31333 // Note: we use the $$rawModelValue as $modelValue might have been 31334 // set to undefined during a view -> model update that found validation 31335 // errors. We can't parse the view here, since that could change 31336 // the model although neither viewValue nor the model on the scope changed 31337 var modelValue = ctrl.$$rawModelValue; 31338 31339 var prevValid = ctrl.$valid; 31340 var prevModelValue = ctrl.$modelValue; 31341 31342 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 31343 31344 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 31345 // If there was no change in validity, don't update the model 31346 // This prevents changing an invalid modelValue to undefined 31347 if (!allowInvalid && prevValid !== allValid) { 31348 // Note: Don't check ctrl.$valid here, as we could have 31349 // external validators (e.g. calculated on the server), 31350 // that just call $setValidity and need the model value 31351 // to calculate their validity. 31352 ctrl.$modelValue = allValid ? modelValue : undefined; 31353 31354 if (ctrl.$modelValue !== prevModelValue) { 31355 ctrl.$$writeModelToScope(); 31356 } 31357 } 31358 }); 31359 31360 }; 31361 31362 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 31363 currentValidationRunId++; 31364 var localValidationRunId = currentValidationRunId; 31365 31366 // check parser error 31367 if (!processParseErrors()) { 31368 validationDone(false); 31369 return; 31370 } 31371 if (!processSyncValidators()) { 31372 validationDone(false); 31373 return; 31374 } 31375 processAsyncValidators(); 31376 31377 function processParseErrors() { 31378 var errorKey = ctrl.$$parserName || 'parse'; 31379 if (isUndefined(parserValid)) {
31380 setValidity(errorKey, null); 31381 } else { 31382 if (!parserValid) { 31383 forEach(ctrl.$validators, function(v, name) { 31384 setValidity(name, null); 31385 }); 31386 forEach(ctrl.$asyncValidators, function(v, name) { 31387 setValidity(name, null); 31388 }); 31389 } 31390 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 31391 setValidity(errorKey, parserValid); 31392 return parserValid; 31393 } 31394 return true; 31395 } 31396 31397 function processSyncValidators() { 31398 var syncValidatorsValid = true; 31399 forEach(ctrl.$validators, function(validator, name) { 31400 var result = validator(modelValue, viewValue); 31401 syncValidatorsValid = syncValidatorsValid && result; 31402 setValidity(name, result); 31403 }); 31404 if (!syncValidatorsValid) { 31405 forEach(ctrl.$asyncValidators, function(v, name) { 31406 setValidity(name, null); 31407 }); 31408 return false; 31409 } 31410 return true; 31411 } 31412 31413 function processAsyncValidators() { 31414 var validatorPromises = []; 31415 var allValid = true; 31416 forEach(ctrl.$asyncValidators, function(validator, name) { 31417 var promise = validator(modelValue, viewValue); 31418 if (!isPromiseLike(promise)) { 31419 throw ngModelMinErr('nopromise', 31420 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 31421 } 31422 setValidity(name, undefined); 31423 validatorPromises.push(promise.then(function() { 31424 setValidity(name, true); 31425 }, function(error) { 31426 allValid = false; 31427 setValidity(name, false); 31428 })); 31429 }); 31430 if (!validatorPromises.length) { 31431 validationDone(true); 31432 } else { 31433 $q.all(validatorPromises).then(function() { 31434 validationDone(allValid); 31435 }, noop); 31436 } 31437 } 31438 31439 function setValidity(name, isValid) { 31440 if (localValidationRunId === currentValidationRunId) { 31441 ctrl.$setValidity(name, isValid); 31442 } 31443 } 31444 31445 function validationDone(allValid) { 31446 if (localValidationRunId === currentValidationRunId) { 31447 31448 doneCallback(allValid); 31449 } 31450 } 31451 }; 31452 31453 /** 31454 * @ngdoc method 31455 * @name ngModel.NgModelController#$commitViewValue 31456 * 31457 * @description 31458 * Commit a pending update to the `$modelValue`. 31459 * 31460 * Updates may be pending by a debounced event or because the input is waiting for a some future 31461 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 31462 * usually handles calling this in response to input events. 31463 */ 31464 this.$commitViewValue = function() { 31465 var viewValue = ctrl.$viewValue; 31466 31467 $timeout.cancel(pendingDebounce); 31468 31469 // If the view value has not changed then we should just exit, except in the case where there is 31470 // a native validator on the element. In this case the validation state may have changed even though 31471 // the viewValue has stayed empty. 31472 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 31473 return; 31474 } 31475 ctrl.$$updateEmptyClasses(viewValue); 31476 ctrl.$$lastCommittedViewValue = viewValue; 31477 31478 // change to dirty 31479 if (ctrl.$pristine) { 31480 this.$setDirty(); 31481 } 31482 this.$$parseAndValidate(); 31483 }; 31484 31485 this.$$parseAndValidate = function() { 31486 var viewValue = ctrl.$$lastCommittedViewValue; 31487 var modelValue = viewValue; 31488 parserValid = isUndefined(modelValue) ? undefined : true; 31489 31490 if (parserValid) { 31491 for (var i = 0; i < ctrl.$parsers.length; i++) { 31492 modelValue = ctrl.$parsers[i](modelValue); 31493 if (isUndefined(modelValue)) { 31494 parserValid = false; 31495 break; 31496 } 31497 } 31498 } 31499 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 31500 // ctrl.$modelValue has not been touched yet... 31501 ctrl.$modelValue = ngModelGet($scope); 31502 } 31503 var prevModelValue = ctrl.$modelValue; 31504 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 31505 ctrl.$$rawModelValue = modelValue; 31506 31507 if (allowInvalid) { 31508 ctrl.$modelValue = modelValue; 31509 writeToModelIfNeeded(); 31510 } 31511 31512 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 31513 // This can happen if e.g. $setViewValue is called from inside a parser 31514 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 31515 if (!allowInvalid) { 31516 // Note: Don't check ctrl.$valid here, as we could have 31517 // external validators (e.g. calculated on the server), 31518 // that just call $setValidity and need the model value 31519 // to calculate their validity. 31520 ctrl.$modelValue = allValid ? modelValue : undefined; 31521 writeToModelIfNeeded(); 31522 } 31523 }); 31524 31525 function writeToModelIfNeeded() { 31526 if (ctrl.$modelValue !== prevModelValue) { 31527 ctrl.$$writeModelToScope(); 31528 } 31529 } 31530 }; 31531 31532 this.$$writeModelToScope = function() { 31533 ngModelSet($scope, ctrl.$modelValue);
31534 forEach(ctrl.$viewChangeListeners, function(listener) { 31535 try { 31536 listener(); 31537 } catch (e) { 31538 $exceptionHandler(e); 31539 } 31540 }); 31541 }; 31542 31543 /** 31544 * @ngdoc method 31545 * @name ngModel.NgModelController#$setViewValue 31546 * 31547 * @description 31548 * Update the view value. 31549 * 31550 * This method should be called when a control wants to change the view value; typically, 31551 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 31552 * directive calls it when the value of the input changes and {@link ng.directive:select select} 31553 * calls it when an option is selected. 31554 * 31555 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 31556 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 31557 * value sent directly for processing, finally to be applied to `$modelValue` and then the 31558 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, 31559 * in the `$viewChangeListeners` list, are called. 31560 * 31561 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 31562 * and the `default` trigger is not listed, all those actions will remain pending until one of the 31563 * `updateOn` events is triggered on the DOM element. 31564 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 31565 * directive is used with a custom debounce for this particular event. 31566 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 31567 * is specified, once the timer runs out. 31568 * 31569 * When used with standard inputs, the view value will always be a string (which is in some cases 31570 * parsed into another type, such as a `Date` object for `input[date]`.) 31571 * However, custom controls might also pass objects to this method. In this case, we should make 31572 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 31573 * perform a deep watch of objects, it only looks for a change of identity. If you only change 31574 * the property of the object then ngModel will not realize that the object has changed and 31575 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 31576 * not change properties of the copy once it has been passed to `$setViewValue`. 31577 * Otherwise you may cause the model value on the scope to change incorrectly. 31578 * 31579 * <div class="alert alert-info"> 31580 * In any case, the value passed to the method should always reflect the current value 31581 * of the control. For example, if you are calling `$setViewValue` for an input element, 31582 * you should pass the input DOM value. Otherwise, the control and the scope model become 31583 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 31584 * the control's DOM value in any way. If we want to change the control's DOM value 31585 * programmatically, we should update the `ngModel` scope expression. Its new value will be 31586 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 31587 * to update the DOM, and finally call `$validate` on it. 31588 * </div> 31589 * 31590 * @param {*} value value from the view. 31591 * @param {string} trigger Event that triggered the update. 31592 */ 31593 this.$setViewValue = function(value, trigger) { 31594 ctrl.$viewValue = value; 31595 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 31596 ctrl.$$debounceViewValueCommit(trigger); 31597 } 31598 }; 31599 31600 this.$$debounceViewValueCommit = function(trigger) { 31601 var debounceDelay = 0, 31602 options = ctrl.$options, 31603 debounce; 31604 31605 if (options && isDefined(options.debounce)) { 31606 debounce = options.debounce; 31607 if (isNumber(debounce)) { 31608 debounceDelay = debounce; 31609 } else if (isNumber(debounce[trigger])) { 31610 debounceDelay = debounce[trigger]; 31611 } else if (isNumber(debounce['default'])) { 31612 debounceDelay = debounce['default']; 31613 } 31614 } 31615 31616 $timeout.cancel(pendingDebounce); 31617 if (debounceDelay) { 31618 pendingDebounce = $timeout(function() { 31619 ctrl.$commitViewValue(); 31620 }, debounceDelay); 31621 } else if ($rootScope.$$phase) { 31622 ctrl.$commitViewValue(); 31623 } else { 31624 $scope.$apply(function() { 31625 ctrl.$commitViewValue(); 31626 }); 31627 } 31628 }; 31629 31630 // model -> value 31631 // Note: we cannot use a normal scope.$watch as we want to detect the following: 31632 // 1. scope value is 'a' 31633 // 2. user enters 'b' 31634 // 3. ng-change kicks in and reverts scope value to 'a' 31635 // ->
31635 scope value did not change since the last digest as 31636 // ng-change executes in apply phase 31637 // 4. view should be changed back to 'a' 31638 $scope.$watch(function ngModelWatch() { 31639 var modelValue = ngModelGet($scope); 31640 31641 // if scope model value and ngModel value are out of sync 31642 // TODO(perf): why not move this to the action fn? 31643 if (modelValue !== ctrl.$modelValue && 31644 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 31645 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 31646 ) { 31647 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 31648 parserValid = undefined; 31649 31650 var formatters = ctrl.$formatters, 31651 idx = formatters.length; 31652 31653 var viewValue = modelValue; 31654 while (idx--) { 31655 viewValue = formatters[idx](viewValue); 31656 } 31657 if (ctrl.$viewValue !== viewValue) { 31658 ctrl.$$updateEmptyClasses(viewValue); 31659 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 31660 ctrl.$render(); 31661 31662 ctrl.$$runValidators(modelValue, viewValue, noop); 31663 } 31664 } 31665 31666 return modelValue; 31667 }); 31668}]; 31669 31670 31671/** 31672 * @ngdoc directive 31673 * @name ngModel 31674 * 31675 * @element input 31676 * @priority 1 31677 * 31678 * @description 31679 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 31680 * property on the scope using {@link ngModel.NgModelController NgModelController}, 31681 * which is created and exposed by this directive. 31682 * 31683 * `ngModel` is responsible for: 31684 * 31685 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 31686 * require. 31687 * - Providing validation behavior (i.e. required, number, email, url). 31688 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 31689 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, 31690 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. 31691 * - Registering the control with its parent {@link ng.directive:form form}. 31692 * 31693 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 31694 * current scope. If the property doesn't already exist on this scope, it will be created 31695 * implicitly and added to the scope. 31696 * 31697 * For best practices on using `ngModel`, see: 31698 * 31699 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 31700 * 31701 * For basic examples, how to use `ngModel`, see: 31702 * 31703 * - {@link ng.directive:input input} 31704 * - {@link input[text] text} 31705 * - {@link input[checkbox] checkbox} 31706 * - {@link input[radio] radio} 31707 * - {@link input[number] number} 31708 * - {@link input[email] email} 31709 * - {@link input[url] url} 31710 * - {@link input[date] date} 31711 * - {@link input[datetime-local] datetime-local} 31712 * - {@link input[time] time} 31713 * - {@link input[month] month} 31714 * - {@link input[week] week} 31715 * - {@link ng.directive:select select} 31716 * - {@link ng.directive:textarea textarea} 31717 * 31718 * # Complex Models (objects or collections) 31719 * 31720 * By default, `ngModel` watches the model by reference, not value. This is important to know when 31721 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the 31722 * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. 31723 * 31724 * The model must be assigned an entirely new object or collection before a re-rendering will occur. 31725 * 31726 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression 31727 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or 31728 * if the select is given the `multiple` attribute. 31729 * 31730 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the 31731 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still 31732 * not trigger a re-rendering of the model. 31733 * 31734 * # CSS classes 31735 * The following CSS classes are added and removed on the associated input/select/textarea element 31736 * depending on the validity of the model. 31737 * 31738 * - `ng-valid`: the model is valid
31739 * - `ng-invalid`: the model is invalid 31740 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 31741 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 31742 * - `ng-pristine`: the control hasn't been interacted with yet 31743 * - `ng-dirty`: the control has been interacted with 31744 * - `ng-touched`: the control has been blurred 31745 * - `ng-untouched`: the control hasn't been blurred 31746 * - `ng-pending`: any `$asyncValidators` are unfulfilled 31747 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined 31748 * by the {@link ngModel.NgModelController#$isEmpty} method 31749 * - `ng-not-empty`: the view contains a non-empty value 31750 * 31751 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 31752 * 31753 * ## Animation Hooks 31754 * 31755 * Animations within models are triggered when any of the associated CSS classes are added and removed 31756 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, 31757 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 31758 * The animations that are triggered within ngModel are similar to how they work in ngClass and 31759 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 31760 * 31761 * The following example shows a simple way to utilize CSS transitions to style an input element 31762 * that has been rendered as invalid after it has been validated: 31763 * 31764 * <pre> 31765 * //be sure to include ngAnimate as a module to hook into more 31766 * //advanced animations 31767 * .my-input { 31768 * transition:0.5s linear all; 31769 * background: white; 31770 * } 31771 * .my-input.ng-invalid { 31772 * background: red; 31773 * color:white; 31774 * } 31775 * </pre> 31776 * 31777 * @example 31778 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 31779 <file name="index.html"> 31780 <script> 31781 angular.module('inputExample', []) 31782 .controller('ExampleController', ['$scope', function($scope) { 31783 $scope.val = '1'; 31784 }]); 31785 </script> 31786 <style> 31787 .my-input { 31788 transition:all linear 0.5s; 31789 background: transparent; 31790 } 31791 .my-input.ng-invalid { 31792 color:white; 31793 background: red; 31794 } 31795 </style> 31796 <p id="inputDescription"> 31797 Update input to see transitions when valid/invalid. 31798 Integer is a valid value. 31799 </p>
31800 <form name="testForm" ng-controller="ExampleController"> 31801 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 31802 aria-describedby="inputDescription" /> 31803 </form> 31804 </file> 31805 * </example> 31806 * 31807 * ## Binding to a getter/setter 31808 * 31809 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 31810 * function that returns a representation of the model when called with zero arguments, and sets 31811 * the internal state of a model when called with an argument. It's sometimes useful to use this 31812 * for models that have an internal representation that's different from what the model exposes 31813 * to the view. 31814 * 31815 * <div class="alert alert-success"> 31816 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 31817 * frequently than other parts of your code. 31818 * </div> 31819 * 31820 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 31821 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 31822 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 31823 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 31824 * 31825 * The following example shows how to use `ngModel` with a getter/setter: 31826 * 31827 * @example 31828 * <example name="ngModel-getter-setter" module="getterSetterExample"> 31829 <file name="index.html">
31830 <div ng-controller="ExampleController"> 31831 <form name="userForm"> 31832 <label>Name: 31833 <input type="text" name="userName" 31834 ng-model="user.name" 31835 ng-model-options="{ getterSetter: true }" /> 31836 </label> 31837 </form> 31838 <pre>user.name = <span ng-bind="user.name()"></span></pre> 31839 </div> 31840 </file> 31841 <file name="app.js"> 31842 angular.module('getterSetterExample', []) 31843 .controller('ExampleController', ['$scope', function($scope) { 31844 var _name = 'Brian'; 31845 $scope.user = { 31846 name: function(newName) { 31847 // Note that newName can be undefined for two reasons: 31848 // 1. Because it is called as a getter and thus called with no arguments 31849 // 2. Because the property should actually be set to undefined. This happens e.g. if the 31850 // input is invalid 31851 return arguments.length ? (_name = newName) : _name; 31852 } 31853 }; 31854 }]); 31855 </file> 31856 * </example> 31857 */ 31858var ngModelDirective = ['$rootScope', function($rootScope) { 31859 return { 31860 restrict: 'A', 31861 require: ['ngModel', '^?form', '^?ngModelOptions'], 31862 controller: NgModelController, 31863 // Prelink needs to run before any input directive 31864 // so that we can set the NgModelOptions in NgModelController 31865 // before anyone else uses it. 31866 priority: 1, 31867 compile: function ngModelCompile(element) { 31868 // Setup initial state of the control 31869 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 31870 31871 return { 31872 pre: function ngModelPreLink(scope, element, attr, ctrls) { 31873 var modelCtrl = ctrls[0], 31874 formCtrl = ctrls[1] || modelCtrl.$$parentForm; 31875 31876 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 31877 31878 // notify others, especially parent forms 31879 formCtrl.$addControl(modelCtrl); 31880 31881 attr.$observe('name', function(newValue) { 31882 if (modelCtrl.$name !== newValue) { 31883 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 31884 } 31885 }); 31886 31887 scope.$on('$destroy', function() { 31888 modelCtrl.$$parentForm.$removeControl(modelCtrl); 31889 }); 31890 }, 31891 post: function ngModelPostLink(scope, element, attr, ctrls) { 31892 var modelCtrl = ctrls[0]; 31893 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 31894 element.on(modelCtrl.$options.updateOn, function(ev) { 31895 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 31896 }); 31897 } 31898 31899 element.on('blur', function(ev) { 31900 if (modelCtrl.$touched) return; 31901 31902 if ($rootScope.$$phase) { 31903 scope.$evalAsync(modelCtrl.$setTouched); 31904 } else { 31905 scope.$apply(modelCtrl.$setTouched); 31906 } 31907 }); 31908 } 31909 }; 31910 } 31911 }; 31912}]; 31913 31914var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 31915 31916/** 31917 * @ngdoc directive 31918 * @name ngModelOptions 31919 * 31920 * @description 31921 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 31922 * events that will trigger a model update and/or a debouncing delay so that the actual update only 31923 * takes place when a timer expires; this timer will be reset after another change takes place. 31924 * 31925 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 31926 * be different from the value in the actual model. This means that if you update the model you 31927 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 31928 * order to make sure it is synchronized with the model and that any debounced action is canceled. 31929 * 31930 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 31931 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 31932 * important because `form` controllers are published to the related scope under the name in their 31933 * `name` attribute. 31934 * 31935 * Any pending changes will take place immediately when an enclosing form is submitted via the 31936 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 31937 * to have access to the updated model. 31938 * 31939 * `ngModelOptions` has an effect on the element it's declared on and its desce
31939ndants. 31940 * 31941 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 31942 * - `updateOn`: string specifying which event should the input be bound to. You can set several 31943 * events using an space delimited list. There is a special event called `default` that 31944 * matches the default events belonging of the control. 31945 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 31946 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 31947 * custom value for each event. For example: 31948 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 31949 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 31950 * not validate correctly instead of the default behavior of setting the model to undefined. 31951 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 31952 `ngModel` as getters/setters. 31953 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 31954 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 31955 * continental US time zone abbreviations, but for general use, use a time zone offset, for 31956 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 31957 * If not specified, the timezone of the browser will be used. 31958 * 31959 * @example 31960 31961 The following example shows how to override immediate updates. Changes on the inputs within the 31962 form will update the model only when the control loses focus (blur event). If `escape` key is 31963 pressed while the input field is focused, the value is reset to the value in the current model. 31964 31965 <example name="ngModelOptions-directive-blur" module="optionsExample"> 31966 <file name="index.html">
31967 <div ng-controller="ExampleController"> 31968 <form name="userForm"> 31969 <label>Name: 31970 <input type="text" name="userName" 31971 ng-model="user.name" 31972 ng-model-options="{ updateOn: 'blur' }" 31973 ng-keyup="cancel($event)" /> 31974 </label><br /> 31975 <label>Other data: 31976 <input type="text" ng-model="user.data" /> 31977 </label><br /> 31978 </form> 31979 <pre>user.name = <span ng-bind="user.name"></span></pre> 31980 <pre>user.data = <span ng-bind="user.data"></span></pre> 31981 </div> 31982 </file> 31983 <file name="app.js"> 31984 angular.module('optionsExample', []) 31985 .controller('ExampleController', ['$scope', function($scope) { 31986 $scope.user = { name: 'John', data: '' }; 31987 31988 $scope.cancel = function(e) { 31989 if (e.keyCode == 27) { 31990 $scope.userForm.userName.$rollbackViewValue(); 31991 } 31992 }; 31993 }]); 31994 </file> 31995 <file name="protractor.js" type="protractor"> 31996 var model = element(by.binding('user.name')); 31997 var input = element(by.model('user.name')); 31998 var other = element(by.model('user.data')); 31999 32000 it('should allow custom events', function() { 32001 input.sendKeys(' Doe'); 32002 input.click(); 32003 expect(model.getText()).toEqual('John'); 32004 other.click(); 32005 expect(model.getText()).toEqual('John Doe'); 32006 }); 32007 32008 it('should $rollbackViewValue when model changes', function() { 32009 input.sendKeys(' Doe'); 32010 expect(input.getAttribute('value')).toEqual('John Doe'); 32011 input.sendKeys(protractor.Key.ESCAPE); 32012 expect(input.getAttribute('value')).toEqual('John'); 32013 other.click(); 32014 expect(model.getText()).toEqual('John'); 32015 }); 32016 </file> 32017 </example> 32018 32019 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 32020 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 32021 32022 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 32023 <file name="index.html">
32024 <div ng-controller="ExampleController"> 32025 <form name="userForm"> 32026 <label>Name: 32027 <input type="text" name="userName" 32028 ng-model="user.name" 32029 ng-model-options="{ debounce: 1000 }" /> 32030 </label> 32031 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 32032 <br /> 32033 </form> 32034 <pre>user.name = <span ng-bind="user.name"></span></pre> 32035 </div> 32036 </file> 32037 <file name="app.js"> 32038 angular.module('optionsExample', []) 32039 .controller('ExampleController', ['$scope', function($scope) { 32040 $scope.user = { name: 'Igor' }; 32041 }]); 32042 </file> 32043 </example> 32044 32045 This one shows how to bind to getter/setters: 32046 32047 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 32048 <file name="index.html"> 32049 <div ng-controller="ExampleController"> 32050 <form name="userForm"> 32051 <label>Name: 32052 <input type="text" name="userName" 32053 ng-model="user.name" 32054 ng-model-options="{ getterSetter: true }" /> 32055 </label> 32056 </form> 32057 <pre>user.name = <span ng-bind="user.name()"></span></pre> 32058 </div> 32059 </file> 32060 <file name="app.js"> 32061 angular.module('getterSetterExample', []) 32062 .controller('ExampleController', ['$scope', function($scope) { 32063 var _name = 'Brian'; 32064 $scope.user = { 32065 name: function(newName) { 32066 // Note that newName can be undefined for two reasons: 32067 // 1. Because it is called as a getter and thus called with no arguments 32068 // 2. Because the property should actually be set to undefined. This happens e.g. if the 32069 // input is invalid 32070 return arguments.length ? (_name = newName) : _name; 32071 } 32072 }; 32073 }]); 32074 </file> 32075 </example> 32076 */ 32077var ngModelOptionsDirective = function() { 32078 return { 32079 restrict: 'A', 32080 controller: ['$scope', '$attrs', function($scope, $attrs) { 32081 var that = this; 32082 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 32083 // Allow adding/overriding bound events 32084 if (isDefined(this.$options.updateOn)) { 32085 this.$options.updateOnDefault = false;
32086 // extract "default" pseudo-event from list of events that can trigger a model update 32087 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 32088 that.$options.updateOnDefault = true; 32089 return ' '; 32090 })); 32091 } else { 32092 this.$options.updateOnDefault = true; 32093 } 32094 }] 32095 }; 32096}; 32097 32098 32099 32100// helper methods 32101function addSetValidityMethod(context) { 32102 var ctrl = context.ctrl, 32103 $element = context.$element, 32104 classCache = {}, 32105 set = context.set, 32106 unset = context.unset, 32107 $animate = context.$animate; 32108 32109 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 32110 32111 ctrl.$setValidity = setValidity; 32112 32113 function setValidity(validationErrorKey, state, controller) { 32114 if (isUndefined(state)) { 32115 createAndSet('$pending', validationErrorKey, controller); 32116 } else { 32117 unsetAndCleanup('$pending', validationErrorKey, controller); 32118 } 32119 if (!isBoolean(state)) { 32120 unset(ctrl.$error, validationErrorKey, controller); 32121 unset(ctrl.$$success, validationErrorKey, controller); 32122 } else { 32123 if (state) { 32124 unset(ctrl.$error, validationErrorKey, controller); 32125 set(ctrl.$$success, validationErrorKey, controller); 32126 } else { 32127 set(ctrl.$error, validationErrorKey, controller); 32128 unset(ctrl.$$success, validationErrorKey, controller); 32129 } 32130 } 32131 if (ctrl.$pending) { 32132 cachedToggleClass(PENDING_CLASS, true); 32133 ctrl.$valid = ctrl.$invalid = undefined; 32134 toggleValidationCss('', null); 32135 } else { 32136 cachedToggleClass(PENDING_CLASS, false); 32137 ctrl.$valid = isObjectEmpty(ctrl.$error); 32138 ctrl.$invalid = !ctrl.$valid; 32139 toggleValidationCss('', ctrl.$valid); 32140 } 32141 32142 // re-read the state as the set/unset methods could have 32143 // combined state in ctrl.$error[validationError] (used for forms), 32144 // where setting/unsetting only increments/decrements the value, 32145 // and does not replace it. 32146 var combinedState; 32147 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 32148 combinedState = undefined; 32149 } else if (ctrl.$error[validationErrorKey]) { 32150 combinedState = false; 32151 } else if (ctrl.$$success[validationErrorKey]) { 32152 combinedState = true; 32153 } else { 32154 combinedState = null; 32155 } 32156
32157 toggleValidationCss(validationErrorKey, combinedState); 32158 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 32159 } 32160 32161 function createAndSet(name, value, controller) { 32162 if (!ctrl[name]) { 32163 ctrl[name] = {}; 32164 } 32165 set(ctrl[name], value, controller); 32166 } 32167 32168 function unsetAndCleanup(name, value, controller) { 32169 if (ctrl[name]) { 32170 unset(ctrl[name], value, controller); 32171 } 32172 if (isObjectEmpty(ctrl[name])) { 32173 ctrl[name] = undefined; 32174 } 32175 } 32176 32177 function cachedToggleClass(className, switchValue) { 32178 if (switchValue && !classCache[className]) { 32179 $animate.addClass($element, className); 32180 classCache[className] = true; 32181 } else if (!switchValue && classCache[className]) { 32182 $animate.removeClass($element, className); 32183 classCache[className] = false; 32184 } 32185 } 32186 32187 function toggleValidationCss(validationErrorKey, isValid) { 32188 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 32189 32190 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 32191 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 32192 } 32193} 32194 32195function isObjectEmpty(obj) { 32196 if (obj) { 32197 for (var prop in obj) { 32198 if (obj.hasOwnProperty(prop)) { 32199 return false; 32200 } 32201 } 32202 } 32203 return true; 32204} 32205 32206/** 32207 * @ngdoc directive 32208 * @name ngNonBindable 32209 * @restrict AC 32210 * @priority 1000 32211 * 32212 * @description 32213 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 32214 * DOM element. This is useful if the element contains what appears to be Angular directives and 32215 * bindings but which should be ignored by Angular. This could be the case if you have a site that 32216 * displays snippets of code, for instance. 32217 * 32218 * @element ANY 32219 * 32220 * @example 32221 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 32222 * but the one wrapped in `ngNonBindable` is left alone. 32223 * 32224 * @example 32225 <example> 32226 <file name="index.html"> 32227 <div>Normal: {{1 + 2}}</div> 32228 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 32229 </file> 32230 <file name="protractor.js" type="protractor"> 32231 it('should check ng-non-bindable', function() { 32232 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 32233 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 32234 }); 32235 </file> 32236 </example> 32237 */ 32238var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 32239 32240/* global jqLiteRemove */ 32241 32242var ngOptionsMinErr = minErr('ngOptions'); 32243 32244/** 32245 * @ngdoc directive 32246 * @name ngOptions 32247 * @restrict A 32248 * 32249 * @description 32250 * 32251 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 32252 * elements for the `<select>` element using the array or object obtained by evaluating the 32253 * `ngOptions` comprehension expression. 32254 * 32255 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 32256 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 32257 * increasing speed by not creating a new scope for each repeated instance, as well as providing 32258 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 32259 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 32260 * to a non-string value. This is because an option element can only be bound to string values at 32261 * present. 32262 * 32263 * When an item in the `<select>` menu is selected, the array element or object property 32264 * represented by the selected option will be bound to the model identified by the `ngModel` 32265 * directive. 32266 * 32267 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 32268 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 32269 * option. See example below for demonstration. 32270 * 32271 * ## Complex Models (objects or collections) 32272 * 32273 * By default, `ngModel` watches the model by reference, not value. This is important to know when 32274 * binding the select to a model that is an object or a collection. 32275 * 32276 * One issue occurs if you want to preselect an option. For example, if you set 32277 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, 32278 * because the objects are not identical. So by default, you should always reference the item in y
32278our collection 32279 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`. 32280 * 32281 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity 32282 * of the item not by reference, but by the result of the `track by` expression. For example, if your 32283 * collection items have an id property, you would `track by item.id`. 32284 * 32285 * A different issue with objects or collections is that ngModel won't detect if an object property or 32286 * a collection item changes. For that reason, `ngOptions` additionally watches the model using 32287 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. 32288 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection 32289 * has not changed identity, but only a property on the object or an item in the collection changes. 32290 * 32291 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 32292 * if the model is an array). This means that changing a property deeper than the first level inside the 32293 * object/collection will not trigger a re-rendering. 32294 * 32295 * ## `select` **`as`** 32296 * 32297 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 32298 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 32299 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 32300 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 32301 * 32302 * 32303 * ### `select` **`as`** and **`track by`** 32304 * 32305 * <div class="alert alert-warning"> 32306 * Be careful when using `select` **`as`** and **`track by`** in the same expression. 32307 * </div> 32308 * 32309 * Given this array of items on the $scope: 32310 * 32311 * ```js 32312 * $scope.items = [{ 32313 * id: 1, 32314 * label: 'aLabel', 32315 * subItem: { name: 'aSubItem' } 32316 * }, { 32317 * id: 2, 32318 * label: 'bLabel', 32319 * subItem: { name: 'bSubItem' } 32320 * }]; 32321 * ``` 32322 * 32323 * This will work: 32324 * 32325 * ```html 32326 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select> 32327 * ``` 32328 * ```js 32329 * $scope.selected = $scope.items[0]; 32330 * ``` 32331 * 32332 * but this will not work: 32333 * 32334 * ```html 32335 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select> 32336 * ``` 32337 * ```js 32338 * $scope.selected = $scope.items[0].subItem; 32339 * ``` 32340 * 32341 * In both examples, the **`track by`** expression is applied successfully to each `item` in the 32342 * `items` array. Because the selected option has been set programmatically in the controller, the 32343 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the 32344 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with 32345 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`** 32346 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value 32347 * is not matched against any `<option>` and the `<select>` appears as having no selected value. 32348 * 32349 * 32350 * @param {string} ngModel Assignable angular expression to data-bind to. 32351 * @param {string=} name Property name of the form under which the control is published. 32352 * @param {string=} required The control is considered valid only if value is entered. 32353 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 32354 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 32355 * `required` when you want to data-bind to the `required` attribute. 32356 * @param {comprehension_expression=} ngOptions in one of the following forms: 32357 * 32358 * * for array data sources: 32359 * * `label` **`for`** `value` **`in`** `array` 32360 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 32361 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 32362 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 32363 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 32364 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 32365 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 32366 * (for including a filter with `track by`) 32367 * * for object data sources:
32368 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 32369 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 32370 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 32371 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 32372 * * `select` **`as`** `label` **`group by`** `group` 32373 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 32374 * * `select` **`as`** `label` **`disable when`** `disable` 32375 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 32376 * 32377 * Where: 32378 * 32379 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 32380 * * `value`: local variable which will refer to each item in the `array` or each property value 32381 * of `object` during iteration. 32382 * * `key`: local variable which will refer to a property name in `object` during iteration. 32383 * * `label`: The result of this expression will be the label for `<option>` element. The 32384 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 32385 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 32386 * element. If not specified, `select` expression will default to `value`. 32387 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 32388 * DOM element. 32389 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 32390 * element. Return `true` to disable. 32391 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 32392 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 32393 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 32394 * even when the options are recreated (e.g. reloaded from the server). 32395 * 32396 * @example 32397 <example module="selectExample"> 32398 <file name="index.html"> 32399 <script> 32400 angular.module('selectExample', []) 32401 .controller('ExampleController', ['$scope', function($scope) { 32402 $scope.colors = [ 32403 {name:'black', shade:'dark'}, 32404 {name:'white', shade:'light', notAnOption: true}, 32405 {name:'red', shade:'dark'}, 32406 {name:'blue', shade:'dark', notAnOption: true}, 32407 {name:'yellow', shade:'light', notAnOption: false} 32408 ]; 32409 $scope.myColor = $scope.colors[2]; // red 32410 }]); 32411 </script>
32412 <div ng-controller="ExampleController"> 32413 <ul> 32414 <li ng-repeat="color in colors"> 32415 <label>Name: <input ng-model="color.name"></label> 32416 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 32417 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 32418 </li> 32419 <li> 32420 <button ng-click="colors.push({})">add</button> 32421 </li> 32422 </ul> 32423 <hr/> 32424 <label>Color (null not allowed): 32425 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 32426 </label><br/> 32427 <label>Color (null allowed): 32428 <span class="nullable"> 32429 <select ng-model="myColor" ng-options="color.name for color in colors"> 32430 <option value="">-- choose color --</option> 32431 </select> 32432 </span></label><br/> 32433 32434 <label>Color grouped by shade: 32435 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 32436 </select> 32437 </label><br/> 32438 32439 <label>Color grouped by shade, with some disabled: 32440 <select ng-model="myColor" 32441 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 32442 </select> 32443 </label><br/> 32444 32445 32446 32447 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 32448 <br/> 32449 <hr/> 32450 Currently selected: {{ {selected_color:myColor} }} 32451 <div style="border:solid 1px black; height:20px" 32452 ng-style="{'background-color':myColor.name}"> 32453 </div> 32454 </div> 32455 </file> 32456 <file name="protractor.js" type="protractor"> 32457 it('should check ng-options', function() { 32458 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 32459 element.all(by.model('myColor')).first().click(); 32460 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 32461 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 32462 element(by.css('.nullable select[ng-model="myColor"]')).click(); 32463 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 32464 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 32465 }); 32466 </file> 32467 </example> 32468 */ 32469 32470// jshint maxlen: false 32471// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 32472var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/; 32473 // 1: value expression (valueFn) 32474 // 2: label expression (displayFn) 32475 // 3: group by expression (groupByFn) 32476 // 4: disable when expression (disableWhenFn) 32477 // 5: array item variable name 32478 // 6: object item key variable name 32479 // 7: object item value variable name 32480 // 8: collection expression 32481 // 9: track by expression 32482// jshint maxlen: 100 32483 32484 32485var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { 32486 32487 function parseOptionsExpression(optionsExp, selectElement, scope) { 32488 32489 var match = optionsExp.match(NG_OPTIONS_REGEXP); 32490 if (!(match)) { 32491 throw ngOptionsMinErr('iexp', 32492 "Expected expression in form of " + 32493 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 32494 " but got '{0}'. Element: {1}", 32495 optionsExp, startingTag(selectElement)); 32496 } 32497 32498 // Extract the parts from the ngOptions expression 32499 32500 // The variable name for the value of the item in the collection 32501 var valueName = match[5] || match[7]; 32502 // The variable name for the key of the item in the collection 32503 var keyName = match[6]; 32504 32505 // An expression that generates the viewValue for an option if there is a label expression 32506 var selectAs = / as /.test(match[0]) && match[1]; 32507 // An expression that is used to track the id of each object in the options collection 32508 var trackBy = match[9]; 32509 // An expression that generates the viewValue for an option if there is no label expression 32510 var valueFn = $parse(match[2] ? match[1] : valueName); 32511 var selectAsFn = selectAs && $parse(selectAs); 32512 var viewValueFn = selectAsFn || valueFn; 32513 var trackByFn = trackBy && $parse(trackBy); 32514 32515 // Get the value by which we are going to track the option 32516 // if we have a trackFn then use that (passing scope and locals) 32517 // otherwise just hash the given viewValue 32518 var getTrackByValueFn = trackBy ? 32519 function(value, locals) { return trackByFn(scope, locals); } : 32520 function getHashOfValue(value) { return hashKey(value); }; 32521 var getTrackByValue = function(value, key) {
vendor: 5,543 bytes, lines 32522-32675
32522 return getTrackByValueFn(value, getLocals(value, key)); 32523 }; 32524 32525 var displayFn = $parse(match[2] || match[1]); 32526 var groupByFn = $parse(match[3] || ''); 32527 var disableWhenFn = $parse(match[4] || ''); 32528 var valuesFn = $parse(match[8]); 32529 32530 var locals = {}; 32531 var getLocals = keyName ? function(value, key) { 32532 locals[keyName] = key; 32533 locals[valueName] = value; 32534 return locals; 32535 } : function(value) { 32536 locals[valueName] = value; 32537 return locals; 32538 }; 32539 32540 32541 function Option(selectValue, viewValue, label, group, disabled) { 32542 this.selectValue = selectValue; 32543 this.viewValue = viewValue; 32544 this.label = label; 32545 this.group = group; 32546 this.disabled = disabled; 32547 } 32548 32549 function getOptionValuesKeys(optionValues) { 32550 var optionValuesKeys; 32551 32552 if (!keyName && isArrayLike(optionValues)) { 32553 optionValuesKeys = optionValues; 32554 } else { 32555 // if object, extract keys, in enumeration order, unsorted 32556 optionValuesKeys = []; 32557 for (var itemKey in optionValues) { 32558 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 32559 optionValuesKeys.push(itemKey); 32560 } 32561 } 32562 } 32563 return optionValuesKeys; 32564 } 32565 32566 return { 32567 trackBy: trackBy, 32568 getTrackByValue: getTrackByValue, 32569 getWatchables: $parse(valuesFn, function(optionValues) { 32570 // Create a collection of things that we would like to watch (watchedArray) 32571 // so that they can all be watched using a single $watchCollection 32572 // that only runs the handler once if anything changes 32573 var watchedArray = []; 32574 optionValues = optionValues || []; 32575 32576 var optionValuesKeys = getOptionValuesKeys(optionValues); 32577 var optionValuesLength = optionValuesKeys.length; 32578 for (var index = 0; index < optionValuesLength; index++) { 32579 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 32580 var value = optionValues[key]; 32581 32582 var locals = getLocals(optionValues[key], key); 32583 var selectValue = getTrackByValueFn(optionValues[key], locals); 32584 watchedArray.push(selectValue); 32585 32586 // Only need to watch the displayFn if there is a specific label expression 32587 if (match[2] || match[1]) { 32588 var label = displayFn(scope, locals); 32589 watchedArray.push(label); 32590 } 32591 32592 // Only need to watch the disableWhenFn if there is a specific disable expression 32593 if (match[4]) { 32594 var disableWhen = disableWhenFn(scope, locals); 32595 watchedArray.push(disableWhen); 32596 } 32597 } 32598 return watchedArray; 32599 }), 32600 32601 getOptions: function() { 32602 32603 var optionItems = []; 32604 var selectValueMap = {}; 32605 32606 // The option values were already computed in the `getWatchables` fn, 32607 // which must have been called to trigger `getOptions` 32608 var optionValues = valuesFn(scope) || []; 32609 var optionValuesKeys = getOptionValuesKeys(optionValues); 32610 var optionValuesLength = optionValuesKeys.length; 32611 32612 for (var index = 0; index < optionValuesLength; index++) { 32613 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 32614 var value = optionValues[key]; 32615 var locals = getLocals(value, key); 32616 var viewValue = viewValueFn(scope, locals); 32617 var selectValue = getTrackByValueFn(viewValue, locals); 32618 var label = displayFn(scope, locals); 32619 var group = groupByFn(scope, locals); 32620 var disabled = disableWhenFn(scope, locals); 32621 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 32622 32623 optionItems.push(optionItem); 32624 selectValueMap[selectValue] = optionItem; 32625 } 32626 32627 return { 32628 items: optionItems, 32629 selectValueMap: selectValueMap, 32630 getOptionFromViewValue: function(value) { 32631 return selectValueMap[getTrackByValue(value)]; 32632 }, 32633 getViewValueFromOption: function(option) { 32634 // If the viewValue could be an object that may be mutated by the application, 32635 // we need to make a copy and not return the reference to the value on the option. 32636 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 32637 } 32638 }; 32639 } 32640 }; 32641 } 32642 32643 32644 // we can't just jqLite('<option>') since jqLite is not smart enough 32645 // to create it in <select> and IE barfs otherwise. 32646 var optionTemplate = document.createElement('option'), 32647 optGroupTemplate = document.createElement('optgroup'); 32648 32649 function ngOptionsPostLink(scope, selectElement, attr, ctrls) { 32650 32651 var selectCtrl = ctrls[0]; 32652 var ngModelCtrl = ctrls[1]; 32653 var multiple = attr.multiple; 32654 32655 // The emptyOption allows the application developer to provide their own custom "empty" 32656 // option when the viewValue does not match any of the option values. 32657 var emptyOption; 32658 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 32659 if (children[i].value === '') { 32660 emptyOption = children.eq(i); 32661 break; 32662 } 32663 } 32664 32665 var providedEmptyOption = !!emptyOption; 32666 32667 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 32668 unknownOption.val('?'); 32669 32670 var options; 32671 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 32672 32673 32674 var renderEmptyOption = function() { 32675 if (!providedEmptyOption) {
32676 selectElement.prepend(emptyOption); 32677 } 32678 selectElement.val(''); 32679 emptyOption.prop('selected', true); // needed for IE 32680 emptyOption.attr('selected', true); 32681 }; 32682 32683 var removeEmptyOption = function() { 32684 if (!providedEmptyOption) { 32685 emptyOption.remove(); 32686 } 32687 }; 32688 32689 32690 var renderUnknownOption = function() { 32691 selectElement.prepend(unknownOption); 32692 selectElement.val('?'); 32693 unknownOption.prop('selected', true); // needed for IE 32694 unknownOption.attr('selected', true); 32695 }; 32696 32697 var removeUnknownOption = function() { 32698 unknownOption.remove(); 32699 }; 32700 32701 // Update the controller methods for multiple selectable options 32702 if (!multiple) { 32703 32704 selectCtrl.writeValue = function writeNgOptionsValue(value) { 32705 var option = options.getOptionFromViewValue(value); 32706 32707 if (option && !option.disabled) { 32708 // Don't update the option when it is already selected. 32709 // For example, the browser will select the first option by default. In that case, 32710 // most properties are set automatically - except the `selected` attribute, which we 32711 // set always 32712 32713 if (selectElement[0].value !== option.selectValue) { 32714 removeUnknownOption(); 32715 removeEmptyOption(); 32716 32717 selectElement[0].value = option.selectValue; 32718 option.element.selected = true; 32719 } 32720 32721 option.element.setAttribute('selected', 'selected'); 32722 } else { 32723 if (value === null || providedEmptyOption) { 32724 removeUnknownOption(); 32725 renderEmptyOption(); 32726 } else { 32727 removeEmptyOption(); 32728 renderUnknownOption(); 32729 } 32730 } 32731 }; 32732 32733 selectCtrl.readValue = function readNgOptionsValue() { 32734 32735 var selectedOption = options.selectValueMap[selectElement.val()]; 32736 32737 if (selectedOption && !selectedOption.disabled) { 32738 removeEmptyOption(); 32739 removeUnknownOption(); 32740 return options.getViewValueFromOption(selectedOption); 32741 } 32742 return null; 32743 }; 32744 32745 // If we are using `track by` then we must watch the tracked value on the model 32746 // since ngModel only watches for object identity change 32747 if (ngOptions.trackBy) { 32748 scope.$watch( 32749 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 32750 function() { ngModelCtrl.$render(); } 32751 ); 32752 } 32753 32754 } else { 32755 32756 ngModelCtrl.$isEmpty = function(value) { 32757 return !value || value.length === 0; 32758 }; 32759 32760 32761 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 32762 options.items.forEach(function(option) { 32763 option.element.selected = false; 32764 }); 32765 32766 if (value) { 32767 value.forEach(function(item) { 32768 var option = options.getOptionFromViewValue(item); 32769 if (option && !option.disabled) option.element.selected = true; 32770 }); 32771 } 32772 }; 32773 32774 32775 selectCtrl.readValue = function readNgOptionsMultiple() { 32776 var selectedValues = selectElement.val() || [], 32777 selections = []; 32778 32779 forEach(selectedValues, function(value) { 32780 var option = options.selectValueMap[value]; 32781 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 32782 }); 32783 32784 return selections; 32785 }; 32786 32787 // If we are using `track by` then we must watch these tracked values on the model 32788 // since ngModel only watches for object identity change 32789 if (ngOptions.trackBy) { 32790 32791 scope.$watchCollection(function() { 32792 if (isArray(ngModelCtrl.$viewValue)) { 32793 return ngModelCtrl.$viewValue.map(function(value) { 32794 return ngOptions.getTrackByValue(value); 32795 }); 32796 } 32797 }, function() { 32798 ngModelCtrl.$render(); 32799 }); 32800 32801 } 32802 } 32803 32804 32805 if (providedEmptyOption) { 32806 32807 // we need to remove it before calling selectElement.empty() because otherwise IE will 32808 // remove the label from the element. wtf? 32809 emptyOption.remove(); 32810 32811 // compile the element since there might be bindings in it 32812 $compile(emptyOption)(scope); 32813 32814 // remove the class, which is added automatically because we recompile the element and it 32815 // becomes the compilation root 32816 emptyOption.removeClass('ng-scope'); 32817 } else { 32818 emptyOption = jqLite(optionTemplate.cloneNode(false)); 32819 } 32820 32821 // We need to do this here to ensure that the options object is defined 32822 // when we first hit it in writeNgOptionsValue 32823 updateOptions(); 32824 32825 // We will re-render the option elements if the option values or labels change 32826 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 32827 32828 // ------------------------------------------------------------------ // 32829 32830 32831 function updateOptionElement(option, element) { 32832 option.element = element; 32833 element.disabled = option.disabled; 32834 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive 32835 // selects in certain circumstances when multiple selects are next to each other and display 32836 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 32837 // See https://github.com/angular/angular.js/issues/11314 for more info. 32838 // This is unfortunately untestable with unit / e2e tests 32839 if (option.label !== element.label) { 32840 element.label = option.label; 32841 element.textContent = option.label; 32842 } 32843 if (option.value !== element.value) element.value = option.selectValue; 32844 } 32845 32846 function addOrReuseElement(parent, current, type, templateElement) { 32847 var element; 32848 // Check whether we can reuse the next element 32849 if (current && lowercase(current.nodeName) === type) { 32850 // The next element is the right type so reuse it 32851 element = current; 32852 } else { 32853 // The next element is not the right type so create a new one 32854 element = templateElement.cloneNode(false); 32855 if (!current) { 32856 // There are no more elements so just append it to the select 32857 parent.appendChild(element); 32858 } else { 32859 // The next element is not a group so insert the new one 32860 parent.insertBefore(element, current); 32861 } 32862 } 32863 return element; 32864 } 32865 32866 32867 function removeExcessElements(current) { 32868 var next; 32869 while (current) { 32870 next = current.nextSibling; 32871 jqLiteRemove(current); 32872 current = next; 32873 } 32874 } 32875 32876 32877 function skipEmptyAndUnknownOptions(current) { 32878 var emptyOption_ = emptyOption && emptyOption[0]; 32879 var unknownOption_ = unknownOption && unknownOption[0]; 32880 32881 // We cannot rely on the extracted empty option being the same as the compiled empty option, 32882 // because the compiled empty option might have been replaced by a comment because 32883 // it had an "element" transclusion directive on it (such as ngIf) 32884 if (emptyOption_ || unknownOption_) { 32885 while (current && 32886 (current === emptyOption_ || 32887 current === unknownOption_ || 32888 current.nodeType === NODE_TYPE_COMMENT || 32889 (nodeName_(current) === 'option' && current.value === ''))) { 32890 current = current.nextSibling; 32891 } 32892 } 32893 return current; 32894 } 32895 32896 32897 function updateOptions() { 32898 32899 var previousValue = options && selectCtrl.readValue(); 32900 32901 options = ngOptions.getOptions(); 32902 32903 var groupMap = {}; 32904 var currentElement = selectElement[0].firstChild; 32905 32906 // Ensure that the empty option is always there if it was explicitly provided 32907 if (providedEmptyOption) {
32908 selectElement.prepend(emptyOption); 32909 } 32910 32911 currentElement = skipEmptyAndUnknownOptions(currentElement); 32912 32913 options.items.forEach(function updateOption(option) { 32914 var group; 32915 var groupElement; 32916 var optionElement; 32917 32918 if (isDefined(option.group)) { 32919 32920 // This option is to live in a group 32921 // See if we have already created this group 32922 group = groupMap[option.group]; 32923 32924 if (!group) { 32925 32926 // We have not already created this group 32927 groupElement = addOrReuseElement(selectElement[0], 32928 currentElement, 32929 'optgroup', 32930 optGroupTemplate); 32931 // Move to the next element 32932 currentElement = groupElement.nextSibling; 32933 32934 // Update the label on the group element 32935 groupElement.label = option.group; 32936 32937 // Store it for use later 32938 group = groupMap[option.group] = { 32939 groupElement: groupElement, 32940 currentOptionElement: groupElement.firstChild 32941 }; 32942 32943 } 32944 32945 // So now we have a group for this option we add the option to the group 32946 optionElement = addOrReuseElement(group.groupElement, 32947 group.currentOptionElement, 32948 'option', 32949 optionTemplate); 32950 updateOptionElement(option, optionElement); 32951 // Move to the next element 32952 group.currentOptionElement = optionElement.nextSibling; 32953 32954 } else { 32955 32956 // This option is not in a group 32957 optionElement = addOrReuseElement(selectElement[0], 32958 currentElement, 32959 'option', 32960 optionTemplate); 32961 updateOptionElement(option, optionElement); 32962 // Move to the next element 32963 currentElement = optionElement.nextSibling; 32964 } 32965 }); 32966 32967 32968 // Now remove all excess options and group 32969 Object.keys(groupMap).forEach(function(key) { 32970 removeExcessElements(groupMap[key].currentOptionElement); 32971 }); 32972 removeExcessElements(currentElement); 32973 32974 ngModelCtrl.$render(); 32975 32976 // Check to see if the value has changed due to the update to the options 32977 if (!ngModelCtrl.$isEmpty(previousValue)) { 32978 var nextValue = selectCtrl.readValue(); 32979 var isNotPrimitive = ngOptions.trackBy || multiple; 32980 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 32981 ngModelCtrl.$setViewValue(nextValue); 32982 ngModelCtrl.$render(); 32983 } 32984 } 32985 32986 } 32987 } 32988 32989 return { 32990 restrict: 'A', 32991 terminal: true, 32992 require: ['select', 'ngModel'], 32993 link: { 32994 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { 32995 // Deactivate the SelectController.register method to prevent 32996 // option directives from accidentally registering themselves 32997 // (and unwanted $destroy handlers etc.) 32998 ctrls[0].registerOption = noop; 32999 }, 33000 post: ngOptionsPostLink 33001 } 33002 }; 33003}]; 33004 33005/** 33006 * @ngdoc directive 33007 * @name ngPluralize 33008 * @restrict EA 33009 * 33010 * @description 33011 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 33012 * These rules are bundled with angular.js, but can be overridden 33013 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 33014 * by specifying the mappings between 33015 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 33016 * and the strings to be displayed. 33017 * 33018 * # Plural categories and explicit number rules 33019 * There are two 33020 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 33021 * in Angular's default en-US locale: "one" and "other". 33022 * 33023 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 33024 * any number that is not 1), an explicit number rule can only match one number. For example, the 33025 * explicit number rule for "3" matches the number 3. There are examples of plural categories 33026 * and explicit number rules throughout the rest of this documentation. 33027 * 33028 * # Configuring ngPluralize 33029 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 33030 * You can also provide an optional attribute, `offset`. 33031 * 33032 * The value of the `count` attribute can be either a string or an {@link guide/expression 33033 * Angular expression}; these are evaluated on the current scope for its bound value. 33034 * 33035 * The `when` attribute specifies the mappings between plural categories and the actual 33036 * string to be displayed. The value of the attribute should be a JSON object. 33037 * 33038 * The following example shows how to configure ngPluralize: 33039 * 33040 * ```html 33041 * <ng-pluralize count="personCount"
33042 when="{'0': 'Nobody is viewing.', 33043 * 'one': '1 person is viewing.', 33044 * 'other': '{} people are viewing.'}"> 33045 * </ng-pluralize> 33046 *``` 33047 * 33048 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 33049 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 33050 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 33051 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 33052 * show "a dozen people are viewing". 33053 * 33054 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 33055 * into pluralized strings. In the previous example, Angular will replace `{}` with 33056 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 33057 * for <span ng-non-bindable>{{numberExpression}}</span>. 33058 * 33059 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 33060 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 33061 * 33062 * # Configuring ngPluralize with offset 33063 * The `offset` attribute allows further customization of pluralized text, which can result in 33064 * a better user experience. For example, instead of the message "4 people are viewing this document", 33065 * you might display "John, Kate and 2 others are viewing this document". 33066 * The offset attribute allows you to offset a number by any desired value. 33067 * Let's take a look at an example: 33068 * 33069 * ```html 33070 * <ng-pluralize count="personCount" offset=2 33071 * when="{'0': 'Nobody is viewing.', 33072 * '1': '{{person1}} is viewing.', 33073 * '2': '{{person1}} and {{person2}} are viewing.', 33074 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 33075 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 33076 * </ng-pluralize> 33077 * ``` 33078 * 33079 * Notice that we are still using two plural categories(one, other), but we added 33080 * three explicit number rules 0, 1 and 2. 33081 * When one person, perhaps John, views the document, "John is viewing" will be shown. 33082 * When three people view the document, no explicit number rule is found, so 33083 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 33084 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 33085 * is shown. 33086 * 33087 * Note that when you specify offsets, you must provide explicit number rules for 33088 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 33089 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 33090 * plural categories "one" and "other". 33091 * 33092 * @param {string|expression} count The variable to be bound to. 33093 * @param {string} when The mapping between plural category to its corresponding strings. 33094 * @param {number=} offset Offset to deduct from the total number. 33095 * 33096 * @example 33097 <example module="pluralizeExampl
vendor: 4,913 bytes, lines 33097-33211
33097e"> 33098 <file name="index.html"> 33099 <script> 33100 angular.module('pluralizeExample', []) 33101 .controller('ExampleController', ['$scope', function($scope) { 33102 $scope.person1 = 'Igor'; 33103 $scope.person2 = 'Misko'; 33104 $scope.personCount = 1; 33105 }]); 33106 </script> 33107 <div ng-controller="ExampleController"> 33108 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 33109 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 33110 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 33111 33112 <!--- Example with simple pluralization rules for en locale ---> 33113 Without Offset: 33114 <ng-pluralize count="personCount" 33115 when="{'0': 'Nobody is viewing.', 33116 'one': '1 person is viewing.', 33117 'other': '{} people are viewing.'}"> 33118 </ng-pluralize><br> 33119 33120 <!--- Example with offset ---> 33121 With Offset(2): 33122 <ng-pluralize count="personCount" offset=2 33123 when="{'0': 'Nobody is viewing.', 33124 '1': '{{person1}} is viewing.', 33125 '2': '{{person1}} and {{person2}} are viewing.', 33126 'one': '{{person1}}, {{person2}} and one other person are viewing.', 33127 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 33128 </ng-pluralize> 33129 </div> 33130 </file> 33131 <file name="protractor.js" type="protractor"> 33132 it('should show correct pluralized string', function() { 33133 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 33134 var withOffset = element.all(by.css('ng-pluralize')).get(1); 33135 var countInput = element(by.model('personCount')); 33136 33137 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 33138 expect(withOffset.getText()).toEqual('Igor is viewing.'); 33139 33140 countInput.clear(); 33141 countInput.sendKeys('0'); 33142 33143 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 33144 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 33145 33146 countInput.clear(); 33147 countInput.sendKeys('2'); 33148 33149 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 33150 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 33151 33152 countInput.clear(); 33153 countInput.sendKeys('3'); 33154 33155 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 33156 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 33157 33158 countInput.clear(); 33159 countInput.sendKeys('4'); 33160 33161 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 33162 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 33163 }); 33164 it('should show data-bound names', function() { 33165 var withOffset = element.all(by.css('ng-pluralize')).get(1); 33166 var personCount = element(by.model('personCount')); 33167 var person1 = element(by.model('person1')); 33168 var person2 = element(by.model('person2')); 33169 personCount.clear(); 33170 personCount.sendKeys('4'); 33171 person1.clear(); 33172 person1.sendKeys('Di'); 33173 person2.clear(); 33174 person2.sendKeys('Vojta'); 33175 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 33176 }); 33177 </file> 33178 </example> 33179 */ 33180var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 33181 var BRACE = /{}/g, 33182 IS_WHEN = /^when(Minus)?(.+)$/; 33183 33184 return { 33185 link: function(scope, element, attr) { 33186 var numberExp = attr.count, 33187 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 33188 offset = attr.offset || 0, 33189 whens = scope.$eval(whenExp) || {}, 33190 whensExpFns = {}, 33191 startSymbol = $interpolate.startSymbol(), 33192 endSymbol = $interpolate.endSymbol(), 33193 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 33194 watchRemover = angular.noop, 33195 lastCount; 33196 33197 forEach(attr, function(expression, attributeName) { 33198 var tmpMatch = IS_WHEN.exec(attributeName); 33199 if (tmpMatch) { 33200 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 33201 whens[whenKey] = element.attr(attr.$attr[attributeName]); 33202 } 33203 }); 33204 forEach(whens, function(expression, key) { 33205 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 33206 33207 }); 33208 33209 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 33210 var count = parseFloat(newVal); 33211 var countIsNaN = isN
vendor: 9,019 bytes, lines 33211-33419
33211aN(count); 33212 33213 if (!countIsNaN && !(count in whens)) { 33214 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 33215 // Otherwise, check it against pluralization rules in $locale service. 33216 count = $locale.pluralCat(count - offset); 33217 } 33218 33219 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 33220 // In JS `NaN !== NaN`, so we have to explicitly check. 33221 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 33222 watchRemover(); 33223 var whenExpFn = whensExpFns[count]; 33224 if (isUndefined(whenExpFn)) { 33225 if (newVal != null) { 33226 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 33227 } 33228 watchRemover = noop; 33229 updateElementText(); 33230 } else { 33231 watchRemover = scope.$watch(whenExpFn, updateElementText); 33232 } 33233 lastCount = count; 33234 } 33235 }); 33236 33237 function updateElementText(newText) { 33238 element.text(newText || ''); 33239 } 33240 } 33241 }; 33242}]; 33243 33244/** 33245 * @ngdoc directive 33246 * @name ngRepeat 33247 * @multiElement 33248 * 33249 * @description 33250 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 33251 * instance gets its own scope, where the given loop variable is set to the current collection item, 33252 * and `$index` is set to the item index or key. 33253 * 33254 * Special properties are exposed on the local scope of each template instance, including: 33255 * 33256 * | Variable | Type | Details | 33257 * |-----------|-----------------|-----------------------------------------------------------------------------| 33258 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 33259 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 33260 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 33261 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 33262 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 33263 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 33264 * 33265 * <div class="alert alert-info"> 33266 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 33267 * This may be useful when, for instance, nesting ngRepeats. 33268 * </div> 33269 * 33270 * 33271 * # Iterating over object properties 33272 * 33273 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 33274 * syntax: 33275 * 33276 * ```js 33277 * <div ng-repeat="(key, value) in myObj"> ... </div> 33278 * ``` 33279 * 33280 * However, there are a limitations compared to array iteration: 33281 * 33282 * - The JavaScript specification does not define the order of keys 33283 * returned for an object, so Angular relies on the order returned by the browser 33284 * when running `for key in myObj`. Browsers generally follow the strategy of providing 33285 * keys in the order in which they were defined, although there are exceptions when keys are deleted 33286 * and reinstated. See the 33287 * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). 33288 * 33289 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because 33290 * it's a prefix used by Angular for public (`$`) and private (`$$`) properties. 33291 * 33292 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with 33293 * objects, and will throw if used with one. 33294 * 33295 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array 33296 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 33297 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 33298 * or implement a `$watch` on the object yourself. 33299 * 33300 * 33301 * # Tracking and Duplicates 33302 * 33303 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in 33304 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM: 33305 * 33306 * * When an item is added, a new instance of the template is added to the DOM. 33307 * * When an item is removed, its template instance is removed from the DOM. 33308 * * When items are reordered, their respective templates are reordered in the DOM. 33309 * 33310 * To minimize creation of DOM elements, `ngRepeat` uses a function 33311 * to "keep track" of all items in the collection and their corresponding DOM elements. 33312 * For example, if an item is added to the collection, ngRepeat will know that all other items 33313 * already have DOM elements, and will not re-render them. 33314 * 33315 * The default tracking function (which tracks items by their identity) does not allow 33316 * duplicate items in arrays. This is because when there are duplicates, it is not possible 33317 * to maintain a one-to-one mapping between collection items and DOM elements. 33318 * 33319 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 33320 * with your own using the `track by` expression. 33321 * 33322 * For example, you may track items by the index of each item in the collection, using the 33323 * special scope property `$index`: 33324 * ```html 33325 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 33326 * {{n}} 33327 * </div> 33328 * ``` 33329 * 33330 * You may also use arbitrary expressions in `track by`, including references to custom functions 33331 * on the scope: 33332 * ```html 33333 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 33334 * {{n}} 33335 * </div> 33336 * ``` 33337 * 33338 * <div class="alert alert-success"> 33339 * If you are working with objects that have an identifier property, you should track 33340 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 33341 * will not have to rebuild the DOM elements for items it has already rendered, even if the 33342 * JavaScript objects in the collection have been substituted for new ones. For large collections, 33343 * this significantly improves rendering performance. If you don't have a unique identifier, 33344 * `track by $index` can also provide a performance boost. 33345 * </div> 33346 * ```html 33347 * <div ng-repeat="model in collection track by model.id"> 33348 * {{model.name}} 33349 * </div> 33350 * ``` 33351 * 33352 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 33353 * `$id` function, which tracks items by their identity: 33354 * ```html 33355 * <div ng-repeat="obj in collection track by $id(obj)"> 33356 * {{obj.prop}} 33357 * </div> 33358 * ``` 33359 * 33360 * <div class="alert alert-warning"> 33361 * **Note:** `track by` must always be the last expression: 33362 * </div> 33363 * ``` 33364 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 33365 * {{model.name}} 33366 * </div> 33367 * ``` 33368 * 33369 * # Special repeat start and end points 33370 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 33371 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 33372 * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) 33373 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 33374 * 33375 * The example below makes use of this feature: 33376 * ```html 33377 * <header ng-repeat-start="item in items"> 33378 * Header {{ item }} 33379 * </header> 33380 * <div class="body"> 33381 * Body {{ item }} 33382 * </div> 33383 * <footer ng-repeat-end> 33384 * Footer {{ item }} 33385 * </footer> 33386 * ``` 33387 * 33388 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 33389 * ```html 33390 * <header> 33391 * Header A 33392 * </header> 33393 * <div class="body"> 33394 * Body A 33395 * </div> 33396 * <footer> 33397 * Footer A 33398 * </footer> 33399 * <header> 33400 * Header B 33401 * </header> 33402 * <div class="body"> 33403 * Body B 33404 * </div> 33405 * <footer> 33406 * Footer B 33407 * </footer> 33408 * ``` 33409 * 33410 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 33411 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 33412 * 33413 * @animations 33414 * | Animation | Occurs | 33415 * |----------------------------------|-------------------------------------| 33416 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter | 33417 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out | 33418 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered | 33419 *
33420 * See the example below for defining CSS animations with ngRepeat. 33421 * 33422 * @element ANY 33423 * @scope 33424 * @priority 1000 33425 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 33426 * formats are currently supported: 33427 * 33428 * * `variable in expression` â where variable is the user defined loop variable and `expression` 33429 * is a scope expression giving the collection to enumerate. 33430 * 33431 * For example: `album in artist.albums`. 33432 * 33433 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 33434 * and `expression` is the scope expression giving the collection to enumerate. 33435 * 33436 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 33437 * 33438 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 33439 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 33440 * is specified, ng-repeat associates elements by identity. It is an error to have 33441 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 33442 * mapped to the same DOM element, which is not possible.) 33443 * 33444 * Note that the tracking expression must come last, after any filters, and the alias expression. 33445 * 33446 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 33447 * will be associated by item identity in the array. 33448 * 33449 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 33450 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 33451 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 33452 * element in the same way in the DOM. 33453 * 33454 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 33455 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 33456 * property is same. 33457 * 33458 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 33459 * to items in conjunction with a tracking expression. 33460 * 33461 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 33462 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 33463 * when a filter is active on the repeater, but the filtered result set is empty. 33464 * 33465 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 33466 * the items have been processed through the filter. 33467 * 33468 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end 33469 * (and not as operator, inside an expression). 33470 * 33471 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 33472 * 33473 * @example 33474 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed 33475 * results by name. New (entering) and removed (leaving) items are animated. 33476 <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true"> 33477 <file name="index.html"> 33478 <div ng-controller="repeatController"> 33479 I have {{friends.length}} friends. They are: 33480 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 33481 <ul class="example-animate-container"> 33482 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 33483 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 33484 </li> 33485 <li class="animate-repeat" ng-if="results.length == 0"> 33486 <strong>No results found...</strong> 33487 </li> 33488 </ul> 33489 </div> 33490 </file> 33491 <file name="script.js"> 33492 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { 33493 $scope.friends = [ 33494 {name:'John', age:25, gender:'boy'}, 33495 {name:'Jessie', age:30, gender:'girl'}, 33496 {name:'Johanna', age:28, gender:'girl'}, 33497 {name:'Joy', age:15, gender:'girl'}, 33498 {name:'Mary', age:28, gender:'girl'}, 33499 {name:'Peter', age:95, gender:'boy'}, 33500 {name:'Sebastian', age:50, gender:'boy'}, 33501 {name:'Erika', age:27, gender:'girl'}, 33502 {name:'Patrick', age:40, gender:'boy'}, 33503 {name:'Samantha', age:60, gender:'girl'} 33504 ]; 33505 }); 33506 </file> 33507 <file name="animations.css"> 33508 .example-animate-container { 33509 background:white; 33510 border:1px solid black; 33511 list-style:none; 33512 margin:0; 33513 padding:0 10px; 33514 } 33515 33516 .animate-repeat { 33517 line-height:30px; 33518 list-style:none; 33519 box-sizing:border-box; 33520 } 33521 33522 .animate-repeat.ng-move, 33523 .animate-repeat.ng-enter, 33524 .animate-repeat.ng-leave { 33525 transition:all linear 0.5s; 33526 } 33527 33528 .animate-repeat.ng-leave.ng-leave-active, 33529 .animate-repeat.ng-move, 33530 .animate-repeat.ng-enter { 33531 opacity:0; 33532 max-height:0; 33533 } 33534 33535 .animate-repeat.ng-leave,
33536 .animate-repeat.ng-move.ng-move-active, 33537 .animate-repeat.ng-enter.ng-enter-active { 33538 opacity:1; 33539 max-height:30px; 33540 } 33541 </file> 33542 <file name="protractor.js" type="protractor"> 33543 var friends = element.all(by.repeater('friend in friends')); 33544 33545 it('should render initial data set', function() { 33546 expect(friends.count()).toBe(10); 33547 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 33548 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 33549 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 33550 expect(element(by.binding('friends.length')).getText()) 33551 .toMatch("I have 10 friends. They are:"); 33552 }); 33553 33554 it('should update repeater when filter predicate changes', function() { 33555 expect(friends.count()).toBe(10); 33556 33557 element(by.model('q')).sendKeys('ma'); 33558 33559 expect(friends.count()).toBe(2); 33560 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 33561 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 33562 }); 33563 </file> 33564 </example> 33565 */ 33566var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) { 33567 var NG_REMOVED = '$$NG_REMOVED'; 33568 var ngRepeatMinErr = minErr('ngRepeat'); 33569 33570 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 33571 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 33572 scope[valueIdentifier] = value; 33573 if (keyIdentifier) scope[keyIdentifier] = key; 33574 scope.$index = index; 33575 scope.$first = (index === 0); 33576 scope.$last = (index === (arrayLength - 1)); 33577 scope.$middle = !(scope.$first || scope.$last); 33578 // jshint bitwise: false 33579 scope.$odd = !(scope.$even = (index&1) === 0); 33580 // jshint bitwise: true 33581 }; 33582 33583 var getBlockStart = function(block) { 33584 return block.clone[0]; 33585 }; 33586 33587 var getBlockEnd = function(block) { 33588 return block.clone[block.clone.length - 1]; 33589 }; 33590 33591 33592 return { 33593 restrict: 'A', 33594 multiElement: true, 33595 transclude: 'element', 33596 priority: 1000, 33597 terminal: true, 33598 $$tlb: true, 33599 compile: function ngRepeatCompile($element, $attr) { 33600 var expression = $attr.ngRepeat; 33601 var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression); 33602 33603 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); 33604 33605 if (!match) { 33606 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 33607 expression); 33608 } 33609 33610 var lhs = match[1]; 33611 var rhs = match[2]; 33612 var aliasAs = match[3]; 33613 var trackByExp = match[4]; 33614 33615 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 33616 33617 if (!match) { 33618 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 33619 lhs); 33620 } 33621 var valueIdentifier = match[3] || match[1]; 33622 var keyIdentifier = match[2]; 33623 33624 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 33625 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 33626 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.", 33627 aliasAs); 33628 } 33629 33630 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 33631 var hashFnLocals = {$id: hashKey}; 33632 33633 if (trackByExp) { 33634 trackByExpGetter = $parse(trackByExp); 33635 } else { 33636 trackByIdArrayFn = function(key, value) { 33637 return hashKey(value); 33638 }; 33639 trackByIdObjFn = function(key) { 33640 return key; 33641 }; 33642 } 33643 33644 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 33645 33646 if (trackByExpGetter) { 33647 trackByIdExpFn = function(key, value, index) {
vendor: 11,339 bytes, lines 33648-33899
33648 // assign key, value, and $index to the locals so that they can be used in hash functions 33649 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 33650 hashFnLocals[valueIdentifier] = value; 33651 hashFnLocals.$index = index; 33652 return trackByExpGetter($scope, hashFnLocals); 33653 }; 33654 } 33655 33656 // Store a list of elements from previous run. This is a hash where key is the item from the 33657 // iterator, and the value is objects with following properties. 33658 // - scope: bound scope 33659 // - element: previous element. 33660 // - index: position 33661 // 33662 // We are using no-proto object so that we don't need to guard against inherited props via 33663 // hasOwnProperty. 33664 var lastBlockMap = createMap(); 33665 33666 //watch props 33667 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 33668 var index, length, 33669 previousNode = $element[0], // node that cloned nodes should be inserted after 33670 // initialized to the comment node anchor 33671 nextNode, 33672 // Same as lastBlockMap but it has the current state. It will become the 33673 // lastBlockMap on the next iteration. 33674 nextBlockMap = createMap(), 33675 collectionLength, 33676 key, value, // key/value of iteration 33677 trackById, 33678 trackByIdFn, 33679 collectionKeys, 33680 block, // last object information {scope, element, id} 33681 nextBlockOrder, 33682 elementsToRemove; 33683 33684 if (aliasAs) { 33685 $scope[aliasAs] = collection; 33686 } 33687 33688 if (isArrayLike(collection)) { 33689 collectionKeys = collection; 33690 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 33691 } else { 33692 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 33693 // if object, extract keys, in enumeration order, unsorted 33694 collectionKeys = []; 33695 for (var itemKey in collection) { 33696 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 33697 collectionKeys.push(itemKey); 33698 } 33699 } 33700 } 33701 33702 collectionLength = collectionKeys.length; 33703 nextBlockOrder = new Array(collectionLength); 33704 33705 // locate existing items 33706 for (index = 0; index < collectionLength; index++) { 33707 key = (collection === collectionKeys) ? index : collectionKeys[index]; 33708 value = collection[key]; 33709 trackById = trackByIdFn(key, value, index); 33710 if (lastBlockMap[trackById]) { 33711 // found previously seen block 33712 block = lastBlockMap[trackById]; 33713 delete lastBlockMap[trackById]; 33714 nextBlockMap[trackById] = block; 33715 nextBlockOrder[index] = block; 33716 } else if (nextBlockMap[trackById]) { 33717 // if collision detected. restore lastBlockMap and throw an error 33718 forEach(nextBlockOrder, function(block) { 33719 if (block && block.scope) lastBlockMap[block.id] = block; 33720 }); 33721 throw ngRepeatMinErr('dupes', 33722 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 33723 expression, trackById, value); 33724 } else { 33725 // new never before seen block 33726 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 33727 nextBlockMap[trackById] = true; 33728 } 33729 } 33730 33731 // remove leftover items 33732 for (var blockKey in lastBlockMap) { 33733 block = lastBlockMap[blockKey]; 33734 elementsToRemove = getBlockNodes(block.clone); 33735 $animate.leave(elementsToRemove); 33736 if (elementsToRemove[0].parentNode) { 33737 // if the element was not removed yet because of pending animation, mark it as deleted 33738 // so that we can ignore it later 33739 for (index = 0, length = elementsToRemove.length; index < length; index++) { 33740 elementsToRemove[index][NG_REMOVED] = true; 33741 } 33742 } 33743 block.scope.$destroy(); 33744 } 33745 33746 // we are not using forEach for perf reasons (trying to avoid #call) 33747 for (index = 0; index < collectionLength; index++) { 33748 key = (collection === collectionKeys) ? index : collectionKeys[index]; 33749 value = collection[key]; 33750 block = nextBlockOrder[index]; 33751 33752 if (block.scope) { 33753 // if we have already seen this object, then we need to reuse the 33754 // associated scope/element 33755 33756 nextNode = previousNode; 33757 33758 // skip nodes that are already pending removal via leave animation 33759 do { 33760 nextNode = nextNode.nextSibling; 33761 } while (nextNode && nextNode[NG_REMOVED]); 33762 33763 if (getBlockStart(block) != nextNode) { 33764 // existing item which got moved 33765 $animate.move(getBlockNodes(block.clone), null, previousNode); 33766 } 33767 previousNode = getBlockEnd(block); 33768 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 33769 } else { 33770 // new item which we don't know about 33771 $transclude(function ngRepeatTransclude(clone, scope) { 33772 block.scope = scope; 33773 // http://jsperf.com/clone-vs-createcomment 33774 var endNode = ngRepeatEndComment.cloneNode(false); 33775 clone[clone.length++] = endNode; 33776 33777 $animate.enter(clone, null, previousNode); 33778 previousNode = endNode; 33779 // Note: We only need the first/last node of the cloned nodes. 33780 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 33781 // by a directive with templateUrl when its template arrives. 33782 block.clone = clone; 33783 nextBlockMap[block.id] = block; 33784 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 33785 }); 33786 } 33787 } 33788 lastBlockMap = nextBlockMap; 33789 }); 33790 }; 33791 } 33792 }; 33793}]; 33794 33795var NG_HIDE_CLASS = 'ng-hide'; 33796var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 33797/** 33798 * @ngdoc directive 33799 * @name ngShow 33800 * @multiElement 33801 * 33802 * @description 33803 * The `ngShow` directive shows or hides the given HTML element based on the expression 33804 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 33805 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 33806 * in AngularJS and sets the display style to none (using an !important flag). 33807 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 33808 * 33809 * ```html 33810 * <!-- when $scope.myValue is truthy (element is visible) --> 33811 * <div ng-show="myValue"></div> 33812 * 33813 * <!-- when $scope.myValue is falsy (element is hidden) --> 33814 * <div ng-show="myValue" class="ng-hide"></div> 33815 * ``` 33816 * 33817 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 33818 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 33819 * from the element causing the element not to appear hidden. 33820 * 33821 * ## Why is !important used? 33822 * 33823 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 33824 * can be easily overridden by heavier selectors. For example, something as simple 33825 * as changing the display style on a HTML list item would make hidden elements appear visible. 33826 * This also becomes a bigger issue when dealing with CSS frameworks. 33827 * 33828 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 33829 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 33830 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 33831 * 33832 * ### Overriding `.ng-hide` 33833 * 33834 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 33835 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 33836 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 33837 * with extra animation classes that can be added. 33838 * 33839 * ```css 33840 * .ng-hide:not(.ng-hide-animate) { 33841 * /* this is just another form of hiding an element */ 33842 * display: block!important; 33843 * position: absolute; 33844 * top: -9999px; 33845 * left: -9999px; 33846 * } 33847 * ``` 33848 * 33849 * By default you don't need to override in CSS anything and the animations will work around the display style. 33850 * 33851 * ## A note about animations with `ngShow` 33852 * 33853 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 33854 * is true and false. This system works like the animation system present with ngClass except that 33855 * you must also include the !important flag to override the display property 33856 * so that you can perform an animation when the element is hidden during the time of the animation. 33857 * 33858 * ```css 33859 * // 33860 * //a working example can be found at the bottom of this page 33861 * // 33862 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 33863 * /* this is required as of 1.3x to properly 33864 * apply all styling in a show/hide animation */ 33865 * transition: 0s linear all; 33866 * } 33867 * 33868 * .my-element.ng-hide-add-active, 33869 * .my-element.ng-hide-remove-active { 33870 * /* the transition is defined in the active class */ 33871 * transition: 1s linear all; 33872 * } 33873 * 33874 * .my-element.ng-hide-add { ... } 33875 * .my-element.ng-hide-add.ng-hide-add-active { ... } 33876 * .my-element.ng-hide-remove { ... } 33877 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 33878 * ``` 33879 * 33880 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display 33881 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 33882 * 33883 * @animations 33884 * | Animation | Occurs | 33885 * |----------------------------------|-------------------------------------| 33886 * | {@link $animate#addClass addClass} `.ng-hide` | after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden | 33887 * | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngShow` expression evaluates to a truthy value and just before contents are set to visible | 33888 * 33889 * @element ANY 33890 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 33891 * then the element is shown or hidden respectively. 33892 * 33893 * @example 33894 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 33895 <file name="index.html"> 33896 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 33897 <div> 33898 Show: 33899 <div class="check-element animate-show" ng-show="checked">
33900 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 33901 </div> 33902 </div> 33903 <div> 33904 Hide: 33905 <div class="check-element animate-show" ng-hide="checked"> 33906 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 33907 </div> 33908 </div> 33909 </file> 33910 <file name="glyphicons.css"> 33911 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 33912 </file> 33913 <file name="animations.css"> 33914 .animate-show { 33915 line-height: 20px; 33916 opacity: 1; 33917 padding: 10px; 33918 border: 1px solid black; 33919 background: white; 33920 } 33921 33922 .animate-show.ng-hide-add, .animate-show.ng-hide-remove { 33923 transition: all linear 0.5s; 33924 } 33925 33926 .animate-show.ng-hide { 33927 line-height: 0; 33928 opacity: 0; 33929 padding: 0 10px; 33930 } 33931 33932 .check-element { 33933 padding: 10px; 33934 border: 1px solid black; 33935 background: white; 33936 } 33937 </file> 33938 <file name="protractor.js" type="protractor"> 33939 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 33940 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 33941 33942 it('should check ng-show / ng-hide', function() { 33943 expect(thumbsUp.isDisplayed()).toBeFalsy(); 33944 expect(thumbsDown.isDisplayed()).toBeTruthy(); 33945 33946 element(by.model('checked')).click(); 33947 33948 expect(thumbsUp.isDisplayed()).toBeTruthy(); 33949 expect(thumbsDown.isDisplayed()).toBeFalsy(); 33950 }); 33951 </file> 33952 </example> 33953 */ 33954var ngShowDirective = ['$animate', function($animate) { 33955 return { 33956 restrict: 'A', 33957 multiElement: true, 33958 link: function(scope, element, attr) { 33959 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 33960 // we're adding a temporary, animation-specific class for ng-hide since this way 33961 // we can control when the element is actually displayed on screen without having 33962 // to have a global/greedy CSS selector that breaks when other animations are run. 33963 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 33964 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 33965 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 33966 }); 33967 }); 33968 } 33969 }; 33970}]; 33971 33972 33973/** 33974 * @ngdoc directive 33975 * @name ngHide 33976 * @multiElement 33977 * 33978 * @description 33979 * The `ngHide` directive shows or hides the given HTML element based on the expression 33980 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 33981 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 33982 * in AngularJS and sets the display style to none (using an !important flag). 33983 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 33984 * 33985 * ```html 33986 * <!-- when $scope.myValue is truthy (element is hidden) --> 33987 * <div ng-hide="myValue" class="ng-hide"></div> 33988 * 33989 * <!-- when $scope.myValue is falsy (element is visible) --> 33990 * <div ng-hide="myValue"></div> 33991 * ``` 33992 * 33993 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 33994 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 33995 * from the element causing the element not to appear hidden. 33996 * 33997 * ## Why is !important used? 33998 * 33999 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 34000 * can be easily overridden by heavier selectors. For example, something as simple 34001 * as changing the display style on a HTML list item would make hidden elements appear visible. 34002 * This also becomes a bigger issue when dealing with CSS frameworks. 34003 * 34004 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 34005 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 34006 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 34007 * 34008 * ### Overriding `.ng-hide` 34009 * 34010 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 34011 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 34012 * class in CSS: 34013 * 34014 * ```css 34015 * .ng-hide { 34016 * /* this is just another form of hiding an element */ 34017 * display: block!important; 34018 * position: absolute; 34019 * top: -9999px; 34020 * left: -9999px; 34021 * } 34022 * ``` 34023 * 34024 * By default you don't need to override in CSS anything and the animations will work around the display style. 34025 * 34026 * ## A note about animations with `ngHide` 34027 * 34028 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 34029 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 34030 * CSS class is added and removed for you instead of your own CSS class. 34031 * 34032 * ```css 34033 * // 34034 * //a working example can be found at the bottom of this page 34035 * // 34036 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 34037 * transition: 0.5s linear all; 34038 * } 34039 * 34040 * .my-element.ng-hide-add { ... } 34041 * .my-element.ng-hide-add.ng-hide-add-active { ... } 34042 * .my-element.ng-hide-remove { ... } 34043 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 34044 * ``` 34045 * 34046 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display 34047 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 34048 * 34049 * @animations 34050 * | Animation | Occurs | 34051 * |----------------------------------|-------------------------------------| 34052 * | {@link $animate#addClass addClass} `.ng-hide` | after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden | 34053 * | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible | 34054 * 34055 * 34056 * @element ANY 34057 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 34058 * the element is shown or hidden respectively. 34059 * 34060 * @example 34061 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 34062 <file name="index.html"> 34063 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 34064 <div> 34065 Show: 34066 <div class="check-element animate-hide" ng-show="checked">
34067 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 34068 </div> 34069 </div> 34070 <div> 34071 Hide: 34072 <div class="check-element animate-hide" ng-hide="checked"> 34073 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 34074 </div> 34075 </div> 34076 </file> 34077 <file name="glyphicons.css"> 34078 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 34079 </file> 34080 <file name="animations.css"> 34081 .animate-hide { 34082 transition: all linear 0.5s; 34083 line-height: 20px; 34084 opacity: 1; 34085 padding: 10px; 34086 border: 1px solid black; 34087 background: white; 34088 } 34089 34090 .animate-hide.ng-hide { 34091 line-height: 0; 34092 opacity: 0; 34093 padding: 0 10px; 34094 } 34095 34096 .check-element { 34097 padding: 10px; 34098 border: 1px solid black; 34099 background: white; 34100 } 34101 </file> 34102 <file name="protractor.js" type="protractor"> 34103 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 34104 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 34105 34106 it('should check ng-show / ng-hide', function() { 34107 expect(thumbsUp.isDisplayed()).toBeFalsy(); 34108 expect(thumbsDown.isDisplayed()).toBeTruthy(); 34109 34110 element(by.model('checked')).click(); 34111 34112 expect(thumbsUp.isDisplayed()).toBeTruthy(); 34113 expect(thumbsDown.isDisplayed()).toBeFalsy(); 34114 }); 34115 </file> 34116 </example> 34117 */ 34118var ngHideDirective = ['$animate', function($animate) { 34119 return { 34120 restrict: 'A', 34121 multiElement: true, 34122 link: function(scope, element, attr) { 34123 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 34124 // The comment inside of the ngShowDirective explains why we add and 34125 // remove a temporary class for the show/hide animation 34126 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 34127 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 34128 }); 34129 }); 34130 } 34131 }; 34132}]; 34133 34134/** 34135 * @ngdoc directive 34136 * @name ngStyle 34137 * @restrict AC 34138 * 34139 * @description 34140 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 34141 * 34142 * @element ANY 34143 * @param {expression} ngStyle 34144 * 34145 * {@link guide/expression Expression} which evals to an 34146 * object whose keys are CSS style names and values are corresponding values for those CSS 34147 * keys. 34148 * 34149 * Since some CSS style names are not valid keys for an object, they must be quoted. 34150 * See the 'background-color' style in the example below. 34151 * 34152 * @example 34153 <example> 34154 <file name="index.html"> 34155 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 34156 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 34157 <input type="button" value="clear" ng-click="myStyle={}"> 34158 <br/> 34159 <span ng-style="myStyle">Sample Text</span> 34160 <pre>myStyle={{myStyle}}</pre> 34161 </file> 34162 <file name="style.css"> 34163 span { 34164 color: black; 34165 } 34166 </file> 34167 <file name="protractor.js" type="protractor"> 34168 var colorSpan = element(by.css('span')); 34169 34170 it('should check ng-style', function() { 34171 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 34172 element(by.css('input[value=\'set color\']')).click(); 34173 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 34174 element(by.css('input[value=clear]')).click(); 34175 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 34176 }); 34177 </file> 34178 </example> 34179 */ 34180var ngStyleDirective = ngDirective(function(scope, element, attr) { 34181 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 34182 if (oldStyles && (newStyles !== oldStyles)) { 34183 forEach(oldStyles, function(val, style) { element.css(style, '');}); 34184 } 34185 if (newStyles) element.css(newStyles); 34186 }, true); 34187}); 34188 34189/** 34190 * @ngdoc directive 34191 * @name ngSwitch 34192 * @restrict EA 34193 * 34194 * @description 34195 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 34196 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 34197 * as specified in the template. 34198 * 34199 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 34200 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 34201 * matches the value obtained from the evaluated expression. In other words, you define a container element 34202 * (where you place the directive), place an expression on the **`on="..."` attribute** 34203 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 34204 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 34205 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 34206 * attribute is displayed. 34207 * 34208 * <div class="alert alert-info"> 34209 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 34210 * as literal string values to match against. 34211 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
34211omeVal"` not against the 34212 * value of the expression `$scope.someVal`. 34213 * </div> 34214 34215 * @animations 34216 * | Animation | Occurs | 34217 * |----------------------------------|-------------------------------------| 34218 * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container | 34219 * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM | 34220 * 34221 * @usage 34222 * 34223 * ``` 34224 * <ANY ng-switch="expression"> 34225 * <ANY ng-switch-when="matchValue1">...</ANY> 34226 * <ANY ng-switch-when="matchValue2">...</ANY> 34227 * <ANY ng-switch-default>...</ANY> 34228 * </ANY> 34229 * ``` 34230 * 34231 * 34232 * @scope 34233 * @priority 1200 34234 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 34235 * On child elements add: 34236 * 34237 * * `ngSwitchWhen`: the case statement to match against. If match then this 34238 * case will be displayed. If the same match appears multiple times, all the 34239 * elements will be displayed. 34240 * * `ngSwitchDefault`: the default case when no other case match. If there 34241 * are multiple default cases, all of them will be displayed when no other 34242 * case match. 34243 * 34244 * 34245 * @example 34246 <example module="switchExample" deps="angular-animate.js" animations="true"> 34247 <file name="index.html">
34248 <div ng-controller="ExampleController"> 34249 <select ng-model="selection" ng-options="item for item in items"> 34250 </select> 34251 <code>selection={{selection}}</code> 34252 <hr/> 34253 <div class="animate-switch-container" 34254 ng-switch on="selection"> 34255 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 34256 <div class="animate-switch" ng-switch-when="home">Home Span</div> 34257 <div class="animate-switch" ng-switch-default>default</div> 34258 </div> 34259 </div> 34260 </file> 34261 <file name="script.js"> 34262 angular.module('switchExample', ['ngAnimate']) 34263 .controller('ExampleController', ['$scope', function($scope) { 34264 $scope.items = ['settings', 'home', 'other']; 34265 $scope.selection = $scope.items[0]; 34266 }]); 34267 </file> 34268 <file name="animations.css"> 34269 .animate-switch-container { 34270 position:relative; 34271 background:white; 34272 border:1px solid black; 34273 height:40px; 34274 overflow:hidden; 34275 } 34276 34277 .animate-switch { 34278 padding:10px; 34279 } 34280 34281 .animate-switch.ng-animate { 34282 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 34283 34284 position:absolute; 34285 top:0; 34286 left:0; 34287 right:0; 34288 bottom:0; 34289 } 34290 34291 .animate-switch.ng-leave.ng-leave-active, 34292 .animate-switch.ng-enter { 34293 top:-50px; 34294 } 34295 .animate-switch.ng-leave, 34296 .animate-switch.ng-enter.ng-enter-active { 34297 top:0; 34298 } 34299 </file> 34300 <file name="protractor.js" type="protractor"> 34301 var switchElem = element(by.css('[ng-switch]')); 34302 var select = element(by.model('selection')); 34303 34304 it('should start in settings', function() { 34305 expect(switchElem.getText()).toMatch(/Settings Div/); 34306 }); 34307 it('should change to home', function() { 34308 select.all(by.css('option')).get(1).click(); 34309 expect(switchElem.getText()).toMatch(/Home Span/); 34310 }); 34311 it('should select default', function() { 34312 select.all(by.css('option')).get(2).click(); 34313 expect(switchElem.getText()).toMatch(/default/); 34314 }); 34315 </file> 34316 </example> 34317 */ 34318var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { 34319 return { 34320 require: 'ngSwitch', 34321 34322 // asks for $scope to fool the BC controller module 34323 controller: ['$scope', function ngSwitchController() { 34324 this.cases = {}; 34325 }], 34326 link: function(scope, element, attr, ngSwitchController) { 34327 var watchExpr = attr.ngSwitch || attr.on, 34328 selectedTranscludes = [], 34329 selectedElements = [], 34330 previousLeaveAnimations = [], 34331 selectedScopes = []; 34332 34333 var spliceFactory = function(array, index) { 34334 return function() { array.splice(index, 1); }; 34335 }; 34336 34337 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 34338 var i, ii; 34339 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 34340 $animate.cancel(previousLeaveAnimations[i]); 34341 } 34342 previousLeaveAnimations.length = 0; 34343 34344 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 34345 var selected = getBlockNodes(selectedElements[i].clone); 34346 selectedScopes[i].$destroy(); 34347 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 34348 promise.then(spliceFactory(previousLeaveAnimations, i)); 34349 } 34350 34351 selectedElements.length = 0; 34352 selectedScopes.length = 0; 34353 34354 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 34355 forEach(selectedTranscludes, function(selectedTransclude) { 34356 selectedTransclude.transclude(function(caseElement, selectedScope) { 34357 selectedScopes.push(selectedScope); 34358 var anchor = selectedTransclude.element; 34359 caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen'); 34360 var block = { clone: caseElement }; 34361 34362 selectedElements.push(block); 34363 $animate.enter(caseElement, anchor.parent(), anchor); 34364 }); 34365 }); 34366 } 34367 }); 34368 } 34369 }; 34370}]; 34371 34372var ngSwitchWhenDirective = ngDirective({ 34373 transclude: 'element', 34374 priority: 1200, 34375 require: '^ngSwitch', 34376 multiElement: true, 34377 link: function(scope, element, attrs, ctrl, $transclude) { 34378 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 34379 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 34380 } 34381}); 34382 34383var ngSwitchDefaultDirective = ngDirective({ 34384 transclude: 'element', 34385 priority: 1200, 34386 require: '^ngSwitch', 34387 multiElement: true, 34388 link: function(scope, element, attr, ctrl, $transclude) { 34389 ctrl.cases['?'] = (ctrl.cases['?'] || []); 34390 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 34391 } 34392}); 34393 34394/** 34395 * @ngdoc directive 34396 * @name ngTransclude 34397 * @restrict EAC 34398 * 34399 * @description 34400 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 34401 * 34402 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name 34403 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. 34404 * 34405 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing 34406 * content of this element will be removed before the transcluded content is inserted. 34407 * If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case 34408 * that no transcluded content is provided. 34409 * 34410 * @element ANY 34411 * 34412 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty 34413 * or its value is the same as the name of the attribute then the default slot is used. 34414 * 34415 * @example 34416 * ### Basic transclusion 34417 * This example demonstrates basic transclusion of content into a component directive. 34418 * <example name="simpleTranscludeExample" module="transcludeExample"> 34419 * <file name="index.html"> 34420 * <script> 34421 * angular.module('transcludeExample', []) 34422 * .directive('pane', function(){ 34423 * return { 34424 * restrict: 'E', 34425 * transclude: true, 34426 * scope: { title:'@' }, 34427 * template: '<div style="border: 1px solid black;">
34427' + 34428 * '<div style="background-color: gray">{{title}}</div>' + 34429 * '<ng-transclude></ng-transclude>' + 34430 * '</div>' 34431 * }; 34432 * }) 34433 * .controller('ExampleController', ['$scope', function($scope) { 34434 * $scope.title = 'Lorem Ipsum'; 34435 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 34436 * }]); 34437 * </script> 34438 * <div ng-controller="ExampleController"> 34439 * <input ng-model="title" aria-label="title"> <br/> 34440 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 34441 * <pane title="{{title}}"><span>{{text}}</span></pane> 34442 * </div> 34443 * </file> 34444 * <file name="protractor.js" type="protractor"> 34445 * it('should have transcluded', function() { 34446 * var titleElement = element(by.model('title')); 34447 * titleElement.clear(); 34448 * titleElement.sendKeys('TITLE'); 34449 * var textElement = element(by.model('text')); 34450 * textElement.clear(); 34451 * textElement.sendKeys('TEXT'); 34452 * expect(element(by.binding('title')).getText()).toEqual('TITLE'); 34453 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 34454 * }); 34455 * </file> 34456 * </example> 34457 * 34458 * @example 34459 * ### Transclude fallback content 34460 * This example shows how to use `NgTransclude` with fallback content, that 34461 * is displayed if no transcluded content is provided. 34462 * 34463 * <example module="transcludeFallbackContentExample"> 34464 * <file name="index.html"> 34465 * <script> 34466 * angular.module('transcludeFallbackContentExample', []) 34467 * .directive('myButton', function(){ 34468 * return { 34469 * restrict: 'E', 34470 * transclude: true, 34471 * scope: true, 34472 * template: '<button style="cursor: pointer;">' + 34473 * '<ng-transclude>' + 34474 * '<b style="color: red;">Button1</b>' + 34475 * '</ng-transclude>' + 34476 * '</button>' 34477 * }; 34478 * }); 34479 * </script> 34480 * <!-- fallback button content --> 34481 * <my-button id="fallback"></my-button> 34482 * <!-- modified button content --> 34483 * <my-button id="modified"> 34484 * <i style="color: green;">Button2</i> 34485 * </my-button> 34486 * </file> 34487 * <file name="protractor.js" type="protractor"> 34488 * it('should have different transclude element content', function() { 34489 * expect(element(by.id('fallback')).getText()).toBe('Button1'); 34490 * expect(element(by.id('modified')).getText()).toBe('Button2'); 34491 * }); 34492 * </file> 34493 * </example> 34494 * 34495 * @example 34496 * ### Multi-slot transclusion 34497 * This example demonstrates using multi-slot transclusion in a component directive. 34498 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample"> 34499 * <file name="index.html"> 34500 * <style> 34501 * .title, .footer { 34502 * background-color: gray 34503 * } 34504 * </style>
34505 * <div ng-controller="ExampleController"> 34506 * <input ng-model="title" aria-label="title"> <br/> 34507 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 34508 * <pane> 34509 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title> 34510 * <pane-body><p>{{text}}</p></pane-body> 34511 * </pane> 34512 * </div> 34513 * </file> 34514 * <file name="app.js"> 34515 * angular.module('multiSlotTranscludeExample', []) 34516 * .directive('pane', function(){ 34517 * return { 34518 * restrict: 'E', 34519 * transclude: { 34520 * 'title': '?paneTitle', 34521 * 'body': 'paneBody', 34522 * 'footer': '?paneFooter' 34523 * }, 34524 * template: '<div style="border: 1px solid black;">' + 34525 * '<div class="title" ng-transclude="title">Fallback Title</div>' + 34526 * '<div ng-transclude="body"></div>' + 34527 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' + 34528 * '</div>' 34529 * }; 34530 * }) 34531 * .controller('ExampleController', ['$scope', function($scope) { 34532 * $scope.title = 'Lorem Ipsum'; 34533 * $scope.link = "https://google.com"; 34534 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 34535 * }]); 34536 * </file> 34537 * <file name="protractor.js" type="protractor"> 34538 * it('should have transcluded the title and the body', function() { 34539 * var titleElement = element(by.model('title')); 34540 * titleElement.clear(); 34541 * titleElement.sendKeys('TITLE'); 34542 * var textElement = element(by.model('text')); 34543 * textElement.clear(); 34544 * textElement.sendKeys('TEXT'); 34545 * expect(element(by.css('.title')).getText()).toEqual('TITLE'); 34546 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 34547 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); 34548 * }); 34549 * </file> 34550 * </example> 34551 */ 34552var ngTranscludeMinErr = minErr('ngTransclude'); 34553var ngTranscludeDirective = ngDirective({ 34554 restrict: 'EAC', 34555 link: function($scope, $element, $attrs, controller, $transclude) { 34556 34557 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { 34558 // If the attribute is of the form: `ng-transclude="ng-transclude"` 34559 // then treat it like the default 34560 $attrs.ngTransclude = ''; 34561 } 34562 34563 function ngTranscludeCloneAttachFn(clone) { 34564 if (clone.length) { 34565 $element.empty(); 34566 $element.append(clone); 34567 } 34568 } 34569 34570 if (!$transclude) { 34571 throw ngTranscludeMinErr('orphan', 34572 'Illegal use of ngTransclude directive in the template! ' + 34573 'No parent directive that requires a transclusion found. ' + 34574 'Element: {0}', 34575 startingTag($element)); 34576 } 34577 34578 // If there is no slot name defined or the slot name is not optional 34579 // then transclude the slot 34580 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; 34581 $transclude(ngTranscludeCloneAttachFn, null, slotName); 34582 } 34583}); 34584 34585/** 34586 * @ngdoc directive 34587 * @name script 34588 * @restrict E 34589 * 34590 * @description 34591 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 34592 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 34593 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 34594 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 34595 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 34596 * 34597 * @param {string} type Must be set to `'text/ng-template'`. 34598 * @param {string} id Cache name of the template. 34599 * 34600 * @example 34601 <example> 34602 <file name="index.html"> 34603 <script type="text/ng-template" id="/tpl.html"> 34604 Content of the template. 34605 </script> 34606 34607 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 34608 <div id="tpl-content" ng-include src="currentTpl"></div> 34609 </file> 34610 <file name="protractor.js" type="protractor"> 34611 it('should load template defined inside script tag', function() {
34612 element(by.css('#tpl-link')).click(); 34613 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 34614 }); 34615 </file> 34616 </example> 34617 */ 34618var scriptDirective = ['$templateCache', function($templateCache) { 34619 return { 34620 restrict: 'E', 34621 terminal: true, 34622 compile: function(element, attr) { 34623 if (attr.type == 'text/ng-template') { 34624 var templateUrl = attr.id, 34625 text = element[0].text; 34626 34627 $templateCache.put(templateUrl, text); 34628 } 34629 } 34630 }; 34631}]; 34632 34633var noopNgModelController = { $setViewValue: noop, $render: noop }; 34634 34635function chromeHack(optionElement) { 34636 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 34637 // Adding an <option selected="selected"> element to a <select required="required"> should 34638 // automatically select the new element 34639 if (optionElement[0].hasAttribute('selected')) { 34640 optionElement[0].selected = true; 34641 } 34642} 34643 34644/** 34645 * @ngdoc type 34646 * @name select.SelectController 34647 * @description 34648 * The controller for the `<select>` directive. This provides support for reading 34649 * and writing the selected value(s) of the control and also coordinates dynamically 34650 * added `<option>` elements, perhaps by an `ngRepeat` directive. 34651 */ 34652var SelectController = 34653 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 34654 34655 var self = this, 34656 optionsMap = new HashMap(); 34657 34658 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 34659 self.ngModelCtrl = noopNgModelController; 34660 34661 // The "unknown" option is one that is prepended to the list if the viewValue 34662 // does not match any of the options. When it is rendered the value of the unknown 34663 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 34664 // 34665 // We can't just jqLite('<option>') since jqLite is not smart enough 34666 // to create it in <select> and IE barfs otherwise. 34667 self.unknownOption = jqLite(document.createElement('option')); 34668 self.renderUnknownOption = function(val) { 34669 var unknownVal = '? ' + hashKey(val) + ' ?'; 34670 self.unknownOption.val(unknownVal); 34671 $element.prepend(self.unknownOption); 34672 $element.val(unknownVal); 34673 }; 34674 34675 $scope.$on('$destroy', function() { 34676 // disable unknown option so that we don't do work when the whole select is being destroyed 34677 self.renderUnknownOption = noop; 34678 }); 34679 34680 self.removeUnknownOption = function() { 34681 if (self.unknownOption.parent()) self.unknownOption.remove(); 34682 }; 34683 34684 34685 // Read the value of the select control, the implementation of this changes depending 34686 // upon whether the select can have multiple values and whether ngOptions is at work. 34687 self.readValue = function readSingleValue() { 34688 self.removeUnknownOption(); 34689 return $element.val(); 34690 }; 34691 34692 34693 // Write the value to the select control, the implementation of this changes depending 34694 // upon whether the select can have multiple values and whether ngOptions is at work. 34695 self.writeValue = function writeSingleValue(value) { 34696 if (self.hasOption(value)) { 34697 self.removeUnknownOption(); 34698 $element.val(value); 34699 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 34700 } else { 34701 if (value == null && self.emptyOption) { 34702 self.removeUnknownOption(); 34703 $element.val(''); 34704 } else { 34705 self.renderUnknownOption(value); 34706 } 34707 } 34708 }; 34709 34710 34711 // Tell the select control that an option, with the given value, has been added 34712 self.addOption = function(value, element) { 34713 // Skip comment nodes, as they only pollute the `optionsMap` 34714 if (element[0].nodeType === NODE_TYPE_COMMENT) return; 34715 34716 assertNotHasOwnProperty(value, '"option value"'); 34717 if (value === '') { 34718 self.emptyOption = element; 34719 } 34720 var count = optionsMap.get(value) || 0; 34721 optionsMap.put(value, count + 1); 34722 self.ngModelCtrl.$render(); 34723 chromeHack(element); 34724 }; 34725 34726 // Tell the select control that an option, with the given value, has been removed 34727 self.removeOption = function(value) { 34728 var count = optionsMap.get(value); 34729 if (count) { 34730 if (count === 1) { 34731 optionsMap.remove(value); 34732 if (value === '') { 34733 self.emptyOption = undefined; 34734 } 34735 } else { 34736 optionsMap.put(value, count - 1); 34737 } 34738 } 34739 }; 34740 34741 // Check whether the select control has an option matching the given value 34742 self.hasOption = function(value) { 34743 return !!optionsMap.get(value); 34744 }; 34745 34746 34747 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) { 34748 34749 if (interpolateValueFn) { 34750 // The value attribute is interpolated 34751 var oldVal; 34752 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 34753 if (isDefined(oldVal)) { 34754 self.removeOption(oldVal); 34755 } 34756 oldVal = newVal; 34757 self.addOption(newVal, optionElement); 34758 }); 34759 } else if (interpolateTextFn) { 34760 // The text content is interpolated 34761 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) { 34762 optionAttrs.$set('value', newVal); 34763 if (oldVal !== newVal) { 34764 self.removeOption(oldVal); 34765 } 34766 self.addOption(newVal, optionElement); 34767 }); 34768 } else { 34769 // The value attribute is static 34770 self.addOption(optionAttrs.value, optionElement); 34771 } 34772 34773 optionElement.on('$destroy', function() { 34774 self.removeOption(optionAttrs.value); 34775 self.ngModelCtrl.$render(); 34776 }); 34777 }; 34778}]; 34779 34780/** 34781 * @ngdoc directive 34782 * @name select 34783 * @restrict E 34784 * 34785 * @description 34786 * HTML `SELECT` element with angular data-binding. 34787 * 34788 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 34789 * between the scope and the `<select>` control (including setting default values). 34790 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 34791 * {@link ngOptions `ngOptions`} directives. 34792 * 34793 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 34794 * to the model identified by the `ngModel` directive. With static or repeated o
34794ptions, this is 34795 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 34796 * If you want dynamic value attributes, you can use interpolation inside the value attribute. 34797 * 34798 * <div class="alert alert-warning"> 34799 * Note that the value of a `select` directive used without `ngOptions` is always a string. 34800 * When the model needs to be bound to a non-string value, you must either explicitly convert it 34801 * using a directive (see example below) or use `ngOptions` to specify the set of options. 34802 * This is because an option element can only be bound to string values at present. 34803 * </div> 34804 * 34805 * If the viewValue of `ngModel` does not match any of the options, then the control 34806 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 34807 * 34808 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 34809 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 34810 * option. See example below for demonstration. 34811 * 34812 * <div class="alert alert-info"> 34813 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 34814 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as 34815 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 34816 * comprehension expression, and additionally in reducing memory and increasing speed by not creating 34817 * a new scope for each repeated instance. 34818 * </div> 34819 * 34820 * 34821 * @param {string} ngModel Assignable angular expression to data-bind to. 34822 * @param {string=} name Property name of the form under which the control is published. 34823 * @param {string=} multiple Allows multiple options to be selected. The selected values will be 34824 * bound to the model as an array. 34825 * @param {string=} required Sets `required` validation error key if the value is not entered. 34826 * @param {string=} ngRequired Adds required attribute and required validation constraint to 34827 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 34828 * when you want to data-bind to the required attribute. 34829 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user 34830 * interaction with the select element. 34831 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 34832 * set on the model on selection. See {@link ngOptions `ngOptions`}. 34833 * 34834 * @example 34835 * ### Simple `select` elements with static options 34836 * 34837 * <example name="static-select" module="staticSelect"> 34838 * <file name="index.html">
34839 * <div ng-controller="ExampleController"> 34840 * <form name="myForm"> 34841 * <label for="singleSelect"> Single select: </label><br> 34842 * <select name="singleSelect" ng-model="data.singleSelect"> 34843 * <option value="option-1">Option 1</option> 34844 * <option value="option-2">Option 2</option> 34845 * </select><br> 34846 * 34847 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 34848 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 34849 * <option value="">---Please select---</option> <!-- not selected / blank option --> 34850 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 34851 * <option value="option-2">Option 2</option> 34852 * </select><br> 34853 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 34854 * <tt>singleSelect = {{data.singleSelect}}</tt> 34855 * 34856 * <hr> 34857 * <label for="multipleSelect"> Multiple select: </label><br> 34858 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 34859 * <option value="option-1">Option 1</option> 34860 * <option value="option-2">Option 2</option> 34861 * <option value="option-3">Option 3</option> 34862 * </select><br> 34863 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 34864 * </form> 34865 * </div> 34866 * </file> 34867 * <file name="app.js"> 34868 * angular.module('staticSelect', []) 34869 * .controller('ExampleController', ['$scope', function($scope) { 34870 * $scope.data = { 34871 * singleSelect: null, 34872 * multipleSelect: [], 34873 * option1: 'option-1', 34874 * }; 34875 * 34876 * $scope.forceUnknownOption = function() { 34877 * $scope.data.singleSelect = 'nonsense'; 34878 * }; 34879 * }]); 34880 * </file> 34881 *</example> 34882 * 34883 * ### Using `ngRepeat` to generate `select` options 34884 * <example name="ngrepeat-select" module="ngrepeatSelect"> 34885 * <file name="index.html">
34886 * <div ng-controller="ExampleController"> 34887 * <form name="myForm"> 34888 * <label for="repeatSelect"> Repeat select: </label> 34889 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> 34890 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 34891 * </select> 34892 * </form> 34893 * <hr> 34894 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> 34895 * </div> 34896 * </file> 34897 * <file name="app.js"> 34898 * angular.module('ngrepeatSelect', []) 34899 * .controller('ExampleController', ['$scope', function($scope) { 34900 * $scope.data = { 34901 * repeatSelect: null, 34902 * availableOptions: [ 34903 * {id: '1', name: 'Option A'}, 34904 * {id: '2', name: 'Option B'}, 34905 * {id: '3', name: 'Option C'} 34906 * ], 34907 * }; 34908 * }]); 34909 * </file> 34910 *</example> 34911 * 34912 * 34913 * ### Using `select` with `ngOptions` and setting a default value 34914 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 34915 * 34916 * <example name="select-with-default-values" module="defaultValueSelect"> 34917 * <file name="index.html">
34918 * <div ng-controller="ExampleController"> 34919 * <form name="myForm"> 34920 * <label for="mySelect">Make a choice:</label> 34921 * <select name="mySelect" id="mySelect" 34922 * ng-options="option.name for option in data.availableOptions track by option.id" 34923 * ng-model="data.selectedOption"></select> 34924 * </form> 34925 * <hr> 34926 * <tt>option = {{data.selectedOption}}</tt><br/> 34927 * </div> 34928 * </file> 34929 * <file name="app.js"> 34930 * angular.module('defaultValueSelect', []) 34931 * .controller('ExampleController', ['$scope', function($scope) { 34932 * $scope.data = { 34933 * availableOptions: [ 34934 * {id: '1', name: 'Option A'}, 34935 * {id: '2', name: 'Option B'}, 34936 * {id: '3', name: 'Option C'} 34937 * ], 34938 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 34939 * }; 34940 * }]); 34941 * </file> 34942 *</example> 34943 * 34944 * 34945 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 34946 * 34947 * <example name="select-with-non-string-options" module="nonStringSelect"> 34948 * <file name="index.html"> 34949 * <select ng-model="model.id" convert-to-number> 34950 * <option value="0">Zero</option> 34951 * <option value="1">One</option> 34952 * <option value="2">Two</option> 34953 * </select> 34954 * {{ model }} 34955 * </file> 34956 * <file name="app.js"> 34957 * angular.module('nonStringSelect', []) 34958 * .run(function($rootScope) { 34959 * $rootScope.model = { id: 2 }; 34960 * }) 34961 * .directive('convertToNumber', function() { 34962 * return { 34963 * require: 'ngModel', 34964 * link: function(scope, element, attrs, ngModel) { 34965 * ngModel.$parsers.push(function(val) { 34966 * return parseInt(val, 10); 34967 * }); 34968 * ngModel.$formatters.push(function(val) { 34969 * return '' + val; 34970 * }); 34971 * } 34972 * }; 34973 * }); 34974 * </file> 34975 * <file name="protractor.js" type="protractor"> 34976 * it('should initialize to model', function() { 34977 * var select = element(by.css('select')); 34978 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 34979 * }); 34980 * </file> 34981 * </example> 34982 * 34983 */ 34984var selectDirective = function() { 34985 34986 return { 34987 restrict: 'E', 34988 require: ['select', '?ngModel'], 34989 controller: SelectController, 34990 priority: 1, 34991 link: { 34992 pre: selectPreLink, 34993 post: selectPostLink 34994 } 34995 }; 34996 34997 function selectPreLink(scope, element, attr, ctrls) { 34998 34999 // if ngModel is not defined, we don't need to do anything 35000 var ngModelCtrl = ctrls[1]; 35001 if (!ngModelCtrl) return; 35002 35003 var selectCtrl = ctrls[0]; 35004 35005 selectCtrl.ngModelCtrl = ngModelCtrl; 35006 35007 // When the selected item(s) changes we delegate getting the value of the select control 35008 // to the `readValue` method, which can be changed if the select can have multiple 35009 // selected values or if the options are being generated by `ngOptions` 35010 element.on('change', function() { 35011 scope.$apply(function() { 35012 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 35013 }); 35014 }); 35015 35016 // If the select allows multiple values then we need to modify how we read and write 35017 // values from and to the control; also what it means for the value to be empty and 35018 // we have to add an extra watch since ngModel doesn't work well with arrays - it 35019 // doesn't trigger rendering if only an item in the array changes. 35020 if (attr.multiple) { 35021 35022 // Read value now needs to check each option to see if it is selected 35023 selectCtrl.readValue = function readMultipleValue() { 35024 var array = []; 35025 forEach(element.find('option'), function(option) { 35026 if (option.selected) { 35027 array.push(option.value); 35028 } 35029 }); 35030 return array; 35031 }; 35032 35033 // Write value now needs to set the selected property of each matching option 35034 selectCtrl.writeValue = function writeMultipleValue(value) { 35035 var items = new HashMap(value); 35036 forEach(element.find('option'), function(option) { 35037 option.selected = isDefined(items.get(option.value)); 35038 }); 35039 }; 35040 35041 // we have to do it on each watch since ngModel watches reference, but 35042 // we need to work of an array, so we need to see if anything was inserted/removed 35043 var lastView, lastViewRef = NaN; 35044 scope.$watch(function selectMultipleWatch() { 35045 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 35046 lastView = shallowCopy(ngModelCtrl.$viewValue); 35047 ngModelCtrl.$render(); 35048 } 35049 lastViewRef = ngModelCtrl.$viewValue; 35050 }); 35051 35052 // If we are a multiple select then value is now a collection 35053 // so the meaning of $isEmpty changes 35054 ngModelCtrl.$isEmpty = function(value) { 35055 return !value || value.length === 0; 35056 }; 35057 35058 } 35059 } 35060 35061 function selectPostLink(scope, element, attrs, ctrls) { 35062 // if ngModel is not defined, we don't need to do anything 35063 var ngModelCtrl = ctrls[1]; 35064 if (!ngModelCtrl) return; 35065 35066 var selectCtrl = ctrls[0]; 35067 35068 // We delegate rendering to the `writeValue` method, which can be changed 35069 // if the select can have multiple selected values or if the options are being 35070 // generated by `ngOptions`. 35071 // This must be done in the postLink fn to prevent $render to be called before 35072 // all nodes have been linked correctly. 35073 ngModelCtrl.$render = function() { 35074 selectCtrl.writeValue(ngModelCtrl.$viewValue); 35075 }; 35076 } 35077}; 35078 35079 35080// The option directive is purely designed to communicate the existence (or lack of) 35081// of dynamically created (and destroyed) option elements to their containing select
vendor: 2,836 bytes, lines 35082-35150
35082// directive via its controller. 35083var optionDirective = ['$interpolate', function($interpolate) { 35084 return { 35085 restrict: 'E', 35086 priority: 100, 35087 compile: function(element, attr) { 35088 if (isDefined(attr.value)) { 35089 // If the value attribute is defined, check if it contains an interpolation 35090 var interpolateValueFn = $interpolate(attr.value, true); 35091 } else { 35092 // If the value attribute is not defined then we fall back to the 35093 // text content of the option element, which may be interpolated 35094 var interpolateTextFn = $interpolate(element.text(), true); 35095 if (!interpolateTextFn) { 35096 attr.$set('value', element.text()); 35097 } 35098 } 35099 35100 return function(scope, element, attr) { 35101 // This is an optimization over using ^^ since we don't want to have to search 35102 // all the way to the root of the DOM for every single option element 35103 var selectCtrlName = '$selectController', 35104 parent = element.parent(), 35105 selectCtrl = parent.data(selectCtrlName) || 35106 parent.parent().data(selectCtrlName); // in case we are in optgroup 35107 35108 if (selectCtrl) { 35109 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn); 35110 } 35111 }; 35112 } 35113 }; 35114}]; 35115 35116var styleDirective = valueFn({ 35117 restrict: 'E', 35118 terminal: false 35119}); 35120 35121/** 35122 * @ngdoc directive 35123 * @name ngRequired 35124 * 35125 * @description 35126 * 35127 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 35128 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be 35129 * applied to custom controls. 35130 * 35131 * The directive sets the `required` attribute on the element if the Angular expression inside 35132 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we 35133 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} 35134 * for more info. 35135 * 35136 * The validator will set the `required` error key to true if the `required` attribute is set and 35137 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the 35138 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the 35139 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing 35140 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. 35141 * 35142 * @example 35143 * <example name="ngRequiredDirective" module="ngRequiredExample"> 35144 * <file name="index.html"> 35145 * <script> 35146 * angular.module('ngRequiredExample', []) 35147 * .controller('ExampleController', ['$scope', function($scope) { 35148 * $scope.required = true; 35149 * }]); 35150 * </script>
35151 * <div ng-controller="ExampleController"> 35152 * <form name="form"> 35153 * <label for="required">Toggle required: </label> 35154 * <input type="checkbox" ng-model="required" id="required" /> 35155 * <br> 35156 * <label for="input">This input must be filled if `required` is true: </label> 35157 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> 35158 * <hr> 35159 * required error set? = <code>{{form.input.$error.required}}</code><br> 35160 * model = <code>{{model}}</code> 35161 * </form> 35162 * </div> 35163 * </file> 35164 * <file name="protractor.js" type="protractor"> 35165 var required = element(by.binding('form.input.$error.required')); 35166 var model = element(by.binding('model')); 35167 var input = element(by.id('input')); 35168 35169 it('should set the required error', function() { 35170 expect(required.getText()).toContain('true'); 35171 35172 input.sendKeys('123'); 35173 expect(required.getText()).not.toContain('true'); 35174 expect(model.getText()).toContain('123'); 35175 }); 35176 * </file> 35177 * </example> 35178 */ 35179var requiredDirective = function() { 35180 return { 35181 restrict: 'A', 35182 require: '?ngModel', 35183 link: function(scope, elm, attr, ctrl) { 35184 if (!ctrl) return; 35185 attr.required = true; // force truthy in case we are on non input element 35186 35187 ctrl.$validators.required = function(modelValue, viewValue) { 35188 return !attr.required || !ctrl.$isEmpty(viewValue); 35189 }; 35190 35191 attr.$observe('required', function() { 35192 ctrl.$validate(); 35193 }); 35194 } 35195 }; 35196}; 35197 35198/** 35199 * @ngdoc directive 35200 * @name ngPattern 35201 * 35202 * @description 35203 * 35204 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 35205 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 35206 * 35207 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 35208 * does not match a RegExp which is obtained by evaluating the Angular expression given in the 35209 * `ngPattern` attribute value: 35210 * * If the expression evaluates to a RegExp object, then this is used directly. 35211 * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it 35212 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 35213 * 35214 * <div class="alert alert-info"> 35215 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 35216 * start at the index of the last search's match, thus not taking the whole input value into 35217 * account. 35218 * </div> 35219 * 35220 * <div class="alert alert-info"> 35221 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two 35222 * differences: 35223 * <ol> 35224 * <li> 35225 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is 35226 * not available. 35227 * </li> 35228 * <li> 35229 * The `ngPattern` attribute must be an expression, while the `pattern` value must be 35230 * interpolated. 35231 * </li> 35232 * </ol> 35233 * </div> 35234 * 35235 * @example 35236 * <example name="ngPatternDirective" module="ngPatternExample"> 35237 * <file name="index.html"> 35238 * <script> 35239 * angular.module('ngPatternExample', []) 35240 * .controller('ExampleController', ['$scope', function($scope) { 35241 * $scope.regex = '\\d+'; 35242 * }]); 35243 * </script>
35244 * <div ng-controller="ExampleController"> 35245 * <form name="form"> 35246 * <label for="regex">Set a pattern (regex string): </label> 35247 * <input type="text" ng-model="regex" id="regex" /> 35248 * <br> 35249 * <label for="input">This input is restricted by the current pattern: </label> 35250 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> 35251 * <hr> 35252 * input valid? = <code>{{form.input.$valid}}</code><br> 35253 * model = <code>{{model}}</code> 35254 * </form> 35255 * </div> 35256 * </file> 35257 * <file name="protractor.js" type="protractor"> 35258 var model = element(by.binding('model')); 35259 var input = element(by.id('input')); 35260 35261 it('should validate the input with the default pattern', function() { 35262 input.sendKeys('aaa'); 35263 expect(model.getText()).not.toContain('aaa'); 35264 35265 input.clear().then(function() { 35266 input.sendKeys('123'); 35267 expect(model.getText()).toContain('123'); 35268 }); 35269 }); 35270 * </file> 35271 * </example> 35272 */ 35273var patternDirective = function() { 35274 return { 35275 restrict: 'A', 35276 require: '?ngModel', 35277 link: function(scope, elm, attr, ctrl) { 35278 if (!ctrl) return; 35279 35280 var regexp, patternExp = attr.ngPattern || attr.pattern; 35281 attr.$observe('pattern', function(regex) { 35282 if (isString(regex) && regex.length > 0) { 35283 regex = new RegExp('^' + regex + '$'); 35284 } 35285 35286 if (regex && !regex.test) { 35287 throw minErr('ngPattern')('noregexp', 35288 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 35289 regex, startingTag(elm)); 35290 } 35291 35292 regexp = regex || undefined; 35293 ctrl.$validate(); 35294 }); 35295 35296 ctrl.$validators.pattern = function(modelValue, viewValue) { 35297 // HTML5 pattern constraint validates the input value, so we validate the viewValue 35298 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 35299 }; 35300 } 35301 }; 35302}; 35303 35304/** 35305 * @ngdoc directive 35306 * @name ngMaxlength 35307 * 35308 * @description 35309 * 35310 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 35311 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 35312 * 35313 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 35314 * is longer than the integer obtained by evaluating the Angular expression given in the 35315 * `ngMaxlength` attribute value. 35316 * 35317 * <div class="alert alert-info"> 35318 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two 35319 * differences: 35320 * <ol> 35321 * <li> 35322 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint 35323 * validation is not available. 35324 * </li> 35325 * <li> 35326 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be 35327 * interpolated. 35328 * </li> 35329 * </ol> 35330 * </div> 35331 * 35332 * @example 35333 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> 35334 * <file name="index.html"> 35335 * <script> 35336 * angular.module('ngMaxlengthExample', []) 35337 * .controller('ExampleController', ['$scope', function($scope) { 35338 * $scope.maxlength = 5; 35339 * }]); 35340 * </script>
35341 * <div ng-controller="ExampleController"> 35342 * <form name="form"> 35343 * <label for="maxlength">Set a maxlength: </label> 35344 * <input type="number" ng-model="maxlength" id="maxlength" /> 35345 * <br> 35346 * <label for="input">This input is restricted by the current maxlength: </label> 35347 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> 35348 * <hr> 35349 * input valid? = <code>{{form.input.$valid}}</code><br> 35350 * model = <code>{{model}}</code> 35351 * </form> 35352 * </div> 35353 * </file> 35354 * <file name="protractor.js" type="protractor"> 35355 var model = element(by.binding('model')); 35356 var input = element(by.id('input')); 35357 35358 it('should validate the input with the default maxlength', function() { 35359 input.sendKeys('abcdef'); 35360 expect(model.getText()).not.toContain('abcdef'); 35361 35362 input.clear().then(function() { 35363 input.sendKeys('abcde'); 35364 expect(model.getText()).toContain('abcde'); 35365 }); 35366 }); 35367 * </file> 35368 * </example> 35369 */ 35370var maxlengthDirective = function() { 35371 return { 35372 restrict: 'A', 35373 require: '?ngModel', 35374 link: function(scope, elm, attr, ctrl) { 35375 if (!ctrl) return; 35376 35377 var maxlength = -1; 35378 attr.$observe('maxlength', function(value) { 35379 var intVal = toInt(value); 35380 maxlength = isNaN(intVal) ? -1 : intVal; 35381 ctrl.$validate(); 35382 }); 35383 ctrl.$validators.maxlength = function(modelValue, viewValue) { 35384 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 35385 }; 35386 } 35387 }; 35388}; 35389 35390/** 35391 * @ngdoc directive 35392 * @name ngMinlength 35393 * 35394 * @description 35395 * 35396 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 35397 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 35398 * 35399 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 35400 * is shorter than the integer obtained by evaluating the Angular expression given in the 35401 * `ngMinlength` attribute value. 35402 * 35403 * <div class="alert alert-info"> 35404 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two 35405 * differences: 35406 * <ol> 35407 * <li> 35408 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint 35409 * validation is not available. 35410 * </li> 35411 * <li> 35412 * The `ngMinlength` value must be an expression, while the `minlength` value must be 35413 * interpolated. 35414 * </li> 35415 * </ol> 35416 * </div> 35417 * 35418 * @example 35419 * <example name="ngMinlengthDirective" module="ngMinlengthExample"> 35420 * <file name="index.html"> 35421 * <script> 35422 * angular.module('ngMinlengthExample', []) 35423 * .controller('ExampleController', ['$scope', function($scope) { 35424 * $scope.minlength = 3; 35425 * }]); 35426 * </script>
35427 * <div ng-controller="ExampleController"> 35428 * <form name="form"> 35429 * <label for="minlength">Set a minlength: </label> 35430 * <input type="number" ng-model="minlength" id="minlength" /> 35431 * <br> 35432 * <label for="input">This input is restricted by the current minlength: </label> 35433 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> 35434 * <hr> 35435 * input valid? = <code>{{form.input.$valid}}</code><br> 35436 * model = <code>{{model}}</code> 35437 * </form> 35438 * </div> 35439 * </file> 35440 * <file name="protractor.js" type="protractor"> 35441 var model = element(by.binding('model')); 35442 var input = element(by.id('input')); 35443 35444 it('should validate the input with the default minlength', function() { 35445 input.sendKeys('ab'); 35446 expect(model.getText()).not.toContain('ab'); 35447 35448 input.sendKeys('abc'); 35449 expect(model.getText()).toContain('abc'); 35450 }); 35451 * </file> 35452 * </example> 35453 */ 35454var minlengthDirective = function() { 35455 return { 35456 restrict: 'A', 35457 require: '?ngModel', 35458 link: function(scope, elm, attr, ctrl) { 35459 if (!ctrl) return; 35460 35461 var minlength = 0; 35462 attr.$observe('minlength', function(value) { 35463 minlength = toInt(value) || 0; 35464 ctrl.$validate(); 35465 }); 35466 ctrl.$validators.minlength = function(modelValue, viewValue) { 35467 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 35468 }; 35469 } 35470 }; 35471}; 35472 35473if (window.angular.bootstrap) { 35474 //AngularJS is already loaded, so we can return here... 35475 if (window.console) { 35476 console.log('WARNING: Tried to load angular more than once.'); 35477 } 35478 return; 35479} 35480 35481//try to bind to jquery now so that one can write jqLite(document).ready() 35482//but we will rebind on bootstrap again. 35483bindJQuery(); 35484 35485publishExternalAPI(angular); 35486 35487angular.module("ngLocale", [], ["$provide", function($provide) { 35488var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 35489function getDecimals(n) { 35490 n = n + ''; 35491 var i = n.indexOf('.'); 35492 return (i == -1) ? 0 : n.length - i - 1; 35493} 35494 35495function getVF(n, opt_precision) { 35496 var v = opt_precision; 35497 35498 if (undefined === v) { 35499 v = Math.min(getDecimals(n), 3); 35500 } 35501 35502 var base = Math.pow(10, v); 35503 var f = ((n * base) | 0) % base; 35504 return {v: v, f: f}; 35505} 35506 35507$provide.value("$locale", { 35508 "DATETIME_FORMATS": { 35509 "AMPMS": [ 35510 "AM", 35511 "PM" 35512 ], 35513 "DAY": [ 35514 "Sunday", 35515 "Monday", 35516 "Tuesday", 35517 "Wednesday", 35518 "Thursday", 35519 "Friday", 35520 "Saturday" 35521 ], 35522 "ERANAMES": [ 35523 "Before Christ", 35524 "Anno Domini" 35525 ], 35526 "ERAS": [ 35527 "BC", 35528 "AD" 35529 ], 35530 "FIRSTDAYOFWEEK": 6, 35531 "MONTH": [ 35532 "January", 35533 "February", 35534 "March", 35535 "April", 35536 "May", 35537 "June", 35538 "July", 35539 "August", 35540 "September", 35541 "October", 35542 "November", 35543 "December" 35544 ], 35545 "SHORTDAY": [ 35546 "Sun", 35547 "Mon", 35548 "Tue", 35549 "Wed", 35550 "Thu", 35551 "Fri", 35552 "Sat" 35553 ], 35554 "SHORTMONTH": [ 35555 "Jan", 35556 "Feb", 35557 "Mar", 35558 "Apr", 35559 "May", 35560 "Jun", 35561 "Jul", 35562 "Aug", 35563 "Sep", 35564 "Oct", 35565 "Nov", 35566 "Dec" 35567 ], 35568 "STANDALONEMONTH": [ 35569 "January", 35570 "February", 35571 "March", 35572 "April", 35573 "May", 35574 "June", 35575 "July", 35576 "August", 35577 "September", 35578 "October", 35579 "November", 35580 "December" 35581 ], 35582 "WEEKENDRANGE": [ 35583 5, 35584 6 35585 ], 35586 "fullDate": "EEEE, MMMM d, y", 35587 "longDate": "MMMM d, y", 35588 "medium": "MMM d, y h:mm:ss a", 35589 "mediumDate": "MMM d, y", 35590 "mediumTime": "h:mm:ss a", 35591 "short": "M/d/yy h:mm a", 35592 "shortDate": "M/d/yy", 35593 "shortTime": "h:mm a" 35594 }, 35595 "NUMBER_FORMATS": { 35596 "CURRENCY_SYM": "$", 35597 "DECIMAL_SEP": ".", 35598 "GROUP_SEP": ",", 35599 "PATTERNS": [ 35600 { 35601 "gSize": 3, 35602 "lgSize": 3, 35603 "maxFrac": 3, 35604 "minFrac": 0, 35605 "minInt": 1, 35606 "negPre": "-", 35607 "negSuf": "", 35608 "posPre": "", 35609 "posSuf": "" 35610 }, 35611 { 35612 "gSize": 3, 35613 "lgSize": 3, 35614 "maxFrac": 2, 35615 "minFrac": 2, 35616 "minInt": 1, 35617 "negPre": "-\u00a4", 35618 "negSuf": "", 35619 "posPre": "\u00a4", 35620 "posSuf": "" 35621 } 35622 ] 35623 }, 35624 "id": "en-us", 35625 "localeID": "en_US", 35626 "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;} 35627}); 35628}]); 35629 35630 jqLite(document).ready(function() { 35631 angularInit(document, bootstrap); 35632 }); 35633 35634})(window, document); 35635 35636!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>'); 35637},{}],5:[function(require,module,exports){ 35638require('./angular'); 35639module.exports = angular; 35640 35641},{"./angular":4}],6:[function(require,module,exports){ 35642// CodeMirror, copyright (c) by Marijn Haverbeke and others 35643// Distributed under an MIT license: http://codemirror.net/LICENSE 35644 35645// This is CodeMirror (http://codemirror.net), a code editor
vendor: 107,460 bytes, lines 35646-38413
35646// implemented in JavaScript on top of the browser's DOM. 35647// 35648// You can find some technical background for some of the code below 35649// at http://marijnhaverbeke.nl/blog/#cm-internals . 35650 35651(function (global, factory) { 35652 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 35653 typeof define === 'function' && define.amd ? define(factory) : 35654 (global.CodeMirror = factory()); 35655}(this, (function () { 'use strict'; 35656 35657// Kludges for bugs and behavior differences that can't be feature 35658// detected are enabled based on userAgent etc sniffing. 35659var userAgent = navigator.userAgent; 35660var platform = navigator.platform; 35661 35662var gecko = /gecko\/\d/i.test(userAgent); 35663var ie_upto10 = /MSIE \d/.test(userAgent); 35664var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); 35665var edge = /Edge\/(\d+)/.exec(userAgent); 35666var ie = ie_upto10 || ie_11up || edge; 35667var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]); 35668var webkit = !edge && /WebKit\//.test(userAgent); 35669var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); 35670var chrome = !edge && /Chrome\//.test(userAgent); 35671var presto = /Opera\//.test(userAgent); 35672var safari = /Apple Computer/.test(navigator.vendor); 35673var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); 35674var phantom = /PhantomJS/.test(userAgent); 35675 35676var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); 35677var android = /Android/.test(userAgent); 35678// This is woefully incomplete. Suggestions for alternative methods welcome. 35679var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); 35680var mac = ios || /Mac/.test(platform); 35681var chromeOS = /\bCrOS\b/.test(userAgent); 35682var windows = /win/i.test(platform); 35683 35684var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); 35685if (presto_version) { presto_version = Number(presto_version[1]); } 35686if (presto_version && presto_version >= 15) { presto = false; webkit = true; } 35687// Some browsers use the wrong event properties to signal cmd/ctrl on OS X 35688var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); 35689var captureRightClick = gecko || (ie && ie_version >= 9); 35690 35691function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } 35692 35693var rmClass = function(node, cls) { 35694 var current = node.className; 35695 var match = classTest(cls).exec(current); 35696 if (match) { 35697 var after = current.slice(match.index + match[0].length); 35698 node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); 35699 } 35700}; 35701 35702function removeChildren(e) { 35703 for (var count = e.childNodes.length; count > 0; --count) 35704 { e.removeChild(e.firstChild); } 35705 return e 35706} 35707 35708function removeChildrenAndAdd(parent, e) { 35709 return removeChildren(parent).appendChild(e) 35710} 35711 35712function elt(tag, content, className, style) { 35713 var e = document.createElement(tag); 35714 if (className) { e.className = className; } 35715 if (style) { e.style.cssText = style; } 35716 if (typeof content == "string") { e.appendChild(document.createTextNode(content)); } 35717 else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } } 35718 return e 35719} 35720// wrapper for elt, which removes the elt from the accessibility tree 35721function eltP(tag, content, className, style) { 35722 var e = elt(tag, content, className, style); 35723 e.setAttribute("role", "presentation"); 35724 return e 35725} 35726 35727var range; 35728if (document.createRange) { range = function(node, start, end, endNode) { 35729 var r = document.createRange(); 35730 r.setEnd(endNode || node, end); 35731 r.setStart(node, start); 35732 return r 35733}; } 35734else { range = function(node, start, end) { 35735 var r = document.body.createTextRange(); 35736 try { r.moveToElementText(node.parentNode); } 35737 catch(e) { return r } 35738 r.collapse(true); 35739 r.moveEnd("character", end); 35740 r.moveStart("character", start); 35741 return r 35742}; } 35743 35744function contains(parent, child) { 35745 if (child.nodeType == 3) // Android browser always returns false when child is a textnode 35746 { child = child.parentNode; } 35747 if (parent.contains) 35748 { return parent.contains(child) } 35749 do { 35750 if (child.nodeType == 11) { child = child.host; } 35751 if (child == parent) { return true } 35752 } while (child = child.parentNode) 35753} 35754 35755function activeElt() { 35756 // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. 35757 // IE < 10 will throw when accessed while the page is loading or in an iframe. 35758 // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. 35759 var activeElement; 35760 try { 35761 activeElement = document.activeElement; 35762 } catch(e) { 35763 activeElement = document.body || null; 35764 } 35765 while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) 35766 { activeElement = activeElement.shadowRoot.activeElement; } 35767 return activeElement 35768} 35769 35770function addClass(node, cls) { 35771 var current = node.className; 35772 if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls; } 35773} 35774function joinClasses(a, b) { 35775 var as = a.split(" "); 35776 for (var i = 0; i < as.length; i++) 35777 { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i]; } } 35778 return b 35779} 35780 35781var selectInput = function(node) { node.select(); }; 35782if (ios) // Mobile Safari apparently has a bug where select() is broken. 35783 { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; } 35784else if (ie) // Suppress mysterious IE10 errors 35785 { selectInput = function(node) { try { node.select(); } catch(_e) {} }; } 35786 35787function bind(f) { 35788 var args = Array.prototype.slice.call(arguments, 1); 35789 return function(){return f.apply(null, args)} 35790} 35791 35792function copyObj(obj, target, overwrite) { 35793 if (!target) { target = {}; } 35794 for (var prop in obj) 35795 { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) 35796 { target[prop] = obj[prop]; } } 35797 return target 35798} 35799 35800// Counts the column offset in a string, taking tabs into account. 35801// Used mostly to find indentation. 35802function countColumn(string, end, tabSize, startIndex, startValue) { 35803 if (end == null) { 35804 end = string.search(/[^\s\u00a0]/); 35805 if (end == -1) { end = string.length; } 35806 } 35807 for (var i = startIndex || 0, n = startValue || 0;;) { 35808 var nextTab = string.indexOf("\t", i); 35809 if (nextTab < 0 || nextTab >= end) 35810 { return n + (end - i) } 35811 n += nextTab - i; 35812 n += tabSize - (n % tabSize); 35813 i = nextTab + 1; 35814 } 35815} 35816 35817var Delayed = function() {this.id = null;}; 35818Delayed.prototype.set = function (ms, f) { 35819 clearTimeout(this.id); 35820 this.id = setTimeout(f, ms); 35821}; 35822 35823function indexOf(array, elt) { 35824 for (var i = 0; i < array.length; ++i) 35825 { if (array[i] == elt) { return i } } 35826 return -1 35827} 35828 35829// Number of pixels added to scroller and sizer to hide scrollbar 35830var scrollerGap = 30; 35831 35832// Returned or thrown by various protocols to signal 'I'm not 35833// handling this'. 35834var Pass = {toString: function(){return "CodeMirror.Pass"}}; 35835 35836// Reused option objects for setSelection & friends 35837var sel_dontScroll = {scroll: false}; 35838var sel_mouse = {origin: "*mouse"}; 35839var sel_move = {origin: "+move"}; 35840 35841// The inverse of countColumn -- find the offset that corresponds to 35842// a particular column. 35843function findColumn(string, goal, tabSize) { 35844 for (var pos = 0, col = 0;;) { 35845 var nextTab = string.indexOf("\t", pos); 35846 if (nextTab == -1) { nextTab = string.length; } 35847 var skipped = nextTab - pos; 35848 if (nextTab == string.length || col + skipped >= goal) 35849 { return pos + Math.min(skipped, goal - col) } 35850 col += nextTab - pos; 35851 col += tabSize - (col % tabSize); 35852 pos = nextTab + 1; 35853 if (col >= goal) { return pos } 35854 } 35855} 35856 35857var spaceStrs = [""]; 35858function spaceStr(n) { 35859 while (spaceStrs.length <= n) 35860 { spaceStrs.push(lst(spaceStrs) + " "); } 35861 return spaceStrs[n] 35862} 35863 35864function lst(arr) { return arr[arr.length-1] } 35865 35866function map(array, f) { 35867 var out = []; 35868 for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); } 35869 return out 35870} 35871 35872function insertSorted(array, value, score) { 35873 var pos = 0, priority = score(value); 35874 while (pos < array.length && score(array[pos]) <= priority) { pos++; } 35875 array.splice(pos, 0, value); 35876} 35877 35878function nothing() {} 35879 35880function createObj(base, props) { 35881 var inst; 35882 if (Object.create) { 35883 inst = Object.create(base); 35884 } else { 35885 nothing.prototype = base; 35886 inst = new nothing(); 35887 } 35888 if (props) { copyObj(props, inst); } 35889 return inst 35890} 35891 35892var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; 35893function isWordCharBasic(ch) { 35894 return /\w/.test(ch) || ch > "\x80" && 35895 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) 35896} 35897function isWordChar(ch, helper) { 35898 if (!helper) { return isWordCharBasic(ch) } 35899 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } 35900 return helper.test(ch) 35901} 35902 35903function isEmpty(obj) { 35904 for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } 35905 return true 35906} 35907 35908// Extending unicode characters. A series of a non-extending char + 35909// any number of extending chars is treated as a single unit as far 35910// as editing and measuring is concerned. This is not fully correct, 35911// since some scripts/fonts/browsers also treat other configurations 35912// of code points as a group. 35913var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; 35914function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } 35915 35916// Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. 35917function skipExtendingChars(str, pos, dir) { 35918 while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; } 35919 return pos 35920} 35921 35922// Returns the value from the range [`from`; `to`] that satisfies 35923// `pred` and is closest to `from`. Assumes that at least `to` 35924// satisfies `pred`. Supports `from` being greater than `to`. 35925function findFirst(pred, from, to) { 35926 // At any point we are certain `to` satisfies `pred`, don't know 35927 // whether `from` does. 35928 var dir = from > to ? -1 : 1; 35929 for (;;) { 35930 if (from == to) { return from } 35931 var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF); 35932 if (mid == from) { return pred(mid) ? from : to } 35933 if (pred(mid)) { to = mid; } 35934 else { from = mid + dir; } 35935 } 35936} 35937 35938// The display handles the DOM integration, both for input reading 35939// and content drawing. It holds references to DOM nodes and 35940// display-related state. 35941 35942function Display(place, doc, input) { 35943 var d = this; 35944 this.input = input; 35945 35946 // Covers bottom-right square when both scrollbars are present. 35947 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); 35948 d.scrollbarFiller.setAttribute("cm-not-content", "true"); 35949 // Covers bottom of gutter when coverGutterNextToScrollbar is on 35950 // and h scrollbar is present. 35951 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); 35952 d.gutterFiller.setAttribute("cm-not-content", "true"); 35953 // Will contain the actual code, positioned to cover the viewport. 35954 d.lineDiv = eltP("div", null, "CodeMirror-code"); 35955 // Elements are added to these to represent selection and cursors. 35956 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); 35957 d.cursorDiv = elt("div", null, "CodeMirror-cursors"); 35958 // A visibility: hidden element used to find the size of things. 35959 d.measure = elt("div", null, "CodeMirror-measure"); 35960 // When lines outside of the viewport are measured, they are drawn in this. 35961 d.lineMeasure = elt("div", null, "CodeMirror-measure"); 35962 // Wraps everything that needs to exist inside the vertically-padded coordinate system 35963 d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], 35964 null, "position: relative; outline: none"); 35965 var lines = eltP("div", [d.lineSpace], "CodeMirror-lines"); 35966 // Moved around its parent to cover visible view. 35967 d.mover = elt("div", [lines], null, "position: relative"); 35968 // Set to the height of the document, allowing scrolling. 35969 d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); 35970 d.sizerWidth = null; 35971 // Behavior of elts with overflow: auto and padding is 35972 // inconsistent across browsers. This is used to ensure the 35973 // scrollable area is big enough. 35974 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); 35975 // Will contain the gutters, if any. 35976 d.gutters = elt("div", null, "CodeMirror-gutters"); 35977 d.lineGutter = null; 35978 // Actual scrollable element. 35979 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); 35980 d.scroller.setAttribute("tabIndex", "-1"); 35981 // The element in which the editor lives. 35982 d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); 35983 35984 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) 35985 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } 35986 if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; } 35987 35988 if (place) { 35989 if (place.appendChild) { place.appendChild(d.wrapper); } 35990 else { place(d.wrapper); } 35991 } 35992 35993 // Current rendered range (may be bigger than the view window). 35994 d.viewFrom = d.viewTo = doc.first; 35995 d.reportedViewFrom = d.reportedViewTo = doc.first; 35996 // Information about the rendered lines. 35997 d.view = []; 35998 d.renderedView = null; 35999 // Holds info about a single rendered line when it was rendered 36000 // for measurement, while not in view. 36001 d.externalMeasured = null; 36002 // Empty space (in pixels) above the view 36003 d.viewOffset = 0; 36004 d.lastWrapHeight = d.lastWrapWidth = 0; 36005 d.updateLineNumbers = null; 36006 36007 d.nativeBarWidth = d.barHeight = d.barWidth = 0; 36008 d.scrollbarsClipped = false; 36009 36010 // Used to only resize the line number gutter when necessary (when 36011 // the amount of lines crosses a boundary that makes its width change) 36012 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; 36013 // Set to true when a non-horizontal-scrolling line widget is 36014 // added. As an optimization, line widget aligning is skipped when 36015 // this is false. 36016 d.alignWidgets = false; 36017 36018 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; 36019 36020 // Tracks the maximum line length so that the horizontal scrollbar 36021 // can be kept static when scrolling. 36022 d.maxLine = null; 36023 d.maxLineLength = 0; 36024 d.maxLineChanged = false; 36025 36026 // Used for measuring wheel scrolling granularity 36027 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; 36028 36029 // True when shift is held down. 36030 d.shift = false; 36031 36032 // Used to track whether anything happened since the context menu 36033 // was opened. 36034 d.selForContextMenu = null; 36035 36036 d.activeTouch = null; 36037 36038 input.init(d); 36039} 36040 36041// Find the line object corresponding to the given line number. 36042function getLine(doc, n) { 36043 n -= doc.first; 36044 if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") } 36045 var chunk = doc; 36046 while (!chunk.lines) { 36047 for (var i = 0;; ++i) { 36048 var child = chunk.children[i], sz = child.chunkSize(); 36049 if (n < sz) { chunk = child; break } 36050 n -= sz; 36051 } 36052 } 36053 return chunk.lines[n] 36054} 36055 36056// Get the part of a document between two positions, as an array of 36057// strings. 36058function getBetween(doc, start, end) { 36059 var out = [], n = start.line; 36060 doc.iter(start.line, end.line + 1, function (line) { 36061 var text = line.text; 36062 if (n == end.line) { text = text.slice(0, end.ch); } 36063 if (n == start.line) { text = text.slice(start.ch); } 36064 out.push(text); 36065 ++n; 36066 }); 36067 return out 36068} 36069// Get the lines between from and to, as array of strings. 36070function getLines(doc, from, to) { 36071 var out = []; 36072 doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value 36073 return out 36074} 36075 36076// Update the height of a line, propagating the height change 36077// upwards to parent nodes. 36078function updateLineHeight(line, height) { 36079 var diff = height - line.height; 36080 if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } } 36081} 36082 36083// Given a line object, find its line number by walking up through 36084// its parent links. 36085function lineNo(line) { 36086 if (line.parent == null) { return null } 36087 var cur = line.parent, no = indexOf(cur.lines, line); 36088 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { 36089 for (var i = 0;; ++i) { 36090 if (chunk.children[i] == cur) { break } 36091 no += chunk.children[i].chunkSize(); 36092 } 36093 } 36094 return no + cur.first 36095} 36096 36097// Find the line at the given vertical position, using the height 36098// information in the document tree. 36099function lineAtHeight(chunk, h) { 36100 var n = chunk.first; 36101 outer: do { 36102 for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { 36103 var child = chunk.children[i$1], ch = child.height; 36104 if (h < ch) { chunk = child; continue outer } 36105 h -= ch; 36106 n += child.chunkSize(); 36107 } 36108 return n 36109 } while (!chunk.lines) 36110 var i = 0; 36111 for (; i < chunk.lines.length; ++i) { 36112 var line = chunk.lines[i], lh = line.height; 36113 if (h < lh) { break } 36114 h -= lh; 36115 } 36116 return n + i 36117} 36118 36119function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} 36120 36121function lineNumberFor(options, i) { 36122 return String(options.lineNumberFormatter(i + options.firstLineNumber)) 36123} 36124 36125// A Pos instance represents a position within the text. 36126function Pos(line, ch, sticky) { 36127 if ( sticky === void 0 ) sticky = null; 36128 36129 if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } 36130 this.line = line; 36131 this.ch = ch; 36132 this.sticky = sticky; 36133} 36134 36135// Compare two positions, return 0 if they are the same, a negative 36136// number when a is less, and a positive number otherwise. 36137function cmp(a, b) { return a.line - b.line || a.ch - b.ch } 36138 36139function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } 36140 36141function copyPos(x) {return Pos(x.line, x.ch)} 36142function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } 36143function minPos(a, b) { return cmp(a, b) < 0 ? a : b } 36144 36145// Most of the external API clips given positions to make sure they 36146// actually exist within the document. 36147function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} 36148function clipPos(doc, pos) { 36149 if (pos.line < doc.first) { return Pos(doc.first, 0) } 36150 var last = doc.first + doc.size - 1; 36151 if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } 36152 return clipToLen(pos, getLine(doc, pos.line).text.length) 36153} 36154function clipToLen(pos, linelen) { 36155 var ch = pos.ch; 36156 if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } 36157 else if (ch < 0) { return Pos(pos.line, 0) } 36158 else { return pos } 36159} 36160function clipPosArray(doc, array) { 36161 var out = []; 36162 for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); } 36163 return out 36164} 36165 36166// Optimize some code when these features are not used. 36167var sawReadOnlySpans = false; 36168var sawCollapsedSpans = false; 36169 36170function seeReadOnlySpans() { 36171 sawReadOnlySpans = true; 36172} 36173 36174function seeCollapsedSpans() { 36175 sawCollapsedSpans = true; 36176} 36177 36178// TEXTMARKER SPANS 36179 36180function MarkedSpan(marker, from, to) { 36181 this.marker = marker; 36182 this.from = from; this.to = to; 36183} 36184 36185// Search an array of spans for a span matching the given marker. 36186function getMarkedSpanFor(spans, marker) { 36187 if (spans) { for (var i = 0; i < spans.length; ++i) { 36188 var span = spans[i]; 36189 if (span.marker == marker) { return span } 36190 } } 36191} 36192// Remove a span from an array, returning undefined if no spans are 36193// left (we don't store arrays for lines without spans). 36194function removeMarkedSpan(spans, span) { 36195 var r; 36196 for (var i = 0; i < spans.length; ++i) 36197 { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } } 36198 return r 36199} 36200// Add a span to a line. 36201function addMarkedSpan(line, span) { 36202 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; 36203 span.marker.attachLine(line); 36204} 36205 36206// Used for the algorithm that adjusts markers for a change in the 36207// document. These functions cut an array of spans at a given 36208// character position, returning an array of remaining chunks (or 36209// undefined if nothing remains). 36210function markedSpansBefore(old, startCh, isInsert) { 36211 var nw; 36212 if (old) { for (var i = 0; i < old.length; ++i) { 36213 var span = old[i], marker = span.marker; 36214 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); 36215 if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { 36216 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); 36217 } 36218 } } 36219 return nw 36220} 36221function markedSpansAfter(old, endCh, isInsert) { 36222 var nw; 36223 if (old) { for (var i = 0; i < old.length; ++i) { 36224 var span = old[i], marker = span.marker; 36225 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); 36226 if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { 36227 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, 36228 span.to == null ? null : span.to - endCh)); 36229 } 36230 } } 36231 return nw 36232} 36233 36234// Given a change object, compute the new set of marker spans that 36235// cover the line in which the change took place. Removes spans 36236// entirely within the change, reconnects spans belonging to the 36237// same marker that appear on both sides of the change, and cuts off 36238// spans partially within the change. Returns an array of span 36239// arrays with one element for each line in (after) the change. 36240function stretchSpansOverChange(doc, change) { 36241 if (change.full) { return null } 36242 var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; 36243 var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; 36244 if (!oldFirst && !oldLast) { return null } 36245 36246 var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; 36247 // Get the spans that 'stick out' on both sides 36248 var first = markedSpansBefore(oldFirst, startCh, isInsert); 36249 var last = markedSpansAfter(oldLast, endCh, isInsert); 36250 36251 // Next, merge those two ends 36252 var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); 36253 if (first) { 36254 // Fix up .to properties of first 36255 for (var i = 0; i < first.length; ++i) { 36256 var span = first[i]; 36257 if (span.to == null) { 36258 var found = getMarkedSpanFor(last, span.marker); 36259 if (!found) { span.to = startCh; } 36260 else if (sameLine) { span.to = found.to == null ? null : found.to + offset; } 36261 } 36262 } 36263 } 36264 if (last) { 36265 // Fix up .from in last (or move them into first in case of sameLine) 36266 for (var i$1 = 0; i$1 < last.length; ++i$1) { 36267 var span$1 = last[i$1]; 36268 if (span$1.to != null) { span$1.to += offset; } 36269 if (span$1.from == null) { 36270 var found$1 = getMarkedSpanFor(first, span$1.marker); 36271 if (!found$1) { 36272 span$1.from = offset; 36273 if (sameLine) { (first || (first = [])).push(span$1); } 36274 } 36275 } else { 36276 span$1.from += offset; 36277 if (sameLine) { (first || (first = [])).push(span$1); } 36278 } 36279 } 36280 } 36281 // Make sure we didn't create any zero-length spans 36282 if (first) { first = clearEmptySpans(first); } 36283 if (last && last != first) { last = clearEmptySpans(last); } 36284 36285 var newMarkers = [first]; 36286 if (!sameLine) { 36287 // Fill gap with whole-line-spans 36288 var gap = change.text.length - 2, gapMarkers; 36289 if (gap > 0 && first) 36290 { for (var i$2 = 0; i$2 < first.length; ++i$2) 36291 { if (first[i$2].to == null) 36292 { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } } 36293 for (var i$3 = 0; i$3 < gap; ++i$3) 36294 { newMarkers.push(gapMarkers); } 36295 newMarkers.push(last); 36296 } 36297 return newMarkers 36298} 36299 36300// Remove spans that are empty and don't have a clearWhenEmpty 36301// option of false. 36302function clearEmptySpans(spans) { 36303 for (var i = 0; i < spans.length; ++i) { 36304 var span = spans[i]; 36305 if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) 36306 { spans.splice(i--, 1); } 36307 } 36308 if (!spans.length) { return null } 36309 return spans 36310} 36311 36312// Used to 'clip' out readOnly ranges when making a change. 36313function removeReadOnlyRanges(doc, from, to) { 36314 var markers = null; 36315 doc.iter(from.line, to.line + 1, function (line) { 36316 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { 36317 var mark = line.markedSpans[i].marker; 36318 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) 36319 { (markers || (markers = [])).push(mark); } 36320 } } 36321 }); 36322 if (!markers) { return null } 36323 var parts = [{from: from, to: to}]; 36324 for (var i = 0; i < markers.length; ++i) { 36325 var mk = markers[i], m = mk.find(0); 36326 for (var j = 0; j < parts.length; ++j) { 36327 var p = parts[j]; 36328 if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } 36329 var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); 36330 if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) 36331 { newParts.push({from: p.from, to: m.from}); } 36332 if (dto > 0 || !mk.inclusiveRight && !dto) 36333 { newParts.push({from: m.to, to: p.to}); } 36334 parts.splice.apply(parts, newParts); 36335 j += newParts.length - 3; 36336 } 36337 } 36338 return parts 36339} 36340 36341// Connect or disconnect spans from a line. 36342function detachMarkedSpans(line) { 36343 var spans = line.markedSpans; 36344 if (!spans) { return } 36345 for (var i = 0; i < spans.length; ++i) 36346 { spans[i].marker.detachLine(line); } 36347 line.markedSpans = null; 36348} 36349function attachMarkedSpans(line, spans) { 36350 if (!spans) { return } 36351 for (var i = 0; i < spans.length; ++i) 36352 { spans[i].marker.attachLine(line); } 36353 line.markedSpans = spans; 36354} 36355 36356// Helpers used when computing which overlapping collapsed span 36357// counts as the larger one. 36358function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } 36359function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } 36360 36361// Returns a number indicating which of two overlapping collapsed 36362// spans is larger (and thus includes the other). Falls back to 36363// comparing ids when the spans cover exactly the same range. 36364function compareCollapsedMarkers(a, b) { 36365 var lenDiff = a.lines.length - b.lines.length; 36366 if (lenDiff != 0) { return lenDiff } 36367 var aPos = a.find(), bPos = b.find(); 36368 var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); 36369 if (fromCmp) { return -fromCmp } 36370 var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); 36371 if (toCmp) { return toCmp } 36372 return b.id - a.id 36373} 36374 36375// Find out whether a line ends or starts in a collapsed span. If 36376// so, return the marker for that span. 36377function collapsedSpanAtSide(line, start) { 36378 var sps = sawCollapsedSpans && line.markedSpans, found; 36379 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { 36380 sp = sps[i]; 36381 if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && 36382 (!found || compareCollapsedMarkers(found, sp.marker) < 0)) 36383 { found = sp.marker; } 36384 } } 36385 return found 36386} 36387function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } 36388function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } 36389 36390// Test whether there exists a collapsed span that partially 36391// overlaps (covers the start or end, but not both) of a new span. 36392// Such overlap is not allowed. 36393function conflictingCollapsedRange(doc, lineNo$$1, from, to, marker) { 36394 var line = getLine(doc, lineNo$$1); 36395 var sps = sawCollapsedSpans && line.markedSpans; 36396 if (sps) { for (var i = 0; i < sps.length; ++i) { 36397 var sp = sps[i]; 36398 if (!sp.marker.collapsed) { continue } 36399 var found = sp.marker.find(0); 36400 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); 36401 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); 36402 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } 36403 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || 36404 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) 36405 { return true } 36406 } } 36407} 36408 36409// A visual line is a line as drawn on the screen. Folding, for 36410// example, can cause multiple logical lines to appear on the same 36411// visual line. This finds the start of the visual line that the 36412// given line is part of (usually that is the line itself). 36413function visualLine(line) { 36414 var merged; 36415 while (merged = collapsedSpanAtStart(line)) 36416 { line = merged.find(-1, true).line; } 36417 return line 36418} 36419 36420function visualLineEnd(line) { 36421 var merged; 36422 while (merged = collapsedSpanAtEnd(line)) 36423 { line = merged.find(1, true).line; } 36424 return line 36425} 36426 36427// Returns an array of logical lines that continue the visual line 36428// started by the argument, or undefined if there are no such lines. 36429function visualLineContinued(line) { 36430 var merged, lines; 36431 while (merged = collapsedSpanAtEnd(line)) { 36432 line = merged.find(1, true).line 36433 ;(lines || (lines = [])).push(line); 36434 } 36435 return lines 36436} 36437 36438// Get the line number of the start of the visual line that the 36439// given line number is part of. 36440function visualLineNo(doc, lineN) { 36441 var line = getLine(doc, lineN), vis = visualLine(line); 36442 if (line == vis) { return lineN } 36443 return lineNo(vis) 36444} 36445 36446// Get the line number of the start of the next visual line after 36447// the given line. 36448function visualLineEndNo(doc, lineN) { 36449 if (lineN > doc.lastLine()) { return lineN } 36450 var line = getLine(doc, lineN), merged; 36451 if (!lineIsHidden(doc, line)) { return lineN } 36452 while (merged = collapsedSpanAtEnd(line)) 36453 { line = merged.find(1, true).line; } 36454 return lineNo(line) + 1 36455} 36456 36457// Compute whether a line is hidden. Lines count as hidden when they 36458// are part of a visual line that starts with another line, or when 36459// they are entirely covered by collapsed, non-widget span. 36460function lineIsHidden(doc, line) { 36461 var sps = sawCollapsedSpans && line.markedSpans; 36462 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { 36463 sp = sps[i]; 36464 if (!sp.marker.collapsed) { continue } 36465 if (sp.from == null) { return true } 36466 if (sp.marker.widgetNode) { continue } 36467 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) 36468 { return true } 36469 } } 36470} 36471function lineIsHiddenInner(doc, line, span) { 36472 if (span.to == null) { 36473 var end = span.marker.find(1, true); 36474 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) 36475 } 36476 if (span.marker.inclusiveRight && span.to == line.text.length) 36477 { return true } 36478 for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { 36479 sp = line.markedSpans[i]; 36480 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && 36481 (sp.to == null || sp.to != span.from) && 36482 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && 36483 lineIsHiddenInner(doc, line, sp)) { return true } 36484 } 36485} 36486 36487// Find the height above the given line. 36488function heightAtLine(lineObj) { 36489 lineObj = visualLine(lineObj); 36490 36491 var h = 0, chunk = lineObj.parent; 36492 for (var i = 0; i < chunk.lines.length; ++i) { 36493 var line = chunk.lines[i]; 36494 if (line == lineObj) { break } 36495 else { h += line.height; } 36496 } 36497 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { 36498 for (var i$1 = 0; i$1 < p.children.length; ++i$1) { 36499 var cur = p.children[i$1]; 36500 if (cur == chunk) { break } 36501 else { h += cur.height; } 36502 } 36503 } 36504 return h 36505} 36506 36507// Compute the character length of a line, taking into account 36508// collapsed ranges (see markText) that might hide parts, and join 36509// other lines onto it. 36510function lineLength(line) { 36511 if (line.height == 0) { return 0 } 36512 var len = line.text.length, merged, cur = line; 36513 while (merged = collapsedSpanAtStart(cur)) { 36514 var found = merged.find(0, true); 36515 cur = found.from.line; 36516 len += found.from.ch - found.to.ch; 36517 } 36518 cur = line; 36519 while (merged = collapsedSpanAtEnd(cur)) { 36520 var found$1 = merged.find(0, true); 36521 len -= cur.text.length - found$1.from.ch; 36522 cur = found$1.to.line; 36523 len += cur.text.length - found$1.to.ch; 36524 } 36525 return len 36526} 36527 36528// Find the longest line in the document. 36529function findMaxLine(cm) { 36530 var d = cm.display, doc = cm.doc; 36531 d.maxLine = getLine(doc, doc.first); 36532 d.maxLineLength = lineLength(d.maxLine); 36533 d.maxLineChanged = true; 36534 doc.iter(function (line) { 36535 var len = lineLength(line); 36536 if (len > d.maxLineLength) { 36537 d.maxLineLength = len; 36538 d.maxLine = line; 36539 } 36540 }); 36541} 36542 36543// BIDI HELPERS 36544 36545function iterateBidiSections(order, from, to, f) { 36546 if (!order) { return f(from, to, "ltr", 0) } 36547 var found = false; 36548 for (var i = 0; i < order.length; ++i) { 36549 var part = order[i]; 36550 if (part.from < to && part.to > from || from == to && part.to == from) { 36551 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i); 36552 found = true; 36553 } 36554 } 36555 if (!found) { f(from, to, "ltr"); } 36556} 36557 36558var bidiOther = null; 36559function getBidiPartAt(order, ch, sticky) { 36560 var found; 36561 bidiOther = null; 36562 for (var i = 0; i < order.length; ++i) { 36563 var cur = order[i]; 36564 if (cur.from < ch && cur.to > ch) { return i } 36565 if (cur.to == ch) { 36566 if (cur.from != cur.to && sticky == "before") { found = i; } 36567 else { bidiOther = i; } 36568 } 36569 if (cur.from == ch) { 36570 if (cur.from != cur.to && sticky != "before") { found = i; } 36571 else { bidiOther = i; } 36572 } 36573 } 36574 return found != null ? found : bidiOther 36575} 36576 36577// Bidirectional ordering algorithm 36578// See http://unicode.org/reports/tr9/tr9-13.html for the algorithm 36579// that this (partially) implements. 36580 36581// One-char codes used for character types: 36582// L (L): Left-to-Right 36583// R (R): Right-to-Left 36584// r (AL): Right-to-Left Arabic 36585// 1 (EN): European Number 36586// + (ES): European Number Separator 36587// % (ET): European Number Terminator 36588// n (AN): Arabic Number 36589// , (CS): Common Number Separator 36590// m (NSM): Non-Spacing Mark 36591// b (BN): Boundary Neutral 36592// s (B): Paragraph Separator 36593// t (S): Segment Separator 36594// w (WS): Whitespace 36595// N (ON): Other Neutrals 36596 36597// Returns null if characters are ordered as they appear 36598// (left-to-right), or an array of sections ({from, to, level} 36599// objects) in the order in which they occur visually. 36600var bidiOrdering = (function() { 36601 // Character types for codepoints 0 to 0xff 36602 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; 36603 // Character types for codepoints 0x600 to 0x6f9 36604 var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111"; 36605 function charType(code) { 36606 if (code <= 0xf7) { return lowTypes.charAt(code) } 36607 else if (0x590 <= code && code <= 0x5f4) { return "R" } 36608 else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } 36609 else if (0x6ee <= code && code <= 0x8ac) { return "r" } 36610 else if (0x2000 <= code && code <= 0x200b) { return "w" } 36611 else if (code == 0x200c) { return "b" } 36612 else { return "L" } 36613 } 36614 36615 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; 36616 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; 36617 36618 function BidiSpan(level, from, to) { 36619 this.level = level; 36620 this.from = from; this.to = to; 36621 } 36622 36623 return function(str, direction) { 36624 var outerType = direction == "ltr" ? "L" : "R"; 36625 36626 if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false } 36627 var len = str.length, types = []; 36628 for (var i = 0; i < len; ++i) 36629 { types.push(charType(str.charCodeAt(i))); } 36630 36631 // W1. Examine each non-spacing mark (NSM) in the level run, and 36632 // change the type of the NSM to the type of the previous 36633 // character. If the NSM is at the start of the level run, it will 36634 // get the type of sor. 36635 for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { 36636 var type = types[i$1]; 36637 if (type == "m") { types[i$1] = prev; } 36638 else { prev = type; } 36639 } 36640 36641 // W2. Search backwards from each instance of a European number 36642 // until the first strong type (R, L, AL, or sor) is found. If an 36643 // AL is found, change the type of the European number to Arabic 36644 // number. 36645 // W3. Change all ALs to R. 36646 for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { 36647 var type$1 = types[i$2]; 36648 if (type$1 == "1" && cur == "r") { types[i$2] = "n"; } 36649 else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R"; } } 36650 } 36651 36652 // W4. A single European separator between two European numbers 36653 // changes to a European number. A single common separator between 36654 // two numbers of the same type changes to that type. 36655 for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { 36656 var type$2 = types[i$3]; 36657 if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1"; } 36658 else if (type$2 == "," && prev$1 == types[i$3+1] && 36659 (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1; } 36660 prev$1 = type$2; 36661 } 36662 36663 // W5. A sequence of European terminators adjacent to European 36664 // numbers changes to all European numbers. 36665 // W6. Otherwise, separators and terminators change to Other 36666 // Neutral. 36667 for (var i$4 = 0; i$4 < len; ++i$4) { 36668 var type$3 = types[i$4]; 36669 if (type$3 == ",") { types[i$4] = "N"; } 36670 else if (type$3 == "%") { 36671 var end = (void 0); 36672 for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} 36673 var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; 36674 for (var j = i$4; j < end; ++j) { types[j] = replace; } 36675 i$4 = end - 1; 36676 } 36677 } 36678 36679 // W7. Search backwards from each instance of a European number 36680 // until the first strong type (R, L, or sor) is found. If an L is 36681 // found, then change the type of the European number to L. 36682 for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { 36683 var type$4 = types[i$5]; 36684 if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L"; } 36685 else if (isStrong.test(type$4)) { cur$1 = type$4; } 36686 } 36687 36688 // N1. A sequence of neutrals takes the direction of the 36689 // surrounding strong text if the text on both sides has the same 36690 // direction. European and Arabic numbers act as if they were R in 36691 // terms of their influence on neutrals. Start-of-level-run (sor) 36692 // and end-of-level-run (eor) are used at level run boundaries. 36693 // N2. Any remaining neutrals take the embedding direction. 36694 for (var i$6 = 0; i$6 < len; ++i$6) { 36695 if (isNeutral.test(types[i$6])) { 36696 var end$1 = (void 0); 36697 for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} 36698 var before = (i$6 ? types[i$6-1] : outerType) == "L"; 36699 var after = (end$1 < len ? types[end$1] : outerType) == "L"; 36700 var replace$1 = before == after ? (before ? "L" : "R") : outerType; 36701 for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; } 36702 i$6 = end$1 - 1; 36703 } 36704 } 36705 36706 // Here we depart from the documented algorithm, in order to avoid 36707 // building up an actual levels array. Since there are only three 36708 // levels (0, 1, 2) in an implementation that doesn't take 36709 // explicit embedding into account, we can build up the order on 36710 // the fly, without following the level-based algorithm. 36711 var order = [], m; 36712 for (var i$7 = 0; i$7 < len;) { 36713 if (countsAsLeft.test(types[i$7])) { 36714 var start = i$7; 36715 for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} 36716 order.push(new BidiSpan(0, start, i$7)); 36717 } else { 36718 var pos = i$7, at = order.length; 36719 for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} 36720 for (var j$2 = pos; j$2 < i$7;) { 36721 if (countsAsNum.test(types[j$2])) { 36722 if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); } 36723 var nstart = j$2; 36724 for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} 36725 order.splice(at, 0, new BidiSpan(2, nstart, j$2)); 36726 pos = j$2; 36727 } else { ++j$2; } 36728 } 36729 if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); } 36730 } 36731 } 36732 if (direction == "ltr") { 36733 if (order[0].level == 1 && (m = str.match(/^\s+/))) { 36734 order[0].from = m[0].length; 36735 order.unshift(new BidiSpan(0, 0, m[0].length)); 36736 } 36737 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { 36738 lst(order).to -= m[0].length; 36739 order.push(new BidiSpan(0, len - m[0].length, len)); 36740 } 36741 } 36742 36743 return direction == "rtl" ? order.reverse() : order 36744 } 36745})(); 36746 36747// Get the bidi ordering for the given line (and cache it). Returns 36748// false for lines that are fully left-to-right, and an array of 36749// BidiSpan objects otherwise. 36750function getOrder(line, direction) { 36751 var order = line.order; 36752 if (order == null) { order = line.order = bidiOrdering(line.text, direction); } 36753 return order 36754} 36755 36756// EVENT HANDLING 36757 36758// Lightweight event framework. on/off also work on DOM nodes, 36759// registering native DOM handlers. 36760 36761var noHandlers = []; 36762 36763var on = function(emitter, type, f) { 36764 if (emitter.addEventListener) { 36765 emitter.addEventListener(type, f, false); 36766 } else if (emitter.attachEvent) { 36767 emitter.attachEvent("on" + type, f); 36768 } else { 36769 var map$$1 = emitter._handlers || (emitter._handlers = {}); 36770 map$$1[type] = (map$$1[type] || noHandlers).concat(f); 36771 } 36772}; 36773 36774function getHandlers(emitter, type) { 36775 return emitter._handlers && emitter._handlers[type] || noHandlers 36776} 36777 36778function off(emitter, type, f) { 36779 if (emitter.removeEventListener) { 36780 emitter.removeEventListener(type, f, false); 36781 } else if (emitter.detachEvent) { 36782 emitter.detachEvent("on" + type, f); 36783 } else { 36784 var map$$1 = emitter._handlers, arr = map$$1 && map$$1[type]; 36785 if (arr) { 36786 var index = indexOf(arr, f); 36787 if (index > -1) 36788 { map$$1[type] = arr.slice(0, index).concat(arr.slice(index + 1)); } 36789 } 36790 } 36791} 36792 36793function signal(emitter, type /*, values...*/) { 36794 var handlers = getHandlers(emitter, type); 36795 if (!handlers.length) { return } 36796 var args = Array.prototype.slice.call(arguments, 2); 36797 for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); } 36798} 36799 36800// The DOM events that CodeMirror handles can be overridden by 36801// registering a (non-DOM) handler on the editor for the event name, 36802// and preventDefault-ing the event in that handler. 36803function signalDOMEvent(cm, e, override) { 36804 if (typeof e == "string") 36805 { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; } 36806 signal(cm, override || e.type, cm, e); 36807 return e_defaultPrevented(e) || e.codemirrorIgnore 36808} 36809 36810function signalCursorActivity(cm) { 36811 var arr = cm._handlers && cm._handlers.cursorActivity; 36812 if (!arr) { return } 36813 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); 36814 for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) 36815 { set.push(arr[i]); } } 36816} 36817 36818function hasHandler(emitter, type) { 36819 return getHandlers(emitter, type).length > 0 36820} 36821 36822// Add on and off methods to a constructor's prototype, to make 36823// registering events on such objects more convenient. 36824function eventMixin(ctor) { 36825 ctor.prototype.on = function(type, f) {on(this, type, f);}; 36826 ctor.prototype.off = function(type, f) {off(this, type, f);}; 36827} 36828 36829// Due to the fact that we still support jurassic IE versions, some 36830// compatibility wrappers are needed. 36831 36832function e_preventDefault(e) { 36833 if (e.preventDefault) { e.preventDefault(); } 36834 else { e.returnValue = false; } 36835} 36836function e_stopPropagation(e) { 36837 if (e.stopPropagation) { e.stopPropagation(); } 36838 else { e.cancelBubble = true; } 36839} 36840function e_defaultPrevented(e) { 36841 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false 36842} 36843function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} 36844 36845function e_target(e) {return e.target || e.srcElement} 36846function e_button(e) { 36847 var b = e.which; 36848 if (b == null) { 36849 if (e.button & 1) { b = 1; } 36850 else if (e.button & 2) { b = 3; } 36851 else if (e.button & 4) { b = 2; } 36852 } 36853 if (mac && e.ctrlKey && b == 1) { b = 3; } 36854 return b 36855} 36856 36857// Detect drag-and-drop 36858var dragAndDrop = function() { 36859 // There is *some* kind of drag-and-drop support in IE6-8, but I 36860 // couldn't get it to work yet. 36861 if (ie && ie_version < 9) { return false } 36862 var div = elt('div'); 36863 return "draggable" in div || "dragDrop" in div 36864}(); 36865 36866var zwspSupported; 36867function zeroWidthElement(measure) { 36868 if (zwspSupported == null) { 36869 var test = elt("span", "\u200b"); 36870 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); 36871 if (measure.firstChild.offsetHeight != 0) 36872 { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); } 36873 } 36874 var node = zwspSupported ? elt("span", "\u200b") : 36875 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); 36876 node.setAttribute("cm-text", ""); 36877 return node 36878} 36879 36880// Feature-detect IE's crummy client rect reporting for bidi text 36881var badBidiRects; 36882function hasBadBidiRects(measure) { 36883 if (badBidiRects != null) { return badBidiRects } 36884 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); 36885 var r0 = range(txt, 0, 1).getBoundingClientRect(); 36886 var r1 = range(txt, 1, 2).getBoundingClientRect(); 36887 removeChildren(measure); 36888 if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) 36889 return badBidiRects = (r1.right - r0.right < 3) 36890} 36891 36892// See if "".split is the broken IE version, if so, provide an 36893// alternative way to split lines. 36894var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { 36895 var pos = 0, result = [], l = string.length; 36896 while (pos <= l) { 36897 var nl = string.indexOf("\n", pos); 36898 if (nl == -1) { nl = string.length; } 36899 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); 36900 var rt = line.indexOf("\r"); 36901 if (rt != -1) { 36902 result.push(line.slice(0, rt)); 36903 pos += rt + 1; 36904 } else { 36905 result.push(line); 36906 pos = nl + 1; 36907 } 36908 } 36909 return result 36910} : function (string) { return string.split(/\r\n?|\n/); }; 36911 36912var hasSelection = window.getSelection ? function (te) { 36913 try { return te.selectionStart != te.selectionEnd } 36914 catch(e) { return false } 36915} : function (te) { 36916 var range$$1; 36917 try {range$$1 = te.ownerDocument.selection.createRange();} 36918 catch(e) {} 36919 if (!range$$1 || range$$1.parentElement() != te) { return false } 36920 return range$$1.compareEndPoints("StartToEnd", range$$1) != 0 36921}; 36922 36923var hasCopyEvent = (function () { 36924 var e = elt("div"); 36925 if ("oncopy" in e) { return true } 36926 e.setAttribute("oncopy", "return;"); 36927 return typeof e.oncopy == "function" 36928})(); 36929 36930var badZoomedRects = null; 36931function hasBadZoomedRects(measure) { 36932 if (badZoomedRects != null) { return badZoomedRects } 36933 var node = removeChildrenAndAdd(measure, elt("span", "x")); 36934 var normal = node.getBoundingClientRect(); 36935 var fromRange = range(node, 0, 1).getBoundingClientRect(); 36936 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 36937} 36938 36939// Known modes, by name and by MIME 36940var modes = {}; 36941var mimeModes = {}; 36942 36943// Extra arguments are stored as the mode's dependencies, which is 36944// used by (legacy) mechanisms like loadmode.js to automatically 36945// load a mode. (Preferred mechanism is the require/define calls.) 36946function defineMode(name, mode) { 36947 if (arguments.length > 2) 36948 { mode.dependencies = Array.prototype.slice.call(arguments, 2); } 36949 modes[name] = mode; 36950} 36951 36952function defineMIME(mime, spec) { 36953 mimeModes[mime] = spec; 36954} 36955 36956// Given a MIME type, a {name, ...options} config object, or a name 36957// string, return a mode config object. 36958function resolveMode(spec) { 36959 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { 36960 spec = mimeModes[spec]; 36961 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { 36962 var found = mimeModes[spec.name]; 36963 if (typeof found == "string") { found = {name: found}; } 36964 spec = createObj(found, spec); 36965 spec.name = found.name; 36966 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { 36967 return resolveMode("application/xml") 36968 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { 36969 return resolveMode("application/json") 36970 } 36971 if (typeof spec == "string") { return {name: spec} } 36972 else { return spec || {name: "null"} } 36973} 36974 36975// Given a mode spec (anything that resolveMode accepts), find and 36976// initialize an actual mode object. 36977function getMode(options, spec) { 36978 spec = resolveMode(spec); 36979 var mfactory = modes[spec.name]; 36980 if (!mfactory) { return getMode(options, "text/plain") } 36981 var modeObj = mfactory(options, spec); 36982 if (modeExtensions.hasOwnProperty(spec.name)) { 36983 var exts = modeExtensions[spec.name]; 36984 for (var prop in exts) { 36985 if (!exts.hasOwnProperty(prop)) { continue } 36986 if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop]; } 36987 modeObj[prop] = exts[prop]; 36988 } 36989 } 36990 modeObj.name = spec.name; 36991 if (spec.helperType) { modeObj.helperType = spec.helperType; } 36992 if (spec.modeProps) { for (var prop$1 in spec.modeProps) 36993 { modeObj[prop$1] = spec.modeProps[prop$1]; } } 36994 36995 return modeObj 36996} 36997 36998// This can be used to attach properties to mode objects from 36999// outside the actual mode definition. 37000var modeExtensions = {}; 37001function extendMode(mode, properties) { 37002 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); 37003 copyObj(properties, exts); 37004} 37005 37006function copyState(mode, state) { 37007 if (state === true) { return state } 37008 if (mode.copyState) { return mode.copyState(state) } 37009 var nstate = {}; 37010 for (var n in state) { 37011 var val = state[n]; 37012 if (val instanceof Array) { val = val.concat([]); } 37013 nstate[n] = val; 37014 } 37015 return nstate 37016} 37017 37018// Given a mode and a state (for that mode), find the inner mode and 37019// state at the position that the state refers to. 37020function innerMode(mode, state) { 37021 var info; 37022 while (mode.innerMode) { 37023 info = mode.innerMode(state); 37024 if (!info || info.mode == mode) { break } 37025 state = info.state; 37026 mode = info.mode; 37027 } 37028 return info || {mode: mode, state: state} 37029} 37030 37031function startState(mode, a1, a2) { 37032 return mode.startState ? mode.startState(a1, a2) : true 37033} 37034 37035// STRING STREAM 37036 37037// Fed to the mode parsers, provides helper functions to make 37038// parsers more succinct. 37039 37040var StringStream = function(string, tabSize, lineOracle) { 37041 this.pos = this.start = 0; 37042 this.string = string; 37043 this.tabSize = tabSize || 8; 37044 this.lastColumnPos = this.lastColumnValue = 0; 37045 this.lineStart = 0; 37046 this.lineOracle = lineOracle; 37047}; 37048 37049StringStream.prototype.eol = function () {return this.pos >= this.string.length}; 37050StringStream.prototype.sol = function () {return this.pos == this.lineStart}; 37051StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; 37052StringStream.prototype.next = function () { 37053 if (this.pos < this.string.length) 37054 { return this.string.charAt(this.pos++) } 37055}; 37056StringStream.prototype.eat = function (match) { 37057 var ch = this.string.charAt(this.pos); 37058 var ok; 37059 if (typeof match == "string") { ok = ch == match; } 37060 else { ok = ch && (match.test ? match.test(ch) : match(ch)); } 37061 if (ok) {++this.pos; return ch} 37062}; 37063StringStream.prototype.eatWhile = function (match) { 37064 var start = this.pos; 37065 while (this.eat(match)){} 37066 return this.pos > start 37067}; 37068StringStream.prototype.eatSpace = function () { 37069 var this$1 = this; 37070 37071 var start = this.pos; 37072 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos; } 37073 return this.pos > start 37074}; 37075StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;}; 37076StringStream.prototype.skipTo = function (ch) { 37077 var found = this.string.indexOf(ch, this.pos); 37078 if (found > -1) {this.pos = found; return true} 37079}; 37080StringStream.prototype.backUp = function (n) {this.pos -= n;}; 37081StringStream.prototype.column = function () { 37082 if (this.lastColumnPos < this.start) { 37083 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); 37084 this.lastColumnPos = this.start; 37085 } 37086 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) 37087}; 37088StringStream.prototype.indentation = function () { 37089 return countColumn(this.string, null, this.tabSize) - 37090 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) 37091}; 37092StringStream.prototype.match = function (pattern, consume, caseInsensitive) { 37093 if (typeof pattern == "string") { 37094 var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; }; 37095 var substr = this.string.substr(this.pos, pattern.length); 37096 if (cased(substr) == cased(pattern)) { 37097 if (consume !== false) { this.pos += pattern.length; } 37098 return true 37099 } 37100 } else { 37101 var match = this.string.slice(this.pos).match(pattern); 37102 if (match && match.index > 0) { return null } 37103 if (match && consume !== false) { this.pos += match[0].length; } 37104 return match 37105 } 37106}; 37107StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; 37108StringStream.prototype.hideFirstChars = function (n, inner) { 37109 this.lineStart += n; 37110 try { return inner() } 37111 finally { this.lineStart -= n; } 37112}; 37113StringStream.prototype.lookAhead = function (n) { 37114 var oracle = this.lineOracle; 37115 return oracle && oracle.lookAhead(n) 37116}; 37117StringStream.prototype.baseToken = function () { 37118 var oracle = this.lineOracle; 37119 return oracle && oracle.baseToken(this.pos) 37120}; 37121 37122var SavedContext = function(state, lookAhead) { 37123 this.state = state; 37124 this.lookAhead = lookAhead; 37125}; 37126 37127var Context = function(doc, state, line, lookAhead) { 37128 this.state = state; 37129 this.doc = doc; 37130 this.line = line; 37131 this.maxLookAhead = lookAhead || 0; 37132 this.baseTokens = null; 37133 this.baseTokenPos = 1; 37134}; 37135 37136Context.prototype.lookAhead = function (n) { 37137 var line = this.doc.getLine(this.line + n); 37138 if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; } 37139 return line 37140}; 37141 37142Context.prototype.baseToken = function (n) { 37143 var this$1 = this; 37144 37145 if (!this.baseTokens) { return null } 37146 while (this.baseTokens[this.baseTokenPos] <= n) 37147 { this$1.baseTokenPos += 2; } 37148 var type = this.baseTokens[this.baseTokenPos + 1]; 37149 return {type: type && type.replace(/( |^)overlay .*/, ""), 37150 size: this.baseTokens[this.baseTokenPos] - n} 37151}; 37152 37153Context.prototype.nextLine = function () { 37154 this.line++; 37155 if (this.maxLookAhead > 0) { this.maxLookAhead--; } 37156}; 37157 37158Context.fromSaved = function (doc, saved, line) { 37159 if (saved instanceof SavedContext) 37160 { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } 37161 else 37162 { return new Context(doc, copyState(doc.mode, saved), line) } 37163}; 37164 37165Context.prototype.save = function (copy) { 37166 var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state; 37167 return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state 37168}; 37169 37170 37171// Compute a style array (an array starting with a mode generation 37172// -- for invalidation -- followed by pairs of end positions and 37173// style strings), which is used to highlight the tokens on the 37174// line. 37175function highlightLine(cm, line, context, forceToEnd) { 37176 // A styles array always starts with a number identifying the 37177 // mode/overlays that it is based on (for easy invalidation). 37178 var st = [cm.state.modeGen], lineClasses = {}; 37179 // Compute the base array of styles 37180 runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, 37181 lineClasses, forceToEnd); 37182 var state = context.state; 37183 37184 // Run overlays, adjust style array. 37185 var loop = function ( o ) { 37186 context.baseTokens = st; 37187 var overlay = cm.state.overlays[o], i = 1, at = 0; 37188 context.state = true; 37189 runMode(cm, line.text, overlay.mode, context, function (end, style) { 37190 var start = i; 37191 // Ensure there's a token end at the current position, and that i points at it 37192 while (at < end) { 37193 var i_end = st[i]; 37194 if (i_end > end) 37195 { st.splice(i, 1, end, st[i+1], i_end); } 37196 i += 2; 37197 at = Math.min(end, i_end); 37198 } 37199 if (!style) { return } 37200 if (overlay.opaque) { 37201 st.splice(start, i - start, end, "overlay " + style); 37202 i = start + 2; 37203 } else { 37204 for (; start < i; start += 2) { 37205 var cur = st[start+1]; 37206 st[start+1] = (cur ? cur + " " : "") + "overlay " + style; 37207 } 37208 } 37209 }, lineClasses); 37210 context.state = state; 37211 context.baseTokens = null; 37212 context.baseTokenPos = 1; 37213 }; 37214 37215 for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); 37216 37217 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} 37218} 37219 37220function getLineStyles(cm, line, updateFrontier) { 37221 if (!line.styles || line.styles[0] != cm.state.modeGen) { 37222 var context = getContextBefore(cm, lineNo(line)); 37223 var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state); 37224 var result = highlightLine(cm, line, context); 37225 if (resetState) { context.state = resetState; } 37226 line.stateAfter = context.save(!resetState); 37227 line.styles = result.styles; 37228 if (result.classes) { line.styleClasses = result.classes; } 37229 else if (line.styleClasses) { line.styleClasses = null; } 37230 if (updateFrontier === cm.doc.highlightFrontier) 37231 { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); } 37232 } 37233 return line.styles 37234} 37235 37236function getContextBefore(cm, n, precise) { 37237 var doc = cm.doc, display = cm.display; 37238 if (!doc.mode.startState) { return new Context(doc, true, n) } 37239 var start = findStartLine(cm, n, precise); 37240 var saved = start > doc.first && getLine(doc, start - 1).stateAfter; 37241 var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start); 37242 37243 doc.iter(start, n, function (line) { 37244 processLine(cm, line.text, context); 37245 var pos = context.line; 37246 line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null; 37247 context.nextLine(); 37248 }); 37249 if (precise) { doc.modeFrontier = context.line; } 37250 return context 37251} 37252 37253// Lightweight form of highlight -- proceed over this line and 37254// update state, but don't save a style array. Used for lines that 37255// aren't currently visible. 37256function processLine(cm, text, context, startAt) { 37257 var mode = cm.doc.mode; 37258 var stream = new StringStream(text, cm.options.tabSize, context); 37259 stream.start = stream.pos = startAt || 0; 37260 if (text == "") { callBlankLine(mode, context.state); } 37261 while (!stream.eol()) { 37262 readToken(mode, stream, context.state); 37263 stream.start = stream.pos; 37264 } 37265} 37266 37267function callBlankLine(mode, state) { 37268 if (mode.blankLine) { return mode.blankLine(state) } 37269 if (!mode.innerMode) { return } 37270 var inner = innerMode(mode, state); 37271 if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } 37272} 37273 37274function readToken(mode, stream, state, inner) { 37275 for (var i = 0; i < 10; i++) { 37276 if (inner) { inner[0] = innerMode(mode, state).mode; } 37277 var style = mode.token(stream, state); 37278 if (stream.pos > stream.start) { return style } 37279 } 37280 throw new Error("Mode " + mode.name + " failed to advance stream.") 37281} 37282 37283var Token = function(stream, type, state) { 37284 this.start = stream.start; this.end = stream.pos; 37285 this.string = stream.current(); 37286 this.type = type || null; 37287 this.state = state; 37288}; 37289 37290// Utility for getTokenAt and getLineTokens 37291function takeToken(cm, pos, precise, asArray) { 37292 var doc = cm.doc, mode = doc.mode, style; 37293 pos = clipPos(doc, pos); 37294 var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise); 37295 var stream = new StringStream(line.text, cm.options.tabSize, context), tokens; 37296 if (asArray) { tokens = []; } 37297 while ((asArray || stream.pos < pos.ch) && !stream.eol()) { 37298 stream.start = stream.pos; 37299 style = readToken(mode, stream, context.state); 37300 if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); } 37301 } 37302 return asArray ? tokens : new Token(stream, style, context.state) 37303} 37304 37305function extractLineClasses(type, output) { 37306 if (type) { for (;;) { 37307 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); 37308 if (!lineClass) { break } 37309 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); 37310 var prop = lineClass[1] ? "bgClass" : "textClass"; 37311 if (output[prop] == null) 37312 { output[prop] = lineClass[2]; } 37313 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) 37314 { output[prop] += " " + lineClass[2]; } 37315 } } 37316 return type 37317} 37318 37319// Run the given mode's parser over a line, calling f for each token. 37320function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { 37321 var flattenSpans = mode.flattenSpans; 37322 if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; } 37323 var curStart = 0, curStyle = null; 37324 var stream = new StringStream(text, cm.options.tabSize, context), style; 37325 var inner = cm.options.addModeClass && [null]; 37326 if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); } 37327 while (!stream.eol()) { 37328 if (stream.pos > cm.options.maxHighlightLength) { 37329 flattenSpans = false; 37330 if (forceToEnd) { processLine(cm, text, context, stream.pos); } 37331 stream.pos = text.length; 37332 style = null; 37333 } else { 37334 style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses); 37335 } 37336 if (inner) { 37337 var mName = inner[0].name; 37338 if (mName) { style = "m-" + (style ? mName + " " + style : mName); } 37339 } 37340 if (!flattenSpans || curStyle != style) { 37341 while (curStart < stream.start) { 37342 curStart = Math.min(stream.start, curStart + 5000); 37343 f(curStart, curStyle); 37344 } 37345 curStyle = style; 37346 } 37347 stream.start = stream.pos; 37348 } 37349 while (curStart < stream.pos) { 37350 // Webkit seems to refuse to render text nodes longer than 57444 37351 // characters, and returns inaccurate measurements in nodes 37352 // starting around 5000 chars. 37353 var pos = Math.min(stream.pos, curStart + 5000); 37354 f(pos, curStyle); 37355 curStart = pos; 37356 } 37357} 37358 37359// Finds the line to start with when starting a parse. Tries to 37360// find a line with a stateAfter, so that it can start with a 37361// valid state. If that fails, it returns the line with the 37362// smallest indentation, which tends to need the least context to 37363// parse correctly. 37364function findStartLine(cm, n, precise) { 37365 var minindent, minline, doc = cm.doc; 37366 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); 37367 for (var search = n; search > lim; --search) { 37368 if (search <= doc.first) { return doc.first } 37369 var line = getLine(doc, search - 1), after = line.stateAfter; 37370 if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) 37371 { return search } 37372 var indented = countColumn(line.text, null, cm.options.tabSize); 37373 if (minline == null || minindent > indented) { 37374 minline = search - 1; 37375 minindent = indented; 37376 } 37377 } 37378 return minline 37379} 37380 37381function retreatFrontier(doc, n) { 37382 doc.modeFrontier = Math.min(doc.modeFrontier, n); 37383 if (doc.highlightFrontier < n - 10) { return } 37384 var start = doc.first; 37385 for (var line = n - 1; line > start; line--) { 37386 var saved = getLine(doc, line).stateAfter; 37387 // change is on 3 37388 // state on line 1 looked ahead 2 -- so saw 3 37389 // test 1 + 2 < 3 should cover this 37390 if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { 37391 start = line + 1; 37392 break 37393 } 37394 } 37395 doc.highlightFrontier = Math.min(doc.highlightFrontier, start); 37396} 37397 37398// LINE DATA STRUCTURE 37399 37400// Line objects. These hold state related to a line, including 37401// highlighting info (the styles array). 37402var Line = function(text, markedSpans, estimateHeight) { 37403 this.text = text; 37404 attachMarkedSpans(this, markedSpans); 37405 this.height = estimateHeight ? estimateHeight(this) : 1; 37406}; 37407 37408Line.prototype.lineNo = function () { return lineNo(this) }; 37409eventMixin(Line); 37410 37411// Change the content (text, markers) of a line. Automatically 37412// invalidates cached information and tries to re-estimate the 37413// line's height. 37414function updateLine(line, text, markedSpans, estimateHeight) { 37415 line.text = text; 37416 if (line.stateAfter) { line.stateAfter = null; } 37417 if (line.styles) { line.styles = null; } 37418 if (line.order != null) { line.order = null; } 37419 detachMarkedSpans(line); 37420 attachMarkedSpans(line, markedSpans); 37421 var estHeight = estimateHeight ? estimateHeight(line) : 1; 37422 if (estHeight != line.height) { updateLineHeight(line, estHeight); } 37423} 37424 37425// Detach a line from the document tree and its markers. 37426function cleanUpLine(line) { 37427 line.parent = null; 37428 detachMarkedSpans(line); 37429} 37430 37431// Convert a style as returned by a mode (either null, or a string 37432// containing one or more styles) to a CSS style. This is cached, 37433// and also looks for line-wide styles. 37434var styleToClassCache = {}; 37435var styleToClassCacheWithMode = {}; 37436function interpretTokenStyle(style, options) { 37437 if (!style || /^\s*$/.test(style)) { return null } 37438 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; 37439 return cache[style] || 37440 (cache[style] = style.replace(/\S+/g, "cm-$&")) 37441} 37442 37443// Render the DOM representation of the text of a line. Also builds 37444// up a 'line map', which points at the DOM nodes that represent 37445// specific stretches of text, and is used by the measuring code. 37446// The returned object contains the DOM node, this map, and 37447// information about line-wide styles that were set by the mode. 37448function buildLineContent(cm, lineView) { 37449 // The padding-right forces the element to have a 'border', which 37450 // is needed on Webkit to be able to get line-level bounding 37451 // rectangles for it (in measureChar). 37452 var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null); 37453 var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, 37454 col: 0, pos: 0, cm: cm, 37455 trailingSpace: false, 37456 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; 37457 lineView.measure = {}; 37458 37459 // Iterate over the logical lines that make up this visual line. 37460 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { 37461 var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0); 37462 builder.pos = 0; 37463 builder.addToken = buildToken; 37464 // Optionally wire in some hacks into the token-rendering 37465 // algorithm, to deal with browser quirks. 37466 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) 37467 { builder.addToken = buildTokenBadBidi(builder.addToken, order); } 37468 builder.map = []; 37469 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); 37470 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); 37471 if (line.styleClasses) { 37472 if (line.styleClasses.bgClass) 37473 { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); } 37474 if (line.styleClasses.textClass) 37475 { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); } 37476 } 37477 37478 // Ensure at least a single node is present, for measuring. 37479 if (builder.map.length == 0) 37480 { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); } 37481 37482 // Store the map and a cache object for the current logical line 37483 if (i == 0) { 37484 lineView.measure.map = builder.map; 37485 lineView.measure.cache = {}; 37486 } else { 37487 (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) 37488 ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}); 37489 } 37490 } 37491 37492 // See issue #2901 37493 if (webkit) { 37494 var last = builder.content.lastChild; 37495 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) 37496 { builder.content.className = "cm-tab-wrap-hack"; } 37497 } 37498 37499 signal(cm, "renderLine", cm, lineView.line, builder.pre); 37500 if (builder.pre.className) 37501 { builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); } 37502 37503 return builder 37504} 37505 37506function defaultSpecialCharPlaceholder(ch) { 37507 var token = elt("span", "\u2022", "cm-invalidchar"); 37508 token.title = "\\u" + ch.charCodeAt(0).toString(16); 37509 token.setAttribute("aria-label", token.title); 37510 return token 37511} 37512 37513// Build up the DOM representation for a single token, and add it to 37514// the line map. Takes care to render special characters separately. 37515function buildToken(builder, text, style, startStyle, endStyle, title, css) { 37516 if (!text) { return } 37517 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text; 37518 var special = builder.cm.state.specialChars, mustWrap = false; 37519 var content; 37520 if (!special.test(text)) { 37521 builder.col += text.length; 37522 content = document.createTextNode(displayText); 37523 builder.map.push(builder.pos, builder.pos + text.length, content); 37524 if (ie && ie_version < 9) { mustWrap = true; } 37525 builder.pos += text.length; 37526 } else { 37527 content = document.createDocumentFragment(); 37528 var pos = 0; 37529 while (true) { 37530 special.lastIndex = pos; 37531 var m = special.exec(text); 37532 var skipped = m ? m.index - pos : text.length - pos; 37533 if (skipped) { 37534 var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); 37535 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])); } 37536 else { content.appendChild(txt); } 37537 builder.map.push(builder.pos, builder.pos + skipped, txt); 37538 builder.col += skipped; 37539 builder.pos += skipped; 37540 } 37541 if (!m) { break } 37542 pos += skipped + 1; 37543 var txt$1 = (void 0); 37544 if (m[0] == "\t") { 37545 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; 37546 txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); 37547 txt$1.setAttribute("role", "presentation"); 37548 txt$1.setAttribute("cm-text", "\t"); 37549 builder.col += tabWidth; 37550 } else if (m[0] == "\r" || m[0] == "\n") { 37551 txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); 37552 txt$1.setAttribute("cm-text", m[0]); 37553 builder.col += 1; 37554 } else { 37555 txt$1 = builder.cm.options.specialCharPlaceholder(m[0]); 37556 txt$1.setAttribute("cm-text", m[0]); 37557 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])); } 37558 else { content.appendChild(txt$1); } 37559 builder.col += 1; 37560 } 37561 builder.map.push(builder.pos, builder.pos + 1, txt$1); 37562 builder.pos++; 37563 } 37564 } 37565 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32; 37566 if (style || startStyle || endStyle || mustWrap || css) { 37567 var fullStyle = style || ""; 37568 if (startStyle) { fullStyle += startStyle; } 37569 if (endStyle) { fullStyle += endStyle; } 37570 var token = elt("span", [content], fullStyle, css); 37571 if (title) { token.title = title; } 37572 return builder.content.appendChild(token) 37573 } 37574 builder.content.appendChild(content); 37575} 37576 37577function splitSpaces(text, trailingBefore) { 37578 if (text.length > 1 && !/ /.test(text)) { return text } 37579 var spaceBefore = trailingBefore, result = ""; 37580 for (var i = 0; i < text.length; i++) { 37581 var ch = text.charAt(i); 37582 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) 37583 { ch = "\u00a0"; } 37584 result += ch; 37585 spaceBefore = ch == " "; 37586 } 37587 return result 37588} 37589 37590// Work around nonsense dimensions being reported for stretches of 37591// right-to-left text. 37592function buildTokenBadBidi(inner, order) { 37593 return function (builder, text, style, startStyle, endStyle, title, css) { 37594 style = style ? style + " cm-force-border" : "cm-force-border"; 37595 var start = builder.pos, end = start + text.length; 37596 for (;;) { 37597 // Find the part that overlaps with the start of this text 37598 var part = (void 0); 37599 for (var i = 0; i < order.length; i++) { 37600 part = order[i]; 37601 if (part.to > start && part.from <= start) { break } 37602 } 37603 if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, title, css) } 37604 inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); 37605 startStyle = null; 37606 text = text.slice(part.to - start); 37607 start = part.to; 37608 } 37609 } 37610} 37611 37612function buildCollapsedSpan(builder, size, marker, ignoreWidget) { 37613 var widget = !ignoreWidget && marker.widgetNode; 37614 if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); } 37615 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { 37616 if (!widget) 37617 { widget = builder.content.appendChild(document.createElement("span")); } 37618 widget.setAttribute("cm-marker", marker.id); 37619 } 37620 if (widget) { 37621 builder.cm.display.input.setUneditable(widget); 37622 builder.content.appendChild(widget); 37623 } 37624 builder.pos += size; 37625 builder.trailingSpace = false; 37626} 37627 37628// Outputs a number of spans to make up a line, taking highlighting 37629// and marked text into account. 37630function insertLineContent(line, builder, styles) { 37631 var spans = line.markedSpans, allText = line.text, at = 0; 37632 if (!spans) { 37633 for (var i$1 = 1; i$1 < styles.length; i$1+=2) 37634 { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); } 37635 return 37636 } 37637 37638 var len = allText.length, pos = 0, i = 1, text = "", style, css; 37639 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; 37640 for (;;) { 37641 if (nextChange == pos) { // Update current marker set 37642 spanStyle = spanEndStyle = spanStartStyle = title = css = ""; 37643 collapsed = null; nextChange = Infinity; 37644 var foundBookmarks = [], endStyles = (void 0); 37645 for (var j = 0; j < spans.length; ++j) { 37646 var sp = spans[j], m = sp.marker; 37647 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { 37648 foundBookmarks.push(m); 37649 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { 37650 if (sp.to != null && sp.to != pos && nextChange > sp.to) { 37651 nextChange = sp.to; 37652 spanEndStyle = ""; 37653 } 37654 if (m.className) { spanStyle += " " + m.className; } 37655 if (m.css) { css = (css ? css + ";" : "") + m.css; } 37656 if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle; } 37657 if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); } 37658 if (m.title && !title) { title = m.title; } 37659 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) 37660 { collapsed = sp; } 37661 } else if (sp.from > pos && nextChange > sp.from) { 37662 nextChange = sp.from; 37663 } 37664 } 37665 if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) 37666 { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1]; } } } 37667 37668 if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) 37669 { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } } 37670 if (collapsed && (collapsed.from || 0) == pos) { 37671 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, 37672 collapsed.marker, collapsed.from == null); 37673 if (collapsed.to == null) { return } 37674 if (collapsed.to == pos) { collapsed = false; } 37675 } 37676 } 37677 if (pos >= len) { break } 37678 37679 var upto = Math.min(len, nextChange); 37680 while (true) { 37681 if (text) { 37682 var end = pos + text.length; 37683 if (!collapsed) { 37684 var tokenText = end > upto ? text.slice(0, upto - pos) : text; 37685 builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, 37686 spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); 37687 } 37688 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} 37689 pos = end; 37690 spanStartStyle = ""; 37691 } 37692 text = allText.slice(at, at = styles[i++]); 37693 style = interpretTokenStyle(styles[i++], builder.cm.options); 37694 } 37695 } 37696} 37697 37698 37699// These objects are used to represent the visible (currently drawn) 37700// part of the document. A LineView may correspond to multiple 37701// logical lines, if those are connected by collapsed ranges. 37702function LineView(doc, line, lineN) { 37703 // The starting line 37704 this.line = line; 37705 // Continuing lines, if any 37706 this.rest = visualLineContinued(line); 37707 // Number of logical lines in this visual line 37708 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; 37709 this.node = this.text = null; 37710 this.hidden = lineIsHidden(doc, line); 37711} 37712 37713// Create a range of LineView objects for the given lines. 37714function buildViewArray(cm, from, to) { 37715 var array = [], nextPos; 37716 for (var pos = from; pos < to; pos = nextPos) { 37717 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); 37718 nextPos = pos + view.size; 37719 array.push(view); 37720 } 37721 return array 37722} 37723 37724var operationGroup = null; 37725 37726function pushOperation(op) { 37727 if (operationGroup) { 37728 operationGroup.ops.push(op); 37729 } else { 37730 op.ownsGroup = operationGroup = { 37731 ops: [op], 37732 delayedCallbacks: [] 37733 }; 37734 } 37735} 37736 37737function fireCallbacksForOps(group) { 37738 // Calls delayed callbacks and cursorActivity handlers until no 37739 // new ones appear 37740 var callbacks = group.delayedCallbacks, i = 0; 37741 do { 37742 for (; i < callbacks.length; i++) 37743 { callbacks[i].call(null); } 37744 for (var j = 0; j < group.ops.length; j++) { 37745 var op = group.ops[j]; 37746 if (op.cursorActivityHandlers) 37747 { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) 37748 { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } } 37749 } 37750 } while (i < callbacks.length) 37751} 37752 37753function finishOperation(op, endCb) { 37754 var group = op.ownsGroup; 37755 if (!group) { return } 37756 37757 try { fireCallbacksForOps(group); } 37758 finally { 37759 operationGroup = null; 37760 endCb(group); 37761 } 37762} 37763 37764var orphanDelayedCallbacks = null; 37765 37766// Often, we want to signal events at a point where we are in the 37767// middle of some work, but don't want the handler to start calling 37768// other methods on the editor, which might be in an inconsistent 37769// state or simply not expect any other events to happen. 37770// signalLater looks whether there are any handlers, and schedules 37771// them to be executed when the last operation ends, or, if no 37772// operation is active, when a timeout fires. 37773function signalLater(emitter, type /*, values...*/) { 37774 var arr = getHandlers(emitter, type); 37775 if (!arr.length) { return } 37776 var args = Array.prototype.slice.call(arguments, 2), list; 37777 if (operationGroup) { 37778 list = operationGroup.delayedCallbacks; 37779 } else if (orphanDelayedCallbacks) { 37780 list = orphanDelayedCallbacks; 37781 } else { 37782 list = orphanDelayedCallbacks = []; 37783 setTimeout(fireOrphanDelayed, 0); 37784 } 37785 var loop = function ( i ) { 37786 list.push(function () { return arr[i].apply(null, args); }); 37787 }; 37788 37789 for (var i = 0; i < arr.length; ++i) 37790 loop( i ); 37791} 37792 37793function fireOrphanDelayed() { 37794 var delayed = orphanDelayedCallbacks; 37795 orphanDelayedCallbacks = null; 37796 for (var i = 0; i < delayed.length; ++i) { delayed[i](); } 37797} 37798 37799// When an aspect of a line changes, a string is added to 37800// lineView.changes. This updates the relevant part of the line's 37801// DOM structure. 37802function updateLineForChanges(cm, lineView, lineN, dims) { 37803 for (var j = 0; j < lineView.changes.length; j++) { 37804 var type = lineView.changes[j]; 37805 if (type == "text") { updateLineText(cm, lineView); } 37806 else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims); } 37807 else if (type == "class") { updateLineClasses(cm, lineView); } 37808 else if (type == "widget") { updateLineWidgets(cm, lineView, dims); } 37809 } 37810 lineView.changes = null; 37811} 37812 37813// Lines with gutter elements, widgets or a background class need to 37814// be wrapped, and have the extra elements added to the wrapper div 37815function ensureLineWrapped(lineView) { 37816 if (lineView.node == lineView.text) { 37817 lineView.node = elt("div", null, null, "position: relative"); 37818 if (lineView.text.parentNode) 37819 { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); } 37820 lineView.node.appendChild(lineView.text); 37821 if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; } 37822 } 37823 return lineView.node 37824} 37825 37826function updateLineBackground(cm, lineView) { 37827 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; 37828 if (cls) { cls += " CodeMirror-linebackground"; } 37829 if (lineView.background) { 37830 if (cls) { lineView.background.className = cls; } 37831 else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } 37832 } else if (cls) { 37833 var wrap = ensureLineWrapped(lineView); 37834 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); 37835 cm.display.input.setUneditable(lineView.background); 37836 } 37837} 37838 37839// Wrapper around buildLineContent which will reuse the structure 37840// in display.externalMeasured when possible. 37841function getLineContent(cm, lineView) { 37842 var ext = cm.display.externalMeasured; 37843 if (ext && ext.line == lineView.line) { 37844 cm.display.externalMeasured = null; 37845 lineView.measure = ext.measure; 37846 return ext.built 37847 } 37848 return buildLineContent(cm, lineView) 37849} 37850 37851// Redraw the line's text. Interacts with the background and text 37852// classes because the mode may output tokens that influence these 37853// classes. 37854function updateLineText(cm, lineView) { 37855 var cls = lineView.text.className; 37856 var built = getLineContent(cm, lineView); 37857 if (lineView.text == lineView.node) { lineView.node = built.pre; } 37858 lineView.text.parentNode.replaceChild(built.pre, lineView.text); 37859 lineView.text = built.pre; 37860 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { 37861 lineView.bgClass = built.bgClass; 37862 lineView.textClass = built.textClass; 37863 updateLineClasses(cm, lineView); 37864 } else if (cls) { 37865 lineView.text.className = cls; 37866 } 37867} 37868 37869function updateLineClasses(cm, lineView) { 37870 updateLineBackground(cm, lineView); 37871 if (lineView.line.wrapClass) 37872 { ensureLineWrapped(lineView).className = lineView.line.wrapClass; } 37873 else if (lineView.node != lineView.text) 37874 { lineView.node.className = ""; } 37875 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; 37876 lineView.text.className = textClass || ""; 37877} 37878 37879function updateLineGutter(cm, lineView, lineN, dims) { 37880 if (lineView.gutter) { 37881 lineView.node.removeChild(lineView.gutter); 37882 lineView.gutter = null; 37883 } 37884 if (lineView.gutterBackground) { 37885 lineView.node.removeChild(lineView.gutterBackground); 37886 lineView.gutterBackground = null; 37887 } 37888 if (lineView.line.gutterClass) { 37889 var wrap = ensureLineWrapped(lineView); 37890 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, 37891 ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")); 37892 cm.display.input.setUneditable(lineView.gutterBackground); 37893 wrap.insertBefore(lineView.gutterBackground, lineView.text); 37894 } 37895 var markers = lineView.line.gutterMarkers; 37896 if (cm.options.lineNumbers || markers) { 37897 var wrap$1 = ensureLineWrapped(lineView); 37898 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")); 37899 cm.display.input.setUneditable(gutterWrap); 37900 wrap$1.insertBefore(gutterWrap, lineView.text); 37901 if (lineView.line.gutterClass) 37902 { gutterWrap.className += " " + lineView.line.gutterClass; } 37903 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) 37904 { lineView.lineNumber = gutterWrap.appendChild( 37905 elt("div", lineNumberFor(cm.options, lineN), 37906 "CodeMirror-linenumber CodeMirror-gutter-elt", 37907 ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))); } 37908 if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { 37909 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; 37910 if (found) 37911 { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", 37912 ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))); } 37913 } } 37914 } 37915} 37916 37917function updateLineWidgets(cm, lineView, dims) { 37918 if (lineView.alignable) { lineView.alignable = null; } 37919 for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { 37920 next = node.nextSibling; 37921 if (node.className == "CodeMirror-linewidget") 37922 { lineView.node.removeChild(node); } 37923 } 37924 insertLineWidgets(cm, lineView, dims); 37925} 37926 37927// Build a line's DOM representation from scratch 37928function buildLineElement(cm, lineView, lineN, dims) { 37929 var built = getLineContent(cm, lineView); 37930 lineView.text = lineView.node = built.pre; 37931 if (built.bgClass) { lineView.bgClass = built.bgClass; } 37932 if (built.textClass) { lineView.textClass = built.textClass; } 37933 37934 updateLineClasses(cm, lineView); 37935 updateLineGutter(cm, lineView, lineN, dims); 37936 insertLineWidgets(cm, lineView, dims); 37937 return lineView.node 37938} 37939 37940// A lineView may contain multiple logical lines (when merged by 37941// collapsed spans). The widgets for all of them need to be drawn. 37942function insertLineWidgets(cm, lineView, dims) { 37943 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); 37944 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) 37945 { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } } 37946} 37947 37948function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { 37949 if (!line.widgets) { return } 37950 var wrap = ensureLineWrapped(lineView); 37951 for (var i = 0, ws = line.widgets; i < ws.length; ++i) { 37952 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); 37953 if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true"); } 37954 positionLineWidget(widget, node, lineView, dims); 37955 cm.display.input.setUneditable(node); 37956 if (allowAbove && widget.above) 37957 { wrap.insertBefore(node, lineView.gutter || lineView.text); } 37958 else 37959 { wrap.appendChild(node); } 37960 signalLater(widget, "redraw"); 37961 } 37962} 37963 37964function positionLineWidget(widget, node, lineView, dims) { 37965 if (widget.noHScroll) { 37966 (lineView.alignable || (lineView.alignable = [])).push(node); 37967 var width = dims.wrapperWidth; 37968 node.style.left = dims.fixedPos + "px"; 37969 if (!widget.coverGutter) { 37970 width -= dims.gutterTotalWidth; 37971 node.style.paddingLeft = dims.gutterTotalWidth + "px"; 37972 } 37973 node.style.width = width + "px"; 37974 } 37975 if (widget.coverGutter) { 37976 node.style.zIndex = 5; 37977 node.style.position = "relative"; 37978 if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px"; } 37979 } 37980} 37981 37982function widgetHeight(widget) { 37983 if (widget.height != null) { return widget.height } 37984 var cm = widget.doc.cm; 37985 if (!cm) { return 0 } 37986 if (!contains(document.body, widget.node)) { 37987 var parentStyle = "position: relative;"; 37988 if (widget.coverGutter) 37989 { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; } 37990 if (widget.noHScroll) 37991 { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; } 37992 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); 37993 } 37994 return widget.height = widget.node.parentNode.offsetHeight 37995} 37996 37997// Return true when the given mouse event happened in a widget 37998function eventInWidget(display, e) { 37999 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { 38000 if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || 38001 (n.parentNode == display.sizer && n != display.mover)) 38002 { return true } 38003 } 38004} 38005 38006// POSITION MEASUREMENT 38007 38008function paddingTop(display) {return display.lineSpace.offsetTop} 38009function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} 38010function paddingH(display) { 38011 if (display.cachedPaddingH) { return display.cachedPaddingH } 38012 var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); 38013 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; 38014 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; 38015 if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; } 38016 return data 38017} 38018 38019function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } 38020function displayWidth(cm) { 38021 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth 38022} 38023function displayHeight(cm) { 38024 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight 38025} 38026 38027// Ensure the lineView.wrapping.heights array is populated. This is 38028// an array of bottom offsets for the lines that make up a drawn 38029// line. When lineWrapping is on, there might be more than one 38030// height. 38031function ensureLineHeights(cm, lineView, rect) { 38032 var wrapping = cm.options.lineWrapping; 38033 var curWidth = wrapping && displayWidth(cm); 38034 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { 38035 var heights = lineView.measure.heights = []; 38036 if (wrapping) { 38037 lineView.measure.width = curWidth; 38038 var rects = lineView.text.firstChild.getClientRects(); 38039 for (var i = 0; i < rects.length - 1; i++) { 38040 var cur = rects[i], next = rects[i + 1]; 38041 if (Math.abs(cur.bottom - next.bottom) > 2) 38042 { heights.push((cur.bottom + next.top) / 2 - rect.top); } 38043 } 38044 } 38045 heights.push(rect.bottom - rect.top); 38046 } 38047} 38048 38049// Find a line map (mapping character offsets to text nodes) and a 38050// measurement cache for the given line number. (A line view might 38051// contain multiple lines when collapsed ranges are present.) 38052function mapFromLineView(lineView, line, lineN) { 38053 if (lineView.line == line) 38054 { return {map: lineView.measure.map, cache: lineView.measure.cache} } 38055 for (var i = 0; i < lineView.rest.length; i++) 38056 { if (lineView.rest[i] == line) 38057 { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } 38058 for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) 38059 { if (lineNo(lineView.rest[i$1]) > lineN) 38060 { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } 38061} 38062 38063// Render a line into the hidden node display.externalMeasured. Used 38064// when measurement is needed for a line that's not in the viewport. 38065function updateExternalMeasurement(cm, line) { 38066 line = visualLine(line); 38067 var lineN = lineNo(line); 38068 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); 38069 view.lineN = lineN; 38070 var built = view.built = buildLineContent(cm, view); 38071 view.text = built.pre; 38072 removeChildrenAndAdd(cm.display.lineMeasure, built.pre); 38073 return view 38074} 38075 38076// Get a {top, bottom, left, right} box (in line-local coordinates) 38077// for a given character. 38078function measureChar(cm, line, ch, bias) { 38079 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) 38080} 38081 38082// Find a line view that corresponds to the given line number. 38083function findViewForLine(cm, lineN) { 38084 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) 38085 { return cm.display.view[findViewIndex(cm, lineN)] } 38086 var ext = cm.display.externalMeasured; 38087 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) 38088 { return ext } 38089} 38090 38091// Measurement can be split in two steps, the set-up work that 38092// applies to the whole line, and the measurement of the actual 38093// character. Functions like coordsChar, that need to do a lot of 38094// measurements in a row, can thus ensure that the set-up work is 38095// only done once. 38096function prepareMeasureForLine(cm, line) { 38097 var lineN = lineNo(line); 38098 var view = findViewForLine(cm, lineN); 38099 if (view && !view.text) { 38100 view = null; 38101 } else if (view && view.changes) { 38102 updateLineForChanges(cm, view, lineN, getDimensions(cm)); 38103 cm.curOp.forceUpdate = true; 38104 } 38105 if (!view) 38106 { view = updateExternalMeasurement(cm, line); } 38107 38108 var info = mapFromLineView(view, line, lineN); 38109 return { 38110 line: line, view: view, rect: null, 38111 map: info.map, cache: info.cache, before: info.before, 38112 hasHeights: false 38113 } 38114} 38115 38116// Given a prepared measurement object, measures the position of an 38117// actual character (or fetches it from the cache). 38118function measureCharPrepared(cm, prepared, ch, bias, varHeight) { 38119 if (prepared.before) { ch = -1; } 38120 var key = ch + (bias || ""), found; 38121 if (prepared.cache.hasOwnProperty(key)) { 38122 found = prepared.cache[key]; 38123 } else { 38124 if (!prepared.rect) 38125 { prepared.rect = prepared.view.text.getBoundingClientRect(); } 38126 if (!prepared.hasHeights) { 38127 ensureLineHeights(cm, prepared.view, prepared.rect); 38128 prepared.hasHeights = true; 38129 } 38130 found = measureCharInner(cm, prepared, ch, bias); 38131 if (!found.bogus) { prepared.cache[key] = found; } 38132 } 38133 return {left: found.left, right: found.right, 38134 top: varHeight ? found.rtop : found.top, 38135 bottom: varHeight ? found.rbottom : found.bottom} 38136} 38137 38138var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; 38139 38140function nodeAndOffsetInLineMap(map$$1, ch, bias) { 38141 var node, start, end, collapse, mStart, mEnd; 38142 // First, search the line map for the text node corresponding to, 38143 // or closest to, the target character. 38144 for (var i = 0; i < map$$1.length; i += 3) { 38145 mStart = map$$1[i]; 38146 mEnd = map$$1[i + 1]; 38147 if (ch < mStart) { 38148 start = 0; end = 1; 38149 collapse = "left"; 38150 } else if (ch < mEnd) { 38151 start = ch - mStart; 38152 end = start + 1; 38153 } else if (i == map$$1.length - 3 || ch == mEnd && map$$1[i + 3] > ch) { 38154 end = mEnd - mStart; 38155 start = end - 1; 38156 if (ch >= mEnd) { collapse = "right"; } 38157 } 38158 if (start != null) { 38159 node = map$$1[i + 2]; 38160 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) 38161 { collapse = bias; } 38162 if (bias == "left" && start == 0) 38163 { while (i && map$$1[i - 2] == map$$1[i - 3] && map$$1[i - 1].insertLeft) { 38164 node = map$$1[(i -= 3) + 2]; 38165 collapse = "left"; 38166 } } 38167 if (bias == "right" && start == mEnd - mStart) 38168 { while (i < map$$1.length - 3 && map$$1[i + 3] == map$$1[i + 4] && !map$$1[i + 5].insertLeft) { 38169 node = map$$1[(i += 3) + 2]; 38170 collapse = "right"; 38171 } } 38172 break 38173 } 38174 } 38175 return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} 38176} 38177 38178function getUsefulRect(rects, bias) { 38179 var rect = nullRect; 38180 if (bias == "left") { for (var i = 0; i < rects.length; i++) { 38181 if ((rect = rects[i]).left != rect.right) { break } 38182 } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { 38183 if ((rect = rects[i$1]).left != rect.right) { break } 38184 } } 38185 return rect 38186} 38187 38188function measureCharInner(cm, prepared, ch, bias) { 38189 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); 38190 var node = place.node, start = place.start, end = place.end, collapse = place.collapse; 38191 38192 var rect; 38193 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. 38194 for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned 38195 while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; } 38196 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; } 38197 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) 38198 { rect = node.parentNode.getBoundingClientRect(); } 38199 else 38200 { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); } 38201 if (rect.left || rect.right || start == 0) { break } 38202 end = start; 38203 start = start - 1; 38204 collapse = "right"; 38205 } 38206 if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); } 38207 } else { // If it is a widget, simply get the box for the whole widget. 38208 if (start > 0) { collapse = bias = "right"; } 38209 var rects; 38210 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) 38211 { rect = rects[bias == "right" ? rects.length - 1 : 0]; } 38212 else 38213 { rect = node.getBoundingClientRect(); } 38214 } 38215 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { 38216 var rSpan = node.parentNode.getClientRects()[0]; 38217 if (rSpan) 38218 { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; } 38219 else 38220 { rect = nullRect; } 38221 } 38222 38223 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; 38224 var mid = (rtop + rbot) / 2; 38225 var heights = prepared.view.measure.heights; 38226 var i = 0; 38227 for (; i < heights.length - 1; i++) 38228 { if (mid < heights[i]) { break } } 38229 var top = i ? heights[i - 1] : 0, bot = heights[i]; 38230 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, 38231 right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, 38232 top: top, bottom: bot}; 38233 if (!rect.left && !rect.right) { result.bogus = true; } 38234 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } 38235 38236 return result 38237} 38238 38239// Work around problem with bounding client rects on ranges being 38240// returned incorrectly when zoomed on IE10 and below. 38241function maybeUpdateRectForZooming(measure, rect) { 38242 if (!window.screen || screen.logicalXDPI == null || 38243 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) 38244 { return rect } 38245 var scaleX = screen.logicalXDPI / screen.deviceXDPI; 38246 var scaleY = screen.logicalYDPI / screen.deviceYDPI; 38247 return {left: rect.left * scaleX, right: rect.right * scaleX, 38248 top: rect.top * scaleY, bottom: rect.bottom * scaleY} 38249} 38250 38251function clearLineMeasurementCacheFor(lineView) { 38252 if (lineView.measure) { 38253 lineView.measure.cache = {}; 38254 lineView.measure.heights = null; 38255 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) 38256 { lineView.measure.caches[i] = {}; } } 38257 } 38258} 38259 38260function clearLineMeasurementCache(cm) { 38261 cm.display.externalMeasure = null; 38262 removeChildren(cm.display.lineMeasure); 38263 for (var i = 0; i < cm.display.view.length; i++) 38264 { clearLineMeasurementCacheFor(cm.display.view[i]); } 38265} 38266 38267function clearCaches(cm) { 38268 clearLineMeasurementCache(cm); 38269 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; 38270 if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; } 38271 cm.display.lineNumChars = null; 38272} 38273 38274function pageScrollX() { 38275 // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 38276 // which causes page_Offset and bounding client rects to use 38277 // different reference viewports and invalidate our calculations. 38278 if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) } 38279 return window.pageXOffset || (document.documentElement || document.body).scrollLeft 38280} 38281function pageScrollY() { 38282 if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) } 38283 return window.pageYOffset || (document.documentElement || document.body).scrollTop 38284} 38285 38286function widgetTopHeight(lineObj) { 38287 var height = 0; 38288 if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) 38289 { height += widgetHeight(lineObj.widgets[i]); } } } 38290 return height 38291} 38292 38293// Converts a {top, bottom, left, right} box from line-local 38294// coordinates into another coordinate system. Context may be one of 38295// "line", "div" (display.lineDiv), "local"./null (editor), "window", 38296// or "page". 38297function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { 38298 if (!includeWidgets) { 38299 var height = widgetTopHeight(lineObj); 38300 rect.top += height; rect.bottom += height; 38301 } 38302 if (context == "line") { return rect } 38303 if (!context) { context = "local"; } 38304 var yOff = heightAtLine(lineObj); 38305 if (context == "local") { yOff += paddingTop(cm.display); } 38306 else { yOff -= cm.display.viewOffset; } 38307 if (context == "page" || context == "window") { 38308 var lOff = cm.display.lineSpace.getBoundingClientRect(); 38309 yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); 38310 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); 38311 rect.left += xOff; rect.right += xOff; 38312 } 38313 rect.top += yOff; rect.bottom += yOff; 38314 return rect 38315} 38316 38317// Coverts a box from "div" coords to another coordinate system. 38318// Context may be "window", "page", "div", or "local"./null. 38319function fromCoordSystem(cm, coords, context) { 38320 if (context == "div") { return coords } 38321 var left = coords.left, top = coords.top; 38322 // First move into "page" coordinate system 38323 if (context == "page") { 38324 left -= pageScrollX(); 38325 top -= pageScrollY(); 38326 } else if (context == "local" || !context) { 38327 var localBox = cm.display.sizer.getBoundingClientRect(); 38328 left += localBox.left; 38329 top += localBox.top; 38330 } 38331 38332 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); 38333 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} 38334} 38335 38336function charCoords(cm, pos, context, lineObj, bias) { 38337 if (!lineObj) { lineObj = getLine(cm.doc, pos.line); } 38338 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) 38339} 38340 38341// Returns a box for a given cursor position, which may have an 38342// 'other' property containing the position of the secondary cursor 38343// on a bidi boundary. 38344// A cursor Pos(line, char, "before") is on the same visual line as `char - 1` 38345// and after `char - 1` in writing order of `char - 1` 38346// A cursor Pos(line, char, "after") is on the same visual line as `char` 38347// and before `char` in writing order of `char` 38348// Examples (upper-case letters are RTL, lower-case are LTR): 38349// Pos(0, 1, ...) 38350// before after 38351// ab a|b a|b 38352// aB a|B aB| 38353// Ab |Ab A|b 38354// AB B|A B|A 38355// Every position after the last character on a line is considered to stick 38356// to the last character on the line. 38357function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { 38358 lineObj = lineObj || getLine(cm.doc, pos.line); 38359 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } 38360 function get(ch, right) { 38361 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); 38362 if (right) { m.left = m.right; } else { m.right = m.left; } 38363 return intoCoordSystem(cm, lineObj, m, context) 38364 } 38365 var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky; 38366 if (ch >= lineObj.text.length) { 38367 ch = lineObj.text.length; 38368 sticky = "before"; 38369 } else if (ch <= 0) { 38370 ch = 0; 38371 sticky = "after"; 38372 } 38373 if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } 38374 38375 function getBidi(ch, partPos, invert) { 38376 var part = order[partPos], right = part.level == 1; 38377 return get(invert ? ch - 1 : ch, right != invert) 38378 } 38379 var partPos = getBidiPartAt(order, ch, sticky); 38380 var other = bidiOther; 38381 var val = getBidi(ch, partPos, sticky == "before"); 38382 if (other != null) { val.other = getBidi(ch, other, sticky != "before"); } 38383 return val 38384} 38385 38386// Used to cheaply estimate the coordinates for a position. Used for 38387// intermediate scroll updates. 38388function estimateCoords(cm, pos) { 38389 var left = 0; 38390 pos = clipPos(cm.doc, pos); 38391 if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; } 38392 var lineObj = getLine(cm.doc, pos.line); 38393 var top = heightAtLine(lineObj) + paddingTop(cm.display); 38394 return {left: left, right: left, top: top, bottom: top + lineObj.height} 38395} 38396 38397// Positions returned by coordsChar contain some extra information. 38398// xRel is the relative x position of the input coordinates compared 38399// to the found position (so xRel > 0 means the coordinates are to 38400// the right of the character position, for example). When outside 38401// is true, that means the coordinates lie outside the line's 38402// vertical range. 38403function PosWithInfo(line, ch, sticky, outside, xRel) { 38404 var pos = Pos(line, ch, sticky); 38405 pos.xRel = xRel; 38406 if (outside) { pos.outside = true; } 38407 return pos 38408} 38409 38410// Compute the character position closest to the given coordinates. 38411// Input must be lineSpace-local ("div" coordinate system). 38412function coordsChar(cm, x, y) { 38413 var doc = cm.doc;
38414 y += cm.display.viewOffset; 38415 if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) } 38416 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; 38417 if (lineN > last) 38418 { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) } 38419 if (x < 0) { x = 0; } 38420 38421 var lineObj = getLine(doc, lineN); 38422 for (;;) { 38423 var found = coordsCharInner(cm, lineObj, lineN, x, y); 38424 var merged = collapsedSpanAtEnd(lineObj); 38425 var mergedPos = merged && merged.find(0, true); 38426 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) 38427 { lineN = lineNo(lineObj = mergedPos.to.line); } 38428 else 38429 { return found } 38430 } 38431} 38432 38433function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { 38434 y -= widgetTopHeight(lineObj); 38435 var end = lineObj.text.length; 38436 var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0); 38437 end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end); 38438 return {begin: begin, end: end} 38439} 38440 38441function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { 38442 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } 38443 var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top; 38444 return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) 38445} 38446 38447// Returns true if the given side of a box is after the given 38448// coordinates, in top-to-bottom, left-to-right order. 38449function boxIsAfter(box, x, y, left) { 38450 return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x 38451} 38452 38453function coordsCharInner(cm, lineObj, lineNo$$1, x, y) { 38454 // Move y into line-local coordinate space 38455 y -= heightAtLine(lineObj); 38456 var preparedMeasure = prepareMeasureForLine(cm, lineObj); 38457 // When directly calling `measureCharPrepared`, we have to adjust 38458 // for the widgets at this line. 38459 var widgetHeight$$1 = widgetTopHeight(lineObj); 38460 var begin = 0, end = lineObj.text.length, ltr = true; 38461 38462 var order = getOrder(lineObj, cm.doc.direction); 38463 // If the line isn't plain left-to-right text, first figure out 38464 // which bidi section the coordinates fall into. 38465 if (order) { 38466 var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) 38467 (cm, lineObj, lineNo$$1, preparedMeasure, order, x, y); 38468 ltr = part.level != 1; 38469 // The awkward -1 offsets are needed because findFirst (called 38470 // on these below) will treat its first bound as inclusive, 38471 // second as exclusive, but we want to actually address the 38472 // characters in the part's range 38473 begin = ltr ? part.from : part.to - 1; 38474 end = ltr ? part.to : part.from - 1; 38475 } 38476 38477 // A binary search to find the first character whose bounding box 38478 // starts after the coordinates. If we run across any whose box wrap 38479 // the coordinates, store that. 38480 var chAround = null, boxAround = null; 38481 var ch = findFirst(function (ch) { 38482 var box = measureCharPrepared(cm, preparedMeasure, ch); 38483 box.top += widgetHeight$$1; box.bottom += widgetHeight$$1; 38484 if (!boxIsAfter(box, x, y, false)) { return false } 38485 if (box.top <= y && box.left <= x) { 38486 chAround = ch; 38487 boxAround = box; 38488 } 38489 return true 38490 }, begin, end); 38491 38492 var baseX, sticky, outside = false;
vendor: 4,694 bytes, lines 38493-38599
38493 // If a box around the coordinates was found, use that 38494 if (boxAround) { 38495 // Distinguish coordinates nearer to the left or right side of the box 38496 var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr; 38497 ch = chAround + (atStart ? 0 : 1); 38498 sticky = atStart ? "after" : "before"; 38499 baseX = atLeft ? boxAround.left : boxAround.right; 38500 } else { 38501 // (Adjust for extended bound, if necessary.) 38502 if (!ltr && (ch == end || ch == begin)) { ch++; } 38503 // To determine which side to associate with, get the box to the 38504 // left of the character and compare it's vertical position to the 38505 // coordinates 38506 sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : 38507 (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight$$1 <= y) == ltr ? 38508 "after" : "before"; 38509 // Now get accurate coordinates for this place, in order to get a 38510 // base X position 38511 var coords = cursorCoords(cm, Pos(lineNo$$1, ch, sticky), "line", lineObj, preparedMeasure); 38512 baseX = coords.left; 38513 outside = y < coords.top || y >= coords.bottom; 38514 } 38515 38516 ch = skipExtendingChars(lineObj.text, ch, 1); 38517 return PosWithInfo(lineNo$$1, ch, sticky, outside, x - baseX) 38518} 38519 38520function coordsBidiPart(cm, lineObj, lineNo$$1, preparedMeasure, order, x, y) { 38521 // Bidi parts are sorted left-to-right, and in a non-line-wrapping 38522 // situation, we can take this ordering to correspond to the visual 38523 // ordering. This finds the first part whose end is after the given 38524 // coordinates. 38525 var index = findFirst(function (i) { 38526 var part = order[i], ltr = part.level != 1; 38527 return boxIsAfter(cursorCoords(cm, Pos(lineNo$$1, ltr ? part.to : part.from, ltr ? "before" : "after"), 38528 "line", lineObj, preparedMeasure), x, y, true) 38529 }, 0, order.length - 1); 38530 var part = order[index]; 38531 // If this isn't the first part, the part's start is also after 38532 // the coordinates, and the coordinates aren't on the same line as 38533 // that start, move one part back. 38534 if (index > 0) { 38535 var ltr = part.level != 1; 38536 var start = cursorCoords(cm, Pos(lineNo$$1, ltr ? part.from : part.to, ltr ? "after" : "before"), 38537 "line", lineObj, preparedMeasure); 38538 if (boxIsAfter(start, x, y, true) && start.top > y) 38539 { part = order[index - 1]; } 38540 } 38541 return part 38542} 38543 38544function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { 38545 // In a wrapped line, rtl text on wrapping boundaries can do things 38546 // that don't correspond to the ordering in our `order` array at 38547 // all, so a binary search doesn't work, and we want to return a 38548 // part that only spans one line so that the binary search in 38549 // coordsCharInner is safe. As such, we first find the extent of the 38550 // wrapped line, and then do a flat search in which we discard any 38551 // spans that aren't on the line. 38552 var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); 38553 var begin = ref.begin; 38554 var end = ref.end; 38555 if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; } 38556 var part = null, closestDist = null; 38557 for (var i = 0; i < order.length; i++) { 38558 var p = order[i]; 38559 if (p.from >= end || p.to <= begin) { continue } 38560 var ltr = p.level != 1; 38561 var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right; 38562 // Weigh against spans ending before this, so that they are only 38563 // picked if nothing ends after 38564 var dist = endX < x ? x - endX + 1e9 : endX - x; 38565 if (!part || closestDist > dist) { 38566 part = p; 38567 closestDist = dist; 38568 } 38569 } 38570 if (!part) { part = order[order.length - 1]; } 38571 // Clip the part to the wrapped line. 38572 if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; } 38573 if (part.to > end) { part = {from: part.from, to: end, level: part.level}; } 38574 return part 38575} 38576 38577var measureText; 38578// Compute the default text height. 38579function textHeight(display) { 38580 if (display.cachedTextHeight != null) { return display.cachedTextHeight } 38581 if (measureText == null) { 38582 measureText = elt("pre"); 38583 // Measure a bunch of lines, for browsers that compute 38584 // fractional heights. 38585 for (var i = 0; i < 49; ++i) { 38586 measureText.appendChild(document.createTextNode("x")); 38587 measureText.appendChild(elt("br")); 38588 } 38589 measureText.appendChild(document.createTextNode("x")); 38590 } 38591 removeChildrenAndAdd(display.measure, measureText); 38592 var height = measureText.offsetHeight / 50; 38593 if (height > 3) { display.cachedTextHeight = height; } 38594 removeChildren(display.measure); 38595 return height || 1 38596} 38597 38598// Compute the default character width. 38599function charWidth(display) {
38600 if (display.cachedCharWidth != null) { return display.cachedCharWidth } 38601 var anchor = elt("span", "xxxxxxxxxx"); 38602 var pre = elt("pre", [anchor]); 38603 removeChildrenAndAdd(display.measure, pre); 38604 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; 38605 if (width > 2) { display.cachedCharWidth = width; } 38606 return width || 10 38607} 38608 38609// Do a bulk-read of the DOM positions and sizes needed to draw the 38610// view, so that we don't interleave reading and writing to the DOM. 38611function getDimensions(cm) { 38612 var d = cm.display, left = {}, width = {}; 38613 var gutterLeft = d.gutters.clientLeft; 38614 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { 38615 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; 38616 width[cm.options.gutters[i]] = n.clientWidth; 38617 } 38618 return {fixedPos: compensateForHScroll(d), 38619 gutterTotalWidth: d.gutters.offsetWidth, 38620 gutterLeft: left, 38621 gutterWidth: width, 38622 wrapperWidth: d.wrapper.clientWidth} 38623} 38624 38625// Computes display.scroller.scrollLeft + display.gutters.offsetWidth, 38626// but using getBoundingClientRect to get a sub-pixel-accurate 38627// result. 38628function compensateForHScroll(display) {
vendor: 95,213 bytes, lines 38629-41094
38629 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left 38630} 38631 38632// Returns a function that estimates the height of a line, to use as 38633// first approximation until the line becomes visible (and is thus 38634// properly measurable). 38635function estimateHeight(cm) { 38636 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; 38637 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); 38638 return function (line) { 38639 if (lineIsHidden(cm.doc, line)) { return 0 } 38640 38641 var widgetsHeight = 0; 38642 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { 38643 if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; } 38644 } } 38645 38646 if (wrapping) 38647 { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } 38648 else 38649 { return widgetsHeight + th } 38650 } 38651} 38652 38653function estimateLineHeights(cm) { 38654 var doc = cm.doc, est = estimateHeight(cm); 38655 doc.iter(function (line) { 38656 var estHeight = est(line); 38657 if (estHeight != line.height) { updateLineHeight(line, estHeight); } 38658 }); 38659} 38660 38661// Given a mouse event, find the corresponding position. If liberal 38662// is false, it checks whether a gutter or scrollbar was clicked, 38663// and returns null if it was. forRect is used by rectangular 38664// selections, and tries to estimate a character position even for 38665// coordinates beyond the right of the text. 38666function posFromMouse(cm, e, liberal, forRect) { 38667 var display = cm.display; 38668 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } 38669 38670 var x, y, space = display.lineSpace.getBoundingClientRect(); 38671 // Fails unpredictably on IE[67] when mouse is dragged around quickly. 38672 try { x = e.clientX - space.left; y = e.clientY - space.top; } 38673 catch (e) { return null } 38674 var coords = coordsChar(cm, x, y), line; 38675 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { 38676 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; 38677 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); 38678 } 38679 return coords 38680} 38681 38682// Find the view element corresponding to a given line. Return null 38683// when the line isn't visible. 38684function findViewIndex(cm, n) { 38685 if (n >= cm.display.viewTo) { return null } 38686 n -= cm.display.viewFrom; 38687 if (n < 0) { return null } 38688 var view = cm.display.view; 38689 for (var i = 0; i < view.length; i++) { 38690 n -= view[i].size; 38691 if (n < 0) { return i } 38692 } 38693} 38694 38695function updateSelection(cm) { 38696 cm.display.input.showSelection(cm.display.input.prepareSelection()); 38697} 38698 38699function prepareSelection(cm, primary) { 38700 if ( primary === void 0 ) primary = true; 38701 38702 var doc = cm.doc, result = {}; 38703 var curFragment = result.cursors = document.createDocumentFragment(); 38704 var selFragment = result.selection = document.createDocumentFragment(); 38705 38706 for (var i = 0; i < doc.sel.ranges.length; i++) { 38707 if (!primary && i == doc.sel.primIndex) { continue } 38708 var range$$1 = doc.sel.ranges[i]; 38709 if (range$$1.from().line >= cm.display.viewTo || range$$1.to().line < cm.display.viewFrom) { continue } 38710 var collapsed = range$$1.empty(); 38711 if (collapsed || cm.options.showCursorWhenSelecting) 38712 { drawSelectionCursor(cm, range$$1.head, curFragment); } 38713 if (!collapsed) 38714 { drawSelectionRange(cm, range$$1, selFragment); } 38715 } 38716 return result 38717} 38718 38719// Draws a cursor for the given range 38720function drawSelectionCursor(cm, head, output) { 38721 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); 38722 38723 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); 38724 cursor.style.left = pos.left + "px"; 38725 cursor.style.top = pos.top + "px"; 38726 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; 38727 38728 if (pos.other) { 38729 // Secondary cursor, shown when on a 'jump' in bi-directional text 38730 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); 38731 otherCursor.style.display = ""; 38732 otherCursor.style.left = pos.other.left + "px"; 38733 otherCursor.style.top = pos.other.top + "px"; 38734 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; 38735 } 38736} 38737 38738function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } 38739 38740// Draws the given range as a highlighted selection 38741function drawSelectionRange(cm, range$$1, output) { 38742 var display = cm.display, doc = cm.doc; 38743 var fragment = document.createDocumentFragment(); 38744 var padding = paddingH(cm.display), leftSide = padding.left; 38745 var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; 38746 var docLTR = doc.direction == "ltr"; 38747 38748 function add(left, top, width, bottom) { 38749 if (top < 0) { top = 0; } 38750 top = Math.round(top); 38751 bottom = Math.round(bottom); 38752 fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px"))); 38753 } 38754 38755 function drawForLine(line, fromArg, toArg) { 38756 var lineObj = getLine(doc, line); 38757 var lineLen = lineObj.text.length; 38758 var start, end; 38759 function coords(ch, bias) { 38760 return charCoords(cm, Pos(line, ch), "div", lineObj, bias) 38761 } 38762 38763 function wrapX(pos, dir, side) { 38764 var extent = wrappedLineExtentChar(cm, lineObj, null, pos); 38765 var prop = (dir == "ltr") == (side == "after") ? "left" : "right"; 38766 var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1); 38767 return coords(ch, prop)[prop] 38768 } 38769 38770 var order = getOrder(lineObj, doc.direction); 38771 iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { 38772 var ltr = dir == "ltr"; 38773 var fromPos = coords(from, ltr ? "left" : "right"); 38774 var toPos = coords(to - 1, ltr ? "right" : "left"); 38775 38776 var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen; 38777 var first = i == 0, last = !order || i == order.length - 1; 38778 if (toPos.top - fromPos.top <= 3) { // Single line 38779 var openLeft = (docLTR ? openStart : openEnd) && first; 38780 var openRight = (docLTR ? openEnd : openStart) && last; 38781 var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left; 38782 var right = openRight ? rightSide : (ltr ? toPos : fromPos).right; 38783 add(left, fromPos.top, right - left, fromPos.bottom); 38784 } else { // Multiple lines 38785 var topLeft, topRight, botLeft, botRight; 38786 if (ltr) { 38787 topLeft = docLTR && openStart && first ? leftSide : fromPos.left; 38788 topRight = docLTR ? rightSide : wrapX(from, dir, "before"); 38789 botLeft = docLTR ? leftSide : wrapX(to, dir, "after"); 38790 botRight = docLTR && openEnd && last ? rightSide : toPos.right; 38791 } else { 38792 topLeft = !docLTR ? leftSide : wrapX(from, dir, "before"); 38793 topRight = !docLTR && openStart && first ? rightSide : fromPos.right; 38794 botLeft = !docLTR && openEnd && last ? leftSide : toPos.left; 38795 botRight = !docLTR ? rightSide : wrapX(to, dir, "after"); 38796 } 38797 add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom); 38798 if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); } 38799 add(botLeft, toPos.top, botRight - botLeft, toPos.bottom); 38800 } 38801 38802 if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; } 38803 if (cmpCoords(toPos, start) < 0) { start = toPos; } 38804 if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; } 38805 if (cmpCoords(toPos, end) < 0) { end = toPos; } 38806 }); 38807 return {start: start, end: end} 38808 } 38809 38810 var sFrom = range$$1.from(), sTo = range$$1.to(); 38811 if (sFrom.line == sTo.line) { 38812 drawForLine(sFrom.line, sFrom.ch, sTo.ch); 38813 } else { 38814 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); 38815 var singleVLine = visualLine(fromLine) == visualLine(toLine); 38816 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; 38817 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; 38818 if (singleVLine) { 38819 if (leftEnd.top < rightStart.top - 2) { 38820 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); 38821 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); 38822 } else { 38823 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); 38824 } 38825 } 38826 if (leftEnd.bottom < rightStart.top) 38827 { add(leftSide, leftEnd.bottom, null, rightStart.top); } 38828 } 38829 38830 output.appendChild(fragment); 38831} 38832 38833// Cursor-blinking 38834function restartBlink(cm) { 38835 if (!cm.state.focused) { return } 38836 var display = cm.display; 38837 clearInterval(display.blinker); 38838 var on = true; 38839 display.cursorDiv.style.visibility = ""; 38840 if (cm.options.cursorBlinkRate > 0) 38841 { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, 38842 cm.options.cursorBlinkRate); } 38843 else if (cm.options.cursorBlinkRate < 0) 38844 { display.cursorDiv.style.visibility = "hidden"; } 38845} 38846 38847function ensureFocus(cm) { 38848 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } 38849} 38850 38851function delayBlurEvent(cm) { 38852 cm.state.delayingBlurEvent = true; 38853 setTimeout(function () { if (cm.state.delayingBlurEvent) { 38854 cm.state.delayingBlurEvent = false; 38855 onBlur(cm); 38856 } }, 100); 38857} 38858 38859function onFocus(cm, e) { 38860 if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; } 38861 38862 if (cm.options.readOnly == "nocursor") { return } 38863 if (!cm.state.focused) { 38864 signal(cm, "focus", cm, e); 38865 cm.state.focused = true; 38866 addClass(cm.display.wrapper, "CodeMirror-focused"); 38867 // This test prevents this from firing when a context 38868 // menu is closed (since the input reset would kill the 38869 // select-all detection hack) 38870 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { 38871 cm.display.input.reset(); 38872 if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730 38873 } 38874 cm.display.input.receivedFocus(); 38875 } 38876 restartBlink(cm); 38877} 38878function onBlur(cm, e) { 38879 if (cm.state.delayingBlurEvent) { return } 38880 38881 if (cm.state.focused) { 38882 signal(cm, "blur", cm, e); 38883 cm.state.focused = false; 38884 rmClass(cm.display.wrapper, "CodeMirror-focused"); 38885 } 38886 clearInterval(cm.display.blinker); 38887 setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150); 38888} 38889 38890// Read the actual heights of the rendered lines, and update their 38891// stored heights to match. 38892function updateHeightsInViewport(cm) { 38893 var display = cm.display; 38894 var prevBottom = display.lineDiv.offsetTop; 38895 for (var i = 0; i < display.view.length; i++) { 38896 var cur = display.view[i], height = (void 0); 38897 if (cur.hidden) { continue } 38898 if (ie && ie_version < 8) { 38899 var bot = cur.node.offsetTop + cur.node.offsetHeight; 38900 height = bot - prevBottom; 38901 prevBottom = bot; 38902 } else { 38903 var box = cur.node.getBoundingClientRect(); 38904 height = box.bottom - box.top; 38905 } 38906 var diff = cur.line.height - height; 38907 if (height < 2) { height = textHeight(display); } 38908 if (diff > .005 || diff < -.005) { 38909 updateLineHeight(cur.line, height); 38910 updateWidgetHeight(cur.line); 38911 if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) 38912 { updateWidgetHeight(cur.rest[j]); } } 38913 } 38914 } 38915} 38916 38917// Read and store the height of line widgets associated with the 38918// given line. 38919function updateWidgetHeight(line) { 38920 if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) { 38921 var w = line.widgets[i], parent = w.node.parentNode; 38922 if (parent) { w.height = parent.offsetHeight; } 38923 } } 38924} 38925 38926// Compute the lines that are visible in a given viewport (defaults 38927// the the current scroll position). viewport may contain top, 38928// height, and ensure (see op.scrollToPos) properties. 38929function visibleLines(display, doc, viewport) { 38930 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; 38931 top = Math.floor(top - paddingTop(display)); 38932 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; 38933 38934 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); 38935 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and 38936 // forces those lines into the viewport (if possible). 38937 if (viewport && viewport.ensure) { 38938 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; 38939 if (ensureFrom < from) { 38940 from = ensureFrom; 38941 to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); 38942 } else if (Math.min(ensureTo, doc.lastLine()) >= to) { 38943 from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); 38944 to = ensureTo; 38945 } 38946 } 38947 return {from: from, to: Math.max(to, from + 1)} 38948} 38949 38950// Re-align line numbers and gutter marks to compensate for 38951// horizontal scrolling. 38952function alignHorizontally(cm) { 38953 var display = cm.display, view = display.view; 38954 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } 38955 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; 38956 var gutterW = display.gutters.offsetWidth, left = comp + "px"; 38957 for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { 38958 if (cm.options.fixedGutter) { 38959 if (view[i].gutter) 38960 { view[i].gutter.style.left = left; } 38961 if (view[i].gutterBackground) 38962 { view[i].gutterBackground.style.left = left; } 38963 } 38964 var align = view[i].alignable; 38965 if (align) { for (var j = 0; j < align.length; j++) 38966 { align[j].style.left = left; } } 38967 } } 38968 if (cm.options.fixedGutter) 38969 { display.gutters.style.left = (comp + gutterW) + "px"; } 38970} 38971 38972// Used to ensure that the line number gutter is still the right 38973// size for the current document size. Returns true when an update 38974// is needed. 38975function maybeUpdateLineNumberWidth(cm) { 38976 if (!cm.options.lineNumbers) { return false } 38977 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; 38978 if (last.length != display.lineNumChars) { 38979 var test = display.measure.appendChild(elt("div", [elt("div", last)], 38980 "CodeMirror-linenumber CodeMirror-gutter-elt")); 38981 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; 38982 display.lineGutter.style.width = ""; 38983 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; 38984 display.lineNumWidth = display.lineNumInnerWidth + padding; 38985 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; 38986 display.lineGutter.style.width = display.lineNumWidth + "px"; 38987 updateGutterSpace(cm); 38988 return true 38989 } 38990 return false 38991} 38992 38993// SCROLLING THINGS INTO VIEW 38994 38995// If an editor sits on the top or bottom of the window, partially 38996// scrolled out of view, this ensures that the cursor is visible. 38997function maybeScrollWindow(cm, rect) { 38998 if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } 38999 39000 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; 39001 if (rect.top + box.top < 0) { doScroll = true; } 39002 else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; } 39003 if (doScroll != null && !phantom) { 39004 var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")); 39005 cm.display.lineSpace.appendChild(scrollNode); 39006 scrollNode.scrollIntoView(doScroll); 39007 cm.display.lineSpace.removeChild(scrollNode); 39008 } 39009} 39010 39011// Scroll a given position into view (immediately), verifying that 39012// it actually became visible (as line heights are accurately 39013// measured, the position of something may 'drift' during drawing). 39014function scrollPosIntoView(cm, pos, end, margin) { 39015 if (margin == null) { margin = 0; } 39016 var rect; 39017 if (!cm.options.lineWrapping && pos == end) { 39018 // Set pos and end to the cursor positions around the character pos sticks to 39019 // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch 39020 // If pos == Pos(_, 0, "before"), pos and end are unchanged 39021 pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos; 39022 end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos; 39023 } 39024 for (var limit = 0; limit < 5; limit++) { 39025 var changed = false; 39026 var coords = cursorCoords(cm, pos); 39027 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); 39028 rect = {left: Math.min(coords.left, endCoords.left), 39029 top: Math.min(coords.top, endCoords.top) - margin, 39030 right: Math.max(coords.left, endCoords.left), 39031 bottom: Math.max(coords.bottom, endCoords.bottom) + margin}; 39032 var scrollPos = calculateScrollPos(cm, rect); 39033 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; 39034 if (scrollPos.scrollTop != null) { 39035 updateScrollTop(cm, scrollPos.scrollTop); 39036 if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; } 39037 } 39038 if (scrollPos.scrollLeft != null) { 39039 setScrollLeft(cm, scrollPos.scrollLeft); 39040 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; } 39041 } 39042 if (!changed) { break } 39043 } 39044 return rect 39045} 39046 39047// Scroll a given set of coordinates into view (immediately). 39048function scrollIntoView(cm, rect) { 39049 var scrollPos = calculateScrollPos(cm, rect); 39050 if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); } 39051 if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); } 39052} 39053 39054// Calculate a new scroll position needed to scroll the given 39055// rectangle into view. Returns an object with scrollTop and 39056// scrollLeft properties. When these are undefined, the 39057// vertical/horizontal position does not need to be adjusted. 39058function calculateScrollPos(cm, rect) { 39059 var display = cm.display, snapMargin = textHeight(cm.display); 39060 if (rect.top < 0) { rect.top = 0; } 39061 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; 39062 var screen = displayHeight(cm), result = {}; 39063 if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; } 39064 var docBottom = cm.doc.height + paddingVert(display); 39065 var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin; 39066 if (rect.top < screentop) { 39067 result.scrollTop = atTop ? 0 : rect.top; 39068 } else if (rect.bottom > screentop + screen) { 39069 var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen); 39070 if (newTop != screentop) { result.scrollTop = newTop; } 39071 } 39072 39073 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; 39074 var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); 39075 var tooWide = rect.right - rect.left > screenw; 39076 if (tooWide) { rect.right = rect.left + screenw; } 39077 if (rect.left < 10) 39078 { result.scrollLeft = 0; } 39079 else if (rect.left < screenleft) 39080 { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); } 39081 else if (rect.right > screenw + screenleft - 3) 39082 { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; } 39083 return result 39084} 39085 39086// Store a relative adjustment to the scroll position in the current 39087// operation (to be applied when the operation finishes). 39088function addToScrollTop(cm, top) { 39089 if (top == null) { return } 39090 resolveScrollToPos(cm); 39091 cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; 39092} 39093 39094// Make sure that at the end of the operation the current cursor is 39095// shown. 39096function ensureCursorVisible(cm) { 39097 resolveScrollToPos(cm); 39098 var cur = cm.getCursor(); 39099 cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin}; 39100} 39101 39102function scrollToCoords(cm, x, y) { 39103 if (x != null || y != null) { resolveScrollToPos(cm); } 39104 if (x != null) { cm.curOp.scrollLeft = x; } 39105 if (y != null) { cm.curOp.scrollTop = y; } 39106} 39107 39108function scrollToRange(cm, range$$1) { 39109 resolveScrollToPos(cm); 39110 cm.curOp.scrollToPos = range$$1; 39111} 39112 39113// When an operation has its scrollToPos property set, and another 39114// scroll action is applied before the end of the operation, this 39115// 'simulates' scrolling that position into view in a cheap way, so 39116// that the effect of intermediate scroll commands is not ignored. 39117function resolveScrollToPos(cm) { 39118 var range$$1 = cm.curOp.scrollToPos; 39119 if (range$$1) { 39120 cm.curOp.scrollToPos = null; 39121 var from = estimateCoords(cm, range$$1.from), to = estimateCoords(cm, range$$1.to); 39122 scrollToCoordsRange(cm, from, to, range$$1.margin); 39123 } 39124} 39125 39126function scrollToCoordsRange(cm, from, to, margin) { 39127 var sPos = calculateScrollPos(cm, { 39128 left: Math.min(from.left, to.left), 39129 top: Math.min(from.top, to.top) - margin, 39130 right: Math.max(from.right, to.right), 39131 bottom: Math.max(from.bottom, to.bottom) + margin 39132 }); 39133 scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop); 39134} 39135 39136// Sync the scrollable area and scrollbars, ensure the viewport 39137// covers the visible area. 39138function updateScrollTop(cm, val) { 39139 if (Math.abs(cm.doc.scrollTop - val) < 2) { return } 39140 if (!gecko) { updateDisplaySimple(cm, {top: val}); } 39141 setScrollTop(cm, val, true); 39142 if (gecko) { updateDisplaySimple(cm); } 39143 startWorker(cm, 100); 39144} 39145 39146function setScrollTop(cm, val, forceScroll) { 39147 val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val); 39148 if (cm.display.scroller.scrollTop == val && !forceScroll) { return } 39149 cm.doc.scrollTop = val; 39150 cm.display.scrollbars.setScrollTop(val); 39151 if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; } 39152} 39153 39154// Sync scroller and scrollbar, ensure the gutter elements are 39155// aligned. 39156function setScrollLeft(cm, val, isScroller, forceScroll) { 39157 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); 39158 if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } 39159 cm.doc.scrollLeft = val; 39160 alignHorizontally(cm); 39161 if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; } 39162 cm.display.scrollbars.setScrollLeft(val); 39163} 39164 39165// SCROLLBARS 39166 39167// Prepare DOM reads needed to update the scrollbars. Done in one 39168// shot to minimize update/measure roundtrips. 39169function measureForScrollbars(cm) { 39170 var d = cm.display, gutterW = d.gutters.offsetWidth; 39171 var docH = Math.round(cm.doc.height + paddingVert(cm.display)); 39172 return { 39173 clientHeight: d.scroller.clientHeight, 39174 viewHeight: d.wrapper.clientHeight, 39175 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, 39176 viewWidth: d.wrapper.clientWidth, 39177 barLeft: cm.options.fixedGutter ? gutterW : 0, 39178 docHeight: docH, 39179 scrollHeight: docH + scrollGap(cm) + d.barHeight, 39180 nativeBarWidth: d.nativeBarWidth, 39181 gutterWidth: gutterW 39182 } 39183} 39184 39185var NativeScrollbars = function(place, scroll, cm) { 39186 this.cm = cm; 39187 var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); 39188 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); 39189 place(vert); place(horiz); 39190 39191 on(vert, "scroll", function () { 39192 if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); } 39193 }); 39194 on(horiz, "scroll", function () { 39195 if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); } 39196 }); 39197 39198 this.checkedZeroWidth = false; 39199 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). 39200 if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; } 39201}; 39202 39203NativeScrollbars.prototype.update = function (measure) { 39204 var needsH = measure.scrollWidth > measure.clientWidth + 1; 39205 var needsV = measure.scrollHeight > measure.clientHeight + 1; 39206 var sWidth = measure.nativeBarWidth; 39207 39208 if (needsV) { 39209 this.vert.style.display = "block"; 39210 this.vert.style.bottom = needsH ? sWidth + "px" : "0"; 39211 var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); 39212 // A bug in IE8 can cause this value to be negative, so guard it. 39213 this.vert.firstChild.style.height = 39214 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; 39215 } else { 39216 this.vert.style.display = ""; 39217 this.vert.firstChild.style.height = "0"; 39218 } 39219 39220 if (needsH) { 39221 this.horiz.style.display = "block"; 39222 this.horiz.style.right = needsV ? sWidth + "px" : "0"; 39223 this.horiz.style.left = measure.barLeft + "px"; 39224 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); 39225 this.horiz.firstChild.style.width = 39226 Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; 39227 } else { 39228 this.horiz.style.display = ""; 39229 this.horiz.firstChild.style.width = "0"; 39230 } 39231 39232 if (!this.checkedZeroWidth && measure.clientHeight > 0) { 39233 if (sWidth == 0) { this.zeroWidthHack(); } 39234 this.checkedZeroWidth = true; 39235 } 39236 39237 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} 39238}; 39239 39240NativeScrollbars.prototype.setScrollLeft = function (pos) { 39241 if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; } 39242 if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); } 39243}; 39244 39245NativeScrollbars.prototype.setScrollTop = function (pos) { 39246 if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; } 39247 if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); } 39248}; 39249 39250NativeScrollbars.prototype.zeroWidthHack = function () { 39251 var w = mac && !mac_geMountainLion ? "12px" : "18px"; 39252 this.horiz.style.height = this.vert.style.width = w; 39253 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; 39254 this.disableHoriz = new Delayed; 39255 this.disableVert = new Delayed; 39256}; 39257 39258NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { 39259 bar.style.pointerEvents = "auto"; 39260 function maybeDisable() { 39261 // To find out whether the scrollbar is still visible, we 39262 // check whether the element under the pixel in the bottom 39263 // right corner of the scrollbar box is the scrollbar box 39264 // itself (when the bar is still visible) or its filler child 39265 // (when the bar is hidden). If it is still visible, we keep 39266 // it enabled, if it's hidden, we disable pointer events. 39267 var box = bar.getBoundingClientRect(); 39268 var elt$$1 = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) 39269 : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1); 39270 if (elt$$1 != bar) { bar.style.pointerEvents = "none"; } 39271 else { delay.set(1000, maybeDisable); } 39272 } 39273 delay.set(1000, maybeDisable); 39274}; 39275 39276NativeScrollbars.prototype.clear = function () { 39277 var parent = this.horiz.parentNode; 39278 parent.removeChild(this.horiz); 39279 parent.removeChild(this.vert); 39280}; 39281 39282var NullScrollbars = function () {}; 39283 39284NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; 39285NullScrollbars.prototype.setScrollLeft = function () {}; 39286NullScrollbars.prototype.setScrollTop = function () {}; 39287NullScrollbars.prototype.clear = function () {}; 39288 39289function updateScrollbars(cm, measure) { 39290 if (!measure) { measure = measureForScrollbars(cm); } 39291 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; 39292 updateScrollbarsInner(cm, measure); 39293 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { 39294 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) 39295 { updateHeightsInViewport(cm); } 39296 updateScrollbarsInner(cm, measureForScrollbars(cm)); 39297 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; 39298 } 39299} 39300 39301// Re-synchronize the fake scrollbars with the actual size of the 39302// content. 39303function updateScrollbarsInner(cm, measure) { 39304 var d = cm.display; 39305 var sizes = d.scrollbars.update(measure); 39306 39307 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; 39308 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; 39309 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"; 39310 39311 if (sizes.right && sizes.bottom) { 39312 d.scrollbarFiller.style.display = "block"; 39313 d.scrollbarFiller.style.height = sizes.bottom + "px"; 39314 d.scrollbarFiller.style.width = sizes.right + "px"; 39315 } else { d.scrollbarFiller.style.display = ""; } 39316 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { 39317 d.gutterFiller.style.display = "block"; 39318 d.gutterFiller.style.height = sizes.bottom + "px"; 39319 d.gutterFiller.style.width = measure.gutterWidth + "px"; 39320 } else { d.gutterFiller.style.display = ""; } 39321} 39322 39323var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; 39324 39325function initScrollbars(cm) { 39326 if (cm.display.scrollbars) { 39327 cm.display.scrollbars.clear(); 39328 if (cm.display.scrollbars.addClass) 39329 { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); } 39330 } 39331 39332 cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { 39333 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); 39334 // Prevent clicks in the scrollbars from killing focus 39335 on(node, "mousedown", function () { 39336 if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); } 39337 }); 39338 node.setAttribute("cm-not-content", "true"); 39339 }, function (pos, axis) { 39340 if (axis == "horizontal") { setScrollLeft(cm, pos); } 39341 else { updateScrollTop(cm, pos); } 39342 }, cm); 39343 if (cm.display.scrollbars.addClass) 39344 { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); } 39345} 39346 39347// Operations are used to wrap a series of changes to the editor 39348// state in such a way that each change won't have to update the 39349// cursor and display (which would be awkward, slow, and 39350// error-prone). Instead, display updates are batched and then all 39351// combined and executed at once. 39352 39353var nextOpId = 0; 39354// Start a new operation. 39355function startOperation(cm) { 39356 cm.curOp = { 39357 cm: cm, 39358 viewChanged: false, // Flag that indicates that lines might need to be redrawn 39359 startHeight: cm.doc.height, // Used to detect need to update scrollbar 39360 forceUpdate: false, // Used to force a redraw 39361 updateInput: null, // Whether to reset the input textarea 39362 typing: false, // Whether this reset should be careful to leave existing text (for compositing) 39363 changeObjs: null, // Accumulated changes, for firing change events 39364 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on 39365 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already 39366 selectionChanged: false, // Whether the selection needs to be redrawn 39367 updateMaxLine: false, // Set when the widest line needs to be determined anew 39368 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet 39369 scrollToPos: null, // Used to scroll to a specific position 39370 focus: false, 39371 id: ++nextOpId // Unique ID 39372 }; 39373 pushOperation(cm.curOp); 39374} 39375 39376// Finish an operation, updating the display and signalling delayed events 39377function endOperation(cm) { 39378 var op = cm.curOp; 39379 finishOperation(op, function (group) { 39380 for (var i = 0; i < group.ops.length; i++) 39381 { group.ops[i].cm.curOp = null; } 39382 endOperations(group); 39383 }); 39384} 39385 39386// The DOM updates done when an operation finishes are batched so 39387// that the minimum number of relayouts are required. 39388function endOperations(group) { 39389 var ops = group.ops; 39390 for (var i = 0; i < ops.length; i++) // Read DOM 39391 { endOperation_R1(ops[i]); } 39392 for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) 39393 { endOperation_W1(ops[i$1]); } 39394 for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM 39395 { endOperation_R2(ops[i$2]); } 39396 for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) 39397 { endOperation_W2(ops[i$3]); } 39398 for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM 39399 { endOperation_finish(ops[i$4]); } 39400} 39401 39402function endOperation_R1(op) { 39403 var cm = op.cm, display = cm.display; 39404 maybeClipScrollbars(cm); 39405 if (op.updateMaxLine) { findMaxLine(cm); } 39406 39407 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || 39408 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || 39409 op.scrollToPos.to.line >= display.viewTo) || 39410 display.maxLineChanged && cm.options.lineWrapping; 39411 op.update = op.mustUpdate && 39412 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); 39413} 39414 39415function endOperation_W1(op) { 39416 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); 39417} 39418 39419function endOperation_R2(op) { 39420 var cm = op.cm, display = cm.display; 39421 if (op.updatedDisplay) { updateHeightsInViewport(cm); } 39422 39423 op.barMeasure = measureForScrollbars(cm); 39424 39425 // If the max line changed since it was last measured, measure it, 39426 // and ensure the document's width matches it. 39427 // updateDisplay_W2 will use these properties to do the actual resizing 39428 if (display.maxLineChanged && !cm.options.lineWrapping) { 39429 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; 39430 cm.display.sizerWidth = op.adjustWidthTo; 39431 op.barMeasure.scrollWidth = 39432 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); 39433 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); 39434 } 39435 39436 if (op.updatedDisplay || op.selectionChanged) 39437 { op.preparedSelection = display.input.prepareSelection(); } 39438} 39439 39440function endOperation_W2(op) { 39441 var cm = op.cm; 39442 39443 if (op.adjustWidthTo != null) { 39444 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; 39445 if (op.maxScrollLeft < cm.doc.scrollLeft) 39446 { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); } 39447 cm.display.maxLineChanged = false; 39448 } 39449 39450 var takeFocus = op.focus && op.focus == activeElt(); 39451 if (op.preparedSelection) 39452 { cm.display.input.showSelection(op.preparedSelection, takeFocus); } 39453 if (op.updatedDisplay || op.startHeight != cm.doc.height) 39454 { updateScrollbars(cm, op.barMeasure); } 39455 if (op.updatedDisplay) 39456 { setDocumentHeight(cm, op.barMeasure); } 39457 39458 if (op.selectionChanged) { restartBlink(cm); } 39459 39460 if (cm.state.focused && op.updateInput) 39461 { cm.display.input.reset(op.typing); } 39462 if (takeFocus) { ensureFocus(op.cm); } 39463} 39464 39465function endOperation_finish(op) { 39466 var cm = op.cm, display = cm.display, doc = cm.doc; 39467 39468 if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); } 39469 39470 // Abort mouse wheel delta measurement, when scrolling explicitly 39471 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) 39472 { display.wheelStartX = display.wheelStartY = null; } 39473 39474 // Propagate the scroll position to the actual DOM scroller 39475 if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); } 39476 39477 if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); } 39478 // If we need to scroll a specific position into view, do so. 39479 if (op.scrollToPos) { 39480 var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), 39481 clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); 39482 maybeScrollWindow(cm, rect); 39483 } 39484 39485 // Fire events for markers that are hidden/unidden by editing or 39486 // undoing 39487 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; 39488 if (hidden) { for (var i = 0; i < hidden.length; ++i) 39489 { if (!hidden[i].lines.length) { signal(hidden[i], "hide"); } } } 39490 if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) 39491 { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide"); } } } 39492 39493 if (display.wrapper.offsetHeight) 39494 { doc.scrollTop = cm.display.scroller.scrollTop; } 39495 39496 // Fire change events, and delayed event handlers 39497 if (op.changeObjs) 39498 { signal(cm, "changes", cm, op.changeObjs); } 39499 if (op.update) 39500 { op.update.finish(); } 39501} 39502 39503// Run the given function in an operation 39504function runInOp(cm, f) { 39505 if (cm.curOp) { return f() } 39506 startOperation(cm); 39507 try { return f() } 39508 finally { endOperation(cm); } 39509} 39510// Wraps a function in an operation. Returns the wrapped function. 39511function operation(cm, f) { 39512 return function() { 39513 if (cm.curOp) { return f.apply(cm, arguments) } 39514 startOperation(cm); 39515 try { return f.apply(cm, arguments) } 39516 finally { endOperation(cm); } 39517 } 39518} 39519// Used to add methods to editor and doc instances, wrapping them in 39520// operations. 39521function methodOp(f) { 39522 return function() { 39523 if (this.curOp) { return f.apply(this, arguments) } 39524 startOperation(this); 39525 try { return f.apply(this, arguments) } 39526 finally { endOperation(this); } 39527 } 39528} 39529function docMethodOp(f) { 39530 return function() { 39531 var cm = this.cm; 39532 if (!cm || cm.curOp) { return f.apply(this, arguments) } 39533 startOperation(cm); 39534 try { return f.apply(this, arguments) } 39535 finally { endOperation(cm); } 39536 } 39537} 39538 39539// Updates the display.view data structure for a given change to the 39540// document. From and to are in pre-change coordinates. Lendiff is 39541// the amount of lines added or subtracted by the change. This is 39542// used for changes that span multiple lines, or change the way 39543// lines are divided into visual lines. regLineChange (below) 39544// registers single-line changes. 39545function regChange(cm, from, to, lendiff) { 39546 if (from == null) { from = cm.doc.first; } 39547 if (to == null) { to = cm.doc.first + cm.doc.size; } 39548 if (!lendiff) { lendiff = 0; } 39549 39550 var display = cm.display; 39551 if (lendiff && to < display.viewTo && 39552 (display.updateLineNumbers == null || display.updateLineNumbers > from)) 39553 { display.updateLineNumbers = from; } 39554 39555 cm.curOp.viewChanged = true; 39556 39557 if (from >= display.viewTo) { // Change after 39558 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) 39559 { resetView(cm); } 39560 } else if (to <= display.viewFrom) { // Change before 39561 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { 39562 resetView(cm); 39563 } else { 39564 display.viewFrom += lendiff; 39565 display.viewTo += lendiff; 39566 } 39567 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap 39568 resetView(cm); 39569 } else if (from <= display.viewFrom) { // Top overlap 39570 var cut = viewCuttingPoint(cm, to, to + lendiff, 1); 39571 if (cut) { 39572 display.view = display.view.slice(cut.index); 39573 display.viewFrom = cut.lineN; 39574 display.viewTo += lendiff; 39575 } else { 39576 resetView(cm); 39577 } 39578 } else if (to >= display.viewTo) { // Bottom overlap 39579 var cut$1 = viewCuttingPoint(cm, from, from, -1); 39580 if (cut$1) { 39581 display.view = display.view.slice(0, cut$1.index); 39582 display.viewTo = cut$1.lineN; 39583 } else { 39584 resetView(cm); 39585 } 39586 } else { // Gap in the middle 39587 var cutTop = viewCuttingPoint(cm, from, from, -1); 39588 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); 39589 if (cutTop && cutBot) { 39590 display.view = display.view.slice(0, cutTop.index) 39591 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) 39592 .concat(display.view.slice(cutBot.index)); 39593 display.viewTo += lendiff; 39594 } else { 39595 resetView(cm); 39596 } 39597 } 39598 39599 var ext = display.externalMeasured; 39600 if (ext) { 39601 if (to < ext.lineN) 39602 { ext.lineN += lendiff; } 39603 else if (from < ext.lineN + ext.size) 39604 { display.externalMeasured = null; } 39605 } 39606} 39607 39608// Register a change to a single line. Type must be one of "text", 39609// "gutter", "class", "widget" 39610function regLineChange(cm, line, type) { 39611 cm.curOp.viewChanged = true; 39612 var display = cm.display, ext = cm.display.externalMeasured; 39613 if (ext && line >= ext.lineN && line < ext.lineN + ext.size) 39614 { display.externalMeasured = null; } 39615 39616 if (line < display.viewFrom || line >= display.viewTo) { return } 39617 var lineView = display.view[findViewIndex(cm, line)]; 39618 if (lineView.node == null) { return } 39619 var arr = lineView.changes || (lineView.changes = []); 39620 if (indexOf(arr, type) == -1) { arr.push(type); } 39621} 39622 39623// Clear the view. 39624function resetView(cm) { 39625 cm.display.viewFrom = cm.display.viewTo = cm.doc.first; 39626 cm.display.view = []; 39627 cm.display.viewOffset = 0; 39628} 39629 39630function viewCuttingPoint(cm, oldN, newN, dir) { 39631 var index = findViewIndex(cm, oldN), diff, view = cm.display.view; 39632 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) 39633 { return {index: index, lineN: newN} } 39634 var n = cm.display.viewFrom; 39635 for (var i = 0; i < index; i++) 39636 { n += view[i].size; } 39637 if (n != oldN) { 39638 if (dir > 0) { 39639 if (index == view.length - 1) { return null } 39640 diff = (n + view[index].size) - oldN; 39641 index++; 39642 } else { 39643 diff = n - oldN; 39644 } 39645 oldN += diff; newN += diff; 39646 } 39647 while (visualLineNo(cm.doc, newN) != newN) { 39648 if (index == (dir < 0 ? 0 : view.length - 1)) { return null } 39649 newN += dir * view[index - (dir < 0 ? 1 : 0)].size; 39650 index += dir; 39651 } 39652 return {index: index, lineN: newN} 39653} 39654 39655// Force the view to cover a given range, adding empty view element 39656// or clipping off existing ones as needed. 39657function adjustView(cm, from, to) { 39658 var display = cm.display, view = display.view; 39659 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { 39660 display.view = buildViewArray(cm, from, to); 39661 display.viewFrom = from; 39662 } else { 39663 if (display.viewFrom > from) 39664 { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); } 39665 else if (display.viewFrom < from) 39666 { display.view = display.view.slice(findViewIndex(cm, from)); } 39667 display.viewFrom = from; 39668 if (display.viewTo < to) 39669 { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); } 39670 else if (display.viewTo > to) 39671 { display.view = display.view.slice(0, findViewIndex(cm, to)); } 39672 } 39673 display.viewTo = to; 39674} 39675 39676// Count the number of lines in the view whose DOM representation is 39677// out of date (or nonexistent). 39678function countDirtyView(cm) { 39679 var view = cm.display.view, dirty = 0; 39680 for (var i = 0; i < view.length; i++) { 39681 var lineView = view[i]; 39682 if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; } 39683 } 39684 return dirty 39685} 39686 39687// HIGHLIGHT WORKER 39688 39689function startWorker(cm, time) { 39690 if (cm.doc.highlightFrontier < cm.display.viewTo) 39691 { cm.state.highlight.set(time, bind(highlightWorker, cm)); } 39692} 39693 39694function highlightWorker(cm) { 39695 var doc = cm.doc; 39696 if (doc.highlightFrontier >= cm.display.viewTo) { return } 39697 var end = +new Date + cm.options.workTime; 39698 var context = getContextBefore(cm, doc.highlightFrontier); 39699 var changedLines = []; 39700 39701 doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { 39702 if (context.line >= cm.display.viewFrom) { // Visible 39703 var oldStyles = line.styles; 39704 var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null; 39705 var highlighted = highlightLine(cm, line, context, true); 39706 if (resetState) { context.state = resetState; } 39707 line.styles = highlighted.styles; 39708 var oldCls = line.styleClasses, newCls = highlighted.classes; 39709 if (newCls) { line.styleClasses = newCls; } 39710 else if (oldCls) { line.styleClasses = null; } 39711 var ischange = !oldStyles || oldStyles.length != line.styles.length || 39712 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); 39713 for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; } 39714 if (ischange) { changedLines.push(context.line); } 39715 line.stateAfter = context.save(); 39716 context.nextLine(); 39717 } else { 39718 if (line.text.length <= cm.options.maxHighlightLength) 39719 { processLine(cm, line.text, context); } 39720 line.stateAfter = context.line % 5 == 0 ? context.save() : null; 39721 context.nextLine(); 39722 } 39723 if (+new Date > end) { 39724 startWorker(cm, cm.options.workDelay); 39725 return true 39726 } 39727 }); 39728 doc.highlightFrontier = context.line; 39729 doc.modeFrontier = Math.max(doc.modeFrontier, context.line); 39730 if (changedLines.length) { runInOp(cm, function () { 39731 for (var i = 0; i < changedLines.length; i++) 39732 { regLineChange(cm, changedLines[i], "text"); } 39733 }); } 39734} 39735 39736// DISPLAY DRAWING 39737 39738var DisplayUpdate = function(cm, viewport, force) { 39739 var display = cm.display; 39740 39741 this.viewport = viewport; 39742 // Store some values that we'll need later (but don't want to force a relayout for) 39743 this.visible = visibleLines(display, cm.doc, viewport); 39744 this.editorIsHidden = !display.wrapper.offsetWidth; 39745 this.wrapperHeight = display.wrapper.clientHeight; 39746 this.wrapperWidth = display.wrapper.clientWidth; 39747 this.oldDisplayWidth = displayWidth(cm); 39748 this.force = force; 39749 this.dims = getDimensions(cm); 39750 this.events = []; 39751}; 39752 39753DisplayUpdate.prototype.signal = function (emitter, type) { 39754 if (hasHandler(emitter, type)) 39755 { this.events.push(arguments); } 39756}; 39757DisplayUpdate.prototype.finish = function () { 39758 var this$1 = this; 39759 39760 for (var i = 0; i < this.events.length; i++) 39761 { signal.apply(null, this$1.events[i]); } 39762}; 39763 39764function maybeClipScrollbars(cm) { 39765 var display = cm.display; 39766 if (!display.scrollbarsClipped && display.scroller.offsetWidth) { 39767 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; 39768 display.heightForcer.style.height = scrollGap(cm) + "px"; 39769 display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; 39770 display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; 39771 display.scrollbarsClipped = true; 39772 } 39773} 39774 39775function selectionSnapshot(cm) { 39776 if (cm.hasFocus()) { return null } 39777 var active = activeElt(); 39778 if (!active || !contains(cm.display.lineDiv, active)) { return null } 39779 var result = {activeElt: active}; 39780 if (window.getSelection) { 39781 var sel = window.getSelection(); 39782 if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { 39783 result.anchorNode = sel.anchorNode; 39784 result.anchorOffset = sel.anchorOffset; 39785 result.focusNode = sel.focusNode; 39786 result.focusOffset = sel.focusOffset; 39787 } 39788 } 39789 return result 39790} 39791 39792function restoreSelection(snapshot) { 39793 if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return } 39794 snapshot.activeElt.focus(); 39795 if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { 39796 var sel = window.getSelection(), range$$1 = document.createRange(); 39797 range$$1.setEnd(snapshot.anchorNode, snapshot.anchorOffset); 39798 range$$1.collapse(false); 39799 sel.removeAllRanges(); 39800 sel.addRange(range$$1); 39801 sel.extend(snapshot.focusNode, snapshot.focusOffset); 39802 } 39803} 39804 39805// Does the actual updating of the line display. Bails out 39806// (returning false) when there is nothing to be done and forced is 39807// false. 39808function updateDisplayIfNeeded(cm, update) { 39809 var display = cm.display, doc = cm.doc; 39810 39811 if (update.editorIsHidden) { 39812 resetView(cm); 39813 return false 39814 } 39815 39816 // Bail out if the visible area is already rendered and nothing changed. 39817 if (!update.force && 39818 update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && 39819 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && 39820 display.renderedView == display.view && countDirtyView(cm) == 0) 39821 { return false } 39822 39823 if (maybeUpdateLineNumberWidth(cm)) { 39824 resetView(cm); 39825 update.dims = getDimensions(cm); 39826 } 39827 39828 // Compute a suitable new viewport (from & to) 39829 var end = doc.first + doc.size; 39830 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); 39831 var to = Math.min(end, update.visible.to + cm.options.viewportMargin); 39832 if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); } 39833 if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); } 39834 if (sawCollapsedSpans) { 39835 from = visualLineNo(cm.doc, from); 39836 to = visualLineEndNo(cm.doc, to); 39837 } 39838 39839 var different = from != display.viewFrom || to != display.viewTo || 39840 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; 39841 adjustView(cm, from, to); 39842 39843 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); 39844 // Position the mover div to align with the current scroll position 39845 cm.display.mover.style.top = display.viewOffset + "px"; 39846 39847 var toUpdate = countDirtyView(cm); 39848 if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && 39849 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) 39850 { return false } 39851 39852 // For big changes, we hide the enclosing element during the 39853 // update, since that speeds up the operations on most browsers. 39854 var selSnapshot = selectionSnapshot(cm); 39855 if (toUpdate > 4) { display.lineDiv.style.display = "none"; } 39856 patchDisplay(cm, display.updateLineNumbers, update.dims); 39857 if (toUpdate > 4) { display.lineDiv.style.display = ""; } 39858 display.renderedView = display.view; 39859 // There might have been a widget with a focused element that got 39860 // hidden or updated, if so re-focus it. 39861 restoreSelection(selSnapshot); 39862 39863 // Prevent selection and cursors from interfering with the scroll 39864 // width and height. 39865 removeChildren(display.cursorDiv); 39866 removeChildren(display.selectionDiv); 39867 display.gutters.style.height = display.sizer.style.minHeight = 0; 39868 39869 if (different) { 39870 display.lastWrapHeight = update.wrapperHeight; 39871 display.lastWrapWidth = update.wrapperWidth; 39872 startWorker(cm, 400); 39873 } 39874 39875 display.updateLineNumbers = null; 39876 39877 return true 39878} 39879 39880function postUpdateDisplay(cm, update) { 39881 var viewport = update.viewport; 39882 39883 for (var first = true;; first = false) { 39884 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { 39885 // Clip forced viewport to actual scrollable area. 39886 if (viewport && viewport.top != null) 39887 { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; } 39888 // Updated line heights might result in the drawn area not 39889 // actually covering the viewport. Keep looping until it does. 39890 update.visible = visibleLines(cm.display, cm.doc, viewport); 39891 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) 39892 { break } 39893 } 39894 if (!updateDisplayIfNeeded(cm, update)) { break } 39895 updateHeightsInViewport(cm); 39896 var barMeasure = measureForScrollbars(cm); 39897 updateSelection(cm); 39898 updateScrollbars(cm, barMeasure); 39899 setDocumentHeight(cm, barMeasure); 39900 update.force = false; 39901 } 39902 39903 update.signal(cm, "update", cm); 39904 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { 39905 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); 39906 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; 39907 } 39908} 39909 39910function updateDisplaySimple(cm, viewport) { 39911 var update = new DisplayUpdate(cm, viewport); 39912 if (updateDisplayIfNeeded(cm, update)) { 39913 updateHeightsInViewport(cm); 39914 postUpdateDisplay(cm, update); 39915 var barMeasure = measureForScrollbars(cm); 39916 updateSelection(cm); 39917 updateScrollbars(cm, barMeasure); 39918 setDocumentHeight(cm, barMeasure); 39919 update.finish(); 39920 } 39921} 39922 39923// Sync the actual display DOM structure with display.view, removing 39924// nodes for lines that are no longer in view, and creating the ones 39925// that are not there yet, and updating the ones that are out of 39926// date. 39927function patchDisplay(cm, updateNumbersFrom, dims) { 39928 var display = cm.display, lineNumbers = cm.options.lineNumbers; 39929 var container = display.lineDiv, cur = container.firstChild; 39930 39931 function rm(node) { 39932 var next = node.nextSibling; 39933 // Works around a throw-scroll bug in OS X Webkit 39934 if (webkit && mac && cm.display.currentWheelTarget == node) 39935 { node.style.display = "none"; } 39936 else 39937 { node.parentNode.removeChild(node); } 39938 return next 39939 } 39940 39941 var view = display.view, lineN = display.viewFrom; 39942 // Loop over the elements in the view, syncing cur (the DOM nodes 39943 // in display.lineDiv) with the view as we go. 39944 for (var i = 0; i < view.length; i++) { 39945 var lineView = view[i]; 39946 if (lineView.hidden) { 39947 } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet 39948 var node = buildLineElement(cm, lineView, lineN, dims); 39949 container.insertBefore(node, cur); 39950 } else { // Already drawn 39951 while (cur != lineView.node) { cur = rm(cur); } 39952 var updateNumber = lineNumbers && updateNumbersFrom != null && 39953 updateNumbersFrom <= lineN && lineView.lineNumber; 39954 if (lineView.changes) { 39955 if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false; } 39956 updateLineForChanges(cm, lineView, lineN, dims); 39957 } 39958 if (updateNumber) { 39959 removeChildren(lineView.lineNumber); 39960 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); 39961 } 39962 cur = lineView.node.nextSibling; 39963 } 39964 lineN += lineView.size; 39965 } 39966 while (cur) { cur = rm(cur); } 39967} 39968 39969function updateGutterSpace(cm) { 39970 var width = cm.display.gutters.offsetWidth; 39971 cm.display.sizer.style.marginLeft = width + "px"; 39972} 39973 39974function setDocumentHeight(cm, measure) { 39975 cm.display.sizer.style.minHeight = measure.docHeight + "px"; 39976 cm.display.heightForcer.style.top = measure.docHeight + "px"; 39977 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"; 39978} 39979 39980// Rebuild the gutter elements, ensure the margin to the left of the 39981// code matches their width. 39982function updateGutters(cm) { 39983 var gutters = cm.display.gutters, specs = cm.options.gutters; 39984 removeChildren(gutters); 39985 var i = 0; 39986 for (; i < specs.length; ++i) { 39987 var gutterClass = specs[i]; 39988 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); 39989 if (gutterClass == "CodeMirror-linenumbers") { 39990 cm.display.lineGutter = gElt; 39991 gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; 39992 } 39993 } 39994 gutters.style.display = i ? "" : "none"; 39995 updateGutterSpace(cm); 39996} 39997 39998// Make sure the gutters options contains the element 39999// "CodeMirror-linenumbers" when the lineNumbers option is true. 40000function setGuttersForLineNumbers(options) { 40001 var found = indexOf(options.gutters, "CodeMirror-linenumbers"); 40002 if (found == -1 && options.lineNumbers) { 40003 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); 40004 } else if (found > -1 && !options.lineNumbers) { 40005 options.gutters = options.gutters.slice(0); 40006 options.gutters.splice(found, 1); 40007 } 40008} 40009 40010// Since the delta values reported on mouse wheel events are 40011// unstandardized between browsers and even browser versions, and 40012// generally horribly unpredictable, this code starts by measuring 40013// the scroll effect that the first few mouse wheel events have, 40014// and, from that, detects the way it can convert deltas to pixel 40015// offsets afterwards. 40016// 40017// The reason we want to know the amount a wheel event will scroll 40018// is that it gives us a chance to update the display before the 40019// actual scrolling happens, reducing flickering. 40020 40021var wheelSamples = 0; 40022var wheelPixelsPerUnit = null; 40023// Fill in a browser-detected starting value on browsers where we 40024// know one. These don't have to be accurate -- the result of them 40025// being wrong would just be a slight flicker on the first wheel 40026// scroll (if it is large enough). 40027if (ie) { wheelPixelsPerUnit = -.53; } 40028else if (gecko) { wheelPixelsPerUnit = 15; } 40029else if (chrome) { wheelPixelsPerUnit = -.7; } 40030else if (safari) { wheelPixelsPerUnit = -1/3; } 40031 40032function wheelEventDelta(e) { 40033 var dx = e.wheelDeltaX, dy = e.wheelDeltaY; 40034 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; } 40035 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; } 40036 else if (dy == null) { dy = e.wheelDelta; } 40037 return {x: dx, y: dy} 40038} 40039function wheelEventPixels(e) { 40040 var delta = wheelEventDelta(e); 40041 delta.x *= wheelPixelsPerUnit; 40042 delta.y *= wheelPixelsPerUnit; 40043 return delta 40044} 40045 40046function onScrollWheel(cm, e) { 40047 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; 40048 40049 var display = cm.display, scroll = display.scroller; 40050 // Quit if there's nothing to scroll here 40051 var canScrollX = scroll.scrollWidth > scroll.clientWidth; 40052 var canScrollY = scroll.scrollHeight > scroll.clientHeight; 40053 if (!(dx && canScrollX || dy && canScrollY)) { return } 40054 40055 // Webkit browsers on OS X abort momentum scrolls when the target 40056 // of the scroll event is removed from the scrollable element. 40057 // This hack (see related code in patchDisplay) makes sure the 40058 // element is kept around. 40059 if (dy && mac && webkit) { 40060 outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { 40061 for (var i = 0; i < view.length; i++) { 40062 if (view[i].node == cur) { 40063 cm.display.currentWheelTarget = cur; 40064 break outer 40065 } 40066 } 40067 } 40068 } 40069 40070 // On some browsers, horizontal scrolling will cause redraws to 40071 // happen before the gutter has been realigned, causing it to 40072 // wriggle around in a most unseemly way. When we have an 40073 // estimated pixels/delta value, we just handle horizontal 40074 // scrolling entirely here. It'll be slightly off from native, but 40075 // better than glitching out. 40076 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { 40077 if (dy && canScrollY) 40078 { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); } 40079 setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)); 40080 // Only prevent default scrolling if vertical scrolling is 40081 // actually possible. Otherwise, it causes vertical scroll 40082 // jitter on OSX trackpads when deltaX is small and deltaY 40083 // is large (issue #3579) 40084 if (!dy || (dy && canScrollY)) 40085 { e_preventDefault(e); } 40086 display.wheelStartX = null; // Abort measurement, if in progress 40087 return 40088 } 40089 40090 // 'Project' the visible viewport to cover the area that is being 40091 // scrolled into view (if we know enough to estimate it). 40092 if (dy && wheelPixelsPerUnit != null) { 40093 var pixels = dy * wheelPixelsPerUnit; 40094 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; 40095 if (pixels < 0) { top = Math.max(0, top + pixels - 50); } 40096 else { bot = Math.min(cm.doc.height, bot + pixels + 50); } 40097 updateDisplaySimple(cm, {top: top, bottom: bot}); 40098 } 40099 40100 if (wheelSamples < 20) { 40101 if (display.wheelStartX == null) { 40102 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; 40103 display.wheelDX = dx; display.wheelDY = dy; 40104 setTimeout(function () { 40105 if (display.wheelStartX == null) { return } 40106 var movedX = scroll.scrollLeft - display.wheelStartX; 40107 var movedY = scroll.scrollTop - display.wheelStartY; 40108 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || 40109 (movedX && display.wheelDX && movedX / display.wheelDX); 40110 display.wheelStartX = display.wheelStartY = null; 40111 if (!sample) { return } 40112 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); 40113 ++wheelSamples; 40114 }, 200); 40115 } else { 40116 display.wheelDX += dx; display.wheelDY += dy; 40117 } 40118 } 40119} 40120 40121// Selection objects are immutable. A new one is created every time 40122// the selection changes. A selection is one or more non-overlapping 40123// (and non-touching) ranges, sorted, and an integer that indicates 40124// which one is the primary selection (the one that's scrolled into 40125// view, that getCursor returns, etc). 40126var Selection = function(ranges, primIndex) { 40127 this.ranges = ranges; 40128 this.primIndex = primIndex; 40129}; 40130 40131Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; 40132 40133Selection.prototype.equals = function (other) { 40134 var this$1 = this; 40135 40136 if (other == this) { return true } 40137 if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } 40138 for (var i = 0; i < this.ranges.length; i++) { 40139 var here = this$1.ranges[i], there = other.ranges[i]; 40140 if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false } 40141 } 40142 return true 40143}; 40144 40145Selection.prototype.deepCopy = function () { 40146 var this$1 = this; 40147 40148 var out = []; 40149 for (var i = 0; i < this.ranges.length; i++) 40150 { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)); } 40151 return new Selection(out, this.primIndex) 40152}; 40153 40154Selection.prototype.somethingSelected = function () { 40155 var this$1 = this; 40156 40157 for (var i = 0; i < this.ranges.length; i++) 40158 { if (!this$1.ranges[i].empty()) { return true } } 40159 return false 40160}; 40161 40162Selection.prototype.contains = function (pos, end) { 40163 var this$1 = this; 40164 40165 if (!end) { end = pos; } 40166 for (var i = 0; i < this.ranges.length; i++) { 40167 var range = this$1.ranges[i]; 40168 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) 40169 { return i } 40170 } 40171 return -1 40172}; 40173 40174var Range = function(anchor, head) { 40175 this.anchor = anchor; this.head = head; 40176}; 40177 40178Range.prototype.from = function () { return minPos(this.anchor, this.head) }; 40179Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; 40180Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; 40181 40182// Take an unsorted, potentially overlapping set of ranges, and 40183// build a selection out of it. 'Consumes' ranges array (modifying 40184// it). 40185function normalizeSelection(ranges, primIndex) { 40186 var prim = ranges[primIndex]; 40187 ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }); 40188 primIndex = indexOf(ranges, prim); 40189 for (var i = 1; i < ranges.length; i++) { 40190 var cur = ranges[i], prev = ranges[i - 1]; 40191 if (cmp(prev.to(), cur.from()) >= 0) { 40192 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); 40193 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; 40194 if (i <= primIndex) { --primIndex; } 40195 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); 40196 } 40197 } 40198 return new Selection(ranges, primIndex) 40199} 40200 40201function simpleSelection(anchor, head) { 40202 return new Selection([new Range(anchor, head || anchor)], 0) 40203} 40204 40205// Compute the position of the end of a change (its 'to' property 40206// refers to the pre-change end). 40207function changeEnd(change) { 40208 if (!change.text) { return change.to } 40209 return Pos(change.from.line + change.text.length - 1, 40210 lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) 40211} 40212 40213// Adjust a position to refer to the post-change position of the 40214// same text, or the end of the change if the change covers it. 40215function adjustForChange(pos, change) { 40216 if (cmp(pos, change.from) < 0) { return pos } 40217 if (cmp(pos, change.to) <= 0) { return changeEnd(change) } 40218 40219 var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; 40220 if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; } 40221 return Pos(line, ch) 40222} 40223 40224function computeSelAfterChange(doc, change) { 40225 var out = []; 40226 for (var i = 0; i < doc.sel.ranges.length; i++) { 40227 var range = doc.sel.ranges[i]; 40228 out.push(new Range(adjustForChange(range.anchor, change), 40229 adjustForChange(range.head, change))); 40230 } 40231 return normalizeSelection(out, doc.sel.primIndex) 40232} 40233 40234function offsetPos(pos, old, nw) { 40235 if (pos.line == old.line) 40236 { return Pos(nw.line, pos.ch - old.ch + nw.ch) } 40237 else 40238 { return Pos(nw.line + (pos.line - old.line), pos.ch) } 40239} 40240 40241// Used by replaceSelections to allow moving the selection to the 40242// start or around the replaced test. Hint may be "start" or "around". 40243function computeReplacedSel(doc, changes, hint) { 40244 var out = []; 40245 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; 40246 for (var i = 0; i < changes.length; i++) { 40247 var change = changes[i]; 40248 var from = offsetPos(change.from, oldPrev, newPrev); 40249 var to = offsetPos(changeEnd(change), oldPrev, newPrev); 40250 oldPrev = change.to; 40251 newPrev = to; 40252 if (hint == "around") { 40253 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; 40254 out[i] = new Range(inv ? to : from, inv ? from : to); 40255 } else { 40256 out[i] = new Range(from, from); 40257 } 40258 } 40259 return new Selection(out, doc.sel.primIndex) 40260} 40261 40262// Used to get the editor into a consistent state again when options change. 40263 40264function loadMode(cm) { 40265 cm.doc.mode = getMode(cm.options, cm.doc.modeOption); 40266 resetModeState(cm); 40267} 40268 40269function resetModeState(cm) { 40270 cm.doc.iter(function (line) { 40271 if (line.stateAfter) { line.stateAfter = null; } 40272 if (line.styles) { line.styles = null; } 40273 }); 40274 cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first; 40275 startWorker(cm, 100); 40276 cm.state.modeGen++; 40277 if (cm.curOp) { regChange(cm); } 40278} 40279 40280// DOCUMENT DATA STRUCTURE 40281 40282// By default, updates that start and end at the beginning of a line 40283// are treated specially, in order to make the association of line 40284// widgets and marker elements with the text behave more intuitive. 40285function isWholeLineUpdate(doc, change) { 40286 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && 40287 (!doc.cm || doc.cm.options.wholeLineUpdateBefore) 40288} 40289 40290// Perform a change on the document data structure. 40291function updateDoc(doc, change, markedSpans, estimateHeight$$1) { 40292 function spansFor(n) {return markedSpans ? markedSpans[n] : null} 40293 function update(line, text, spans) { 40294 updateLine(line, text, spans, estimateHeight$$1); 40295 signalLater(line, "change", line, change); 40296 } 40297 function linesFor(start, end) { 40298 var result = []; 40299 for (var i = start; i < end; ++i) 40300 { result.push(new Line(text[i], spansFor(i), estimateHeight$$1)); } 40301 return result 40302 } 40303 40304 var from = change.from, to = change.to, text = change.text; 40305 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); 40306 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; 40307 40308 // Adjust the line structure 40309 if (change.full) { 40310 doc.insert(0, linesFor(0, text.length)); 40311 doc.remove(text.length, doc.size - text.length); 40312 } else if (isWholeLineUpdate(doc, change)) { 40313 // This is a whole-line replace. Treated specially to make 40314 // sure line objects move the way they are supposed to. 40315 var added = linesFor(0, text.length - 1); 40316 update(lastLine, lastLine.text, lastSpans); 40317 if (nlines) { doc.remove(from.line, nlines); } 40318 if (added.length) { doc.insert(from.line, added); } 40319 } else if (firstLine == lastLine) { 40320 if (text.length == 1) { 40321 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); 40322 } else { 40323 var added$1 = linesFor(1, text.length - 1); 40324 added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight$$1)); 40325 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); 40326 doc.insert(from.line + 1, added$1); 40327 } 40328 } else if (text.length == 1) { 40329 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); 40330 doc.remove(from.line + 1, nlines); 40331 } else { 40332 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); 40333 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); 40334 var added$2 = linesFor(1, text.length - 1); 40335 if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); } 40336 doc.insert(from.line + 1, added$2); 40337 } 40338 40339 signalLater(doc, "change", doc, change); 40340} 40341 40342// Call f for all linked documents. 40343function linkedDocs(doc, f, sharedHistOnly) { 40344 function propagate(doc, skip, sharedHist) { 40345 if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { 40346 var rel = doc.linked[i]; 40347 if (rel.doc == skip) { continue } 40348 var shared = sharedHist && rel.sharedHist; 40349 if (sharedHistOnly && !shared) { continue } 40350 f(rel.doc, shared); 40351 propagate(rel.doc, doc, shared); 40352 } } 40353 } 40354 propagate(doc, null, true); 40355} 40356 40357// Attach a document to an editor. 40358function attachDoc(cm, doc) { 40359 if (doc.cm) { throw new Error("This document is already in use.") } 40360 cm.doc = doc; 40361 doc.cm = cm; 40362 estimateLineHeights(cm); 40363 loadMode(cm); 40364 setDirectionClass(cm); 40365 if (!cm.options.lineWrapping) { findMaxLine(cm); } 40366 cm.options.mode = doc.modeOption; 40367 regChange(cm); 40368} 40369 40370function setDirectionClass(cm) { 40371 (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl"); 40372} 40373 40374function directionChanged(cm) { 40375 runInOp(cm, function () { 40376 setDirectionClass(cm); 40377 regChange(cm); 40378 }); 40379} 40380 40381function History(startGen) { 40382 // Arrays of change events and selections. Doing something adds an 40383 // event to done and clears undo. Undoing moves events from done 40384 // to undone, redoing moves them in the other direction. 40385 this.done = []; this.undone = []; 40386 this.undoDepth = Infinity; 40387 // Used to track when changes can be merged into a single undo 40388 // event 40389 this.lastModTime = this.lastSelTime = 0; 40390 this.lastOp = this.lastSelOp = null; 40391 this.lastOrigin = this.lastSelOrigin = null; 40392 // Used by the isClean() method 40393 this.generation = this.maxGeneration = startGen || 1; 40394} 40395 40396// Create a history change event from an updateDoc-style change 40397// object. 40398function historyChangeFromChange(doc, change) { 40399 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; 40400 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); 40401 linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true); 40402 return histChange 40403} 40404 40405// Pop all selection events off the end of a history array. Stop at 40406// a change event. 40407function clearSelectionEvents(array) { 40408 while (array.length) { 40409 var last = lst(array); 40410 if (last.ranges) { array.pop(); } 40411 else { break } 40412 } 40413} 40414 40415// Find the top change event in the history. Pop off selection 40416// events that are in the way. 40417function lastChangeEvent(hist, force) { 40418 if (force) { 40419 clearSelectionEvents(hist.done); 40420 return lst(hist.done) 40421 } else if (hist.done.length && !lst(hist.done).ranges) { 40422 return lst(hist.done) 40423 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { 40424 hist.done.pop(); 40425 return lst(hist.done) 40426 } 40427} 40428 40429// Register a change in the history. Merges changes that are within 40430// a single operation, or are close together with an origin that 40431// allows merging (starting with "+") into a single event. 40432function addChangeToHistory(doc, change, selAfter, opId) { 40433 var hist = doc.history; 40434 hist.undone.length = 0; 40435 var time = +new Date, cur; 40436 var last; 40437 40438 if ((hist.lastOp == opId || 40439 hist.lastOrigin == change.origin && change.origin && 40440 ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || 40441 change.origin.charAt(0) == "*")) && 40442 (cur = lastChangeEvent(hist, hist.lastOp == opId))) { 40443 // Merge this change into the last event 40444 last = lst(cur.changes); 40445 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { 40446 // Optimized case for simple insertion -- don't want to add 40447 // new changesets for every character typed 40448 last.to = changeEnd(change); 40449 } else { 40450 // Add new sub-event 40451 cur.changes.push(historyChangeFromChange(doc, change)); 40452 } 40453 } else { 40454 // Can not be merged, start a new event. 40455 var before = lst(hist.done); 40456 if (!before || !before.ranges) 40457 { pushSelectionToHistory(doc.sel, hist.done); } 40458 cur = {changes: [historyChangeFromChange(doc, change)], 40459 generation: hist.generation}; 40460 hist.done.push(cur); 40461 while (hist.done.length > hist.undoDepth) { 40462 hist.done.shift(); 40463 if (!hist.done[0].ranges) { hist.done.shift(); } 40464 } 40465 } 40466 hist.done.push(selAfter); 40467 hist.generation = ++hist.maxGeneration; 40468 hist.lastModTime = hist.lastSelTime = time; 40469 hist.lastOp = hist.lastSelOp = opId; 40470 hist.lastOrigin = hist.lastSelOrigin = change.origin; 40471 40472 if (!last) { signal(doc, "historyAdded"); } 40473} 40474 40475function selectionEventCanBeMerged(doc, origin, prev, sel) { 40476 var ch = origin.charAt(0); 40477 return ch == "*" || 40478 ch == "+" && 40479 prev.ranges.length == sel.ranges.length && 40480 prev.somethingSelected() == sel.somethingSelected() && 40481 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) 40482} 40483 40484// Called whenever the selection changes, sets the new selection as 40485// the pending selection in the history, and pushes the old pending 40486// selection into the 'done' array when it was significantly 40487// different (in number of selected ranges, emptiness, or time). 40488function addSelectionToHistory(doc, sel, opId, options) { 40489 var hist = doc.history, origin = options && options.origin; 40490 40491 // A new event is started when the previous origin does not match 40492 // the current, or the origins don't allow matching. Origins 40493 // starting with * are always merged, those starting with + are 40494 // merged when similar and close together in time. 40495 if (opId == hist.lastSelOp || 40496 (origin && hist.lastSelOrigin == origin && 40497 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || 40498 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) 40499 { hist.done[hist.done.length - 1] = sel; } 40500 else 40501 { pushSelectionToHistory(sel, hist.done); } 40502 40503 hist.lastSelTime = +new Date; 40504 hist.lastSelOrigin = origin; 40505 hist.lastSelOp = opId; 40506 if (options && options.clearRedo !== false) 40507 { clearSelectionEvents(hist.undone); } 40508} 40509 40510function pushSelectionToHistory(sel, dest) { 40511 var top = lst(dest); 40512 if (!(top && top.ranges && top.equals(sel))) 40513 { dest.push(sel); } 40514} 40515 40516// Used to store marked span information in the history. 40517function attachLocalSpans(doc, change, from, to) { 40518 var existing = change["spans_" + doc.id], n = 0; 40519 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { 40520 if (line.markedSpans) 40521 { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; } 40522 ++n; 40523 }); 40524} 40525 40526// When un/re-doing restores text containing marked spans, those 40527// that have been explicitly cleared should not be restored. 40528function removeClearedSpans(spans) { 40529 if (!spans) { return null } 40530 var out; 40531 for (var i = 0; i < spans.length; ++i) { 40532 if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } } 40533 else if (out) { out.push(spans[i]); } 40534 } 40535 return !out ? spans : out.length ? out : null 40536} 40537 40538// Retrieve and filter the old marked spans stored in a change event. 40539function getOldSpans(doc, change) { 40540 var found = change["spans_" + doc.id]; 40541 if (!found) { return null } 40542 var nw = []; 40543 for (var i = 0; i < change.text.length; ++i) 40544 { nw.push(removeClearedSpans(found[i])); } 40545 return nw 40546} 40547 40548// Used for un/re-doing changes from the history. Combines the 40549// result of computing the existing spans with the set of spans that 40550// existed in the history (so that deleting around a span and then 40551// undoing brings back the span). 40552function mergeOldSpans(doc, change) { 40553 var old = getOldSpans(doc, change); 40554 var stretched = stretchSpansOverChange(doc, change); 40555 if (!old) { return stretched } 40556 if (!stretched) { return old } 40557 40558 for (var i = 0; i < old.length; ++i) { 40559 var oldCur = old[i], stretchCur = stretched[i]; 40560 if (oldCur && stretchCur) { 40561 spans: for (var j = 0; j < stretchCur.length; ++j) { 40562 var span = stretchCur[j]; 40563 for (var k = 0; k < oldCur.length; ++k) 40564 { if (oldCur[k].marker == span.marker) { continue spans } } 40565 oldCur.push(span); 40566 } 40567 } else if (stretchCur) { 40568 old[i] = stretchCur; 40569 } 40570 } 40571 return old 40572} 40573 40574// Used both to provide a JSON-safe object in .getHistory, and, when 40575// detaching a document, to split the history in two 40576function copyHistoryArray(events, newGroup, instantiateSel) { 40577 var copy = []; 40578 for (var i = 0; i < events.length; ++i) { 40579 var event = events[i]; 40580 if (event.ranges) { 40581 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); 40582 continue 40583 } 40584 var changes = event.changes, newChanges = []; 40585 copy.push({changes: newChanges}); 40586 for (var j = 0; j < changes.length; ++j) { 40587 var change = changes[j], m = (void 0); 40588 newChanges.push({from: change.from, to: change.to, text: change.text}); 40589 if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { 40590 if (indexOf(newGroup, Number(m[1])) > -1) { 40591 lst(newChanges)[prop] = change[prop]; 40592 delete change[prop]; 40593 } 40594 } } } 40595 } 40596 } 40597 return copy 40598} 40599 40600// The 'scroll' parameter given to many of these indicated whether 40601// the new cursor position should be scrolled into view after 40602// modifying the selection. 40603 40604// If shift is held or the extend flag is set, extends a range to 40605// include a given position (and optionally a second position). 40606// Otherwise, simply returns the range between the given positions. 40607// Used for cursor motion and such. 40608function extendRange(range, head, other, extend) { 40609 if (extend) { 40610 var anchor = range.anchor; 40611 if (other) { 40612 var posBefore = cmp(head, anchor) < 0; 40613 if (posBefore != (cmp(other, anchor) < 0)) { 40614 anchor = head; 40615 head = other; 40616 } else if (posBefore != (cmp(head, other) < 0)) { 40617 head = other; 40618 } 40619 } 40620 return new Range(anchor, head) 40621 } else { 40622 return new Range(other || head, head) 40623 } 40624} 40625 40626// Extend the primary selection range, discard the rest. 40627function extendSelection(doc, head, other, options, extend) { 40628 if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); } 40629 setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options); 40630} 40631 40632// Extend all selections (pos is an array of selections with length 40633// equal the number of selections) 40634function extendSelections(doc, heads, options) { 40635 var out = []; 40636 var extend = doc.cm && (doc.cm.display.shift || doc.extend); 40637 for (var i = 0; i < doc.sel.ranges.length; i++) 40638 { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); } 40639 var newSel = normalizeSelection(out, doc.sel.primIndex); 40640 setSelection(doc, newSel, options); 40641} 40642 40643// Updates a single range in the selection. 40644function replaceOneSelection(doc, i, range, options) { 40645 var ranges = doc.sel.ranges.slice(0); 40646 ranges[i] = range; 40647 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); 40648} 40649 40650// Reset the selection to a single range. 40651function setSimpleSelection(doc, anchor, head, options) { 40652 setSelection(doc, simpleSelection(anchor, head), options); 40653} 40654 40655// Give beforeSelectionChange handlers a change to influence a 40656// selection update. 40657function filterSelectionChange(doc, sel, options) { 40658 var obj = { 40659 ranges: sel.ranges, 40660 update: function(ranges) { 40661 var this$1 = this; 40662 40663 this.ranges = []; 40664 for (var i = 0; i < ranges.length; i++) 40665 { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), 40666 clipPos(doc, ranges[i].head)); } 40667 }, 40668 origin: options && options.origin 40669 }; 40670 signal(doc, "beforeSelectionChange", doc, obj); 40671 if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj); } 40672 if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.ranges.length - 1) } 40673 else { return sel } 40674} 40675 40676function setSelectionReplaceHistory(doc, sel, options) { 40677 var done = doc.history.done, last = lst(done); 40678 if (last && last.ranges) { 40679 done[done.length - 1] = sel; 40680 setSelectionNoUndo(doc, sel, options); 40681 } else { 40682 setSelection(doc, sel, options); 40683 } 40684} 40685 40686// Set a new selection. 40687function setSelection(doc, sel, options) { 40688 setSelectionNoUndo(doc, sel, options); 40689 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); 40690} 40691 40692function setSelectionNoUndo(doc, sel, options) { 40693 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) 40694 { sel = filterSelectionChange(doc, sel, options); } 40695 40696 var bias = options && options.bias || 40697 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); 40698 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); 40699 40700 if (!(options && options.scroll === false) && doc.cm) 40701 { ensureCursorVisible(doc.cm); } 40702} 40703 40704function setSelectionInner(doc, sel) { 40705 if (sel.equals(doc.sel)) { return } 40706 40707 doc.sel = sel; 40708 40709 if (doc.cm) { 40710 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; 40711 signalCursorActivity(doc.cm); 40712 } 40713 signalLater(doc, "cursorActivity", doc); 40714} 40715 40716// Verify that the selection does not partially select any atomic 40717// marked ranges. 40718function reCheckSelection(doc) { 40719 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)); 40720} 40721 40722// Return a selection that does not partially select any atomic 40723// ranges. 40724function skipAtomicInSelection(doc, sel, bias, mayClear) { 40725 var out; 40726 for (var i = 0; i < sel.ranges.length; i++) { 40727 var range = sel.ranges[i]; 40728 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; 40729 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); 40730 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear); 40731 if (out || newAnchor != range.anchor || newHead != range.head) { 40732 if (!out) { out = sel.ranges.slice(0, i); } 40733 out[i] = new Range(newAnchor, newHead); 40734 } 40735 } 40736 return out ? normalizeSelection(out, sel.primIndex) : sel 40737} 40738 40739function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { 40740 var line = getLine(doc, pos.line); 40741 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { 40742 var sp = line.markedSpans[i], m = sp.marker; 40743 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && 40744 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { 40745 if (mayClear) { 40746 signal(m, "beforeCursorEnter"); 40747 if (m.explicitlyCleared) { 40748 if (!line.markedSpans) { break } 40749 else {--i; continue} 40750 } 40751 } 40752 if (!m.atomic) { continue } 40753 40754 if (oldPos) { 40755 var near = m.find(dir < 0 ? 1 : -1), diff = (void 0); 40756 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) 40757 { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); } 40758 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) 40759 { return skipAtomicInner(doc, near, pos, dir, mayClear) } 40760 } 40761 40762 var far = m.find(dir < 0 ? -1 : 1); 40763 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) 40764 { far = movePos(doc, far, dir, far.line == pos.line ? line : null); } 40765 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null 40766 } 40767 } } 40768 return pos 40769} 40770 40771// Ensure a given position is not inside an atomic range. 40772function skipAtomic(doc, pos, oldPos, bias, mayClear) { 40773 var dir = bias || 1; 40774 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || 40775 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || 40776 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || 40777 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); 40778 if (!found) { 40779 doc.cantEdit = true; 40780 return Pos(doc.first, 0) 40781 } 40782 return found 40783} 40784 40785function movePos(doc, pos, dir, line) { 40786 if (dir < 0 && pos.ch == 0) { 40787 if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } 40788 else { return null } 40789 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { 40790 if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } 40791 else { return null } 40792 } else { 40793 return new Pos(pos.line, pos.ch + dir) 40794 } 40795} 40796 40797function selectAll(cm) { 40798 cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll); 40799} 40800 40801// UPDATING 40802 40803// Allow "beforeChange" event handlers to influence a change 40804function filterChange(doc, change, update) { 40805 var obj = { 40806 canceled: false, 40807 from: change.from, 40808 to: change.to, 40809 text: change.text, 40810 origin: change.origin, 40811 cancel: function () { return obj.canceled = true; } 40812 }; 40813 if (update) { obj.update = function (from, to, text, origin) { 40814 if (from) { obj.from = clipPos(doc, from); } 40815 if (to) { obj.to = clipPos(doc, to); } 40816 if (text) { obj.text = text; } 40817 if (origin !== undefined) { obj.origin = origin; } 40818 }; } 40819 signal(doc, "beforeChange", doc, obj); 40820 if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj); } 40821 40822 if (obj.canceled) { return null } 40823 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} 40824} 40825 40826// Apply a change to a document, and add it to the document's 40827// history, and propagating it to all linked documents. 40828function makeChange(doc, change, ignoreReadOnly) { 40829 if (doc.cm) { 40830 if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } 40831 if (doc.cm.state.suppressEdits) { return } 40832 } 40833 40834 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { 40835 change = filterChange(doc, change, true); 40836 if (!change) { return } 40837 } 40838 40839 // Possibly split or suppress the update based on the presence 40840 // of read-only spans in its range. 40841 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); 40842 if (split) { 40843 for (var i = split.length - 1; i >= 0; --i) 40844 { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}); } 40845 } else { 40846 makeChangeInner(doc, change); 40847 } 40848} 40849 40850function makeChangeInner(doc, change) { 40851 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } 40852 var selAfter = computeSelAfterChange(doc, change); 40853 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); 40854 40855 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); 40856 var rebased = []; 40857 40858 linkedDocs(doc, function (doc, sharedHist) { 40859 if (!sharedHist && indexOf(rebased, doc.history) == -1) { 40860 rebaseHist(doc.history, change); 40861 rebased.push(doc.history); 40862 } 40863 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); 40864 }); 40865} 40866 40867// Revert a change stored in a document's history. 40868function makeChangeFromHistory(doc, type, allowSelectionOnly) { 40869 var suppress = doc.cm && doc.cm.state.suppressEdits; 40870 if (suppress && !allowSelectionOnly) { return } 40871 40872 var hist = doc.history, event, selAfter = doc.sel; 40873 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; 40874 40875 // Verify that there is a useable event (so that ctrl-z won't 40876 // needlessly clear selection events) 40877 var i = 0; 40878 for (; i < source.length; i++) { 40879 event = source[i]; 40880 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) 40881 { break } 40882 } 40883 if (i == source.length) { return } 40884 hist.lastOrigin = hist.lastSelOrigin = null; 40885 40886 for (;;) { 40887 event = source.pop(); 40888 if (event.ranges) { 40889 pushSelectionToHistory(event, dest); 40890 if (allowSelectionOnly && !event.equals(doc.sel)) { 40891 setSelection(doc, event, {clearRedo: false}); 40892 return 40893 } 40894 selAfter = event; 40895 } else if (suppress) { 40896 source.push(event); 40897 return 40898 } else { break } 40899 } 40900 40901 // Build up a reverse change object to add to the opposite history 40902 // stack (redo when undoing, and vice versa). 40903 var antiChanges = []; 40904 pushSelectionToHistory(selAfter, dest); 40905 dest.push({changes: antiChanges, generation: hist.generation}); 40906 hist.generation = event.generation || ++hist.maxGeneration; 40907 40908 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); 40909 40910 var loop = function ( i ) { 40911 var change = event.changes[i]; 40912 change.origin = type; 40913 if (filter && !filterChange(doc, change, false)) { 40914 source.length = 0; 40915 return {} 40916 } 40917 40918 antiChanges.push(historyChangeFromChange(doc, change)); 40919 40920 var after = i ? computeSelAfterChange(doc, change) : lst(source); 40921 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); 40922 if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); } 40923 var rebased = []; 40924 40925 // Propagate to the linked documents 40926 linkedDocs(doc, function (doc, sharedHist) { 40927 if (!sharedHist && indexOf(rebased, doc.history) == -1) { 40928 rebaseHist(doc.history, change); 40929 rebased.push(doc.history); 40930 } 40931 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); 40932 }); 40933 }; 40934 40935 for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { 40936 var returned = loop( i$1 ); 40937 40938 if ( returned ) return returned.v; 40939 } 40940} 40941 40942// Sub-views need their line numbers shifted when text is added 40943// above or below them in the parent document. 40944function shiftDoc(doc, distance) { 40945 if (distance == 0) { return } 40946 doc.first += distance; 40947 doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( 40948 Pos(range.anchor.line + distance, range.anchor.ch), 40949 Pos(range.head.line + distance, range.head.ch) 40950 ); }), doc.sel.primIndex); 40951 if (doc.cm) { 40952 regChange(doc.cm, doc.first, doc.first - distance, distance); 40953 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) 40954 { regLineChange(doc.cm, l, "gutter"); } 40955 } 40956} 40957 40958// More lower-level change function, handling only a single document 40959// (not linked ones). 40960function makeChangeSingleDoc(doc, change, selAfter, spans) { 40961 if (doc.cm && !doc.cm.curOp) 40962 { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } 40963 40964 if (change.to.line < doc.first) { 40965 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); 40966 return 40967 } 40968 if (change.from.line > doc.lastLine()) { return } 40969 40970 // Clip the change to the size of this doc 40971 if (change.from.line < doc.first) { 40972 var shift = change.text.length - 1 - (doc.first - change.from.line); 40973 shiftDoc(doc, shift); 40974 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), 40975 text: [lst(change.text)], origin: change.origin}; 40976 } 40977 var last = doc.lastLine(); 40978 if (change.to.line > last) { 40979 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), 40980 text: [change.text[0]], origin: change.origin}; 40981 } 40982 40983 change.removed = getBetween(doc, change.from, change.to); 40984 40985 if (!selAfter) { selAfter = computeSelAfterChange(doc, change); } 40986 if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); } 40987 else { updateDoc(doc, change, spans); } 40988 setSelectionNoUndo(doc, selAfter, sel_dontScroll); 40989} 40990 40991// Handle the interaction of a change to a document with the editor 40992// that this document is part of. 40993function makeChangeSingleDocInEditor(cm, change, spans) { 40994 var doc = cm.doc, display = cm.display, from = change.from, to = change.to; 40995 40996 var recomputeMaxLength = false, checkWidthStart = from.line; 40997 if (!cm.options.lineWrapping) { 40998 checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); 40999 doc.iter(checkWidthStart, to.line + 1, function (line) { 41000 if (line == display.maxLine) { 41001 recomputeMaxLength = true; 41002 return true 41003 } 41004 }); 41005 } 41006 41007 if (doc.sel.contains(change.from, change.to) > -1) 41008 { signalCursorActivity(cm); } 41009 41010 updateDoc(doc, change, spans, estimateHeight(cm)); 41011 41012 if (!cm.options.lineWrapping) { 41013 doc.iter(checkWidthStart, from.line + change.text.length, function (line) { 41014 var len = lineLength(line); 41015 if (len > display.maxLineLength) { 41016 display.maxLine = line; 41017 display.maxLineLength = len; 41018 display.maxLineChanged = true; 41019 recomputeMaxLength = false; 41020 } 41021 }); 41022 if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; } 41023 } 41024 41025 retreatFrontier(doc, from.line); 41026 startWorker(cm, 400); 41027 41028 var lendiff = change.text.length - (to.line - from.line) - 1; 41029 // Remember that these lines changed, for updating the display 41030 if (change.full) 41031 { regChange(cm); } 41032 else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) 41033 { regLineChange(cm, from.line, "text"); } 41034 else 41035 { regChange(cm, from.line, to.line + 1, lendiff); } 41036 41037 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); 41038 if (changeHandler || changesHandler) { 41039 var obj = { 41040 from: from, to: to, 41041 text: change.text, 41042 removed: change.removed, 41043 origin: change.origin 41044 }; 41045 if (changeHandler) { signalLater(cm, "change", cm, obj); } 41046 if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); } 41047 } 41048 cm.display.selForContextMenu = null; 41049} 41050 41051function replaceRange(doc, code, from, to, origin) { 41052 if (!to) { to = from; } 41053 if (cmp(to, from) < 0) { var assign; 41054 (assign = [to, from], from = assign[0], to = assign[1]); } 41055 if (typeof code == "string") { code = doc.splitLines(code); } 41056 makeChange(doc, {from: from, to: to, text: code, origin: origin}); 41057} 41058 41059// Rebasing/resetting history to deal with externally-sourced changes 41060 41061function rebaseHistSelSingle(pos, from, to, diff) { 41062 if (to < pos.line) { 41063 pos.line += diff; 41064 } else if (from < pos.line) { 41065 pos.line = from; 41066 pos.ch = 0; 41067 } 41068} 41069 41070// Tries to rebase an array of history events given a change in the 41071// document. If the change touches the same lines as the event, the 41072// event, and everything 'behind' it, is discarded. If the change is 41073// before the event, the event's positions are updated. Uses a 41074// copy-on-write scheme for the positions, to avoid having to 41075// reallocate them all on every rebase, but also avoid problems with 41076// shared position objects being unsafely updated. 41077function rebaseHistArray(array, from, to, diff) { 41078 for (var i = 0; i < array.length; ++i) { 41079 var sub = array[i], ok = true; 41080 if (sub.ranges) { 41081 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } 41082 for (var j = 0; j < sub.ranges.length; j++) { 41083 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); 41084 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); 41085 } 41086 continue 41087 } 41088 for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { 41089 var cur = sub.changes[j$1]; 41090 if (to < cur.from.line) { 41091 cur.from = Pos(cur.from.line + diff, cur.from.ch); 41092 cur.to = Pos(cur.to.line + diff, cur.to.ch); 41093 } else if (from <= cur.to.line) { 41094 ok = false;
41095 break 41096 } 41097 } 41098 if (!ok) { 41099 array.splice(0, i + 1); 41100 i = 0; 41101 } 41102 } 41103} 41104 41105function rebaseHist(hist, change) { 41106 var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; 41107 rebaseHistArray(hist.done, from, to, diff); 41108 rebaseHistArray(hist.undone, from, to, diff); 41109} 41110 41111// Utility for applying a change to a line by handle or number, 41112// returning the number and optionally registering the line as 41113// changed. 41114function changeLine(doc, handle, changeType, op) { 41115 var no = handle, line = handle; 41116 if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)); } 41117 else { no = lineNo(handle); } 41118 if (no == null) { return null } 41119 if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); } 41120 return line 41121} 41122 41123// The document is represented as a BTree consisting of leaves, with 41124// chunk of lines in them, and branches, with up to ten leaves or 41125// other branch nodes below them. The top node is always a branch 41126// node, and is the document object itself (meaning it has 41127// additional methods and properties). 41128// 41129// All nodes have parent links. The tree is used both to go from 41130// line numbers to line objects, and to go from objects to numbers. 41131// It also indexes by height, and is used to convert between height 41132// and line object, and to find the total height of the document. 41133// 41134// See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html 41135 41136function LeafChunk(lines) { 41137 var this$1 = this; 41138 41139 this.lines = lines; 41140 this.parent = null; 41141 var height = 0; 41142 for (var i = 0; i < lines.length; ++i) { 41143 lines[i].parent = this$1; 41144 height += lines[i].height; 41145 } 41146 this.height = height; 41147} 41148 41149LeafChunk.prototype = { 41150 chunkSize: function() { return this.lines.length }, 41151 41152 // Remove the n lines at offset 'at'. 41153 removeInner: function(at, n) { 41154 var this$1 = this; 41155 41156 for (var i = at, e = at + n; i < e; ++i) { 41157 var line = this$1.lines[i]; 41158 this$1.height -= line.height; 41159 cleanUpLine(line); 41160 signalLater(line, "delete"); 41161 } 41162 this.lines.splice(at, n); 41163 }, 41164 41165 // Helper used to collapse a small branch into a single leaf. 41166 collapse: function(lines) { 41167 lines.push.apply(lines, this.lines); 41168 }, 41169 41170 // Insert the given array of lines at offset 'at', count them as 41171 // having the given height. 41172 insertInner: function(at, lines, height) { 41173 var this$1 = this; 41174 41175 this.height += height; 41176 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); 41177 for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1; } 41178 }, 41179 41180 // Used to iterate over a part of the tree. 41181 iterN: function(at, n, op) { 41182 var this$1 = this; 41183 41184 for (var e = at + n; at < e; ++at) 41185 { if (op(this$1.lines[at])) { return true } } 41186 } 41187}; 41188 41189function BranchChunk(children) { 41190 var this$1 = this; 41191 41192 this.children = children; 41193 var size = 0, height = 0; 41194 for (var i = 0; i < children.length; ++i) { 41195 var ch = children[i]; 41196 size += ch.chunkSize(); height += ch.height; 41197 ch.parent = this$1; 41198 } 41199 this.size = size; 41200 this.height = height; 41201 this.parent = null; 41202} 41203 41204BranchChunk.prototype = { 41205 chunkSize: function() { return this.size }, 41206 41207 removeInner: function(at, n) { 41208 var this$1 = this; 41209 41210 this.size -= n; 41211 for (var i = 0; i < this.children.length; ++i) { 41212 var child = this$1.children[i], sz = child.chunkSize(); 41213 if (at < sz) { 41214 var rm = Math.min(n, sz - at), oldHeight = child.height; 41215 child.removeInner(at, rm); 41216 this$1.height -= oldHeight - child.height; 41217 if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null; } 41218 if ((n -= rm) == 0) { break } 41219 at = 0; 41220 } else { at -= sz; } 41221 } 41222 // If the result is smaller than 25 lines, ensure that it is a 41223 // single leaf node. 41224 if (this.size - n < 25 && 41225 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { 41226 var lines = []; 41227 this.collapse(lines); 41228 this.children = [new LeafChunk(lines)]; 41229 this.children[0].parent = this; 41230 } 41231 }, 41232 41233 collapse: function(lines) { 41234 var this$1 = this; 41235 41236 for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines); } 41237 }, 41238 41239 insertInner: function(at, lines, height) { 41240 var this$1 = this; 41241 41242 this.size += lines.length; 41243 this.height += height; 41244 for (var i = 0; i < this.children.length; ++i) { 41245 var child = this$1.children[i], sz = child.chunkSize(); 41246 if (at <= sz) { 41247 child.insertInner(at, lines, height); 41248 if (child.lines && child.lines.length > 50) { 41249 // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. 41250 // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. 41251 var remaining = child.lines.length % 25 + 25; 41252 for (var pos = remaining; pos < child.lines.length;) { 41253 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); 41254 child.height -= leaf.height; 41255 this$1.children.splice(++i, 0, leaf); 41256 leaf.parent = this$1; 41257 } 41258 child.lines = child.lines.slice(0, remaining); 41259 this$1.maybeSpill(); 41260 } 41261 break 41262 } 41263 at -= sz; 41264 } 41265 }, 41266 41267 // When a node has grown, check whether it should be split. 41268 maybeSpill: function() { 41269 if (this.children.length <= 10) { return } 41270 var me = this; 41271 do { 41272 var spilled = me.children.splice(me.children.length - 5, 5); 41273 var sibling = new BranchChunk(spilled); 41274 if (!me.parent) { // Become the parent node 41275 var copy = new BranchChunk(me.children); 41276 copy.parent = me; 41277 me.children = [copy, sibling]; 41278 me = copy; 41279 } else { 41280 me.size -= sibling.size; 41281 me.height -= sibling.height; 41282 var myIndex = indexOf(me.parent.children, me); 41283 me.parent.children.splice(myIndex + 1, 0, sibling); 41284 } 41285 sibling.parent = me.parent; 41286 } while (me.children.length > 10) 41287 me.parent.maybeSpill(); 41288 }, 41289 41290 iterN: function(at, n, op) { 41291 var this$1 = this; 41292 41293 for (var i = 0; i < this.children.length; ++i) { 41294 var child = this$1.children[i], sz = child.chunkSize(); 41295 if (at < sz) { 41296 var used = Math.min(n, sz - at); 41297 if (child.iterN(at, used, op)) { return true } 41298 if ((n -= used) == 0) { break } 41299 at = 0; 41300 } else { at -= sz; } 41301 } 41302 } 41303}; 41304 41305// Line widgets are block elements displayed above or below a line. 41306 41307var LineWidget = function(doc, node, options) { 41308 var this$1 = this; 41309 41310 if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) 41311 { this$1[opt] = options[opt]; } } } 41312 this.doc = doc; 41313 this.node = node; 41314}; 41315
vendor: 153,296 bytes, lines 41316-45274
41316LineWidget.prototype.clear = function () { 41317 var this$1 = this; 41318 41319 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); 41320 if (no == null || !ws) { return } 41321 for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1); } } 41322 if (!ws.length) { line.widgets = null; } 41323 var height = widgetHeight(this); 41324 updateLineHeight(line, Math.max(0, line.height - height)); 41325 if (cm) { 41326 runInOp(cm, function () { 41327 adjustScrollWhenAboveVisible(cm, line, -height); 41328 regLineChange(cm, no, "widget"); 41329 }); 41330 signalLater(cm, "lineWidgetCleared", cm, this, no); 41331 } 41332}; 41333 41334LineWidget.prototype.changed = function () { 41335 var this$1 = this; 41336 41337 var oldH = this.height, cm = this.doc.cm, line = this.line; 41338 this.height = null; 41339 var diff = widgetHeight(this) - oldH; 41340 if (!diff) { return } 41341 updateLineHeight(line, line.height + diff); 41342 if (cm) { 41343 runInOp(cm, function () { 41344 cm.curOp.forceUpdate = true; 41345 adjustScrollWhenAboveVisible(cm, line, diff); 41346 signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)); 41347 }); 41348 } 41349}; 41350eventMixin(LineWidget); 41351 41352function adjustScrollWhenAboveVisible(cm, line, diff) { 41353 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) 41354 { addToScrollTop(cm, diff); } 41355} 41356 41357function addLineWidget(doc, handle, node, options) { 41358 var widget = new LineWidget(doc, node, options); 41359 var cm = doc.cm; 41360 if (cm && widget.noHScroll) { cm.display.alignWidgets = true; } 41361 changeLine(doc, handle, "widget", function (line) { 41362 var widgets = line.widgets || (line.widgets = []); 41363 if (widget.insertAt == null) { widgets.push(widget); } 41364 else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); } 41365 widget.line = line; 41366 if (cm && !lineIsHidden(doc, line)) { 41367 var aboveVisible = heightAtLine(line) < doc.scrollTop; 41368 updateLineHeight(line, line.height + widgetHeight(widget)); 41369 if (aboveVisible) { addToScrollTop(cm, widget.height); } 41370 cm.curOp.forceUpdate = true; 41371 } 41372 return true 41373 }); 41374 if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)); } 41375 return widget 41376} 41377 41378// TEXTMARKERS 41379 41380// Created with markText and setBookmark methods. A TextMarker is a 41381// handle that can be used to clear or find a marked position in the 41382// document. Line objects hold arrays (markedSpans) containing 41383// {from, to, marker} object pointing to such marker objects, and 41384// indicating that such a marker is present on that line. Multiple 41385// lines may point to the same marker when it spans across lines. 41386// The spans will have null for their from/to properties when the 41387// marker continues beyond the start/end of the line. Markers have 41388// links back to the lines they currently touch. 41389 41390// Collapsed markers have unique ids, in order to be able to order 41391// them, which is needed for uniquely determining an outer marker 41392// when they overlap (they may nest, but not partially overlap). 41393var nextMarkerId = 0; 41394 41395var TextMarker = function(doc, type) { 41396 this.lines = []; 41397 this.type = type; 41398 this.doc = doc; 41399 this.id = ++nextMarkerId; 41400}; 41401 41402// Clear the marker. 41403TextMarker.prototype.clear = function () { 41404 var this$1 = this; 41405 41406 if (this.explicitlyCleared) { return } 41407 var cm = this.doc.cm, withOp = cm && !cm.curOp; 41408 if (withOp) { startOperation(cm); } 41409 if (hasHandler(this, "clear")) { 41410 var found = this.find(); 41411 if (found) { signalLater(this, "clear", found.from, found.to); } 41412 } 41413 var min = null, max = null; 41414 for (var i = 0; i < this.lines.length; ++i) { 41415 var line = this$1.lines[i]; 41416 var span = getMarkedSpanFor(line.markedSpans, this$1); 41417 if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text"); } 41418 else if (cm) { 41419 if (span.to != null) { max = lineNo(line); } 41420 if (span.from != null) { min = lineNo(line); } 41421 } 41422 line.markedSpans = removeMarkedSpan(line.markedSpans, span); 41423 if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) 41424 { updateLineHeight(line, textHeight(cm.display)); } 41425 } 41426 if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { 41427 var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual); 41428 if (len > cm.display.maxLineLength) { 41429 cm.display.maxLine = visual; 41430 cm.display.maxLineLength = len; 41431 cm.display.maxLineChanged = true; 41432 } 41433 } } 41434 41435 if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); } 41436 this.lines.length = 0; 41437 this.explicitlyCleared = true; 41438 if (this.atomic && this.doc.cantEdit) { 41439 this.doc.cantEdit = false; 41440 if (cm) { reCheckSelection(cm.doc); } 41441 } 41442 if (cm) { signalLater(cm, "markerCleared", cm, this, min, max); } 41443 if (withOp) { endOperation(cm); } 41444 if (this.parent) { this.parent.clear(); } 41445}; 41446 41447// Find the position of the marker in the document. Returns a {from, 41448// to} object by default. Side can be passed to get a specific side 41449// -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the 41450// Pos objects returned contain a line object, rather than a line 41451// number (used to prevent looking up the same line twice). 41452TextMarker.prototype.find = function (side, lineObj) { 41453 var this$1 = this; 41454 41455 if (side == null && this.type == "bookmark") { side = 1; } 41456 var from, to; 41457 for (var i = 0; i < this.lines.length; ++i) { 41458 var line = this$1.lines[i]; 41459 var span = getMarkedSpanFor(line.markedSpans, this$1); 41460 if (span.from != null) { 41461 from = Pos(lineObj ? line : lineNo(line), span.from); 41462 if (side == -1) { return from } 41463 } 41464 if (span.to != null) { 41465 to = Pos(lineObj ? line : lineNo(line), span.to); 41466 if (side == 1) { return to } 41467 } 41468 } 41469 return from && {from: from, to: to} 41470}; 41471 41472// Signals that the marker's widget changed, and surrounding layout 41473// should be recomputed. 41474TextMarker.prototype.changed = function () { 41475 var this$1 = this; 41476 41477 var pos = this.find(-1, true), widget = this, cm = this.doc.cm; 41478 if (!pos || !cm) { return } 41479 runInOp(cm, function () { 41480 var line = pos.line, lineN = lineNo(pos.line); 41481 var view = findViewForLine(cm, lineN); 41482 if (view) { 41483 clearLineMeasurementCacheFor(view); 41484 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; 41485 } 41486 cm.curOp.updateMaxLine = true; 41487 if (!lineIsHidden(widget.doc, line) && widget.height != null) { 41488 var oldHeight = widget.height; 41489 widget.height = null; 41490 var dHeight = widgetHeight(widget) - oldHeight; 41491 if (dHeight) 41492 { updateLineHeight(line, line.height + dHeight); } 41493 } 41494 signalLater(cm, "markerChanged", cm, this$1); 41495 }); 41496}; 41497 41498TextMarker.prototype.attachLine = function (line) { 41499 if (!this.lines.length && this.doc.cm) { 41500 var op = this.doc.cm.curOp; 41501 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) 41502 { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); } 41503 } 41504 this.lines.push(line); 41505}; 41506 41507TextMarker.prototype.detachLine = function (line) { 41508 this.lines.splice(indexOf(this.lines, line), 1); 41509 if (!this.lines.length && this.doc.cm) { 41510 var op = this.doc.cm.curOp;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); 41511 } 41512}; 41513eventMixin(TextMarker); 41514 41515// Create a marker, wire it up to the right lines, and 41516function markText(doc, from, to, options, type) { 41517 // Shared markers (across linked documents) are handled separately 41518 // (markTextShared will call out to this again, once per 41519 // document). 41520 if (options && options.shared) { return markTextShared(doc, from, to, options, type) } 41521 // Ensure we are in an operation. 41522 if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } 41523 41524 var marker = new TextMarker(doc, type), diff = cmp(from, to); 41525 if (options) { copyObj(options, marker, false); } 41526 // Don't connect empty markers unless clearWhenEmpty is false 41527 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) 41528 { return marker } 41529 if (marker.replacedWith) { 41530 // Showing up as a widget implies collapsed (widget replaces text) 41531 marker.collapsed = true; 41532 marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget"); 41533 if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true"); } 41534 if (options.insertLeft) { marker.widgetNode.insertLeft = true; } 41535 } 41536 if (marker.collapsed) { 41537 if (conflictingCollapsedRange(doc, from.line, from, to, marker) || 41538 from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) 41539 { throw new Error("Inserting collapsed marker partially overlapping an existing one") } 41540 seeCollapsedSpans(); 41541 } 41542 41543 if (marker.addToHistory) 41544 { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); } 41545 41546 var curLine = from.line, cm = doc.cm, updateMaxLine; 41547 doc.iter(curLine, to.line + 1, function (line) { 41548 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) 41549 { updateMaxLine = true; } 41550 if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); } 41551 addMarkedSpan(line, new MarkedSpan(marker, 41552 curLine == from.line ? from.ch : null, 41553 curLine == to.line ? to.ch : null)); 41554 ++curLine; 41555 }); 41556 // lineIsHidden depends on the presence of the spans, so needs a second pass 41557 if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { 41558 if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); } 41559 }); } 41560 41561 if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }); } 41562 41563 if (marker.readOnly) { 41564 seeReadOnlySpans(); 41565 if (doc.history.done.length || doc.history.undone.length) 41566 { doc.clearHistory(); } 41567 } 41568 if (marker.collapsed) { 41569 marker.id = ++nextMarkerId; 41570 marker.atomic = true; 41571 } 41572 if (cm) { 41573 // Sync editor state 41574 if (updateMaxLine) { cm.curOp.updateMaxLine = true; } 41575 if (marker.collapsed) 41576 { regChange(cm, from.line, to.line + 1); } 41577 else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) 41578 { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text"); } } 41579 if (marker.atomic) { reCheckSelection(cm.doc); } 41580 signalLater(cm, "markerAdded", cm, marker); 41581 } 41582 return marker 41583} 41584 41585// SHARED TEXTMARKERS 41586 41587// A shared marker spans multiple linked documents. It is 41588// implemented as a meta-marker-object controlling multiple normal 41589// markers. 41590var SharedTextMarker = function(markers, primary) { 41591 var this$1 = this; 41592 41593 this.markers = markers; 41594 this.primary = primary; 41595 for (var i = 0; i < markers.length; ++i) 41596 { markers[i].parent = this$1; } 41597}; 41598 41599SharedTextMarker.prototype.clear = function () { 41600 var this$1 = this; 41601 41602 if (this.explicitlyCleared) { return } 41603 this.explicitlyCleared = true; 41604 for (var i = 0; i < this.markers.length; ++i) 41605 { this$1.markers[i].clear(); } 41606 signalLater(this, "clear"); 41607}; 41608 41609SharedTextMarker.prototype.find = function (side, lineObj) { 41610 return this.primary.find(side, lineObj) 41611}; 41612eventMixin(SharedTextMarker); 41613 41614function markTextShared(doc, from, to, options, type) { 41615 options = copyObj(options); 41616 options.shared = false; 41617 var markers = [markText(doc, from, to, options, type)], primary = markers[0]; 41618 var widget = options.widgetNode; 41619 linkedDocs(doc, function (doc) { 41620 if (widget) { options.widgetNode = widget.cloneNode(true); } 41621 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); 41622 for (var i = 0; i < doc.linked.length; ++i) 41623 { if (doc.linked[i].isParent) { return } } 41624 primary = lst(markers); 41625 }); 41626 return new SharedTextMarker(markers, primary) 41627} 41628 41629function findSharedMarkers(doc) { 41630 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) 41631} 41632 41633function copySharedMarkers(doc, markers) { 41634 for (var i = 0; i < markers.length; i++) { 41635 var marker = markers[i], pos = marker.find(); 41636 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); 41637 if (cmp(mFrom, mTo)) { 41638 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); 41639 marker.markers.push(subMark); 41640 subMark.parent = marker; 41641 } 41642 } 41643} 41644 41645function detachSharedMarkers(markers) { 41646 var loop = function ( i ) { 41647 var marker = markers[i], linked = [marker.primary.doc]; 41648 linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }); 41649 for (var j = 0; j < marker.markers.length; j++) { 41650 var subMarker = marker.markers[j]; 41651 if (indexOf(linked, subMarker.doc) == -1) { 41652 subMarker.parent = null; 41653 marker.markers.splice(j--, 1); 41654 } 41655 } 41656 }; 41657 41658 for (var i = 0; i < markers.length; i++) loop( i ); 41659} 41660 41661var nextDocId = 0; 41662var Doc = function(text, mode, firstLine, lineSep, direction) { 41663 if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } 41664 if (firstLine == null) { firstLine = 0; } 41665 41666 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); 41667 this.first = firstLine; 41668 this.scrollTop = this.scrollLeft = 0; 41669 this.cantEdit = false; 41670 this.cleanGeneration = 1; 41671 this.modeFrontier = this.highlightFrontier = firstLine; 41672 var start = Pos(firstLine, 0); 41673 this.sel = simpleSelection(start); 41674 this.history = new History(null); 41675 this.id = ++nextDocId; 41676 this.modeOption = mode; 41677 this.lineSep = lineSep; 41678 this.direction = (direction == "rtl") ? "rtl" : "ltr"; 41679 this.extend = false; 41680 41681 if (typeof text == "string") { text = this.splitLines(text); } 41682 updateDoc(this, {from: start, to: start, text: text}); 41683 setSelection(this, simpleSelection(start), sel_dontScroll); 41684}; 41685 41686Doc.prototype = createObj(BranchChunk.prototype, { 41687 constructor: Doc, 41688 // Iterate over the document. Supports two forms -- with only one 41689 // argument, it calls that for each line in the document. With 41690 // three, it iterates over the range given by the first two (with 41691 // the second being non-inclusive). 41692 iter: function(from, to, op) { 41693 if (op) { this.iterN(from - this.first, to - from, op); } 41694 else { this.iterN(this.first, this.first + this.size, from); } 41695 }, 41696 41697 // Non-public interface for adding and removing lines. 41698 insert: function(at, lines) { 41699 var height = 0; 41700 for (var i = 0; i < lines.length; ++i) { height += lines[i].height; } 41701 this.insertInner(at - this.first, lines, height); 41702 }, 41703 remove: function(at, n) { this.removeInner(at - this.first, n); }, 41704 41705 // From here, the methods are part of the public interface. Most 41706 // are also available from CodeMirror (editor) instances. 41707 41708 getValue: function(lineSep) { 41709 var lines = getLines(this, this.first, this.first + this.size); 41710 if (lineSep === false) { return lines } 41711 return lines.join(lineSep || this.lineSeparator()) 41712 }, 41713 setValue: docMethodOp(function(code) { 41714 var top = Pos(this.first, 0), last = this.first + this.size - 1; 41715 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), 41716 text: this.splitLines(code), origin: "setValue", full: true}, true); 41717 if (this.cm) { scrollToCoords(this.cm, 0, 0); } 41718 setSelection(this, simpleSelection(top), sel_dontScroll); 41719 }), 41720 replaceRange: function(code, from, to, origin) { 41721 from = clipPos(this, from); 41722 to = to ? clipPos(this, to) : from; 41723 replaceRange(this, code, from, to, origin); 41724 }, 41725 getRange: function(from, to, lineSep) { 41726 var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); 41727 if (lineSep === false) { return lines } 41728 return lines.join(lineSep || this.lineSeparator()) 41729 }, 41730 41731 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, 41732 41733 getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, 41734 getLineNumber: function(line) {return lineNo(line)}, 41735 41736 getLineHandleVisualStart: function(line) { 41737 if (typeof line == "number") { line = getLine(this, line); } 41738 return visualLine(line) 41739 }, 41740 41741 lineCount: function() {return this.size}, 41742 firstLine: function() {return this.first}, 41743 lastLine: function() {return this.first + this.size - 1}, 41744 41745 clipPos: function(pos) {return clipPos(this, pos)}, 41746 41747 getCursor: function(start) { 41748 var range$$1 = this.sel.primary(), pos; 41749 if (start == null || start == "head") { pos = range$$1.head; } 41750 else if (start == "anchor") { pos = range$$1.anchor; } 41751 else if (start == "end" || start == "to" || start === false) { pos = range$$1.to(); } 41752 else { pos = range$$1.from(); } 41753 return pos 41754 }, 41755 listSelections: function() { return this.sel.ranges }, 41756 somethingSelected: function() {return this.sel.somethingSelected()}, 41757 41758 setCursor: docMethodOp(function(line, ch, options) { 41759 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); 41760 }), 41761 setSelection: docMethodOp(function(anchor, head, options) { 41762 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); 41763 }), 41764 extendSelection: docMethodOp(function(head, other, options) { 41765 extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); 41766 }), 41767 extendSelections: docMethodOp(function(heads, options) { 41768 extendSelections(this, clipPosArray(this, heads), options); 41769 }), 41770 extendSelectionsBy: docMethodOp(function(f, options) { 41771 var heads = map(this.sel.ranges, f); 41772 extendSelections(this, clipPosArray(this, heads), options); 41773 }), 41774 setSelections: docMethodOp(function(ranges, primary, options) { 41775 var this$1 = this; 41776 41777 if (!ranges.length) { return } 41778 var out = []; 41779 for (var i = 0; i < ranges.length; i++) 41780 { out[i] = new Range(clipPos(this$1, ranges[i].anchor), 41781 clipPos(this$1, ranges[i].head)); } 41782 if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); } 41783 setSelection(this, normalizeSelection(out, primary), options); 41784 }), 41785 addSelection: docMethodOp(function(anchor, head, options) { 41786 var ranges = this.sel.ranges.slice(0); 41787 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); 41788 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); 41789 }), 41790 41791 getSelection: function(lineSep) { 41792 var this$1 = this; 41793 41794 var ranges = this.sel.ranges, lines; 41795 for (var i = 0; i < ranges.length; i++) { 41796 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()); 41797 lines = lines ? lines.concat(sel) : sel; 41798 } 41799 if (lineSep === false) { return lines } 41800 else { return lines.join(lineSep || this.lineSeparator()) } 41801 }, 41802 getSelections: function(lineSep) { 41803 var this$1 = this; 41804 41805 var parts = [], ranges = this.sel.ranges; 41806 for (var i = 0; i < ranges.length; i++) { 41807 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()); 41808 if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()); } 41809 parts[i] = sel; 41810 } 41811 return parts 41812 }, 41813 replaceSelection: function(code, collapse, origin) { 41814 var dup = []; 41815 for (var i = 0; i < this.sel.ranges.length; i++) 41816 { dup[i] = code; } 41817 this.replaceSelections(dup, collapse, origin || "+input"); 41818 }, 41819 replaceSelections: docMethodOp(function(code, collapse, origin) { 41820 var this$1 = this; 41821 41822 var changes = [], sel = this.sel; 41823 for (var i = 0; i < sel.ranges.length; i++) { 41824 var range$$1 = sel.ranges[i]; 41825 changes[i] = {from: range$$1.from(), to: range$$1.to(), text: this$1.splitLines(code[i]), origin: origin}; 41826 } 41827 var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); 41828 for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) 41829 { makeChange(this$1, changes[i$1]); } 41830 if (newSel) { setSelectionReplaceHistory(this, newSel); } 41831 else if (this.cm) { ensureCursorVisible(this.cm); } 41832 }), 41833 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), 41834 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), 41835 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), 41836 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), 41837 41838 setExtending: function(val) {this.extend = val;}, 41839 getExtending: function() {return this.extend}, 41840 41841 historySize: function() { 41842 var hist = this.history, done = 0, undone = 0; 41843 for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } } 41844 for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } } 41845 return {undo: done, redo: undone} 41846 }, 41847 clearHistory: function() {this.history = new History(this.history.maxGeneration);}, 41848 41849 markClean: function() { 41850 this.cleanGeneration = this.changeGeneration(true); 41851 }, 41852 changeGeneration: function(forceSplit) { 41853 if (forceSplit) 41854 { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; } 41855 return this.history.generation 41856 }, 41857 isClean: function (gen) { 41858 return this.history.generation == (gen || this.cleanGeneration) 41859 }, 41860 41861 getHistory: function() { 41862 return {done: copyHistoryArray(this.history.done), 41863 undone: copyHistoryArray(this.history.undone)} 41864 }, 41865 setHistory: function(histData) { 41866 var hist = this.history = new History(this.history.maxGeneration); 41867 hist.done = copyHistoryArray(histData.done.slice(0), null, true); 41868 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); 41869 }, 41870 41871 setGutterMarker: docMethodOp(function(line, gutterID, value) { 41872 return changeLine(this, line, "gutter", function (line) { 41873 var markers = line.gutterMarkers || (line.gutterMarkers = {}); 41874 markers[gutterID] = value; 41875 if (!value && isEmpty(markers)) { line.gutterMarkers = null; } 41876 return true 41877 }) 41878 }), 41879 41880 clearGutter: docMethodOp(function(gutterID) { 41881 var this$1 = this; 41882 41883 this.iter(function (line) { 41884 if (line.gutterMarkers && line.gutterMarkers[gutterID]) { 41885 changeLine(this$1, line, "gutter", function () { 41886 line.gutterMarkers[gutterID] = null; 41887 if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; } 41888 return true 41889 }); 41890 } 41891 }); 41892 }), 41893 41894 lineInfo: function(line) { 41895 var n; 41896 if (typeof line == "number") { 41897 if (!isLine(this, line)) { return null } 41898 n = line; 41899 line = getLine(this, line); 41900 if (!line) { return null } 41901 } else { 41902 n = lineNo(line); 41903 if (n == null) { return null } 41904 } 41905 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, 41906 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, 41907 widgets: line.widgets} 41908 }, 41909 41910 addLineClass: docMethodOp(function(handle, where, cls) { 41911 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { 41912 var prop = where == "text" ? "textClass" 41913 : where == "background" ? "bgClass" 41914 : where == "gutter" ? "gutterClass" : "wrapClass"; 41915 if (!line[prop]) { line[prop] = cls; } 41916 else if (classTest(cls).test(line[prop])) { return false } 41917 else { line[prop] += " " + cls; } 41918 return true 41919 }) 41920 }), 41921 removeLineClass: docMethodOp(function(handle, where, cls) { 41922 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { 41923 var prop = where == "text" ? "textClass" 41924 : where == "background" ? "bgClass" 41925 : where == "gutter" ? "gutterClass" : "wrapClass"; 41926 var cur = line[prop]; 41927 if (!cur) { return false } 41928 else if (cls == null) { line[prop] = null; } 41929 else { 41930 var found = cur.match(classTest(cls)); 41931 if (!found) { return false } 41932 var end = found.index + found[0].length; 41933 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; 41934 } 41935 return true 41936 }) 41937 }), 41938 41939 addLineWidget: docMethodOp(function(handle, node, options) { 41940 return addLineWidget(this, handle, node, options) 41941 }), 41942 removeLineWidget: function(widget) { widget.clear(); }, 41943 41944 markText: function(from, to, options) { 41945 return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") 41946 }, 41947 setBookmark: function(pos, options) { 41948 var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), 41949 insertLeft: options && options.insertLeft, 41950 clearWhenEmpty: false, shared: options && options.shared, 41951 handleMouseEvents: options && options.handleMouseEvents}; 41952 pos = clipPos(this, pos); 41953 return markText(this, pos, pos, realOpts, "bookmark") 41954 }, 41955 findMarksAt: function(pos) { 41956 pos = clipPos(this, pos); 41957 var markers = [], spans = getLine(this, pos.line).markedSpans; 41958 if (spans) { for (var i = 0; i < spans.length; ++i) { 41959 var span = spans[i]; 41960 if ((span.from == null || span.from <= pos.ch) && 41961 (span.to == null || span.to >= pos.ch)) 41962 { markers.push(span.marker.parent || span.marker); } 41963 } } 41964 return markers 41965 }, 41966 findMarks: function(from, to, filter) { 41967 from = clipPos(this, from); to = clipPos(this, to); 41968 var found = [], lineNo$$1 = from.line; 41969 this.iter(from.line, to.line + 1, function (line) { 41970 var spans = line.markedSpans; 41971 if (spans) { for (var i = 0; i < spans.length; i++) { 41972 var span = spans[i]; 41973 if (!(span.to != null && lineNo$$1 == from.line && from.ch >= span.to || 41974 span.from == null && lineNo$$1 != from.line || 41975 span.from != null && lineNo$$1 == to.line && span.from >= to.ch) && 41976 (!filter || filter(span.marker))) 41977 { found.push(span.marker.parent || span.marker); } 41978 } } 41979 ++lineNo$$1; 41980 }); 41981 return found 41982 }, 41983 getAllMarks: function() { 41984 var markers = []; 41985 this.iter(function (line) { 41986 var sps = line.markedSpans; 41987 if (sps) { for (var i = 0; i < sps.length; ++i) 41988 { if (sps[i].from != null) { markers.push(sps[i].marker); } } } 41989 }); 41990 return markers 41991 }, 41992 41993 posFromIndex: function(off) { 41994 var ch, lineNo$$1 = this.first, sepSize = this.lineSeparator().length; 41995 this.iter(function (line) { 41996 var sz = line.text.length + sepSize; 41997 if (sz > off) { ch = off; return true } 41998 off -= sz; 41999 ++lineNo$$1; 42000 }); 42001 return clipPos(this, Pos(lineNo$$1, ch)) 42002 }, 42003 indexFromPos: function (coords) { 42004 coords = clipPos(this, coords); 42005 var index = coords.ch; 42006 if (coords.line < this.first || coords.ch < 0) { return 0 } 42007 var sepSize = this.lineSeparator().length; 42008 this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value 42009 index += line.text.length + sepSize; 42010 }); 42011 return index 42012 }, 42013 42014 copy: function(copyHistory) { 42015 var doc = new Doc(getLines(this, this.first, this.first + this.size), 42016 this.modeOption, this.first, this.lineSep, this.direction); 42017 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; 42018 doc.sel = this.sel; 42019 doc.extend = false; 42020 if (copyHistory) { 42021 doc.history.undoDepth = this.history.undoDepth; 42022 doc.setHistory(this.getHistory()); 42023 } 42024 return doc 42025 }, 42026 42027 linkedDoc: function(options) { 42028 if (!options) { options = {}; } 42029 var from = this.first, to = this.first + this.size; 42030 if (options.from != null && options.from > from) { from = options.from; } 42031 if (options.to != null && options.to < to) { to = options.to; } 42032 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction); 42033 if (options.sharedHist) { copy.history = this.history 42034 ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); 42035 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; 42036 copySharedMarkers(copy, findSharedMarkers(this)); 42037 return copy 42038 }, 42039 unlinkDoc: function(other) { 42040 var this$1 = this; 42041 42042 if (other instanceof CodeMirror$1) { other = other.doc; } 42043 if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { 42044 var link = this$1.linked[i]; 42045 if (link.doc != other) { continue } 42046 this$1.linked.splice(i, 1); 42047 other.unlinkDoc(this$1); 42048 detachSharedMarkers(findSharedMarkers(this$1)); 42049 break 42050 } } 42051 // If the histories were shared, split them again 42052 if (other.history == this.history) { 42053 var splitIds = [other.id]; 42054 linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true); 42055 other.history = new History(null); 42056 other.history.done = copyHistoryArray(this.history.done, splitIds); 42057 other.history.undone = copyHistoryArray(this.history.undone, splitIds); 42058 } 42059 }, 42060 iterLinkedDocs: function(f) {linkedDocs(this, f);}, 42061 42062 getMode: function() {return this.mode}, 42063 getEditor: function() {return this.cm}, 42064 42065 splitLines: function(str) { 42066 if (this.lineSep) { return str.split(this.lineSep) } 42067 return splitLinesAuto(str) 42068 }, 42069 lineSeparator: function() { return this.lineSep || "\n" }, 42070 42071 setDirection: docMethodOp(function (dir) { 42072 if (dir != "rtl") { dir = "ltr"; } 42073 if (dir == this.direction) { return } 42074 this.direction = dir; 42075 this.iter(function (line) { return line.order = null; }); 42076 if (this.cm) { directionChanged(this.cm); } 42077 }) 42078}); 42079 42080// Public alias. 42081Doc.prototype.eachLine = Doc.prototype.iter; 42082 42083// Kludge to work around strange IE behavior where it'll sometimes 42084// re-fire a series of drag-related events right after the drop (#1551) 42085var lastDrop = 0; 42086 42087function onDrop(e) { 42088 var cm = this; 42089 clearDragCursor(cm); 42090 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) 42091 { return } 42092 e_preventDefault(e); 42093 if (ie) { lastDrop = +new Date; } 42094 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; 42095 if (!pos || cm.isReadOnly()) { return } 42096 // Might be a file drop, in which case we simply extract the text 42097 // and insert it. 42098 if (files && files.length && window.FileReader && window.File) { 42099 var n = files.length, text = Array(n), read = 0; 42100 var loadFile = function (file, i) { 42101 if (cm.options.allowDropFileTypes && 42102 indexOf(cm.options.allowDropFileTypes, file.type) == -1) 42103 { return } 42104 42105 var reader = new FileReader; 42106 reader.onload = operation(cm, function () { 42107 var content = reader.result; 42108 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = ""; } 42109 text[i] = content; 42110 if (++read == n) { 42111 pos = clipPos(cm.doc, pos); 42112 var change = {from: pos, to: pos, 42113 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), 42114 origin: "paste"}; 42115 makeChange(cm.doc, change); 42116 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); 42117 } 42118 }); 42119 reader.readAsText(file); 42120 }; 42121 for (var i = 0; i < n; ++i) { loadFile(files[i], i); } 42122 } else { // Normal drop 42123 // Don't do a replace if the drop happened inside of the selected text. 42124 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { 42125 cm.state.draggingText(e); 42126 // Ensure the editor is re-focused 42127 setTimeout(function () { return cm.display.input.focus(); }, 20); 42128 return 42129 } 42130 try { 42131 var text$1 = e.dataTransfer.getData("Text"); 42132 if (text$1) { 42133 var selected; 42134 if (cm.state.draggingText && !cm.state.draggingText.copy) 42135 { selected = cm.listSelections(); } 42136 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); 42137 if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) 42138 { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag"); } } 42139 cm.replaceSelection(text$1, "around", "paste"); 42140 cm.display.input.focus(); 42141 } 42142 } 42143 catch(e){} 42144 } 42145} 42146 42147function onDragStart(cm, e) { 42148 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } 42149 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } 42150 42151 e.dataTransfer.setData("Text", cm.getSelection()); 42152 e.dataTransfer.effectAllowed = "copyMove"; 42153 42154 // Use dummy image instead of default browsers image. 42155 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. 42156 if (e.dataTransfer.setDragImage && !safari) { 42157 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); 42158 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; 42159 if (presto) { 42160 img.width = img.height = 1; 42161 cm.display.wrapper.appendChild(img); 42162 // Force a relayout, or Opera won't use our image for some obscure reason 42163 img._top = img.offsetTop; 42164 } 42165 e.dataTransfer.setDragImage(img, 0, 0); 42166 if (presto) { img.parentNode.removeChild(img); } 42167 } 42168} 42169 42170function onDragOver(cm, e) { 42171 var pos = posFromMouse(cm, e); 42172 if (!pos) { return } 42173 var frag = document.createDocumentFragment(); 42174 drawSelectionCursor(cm, pos, frag); 42175 if (!cm.display.dragCursor) { 42176 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); 42177 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); 42178 } 42179 removeChildrenAndAdd(cm.display.dragCursor, frag); 42180} 42181 42182function clearDragCursor(cm) { 42183 if (cm.display.dragCursor) { 42184 cm.display.lineSpace.removeChild(cm.display.dragCursor); 42185 cm.display.dragCursor = null; 42186 } 42187} 42188 42189// These must be handled carefully, because naively registering a 42190// handler for each editor will cause the editors to never be 42191// garbage collected. 42192 42193function forEachCodeMirror(f) { 42194 if (!document.getElementsByClassName) { return } 42195 var byClass = document.getElementsByClassName("CodeMirror"); 42196 for (var i = 0; i < byClass.length; i++) { 42197 var cm = byClass[i].CodeMirror; 42198 if (cm) { f(cm); } 42199 } 42200} 42201 42202var globalsRegistered = false; 42203function ensureGlobalHandlers() { 42204 if (globalsRegistered) { return } 42205 registerGlobalHandlers(); 42206 globalsRegistered = true; 42207} 42208function registerGlobalHandlers() { 42209 // When the window resizes, we need to refresh active editors. 42210 var resizeTimer; 42211 on(window, "resize", function () { 42212 if (resizeTimer == null) { resizeTimer = setTimeout(function () { 42213 resizeTimer = null; 42214 forEachCodeMirror(onResize); 42215 }, 100); } 42216 }); 42217 // When the window loses focus, we want to show the editor as blurred 42218 on(window, "blur", function () { return forEachCodeMirror(onBlur); }); 42219} 42220// Called when the window resizes 42221function onResize(cm) { 42222 var d = cm.display; 42223 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) 42224 { return } 42225 // Might be a text scaling operation, clear size caches. 42226 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; 42227 d.scrollbarsClipped = false; 42228 cm.setSize(); 42229} 42230 42231var keyNames = { 42232 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 42233 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 42234 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 42235 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 42236 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", 145: "ScrollLock", 42237 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 42238 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", 42239 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" 42240}; 42241 42242// Number keys 42243for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); } 42244// Alphabetic keys 42245for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); } 42246// Function keys 42247for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2; } 42248 42249var keyMap = {}; 42250 42251keyMap.basic = { 42252 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", 42253 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", 42254 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", 42255 "Tab": "defaultTab", "Shift-Tab": "indentAuto", 42256 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", 42257 "Esc": "singleSelection" 42258}; 42259// Note that the save and find-related commands aren't defined by 42260// default. User code or addons can define them. Unknown commands 42261// are simply ignored. 42262keyMap.pcDefault = { 42263 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", 42264 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", 42265 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", 42266 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", 42267 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", 42268 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", 42269 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", 42270 fallthrough: "basic" 42271}; 42272// Very basic readline/emacs-style bindings, which are standard on Mac. 42273keyMap.emacsy = { 42274 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", 42275 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", 42276 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", 42277 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", 42278 "Ctrl-O": "openLine" 42279}; 42280keyMap.macDefault = { 42281 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", 42282 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", 42283 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", 42284 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", 42285 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", 42286 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", 42287 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", 42288 fallthrough: ["basic", "emacsy"] 42289}; 42290keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; 42291 42292// KEYMAP DISPATCH 42293 42294function normalizeKeyName(name) { 42295 var parts = name.split(/-(?!$)/); 42296 name = parts[parts.length - 1]; 42297 var alt, ctrl, shift, cmd; 42298 for (var i = 0; i < parts.length - 1; i++) { 42299 var mod = parts[i]; 42300 if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; } 42301 else if (/^a(lt)?$/i.test(mod)) { alt = true; } 42302 else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; } 42303 else if (/^s(hift)?$/i.test(mod)) { shift = true; } 42304 else { throw new Error("Unrecognized modifier name: " + mod) } 42305 } 42306 if (alt) { name = "Alt-" + name; } 42307 if (ctrl) { name = "Ctrl-" + name; } 42308 if (cmd) { name = "Cmd-" + name; } 42309 if (shift) { name = "Shift-" + name; } 42310 return name 42311} 42312 42313// This is a kludge to keep keymaps mostly working as raw objects 42314// (backwards compatibility) while at the same time support features 42315// like normalization and multi-stroke key bindings. It compiles a 42316// new normalized keymap, and then updates the old object to reflect 42317// this. 42318function normalizeKeyMap(keymap) { 42319 var copy = {}; 42320 for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { 42321 var value = keymap[keyname]; 42322 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } 42323 if (value == "...") { delete keymap[keyname]; continue } 42324 42325 var keys = map(keyname.split(" "), normalizeKeyName); 42326 for (var i = 0; i < keys.length; i++) { 42327 var val = (void 0), name = (void 0); 42328 if (i == keys.length - 1) { 42329 name = keys.join(" "); 42330 val = value; 42331 } else { 42332 name = keys.slice(0, i + 1).join(" "); 42333 val = "..."; 42334 } 42335 var prev = copy[name]; 42336 if (!prev) { copy[name] = val; } 42337 else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } 42338 } 42339 delete keymap[keyname]; 42340 } } 42341 for (var prop in copy) { keymap[prop] = copy[prop]; } 42342 return keymap 42343} 42344 42345function lookupKey(key, map$$1, handle, context) { 42346 map$$1 = getKeyMap(map$$1); 42347 var found = map$$1.call ? map$$1.call(key, context) : map$$1[key]; 42348 if (found === false) { return "nothing" } 42349 if (found === "...") { return "multi" } 42350 if (found != null && handle(found)) { return "handled" } 42351 42352 if (map$$1.fallthrough) { 42353 if (Object.prototype.toString.call(map$$1.fallthrough) != "[object Array]") 42354 { return lookupKey(key, map$$1.fallthrough, handle, context) } 42355 for (var i = 0; i < map$$1.fallthrough.length; i++) { 42356 var result = lookupKey(key, map$$1.fallthrough[i], handle, context); 42357 if (result) { return result } 42358 } 42359 } 42360} 42361 42362// Modifier key presses don't count as 'real' key presses for the 42363// purpose of keymap fallthrough. 42364function isModifierKey(value) { 42365 var name = typeof value == "string" ? value : keyNames[value.keyCode]; 42366 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" 42367} 42368 42369function addModifierNames(name, event, noShift) { 42370 var base = name; 42371 if (event.altKey && base != "Alt") { name = "Alt-" + name; } 42372 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name; } 42373 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name; } 42374 if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name; } 42375 return name 42376} 42377 42378// Look up the name of a key as indicated by an event object. 42379function keyName(event, noShift) { 42380 if (presto && event.keyCode == 34 && event["char"]) { return false } 42381 var name = keyNames[event.keyCode]; 42382 if (name == null || event.altGraphKey) { return false } 42383 // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, 42384 // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) 42385 if (event.keyCode == 3 && event.code) { name = event.code; } 42386 return addModifierNames(name, event, noShift) 42387} 42388 42389function getKeyMap(val) { 42390 return typeof val == "string" ? keyMap[val] : val 42391} 42392 42393// Helper for deleting text near the selection(s), used to implement 42394// backspace, delete, and similar functionality. 42395function deleteNearSelection(cm, compute) { 42396 var ranges = cm.doc.sel.ranges, kill = []; 42397 // Build up a set of ranges to kill first, merging overlapping 42398 // ranges. 42399 for (var i = 0; i < ranges.length; i++) { 42400 var toKill = compute(ranges[i]); 42401 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { 42402 var replaced = kill.pop(); 42403 if (cmp(replaced.from, toKill.from) < 0) { 42404 toKill.from = replaced.from; 42405 break 42406 } 42407 } 42408 kill.push(toKill); 42409 } 42410 // Next, remove those actual ranges. 42411 runInOp(cm, function () { 42412 for (var i = kill.length - 1; i >= 0; i--) 42413 { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); } 42414 ensureCursorVisible(cm); 42415 }); 42416} 42417 42418function moveCharLogically(line, ch, dir) { 42419 var target = skipExtendingChars(line.text, ch + dir, dir); 42420 return target < 0 || target > line.text.length ? null : target 42421} 42422 42423function moveLogically(line, start, dir) { 42424 var ch = moveCharLogically(line, start.ch, dir); 42425 return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") 42426} 42427 42428function endOfLine(visually, cm, lineObj, lineNo, dir) { 42429 if (visually) { 42430 var order = getOrder(lineObj, cm.doc.direction); 42431 if (order) { 42432 var part = dir < 0 ? lst(order) : order[0]; 42433 var moveInStorageOrder = (dir < 0) == (part.level == 1); 42434 var sticky = moveInStorageOrder ? "after" : "before"; 42435 var ch; 42436 // With a wrapped rtl chunk (possibly spanning multiple bidi parts), 42437 // it could be that the last bidi part is not on the last visual line, 42438 // since visual lines contain content order-consecutive chunks. 42439 // Thus, in rtl, we are looking for the first (content-order) character 42440 // in the rtl chunk that is on the last line (that is, the same line 42441 // as the last (content-order) character). 42442 if (part.level > 0 || cm.doc.direction == "rtl") { 42443 var prep = prepareMeasureForLine(cm, lineObj); 42444 ch = dir < 0 ? lineObj.text.length - 1 : 0; 42445 var targetTop = measureCharPrepared(cm, prep, ch).top; 42446 ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch); 42447 if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1); } 42448 } else { ch = dir < 0 ? part.to : part.from; } 42449 return new Pos(lineNo, ch, sticky) 42450 } 42451 } 42452 return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") 42453} 42454 42455function moveVisually(cm, line, start, dir) { 42456 var bidi = getOrder(line, cm.doc.direction); 42457 if (!bidi) { return moveLogically(line, start, dir) } 42458 if (start.ch >= line.text.length) { 42459 start.ch = line.text.length; 42460 start.sticky = "before"; 42461 } else if (start.ch <= 0) { 42462 start.ch = 0; 42463 start.sticky = "after"; 42464 } 42465 var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos]; 42466 if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { 42467 // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, 42468 // nothing interesting happens. 42469 return moveLogically(line, start, dir) 42470 } 42471 42472 var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); }; 42473 var prep; 42474 var getWrappedLineExtent = function (ch) { 42475 if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } 42476 prep = prep || prepareMeasureForLine(cm, line); 42477 return wrappedLineExtentChar(cm, line, prep, ch) 42478 }; 42479 var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch); 42480 42481 if (cm.doc.direction == "rtl" || part.level == 1) { 42482 var moveInStorageOrder = (part.level == 1) == (dir < 0); 42483 var ch = mv(start, moveInStorageOrder ? 1 : -1); 42484 if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { 42485 // Case 2: We move within an rtl part or in an rtl editor on the same visual line 42486 var sticky = moveInStorageOrder ? "before" : "after"; 42487 return new Pos(start.line, ch, sticky) 42488 } 42489 } 42490 42491 // Case 3: Could not move within this bidi part in this visual line, so leave 42492 // the current bidi part 42493 42494 var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { 42495 var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder 42496 ? new Pos(start.line, mv(ch, 1), "before") 42497 : new Pos(start.line, ch, "after"); }; 42498 42499 for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { 42500 var part = bidi[partPos]; 42501 var moveInStorageOrder = (dir > 0) == (part.level != 1); 42502 var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1); 42503 if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } 42504 ch = moveInStorageOrder ? part.from : mv(part.to, -1); 42505 if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } 42506 } 42507 }; 42508 42509 // Case 3a: Look for other bidi parts on the same visual line 42510 var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent); 42511 if (res) { return res } 42512 42513 // Case 3b: Look for other bidi parts on the next visual line 42514 var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1); 42515 if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { 42516 res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)); 42517 if (res) { return res } 42518 } 42519 42520 // Case 4: Nowhere to move 42521 return null 42522} 42523 42524// Commands are parameter-less actions that can be performed on an 42525// editor, mostly used for keybindings. 42526var commands = { 42527 selectAll: selectAll, 42528 singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, 42529 killLine: function (cm) { return deleteNearSelection(cm, function (range) { 42530 if (range.empty()) { 42531 var len = getLine(cm.doc, range.head.line).text.length; 42532 if (range.head.ch == len && range.head.line < cm.lastLine()) 42533 { return {from: range.head, to: Pos(range.head.line + 1, 0)} } 42534 else 42535 { return {from: range.head, to: Pos(range.head.line, len)} } 42536 } else { 42537 return {from: range.from(), to: range.to()} 42538 } 42539 }); }, 42540 deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ 42541 from: Pos(range.from().line, 0), 42542 to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) 42543 }); }); }, 42544 delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ 42545 from: Pos(range.from().line, 0), to: range.from() 42546 }); }); }, 42547 delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { 42548 var top = cm.charCoords(range.head, "div").top + 5; 42549 var leftPos = cm.coordsChar({left: 0, top: top}, "div"); 42550 return {from: leftPos, to: range.from()} 42551 }); }, 42552 delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { 42553 var top = cm.charCoords(range.head, "div").top + 5; 42554 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); 42555 return {from: range.from(), to: rightPos } 42556 }); }, 42557 undo: function (cm) { return cm.undo(); }, 42558 redo: function (cm) { return cm.redo(); }, 42559 undoSelection: function (cm) { return cm.undoSelection(); }, 42560 redoSelection: function (cm) { return cm.redoSelection(); }, 42561 goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, 42562 goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, 42563 goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, 42564 {origin: "+move", bias: 1} 42565 ); }, 42566 goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, 42567 {origin: "+move", bias: 1} 42568 ); }, 42569 goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, 42570 {origin: "+move", bias: -1} 42571 ); }, 42572 goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { 42573 var top = cm.cursorCoords(range.head, "div").top + 5; 42574 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") 42575 }, sel_move); }, 42576 goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { 42577 var top = cm.cursorCoords(range.head, "div").top + 5; 42578 return cm.coordsChar({left: 0, top: top}, "div") 42579 }, sel_move); }, 42580 goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { 42581 var top = cm.cursorCoords(range.head, "div").top + 5; 42582 var pos = cm.coordsChar({left: 0, top: top}, "div"); 42583 if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } 42584 return pos 42585 }, sel_move); }, 42586 goLineUp: function (cm) { return cm.moveV(-1, "line"); }, 42587 goLineDown: function (cm) { return cm.moveV(1, "line"); }, 42588 goPageUp: function (cm) { return cm.moveV(-1, "page"); }, 42589 goPageDown: function (cm) { return cm.moveV(1, "page"); }, 42590 goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, 42591 goCharRight: function (cm) { return cm.moveH(1, "char"); }, 42592 goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, 42593 goColumnRight: function (cm) { return cm.moveH(1, "column"); }, 42594 goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, 42595 goGroupRight: function (cm) { return cm.moveH(1, "group"); }, 42596 goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, 42597 goWordRight: function (cm) { return cm.moveH(1, "word"); }, 42598 delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, 42599 delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, 42600 delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, 42601 delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, 42602 delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, 42603 delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, 42604 indentAuto: function (cm) { return cm.indentSelection("smart"); }, 42605 indentMore: function (cm) { return cm.indentSelection("add"); }, 42606 indentLess: function (cm) { return cm.indentSelection("subtract"); }, 42607 insertTab: function (cm) { return cm.replaceSelection("\t"); }, 42608 insertSoftTab: function (cm) { 42609 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; 42610 for (var i = 0; i < ranges.length; i++) { 42611 var pos = ranges[i].from(); 42612 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); 42613 spaces.push(spaceStr(tabSize - col % tabSize)); 42614 } 42615 cm.replaceSelections(spaces); 42616 }, 42617 defaultTab: function (cm) { 42618 if (cm.somethingSelected()) { cm.indentSelection("add"); } 42619 else { cm.execCommand("insertTab"); } 42620 }, 42621 // Swap the two chars left and right of each selection's head. 42622 // Move cursor behind the two swapped characters afterwards. 42623 // 42624 // Doesn't consider line feeds a character. 42625 // Doesn't scan more than one line above to find a character. 42626 // Doesn't do anything on an empty line. 42627 // Doesn't do anything with non-empty selections. 42628 transposeChars: function (cm) { return runInOp(cm, function () { 42629 var ranges = cm.listSelections(), newSel = []; 42630 for (var i = 0; i < ranges.length; i++) { 42631 if (!ranges[i].empty()) { continue } 42632 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; 42633 if (line) { 42634 if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); } 42635 if (cur.ch > 0) { 42636 cur = new Pos(cur.line, cur.ch + 1); 42637 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), 42638 Pos(cur.line, cur.ch - 2), cur, "+transpose"); 42639 } else if (cur.line > cm.doc.first) { 42640 var prev = getLine(cm.doc, cur.line - 1).text; 42641 if (prev) { 42642 cur = new Pos(cur.line, 1); 42643 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + 42644 prev.charAt(prev.length - 1), 42645 Pos(cur.line - 1, prev.length - 1), cur, "+transpose"); 42646 } 42647 } 42648 } 42649 newSel.push(new Range(cur, cur)); 42650 } 42651 cm.setSelections(newSel); 42652 }); }, 42653 newlineAndIndent: function (cm) { return runInOp(cm, function () { 42654 var sels = cm.listSelections(); 42655 for (var i = sels.length - 1; i >= 0; i--) 42656 { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input"); } 42657 sels = cm.listSelections(); 42658 for (var i$1 = 0; i$1 < sels.length; i$1++) 42659 { cm.indentLine(sels[i$1].from().line, null, true); } 42660 ensureCursorVisible(cm); 42661 }); }, 42662 openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, 42663 toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } 42664}; 42665 42666 42667function lineStart(cm, lineN) { 42668 var line = getLine(cm.doc, lineN); 42669 var visual = visualLine(line); 42670 if (visual != line) { lineN = lineNo(visual); } 42671 return endOfLine(true, cm, visual, lineN, 1) 42672} 42673function lineEnd(cm, lineN) { 42674 var line = getLine(cm.doc, lineN); 42675 var visual = visualLineEnd(line); 42676 if (visual != line) { lineN = lineNo(visual); } 42677 return endOfLine(true, cm, line, lineN, -1) 42678} 42679function lineStartSmart(cm, pos) { 42680 var start = lineStart(cm, pos.line); 42681 var line = getLine(cm.doc, start.line); 42682 var order = getOrder(line, cm.doc.direction); 42683 if (!order || order[0].level == 0) { 42684 var firstNonWS = Math.max(0, line.text.search(/\S/)); 42685 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; 42686 return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) 42687 } 42688 return start 42689} 42690 42691// Run a handler that was bound to a key. 42692function doHandleBinding(cm, bound, dropShift) { 42693 if (typeof bound == "string") { 42694 bound = commands[bound]; 42695 if (!bound) { return false } 42696 } 42697 // Ensure previous input has been read, so that the handler sees a 42698 // consistent view of the document 42699 cm.display.input.ensurePolled(); 42700 var prevShift = cm.display.shift, done = false; 42701 try { 42702 if (cm.isReadOnly()) { cm.state.suppressEdits = true; } 42703 if (dropShift) { cm.display.shift = false; } 42704 done = bound(cm) != Pass; 42705 } finally { 42706 cm.display.shift = prevShift; 42707 cm.state.suppressEdits = false; 42708 } 42709 return done 42710} 42711 42712function lookupKeyForEditor(cm, name, handle) { 42713 for (var i = 0; i < cm.state.keyMaps.length; i++) { 42714 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); 42715 if (result) { return result } 42716 } 42717 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) 42718 || lookupKey(name, cm.options.keyMap, handle, cm) 42719} 42720 42721// Note that, despite the name, this function is also used to check 42722// for bound mouse clicks. 42723 42724var stopSeq = new Delayed; 42725 42726function dispatchKey(cm, name, e, handle) { 42727 var seq = cm.state.keySeq; 42728 if (seq) { 42729 if (isModifierKey(name)) { return "handled" } 42730 if (/\'$/.test(name)) 42731 { cm.state.keySeq = null; } 42732 else 42733 { stopSeq.set(50, function () { 42734 if (cm.state.keySeq == seq) { 42735 cm.state.keySeq = null; 42736 cm.display.input.reset(); 42737 } 42738 }); } 42739 if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } 42740 } 42741 return dispatchKeyInner(cm, name, e, handle) 42742} 42743 42744function dispatchKeyInner(cm, name, e, handle) { 42745 var result = lookupKeyForEditor(cm, name, handle); 42746 42747 if (result == "multi") 42748 { cm.state.keySeq = name; } 42749 if (result == "handled") 42750 { signalLater(cm, "keyHandled", cm, name, e); } 42751 42752 if (result == "handled" || result == "multi") { 42753 e_preventDefault(e); 42754 restartBlink(cm); 42755 } 42756 42757 return !!result 42758} 42759 42760// Handle a key from the keydown event. 42761function handleKeyBinding(cm, e) { 42762 var name = keyName(e, true); 42763 if (!name) { return false } 42764 42765 if (e.shiftKey && !cm.state.keySeq) { 42766 // First try to resolve full name (including 'Shift-'). Failing 42767 // that, see if there is a cursor-motion command (starting with 42768 // 'go') bound to the keyname without 'Shift-'. 42769 return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) 42770 || dispatchKey(cm, name, e, function (b) { 42771 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) 42772 { return doHandleBinding(cm, b) } 42773 }) 42774 } else { 42775 return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) 42776 } 42777} 42778 42779// Handle a key from the keypress event 42780function handleCharBinding(cm, e, ch) { 42781 return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) 42782} 42783 42784var lastStoppedKey = null; 42785function onKeyDown(e) { 42786 var cm = this; 42787 cm.curOp.focus = activeElt(); 42788 if (signalDOMEvent(cm, e)) { return } 42789 // IE does strange things with escape. 42790 if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; } 42791 var code = e.keyCode; 42792 cm.display.shift = code == 16 || e.shiftKey; 42793 var handled = handleKeyBinding(cm, e); 42794 if (presto) { 42795 lastStoppedKey = handled ? code : null; 42796 // Opera has no cut event... we try to at least catch the key combo 42797 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) 42798 { cm.replaceSelection("", null, "cut"); } 42799 } 42800 42801 // Turn mouse into crosshair when Alt is held on Mac. 42802 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) 42803 { showCrossHair(cm); } 42804} 42805 42806function showCrossHair(cm) { 42807 var lineDiv = cm.display.lineDiv; 42808 addClass(lineDiv, "CodeMirror-crosshair"); 42809 42810 function up(e) { 42811 if (e.keyCode == 18 || !e.altKey) { 42812 rmClass(lineDiv, "CodeMirror-crosshair"); 42813 off(document, "keyup", up); 42814 off(document, "mouseover", up); 42815 } 42816 } 42817 on(document, "keyup", up); 42818 on(document, "mouseover", up); 42819} 42820 42821function onKeyUp(e) { 42822 if (e.keyCode == 16) { this.doc.sel.shift = false; } 42823 signalDOMEvent(this, e); 42824} 42825 42826function onKeyPress(e) { 42827 var cm = this; 42828 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } 42829 var keyCode = e.keyCode, charCode = e.charCode; 42830 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} 42831 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } 42832 var ch = String.fromCharCode(charCode == null ? keyCode : charCode); 42833 // Some browsers fire keypress events for backspace 42834 if (ch == "\x08") { return } 42835 if (handleCharBinding(cm, e, ch)) { return } 42836 cm.display.input.onKeyPress(e); 42837} 42838 42839var DOUBLECLICK_DELAY = 400; 42840 42841var PastClick = function(time, pos, button) { 42842 this.time = time; 42843 this.pos = pos; 42844 this.button = button; 42845}; 42846 42847PastClick.prototype.compare = function (time, pos, button) { 42848 return this.time + DOUBLECLICK_DELAY > time && 42849 cmp(pos, this.pos) == 0 && button == this.button 42850}; 42851 42852var lastClick; 42853var lastDoubleClick; 42854function clickRepeat(pos, button) { 42855 var now = +new Date; 42856 if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { 42857 lastClick = lastDoubleClick = null; 42858 return "triple" 42859 } else if (lastClick && lastClick.compare(now, pos, button)) { 42860 lastDoubleClick = new PastClick(now, pos, button); 42861 lastClick = null; 42862 return "double" 42863 } else { 42864 lastClick = new PastClick(now, pos, button); 42865 lastDoubleClick = null; 42866 return "single" 42867 } 42868} 42869 42870// A mouse down can be a single click, double click, triple click, 42871// start of selection drag, start of text drag, new cursor 42872// (ctrl-click), rectangle drag (alt-drag), or xwin 42873// middle-click-paste. Or it might be a click on something we should 42874// not interfere with, such as a scrollbar or widget. 42875function onMouseDown(e) { 42876 var cm = this, display = cm.display; 42877 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } 42878 display.input.ensurePolled(); 42879 display.shift = e.shiftKey; 42880 42881 if (eventInWidget(display, e)) { 42882 if (!webkit) { 42883 // Briefly turn off draggability, to allow widgets to do 42884 // normal dragging things. 42885 display.scroller.draggable = false; 42886 setTimeout(function () { return display.scroller.draggable = true; }, 100); 42887 } 42888 return 42889 } 42890 if (clickInGutter(cm, e)) { return } 42891 var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single"; 42892 window.focus(); 42893 42894 // #3261: make sure, that we're not starting a second selection 42895 if (button == 1 && cm.state.selectingText) 42896 { cm.state.selectingText(e); } 42897 42898 if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } 42899 42900 if (button == 1) { 42901 if (pos) { leftButtonDown(cm, pos, repeat, e); } 42902 else if (e_target(e) == display.scroller) { e_preventDefault(e); } 42903 } else if (button == 2) { 42904 if (pos) { extendSelection(cm.doc, pos); } 42905 setTimeout(function () { return display.input.focus(); }, 20); 42906 } else if (button == 3) { 42907 if (captureRightClick) { onContextMenu(cm, e); } 42908 else { delayBlurEvent(cm); } 42909 } 42910} 42911 42912function handleMappedButton(cm, button, pos, repeat, event) { 42913 var name = "Click"; 42914 if (repeat == "double") { name = "Double" + name; } 42915 else if (repeat == "triple") { name = "Triple" + name; } 42916 name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name; 42917 42918 return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { 42919 if (typeof bound == "string") { bound = commands[bound]; } 42920 if (!bound) { return false } 42921 var done = false; 42922 try { 42923 if (cm.isReadOnly()) { cm.state.suppressEdits = true; } 42924 done = bound(cm, pos) != Pass; 42925 } finally { 42926 cm.state.suppressEdits = false; 42927 } 42928 return done 42929 }) 42930} 42931 42932function configureMouse(cm, repeat, event) { 42933 var option = cm.getOption("configureMouse"); 42934 var value = option ? option(cm, repeat, event) : {}; 42935 if (value.unit == null) { 42936 var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey; 42937 value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line"; 42938 } 42939 if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; } 42940 if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; } 42941 if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); } 42942 return value 42943} 42944 42945function leftButtonDown(cm, pos, repeat, event) { 42946 if (ie) { setTimeout(bind(ensureFocus, cm), 0); } 42947 else { cm.curOp.focus = activeElt(); } 42948 42949 var behavior = configureMouse(cm, repeat, event); 42950 42951 var sel = cm.doc.sel, contained; 42952 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && 42953 repeat == "single" && (contained = sel.contains(pos)) > -1 && 42954 (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && 42955 (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) 42956 { leftButtonStartDrag(cm, event, pos, behavior); } 42957 else 42958 { leftButtonSelect(cm, event, pos, behavior); } 42959} 42960 42961// Start a text drag. When it ends, see if any dragging actually 42962// happen, and treat as a click if it didn't. 42963function leftButtonStartDrag(cm, event, pos, behavior) { 42964 var display = cm.display, moved = false; 42965 var dragEnd = operation(cm, function (e) { 42966 if (webkit) { display.scroller.draggable = false; } 42967 cm.state.draggingText = false; 42968 off(display.wrapper.ownerDocument, "mouseup", dragEnd); 42969 off(display.wrapper.ownerDocument, "mousemove", mouseMove); 42970 off(display.scroller, "dragstart", dragStart); 42971 off(display.scroller, "drop", dragEnd); 42972 if (!moved) { 42973 e_preventDefault(e); 42974 if (!behavior.addNew) 42975 { extendSelection(cm.doc, pos, null, null, behavior.extend); } 42976 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) 42977 if (webkit || ie && ie_version == 9) 42978 { setTimeout(function () {display.wrapper.ownerDocument.body.focus(); display.input.focus();}, 20); } 42979 else 42980 { display.input.focus(); } 42981 } 42982 }); 42983 var mouseMove = function(e2) { 42984 moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10; 42985 }; 42986 var dragStart = function () { return moved = true; }; 42987 // Let the drag handler handle this. 42988 if (webkit) { display.scroller.draggable = true; } 42989 cm.state.draggingText = dragEnd; 42990 dragEnd.copy = !behavior.moveOnDrag; 42991 // IE's approach to draggable 42992 if (display.scroller.dragDrop) { display.scroller.dragDrop(); } 42993 on(display.wrapper.ownerDocument, "mouseup", dragEnd); 42994 on(display.wrapper.ownerDocument, "mousemove", mouseMove); 42995 on(display.scroller, "dragstart", dragStart); 42996 on(display.scroller, "drop", dragEnd); 42997 42998 delayBlurEvent(cm); 42999 setTimeout(function () { return display.input.focus(); }, 20); 43000} 43001 43002function rangeForUnit(cm, pos, unit) { 43003 if (unit == "char") { return new Range(pos, pos) } 43004 if (unit == "word") { return cm.findWordAt(pos) } 43005 if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } 43006 var result = unit(cm, pos); 43007 return new Range(result.from, result.to) 43008} 43009 43010// Normal selection, as opposed to text dragging. 43011function leftButtonSelect(cm, event, start, behavior) { 43012 var display = cm.display, doc = cm.doc; 43013 e_preventDefault(event); 43014 43015 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; 43016 if (behavior.addNew && !behavior.extend) { 43017 ourIndex = doc.sel.contains(start); 43018 if (ourIndex > -1) 43019 { ourRange = ranges[ourIndex]; } 43020 else 43021 { ourRange = new Range(start, start); } 43022 } else { 43023 ourRange = doc.sel.primary(); 43024 ourIndex = doc.sel.primIndex; 43025 } 43026 43027 if (behavior.unit == "rectangle") { 43028 if (!behavior.addNew) { ourRange = new Range(start, start); } 43029 start = posFromMouse(cm, event, true, true); 43030 ourIndex = -1; 43031 } else { 43032 var range$$1 = rangeForUnit(cm, start, behavior.unit); 43033 if (behavior.extend) 43034 { ourRange = extendRange(ourRange, range$$1.anchor, range$$1.head, behavior.extend); } 43035 else 43036 { ourRange = range$$1; } 43037 } 43038 43039 if (!behavior.addNew) { 43040 ourIndex = 0; 43041 setSelection(doc, new Selection([ourRange], 0), sel_mouse); 43042 startSel = doc.sel; 43043 } else if (ourIndex == -1) { 43044 ourIndex = ranges.length; 43045 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), 43046 {scroll: false, origin: "*mouse"}); 43047 } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { 43048 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), 43049 {scroll: false, origin: "*mouse"}); 43050 startSel = doc.sel; 43051 } else { 43052 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); 43053 } 43054 43055 var lastPos = start; 43056 function extendTo(pos) { 43057 if (cmp(lastPos, pos) == 0) { return } 43058 lastPos = pos; 43059 43060 if (behavior.unit == "rectangle") { 43061 var ranges = [], tabSize = cm.options.tabSize; 43062 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); 43063 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); 43064 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); 43065 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); 43066 line <= end; line++) { 43067 var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); 43068 if (left == right) 43069 { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); } 43070 else if (text.length > leftPos) 43071 { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); } 43072 } 43073 if (!ranges.length) { ranges.push(new Range(start, start)); } 43074 setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), 43075 {origin: "*mouse", scroll: false}); 43076 cm.scrollIntoView(pos); 43077 } else { 43078 var oldRange = ourRange; 43079 var range$$1 = rangeForUnit(cm, pos, behavior.unit); 43080 var anchor = oldRange.anchor, head; 43081 if (cmp(range$$1.anchor, anchor) > 0) { 43082 head = range$$1.head; 43083 anchor = minPos(oldRange.from(), range$$1.anchor); 43084 } else { 43085 head = range$$1.anchor; 43086 anchor = maxPos(oldRange.to(), range$$1.head); 43087 } 43088 var ranges$1 = startSel.ranges.slice(0); 43089 ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)); 43090 setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse); 43091 } 43092 } 43093 43094 var editorSize = display.wrapper.getBoundingClientRect(); 43095 // Used to ensure timeout re-tries don't fire when another extend 43096 // happened in the meantime (clearTimeout isn't reliable -- at 43097 // least on Chrome, the timeouts still happen even when cleared, 43098 // if the clear happens after their scheduled firing time). 43099 var counter = 0; 43100 43101 function extend(e) { 43102 var curCount = ++counter; 43103 var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle"); 43104 if (!cur) { return } 43105 if (cmp(cur, lastPos) != 0) { 43106 cm.curOp.focus = activeElt(); 43107 extendTo(cur); 43108 var visible = visibleLines(display, doc); 43109 if (cur.line >= visible.to || cur.line < visible.from) 43110 { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); } 43111 } else { 43112 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; 43113 if (outside) { setTimeout(operation(cm, function () { 43114 if (counter != curCount) { return } 43115 display.scroller.scrollTop += outside; 43116 extend(e); 43117 }), 50); } 43118 } 43119 } 43120 43121 function done(e) { 43122 cm.state.selectingText = false; 43123 counter = Infinity; 43124 e_preventDefault(e); 43125 display.input.focus(); 43126 off(display.wrapper.ownerDocument, "mousemove", move); 43127 off(display.wrapper.ownerDocument, "mouseup", up); 43128 doc.history.lastSelOrigin = null; 43129 } 43130 43131 var move = operation(cm, function (e) { 43132 if (!e_button(e)) { done(e); } 43133 else { extend(e); } 43134 }); 43135 var up = operation(cm, done); 43136 cm.state.selectingText = up; 43137 on(display.wrapper.ownerDocument, "mousemove", move); 43138 on(display.wrapper.ownerDocument, "mouseup", up); 43139} 43140 43141// Used when mouse-selecting to adjust the anchor to the proper side 43142// of a bidi jump depending on the visual position of the head. 43143function bidiSimplify(cm, range$$1) { 43144 var anchor = range$$1.anchor; 43145 var head = range$$1.head; 43146 var anchorLine = getLine(cm.doc, anchor.line); 43147 if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range$$1 } 43148 var order = getOrder(anchorLine); 43149 if (!order) { return range$$1 } 43150 var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index]; 43151 if (part.from != anchor.ch && part.to != anchor.ch) { return range$$1 } 43152 var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1); 43153 if (boundary == 0 || boundary == order.length) { return range$$1 } 43154 43155 // Compute the relative visual position of the head compared to the 43156 // anchor (<0 is to the left, >0 to the right) 43157 var leftSide; 43158 if (head.line != anchor.line) { 43159 leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0; 43160 } else { 43161 var headIndex = getBidiPartAt(order, head.ch, head.sticky); 43162 var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1); 43163 if (headIndex == boundary - 1 || headIndex == boundary) 43164 { leftSide = dir < 0; } 43165 else 43166 { leftSide = dir > 0; } 43167 } 43168 43169 var usePart = order[boundary + (leftSide ? -1 : 0)]; 43170 var from = leftSide == (usePart.level == 1); 43171 var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before"; 43172 return anchor.ch == ch && anchor.sticky == sticky ? range$$1 : new Range(new Pos(anchor.line, ch, sticky), head) 43173} 43174 43175 43176// Determines whether an event happened in the gutter, and fires the 43177// handlers for the corresponding event. 43178function gutterEvent(cm, e, type, prevent) { 43179 var mX, mY; 43180 if (e.touches) { 43181 mX = e.touches[0].clientX; 43182 mY = e.touches[0].clientY; 43183 } else { 43184 try { mX = e.clientX; mY = e.clientY; } 43185 catch(e) { return false } 43186 } 43187 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } 43188 if (prevent) { e_preventDefault(e); } 43189 43190 var display = cm.display; 43191 var lineBox = display.lineDiv.getBoundingClientRect(); 43192 43193 if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } 43194 mY -= lineBox.top - display.viewOffset; 43195 43196 for (var i = 0; i < cm.options.gutters.length; ++i) { 43197 var g = display.gutters.childNodes[i]; 43198 if (g && g.getBoundingClientRect().right >= mX) { 43199 var line = lineAtHeight(cm.doc, mY); 43200 var gutter = cm.options.gutters[i]; 43201 signal(cm, type, cm, line, gutter, e); 43202 return e_defaultPrevented(e) 43203 } 43204 } 43205} 43206 43207function clickInGutter(cm, e) { 43208 return gutterEvent(cm, e, "gutterClick", true) 43209} 43210 43211// CONTEXT MENU HANDLING 43212 43213// To make the context menu work, we need to briefly unhide the 43214// textarea (making it as unobtrusive as possible) to let the 43215// right-click take effect on it. 43216function onContextMenu(cm, e) { 43217 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } 43218 if (signalDOMEvent(cm, e, "contextmenu")) { return } 43219 cm.display.input.onContextMenu(e); 43220} 43221 43222function contextMenuInGutter(cm, e) { 43223 if (!hasHandler(cm, "gutterContextMenu")) { return false } 43224 return gutterEvent(cm, e, "gutterContextMenu", false) 43225} 43226 43227function themeChanged(cm) { 43228 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + 43229 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); 43230 clearCaches(cm); 43231} 43232 43233var Init = {toString: function(){return "CodeMirror.Init"}}; 43234 43235var defaults = {}; 43236var optionHandlers = {}; 43237 43238function defineOptions(CodeMirror) { 43239 var optionHandlers = CodeMirror.optionHandlers; 43240 43241 function option(name, deflt, handle, notOnInit) { 43242 CodeMirror.defaults[name] = deflt; 43243 if (handle) { optionHandlers[name] = 43244 notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; } 43245 } 43246 43247 CodeMirror.defineOption = option; 43248 43249 // Passed to option handlers when there is no old value. 43250 CodeMirror.Init = Init; 43251 43252 // These two are, on init, called from the constructor because they 43253 // have to be initialized before the editor can start at all. 43254 option("value", "", function (cm, val) { return cm.setValue(val); }, true); 43255 option("mode", null, function (cm, val) { 43256 cm.doc.modeOption = val; 43257 loadMode(cm); 43258 }, true); 43259 43260 option("indentUnit", 2, loadMode, true); 43261 option("indentWithTabs", false); 43262 option("smartIndent", true); 43263 option("tabSize", 4, function (cm) { 43264 resetModeState(cm); 43265 clearCaches(cm); 43266 regChange(cm); 43267 }, true); 43268 43269 option("lineSeparator", null, function (cm, val) { 43270 cm.doc.lineSep = val; 43271 if (!val) { return } 43272 var newBreaks = [], lineNo = cm.doc.first; 43273 cm.doc.iter(function (line) { 43274 for (var pos = 0;;) { 43275 var found = line.text.indexOf(val, pos); 43276 if (found == -1) { break } 43277 pos = found + val.length; 43278 newBreaks.push(Pos(lineNo, found)); 43279 } 43280 lineNo++; 43281 }); 43282 for (var i = newBreaks.length - 1; i >= 0; i--) 43283 { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); } 43284 }); 43285 option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { 43286 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); 43287 if (old != Init) { cm.refresh(); } 43288 }); 43289 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true); 43290 option("electricChars", true); 43291 option("inputStyle", mobile ? "contenteditable" : "textarea", function () { 43292 throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME 43293 }, true); 43294 option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true); 43295 option("rtlMoveVisually", !windows); 43296 option("wholeLineUpdateBefore", true); 43297 43298 option("theme", "default", function (cm) { 43299 themeChanged(cm); 43300 guttersChanged(cm); 43301 }, true); 43302 option("keyMap", "default", function (cm, val, old) { 43303 var next = getKeyMap(val); 43304 var prev = old != Init && getKeyMap(old); 43305 if (prev && prev.detach) { prev.detach(cm, next); } 43306 if (next.attach) { next.attach(cm, prev || null); } 43307 }); 43308 option("extraKeys", null); 43309 option("configureMouse", null); 43310 43311 option("lineWrapping", false, wrappingChanged, true); 43312 option("gutters", [], function (cm) { 43313 setGuttersForLineNumbers(cm.options); 43314 guttersChanged(cm); 43315 }, true); 43316 option("fixedGutter", true, function (cm, val) { 43317 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; 43318 cm.refresh(); 43319 }, true); 43320 option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true); 43321 option("scrollbarStyle", "native", function (cm) { 43322 initScrollbars(cm); 43323 updateScrollbars(cm); 43324 cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); 43325 cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); 43326 }, true); 43327 option("lineNumbers", false, function (cm) { 43328 setGuttersForLineNumbers(cm.options); 43329 guttersChanged(cm); 43330 }, true); 43331 option("firstLineNumber", 1, guttersChanged, true); 43332 option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true); 43333 option("showCursorWhenSelecting", false, updateSelection, true); 43334 43335 option("resetSelectionOnContextMenu", true); 43336 option("lineWiseCopyCut", true); 43337 option("pasteLinesPerSelection", true); 43338 43339 option("readOnly", false, function (cm, val) { 43340 if (val == "nocursor") { 43341 onBlur(cm); 43342 cm.display.input.blur(); 43343 } 43344 cm.display.input.readOnlyChanged(val); 43345 }); 43346 option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true); 43347 option("dragDrop", true, dragDropChanged); 43348 option("allowDropFileTypes", null); 43349 43350 option("cursorBlinkRate", 530); 43351 option("cursorScrollMargin", 0); 43352 option("cursorHeight", 1, updateSelection, true); 43353 option("singleCursorHeightPerLine", true, updateSelection, true); 43354 option("workTime", 100); 43355 option("workDelay", 100); 43356 option("flattenSpans", true, resetModeState, true); 43357 option("addModeClass", false, resetModeState, true); 43358 option("pollInterval", 100); 43359 option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }); 43360 option("historyEventDelay", 1250); 43361 option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true); 43362 option("maxHighlightLength", 10000, resetModeState, true); 43363 option("moveInputWithCursor", true, function (cm, val) { 43364 if (!val) { cm.display.input.resetPosition(); } 43365 }); 43366 43367 option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }); 43368 option("autofocus", null); 43369 option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true); 43370} 43371 43372function guttersChanged(cm) { 43373 updateGutters(cm); 43374 regChange(cm); 43375 alignHorizontally(cm); 43376} 43377 43378function dragDropChanged(cm, value, old) { 43379 var wasOn = old && old != Init; 43380 if (!value != !wasOn) { 43381 var funcs = cm.display.dragFunctions; 43382 var toggle = value ? on : off; 43383 toggle(cm.display.scroller, "dragstart", funcs.start); 43384 toggle(cm.display.scroller, "dragenter", funcs.enter); 43385 toggle(cm.display.scroller, "dragover", funcs.over); 43386 toggle(cm.display.scroller, "dragleave", funcs.leave); 43387 toggle(cm.display.scroller, "drop", funcs.drop); 43388 } 43389} 43390 43391function wrappingChanged(cm) { 43392 if (cm.options.lineWrapping) { 43393 addClass(cm.display.wrapper, "CodeMirror-wrap"); 43394 cm.display.sizer.style.minWidth = ""; 43395 cm.display.sizerWidth = null; 43396 } else { 43397 rmClass(cm.display.wrapper, "CodeMirror-wrap"); 43398 findMaxLine(cm); 43399 } 43400 estimateLineHeights(cm); 43401 regChange(cm); 43402 clearCaches(cm); 43403 setTimeout(function () { return updateScrollbars(cm); }, 100); 43404} 43405 43406// A CodeMirror instance represents an editor. This is the object 43407// that user code is usually dealing with. 43408 43409function CodeMirror$1(place, options) { 43410 var this$1 = this; 43411 43412 if (!(this instanceof CodeMirror$1)) { return new CodeMirror$1(place, options) } 43413 43414 this.options = options = options ? copyObj(options) : {}; 43415 // Determine effective options based on given values and defaults. 43416 copyObj(defaults, options, false); 43417 setGuttersForLineNumbers(options); 43418 43419 var doc = options.value; 43420 if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); } 43421 this.doc = doc; 43422 43423 var input = new CodeMirror$1.inputStyles[options.inputStyle](this); 43424 var display = this.display = new Display(place, doc, input); 43425 display.wrapper.CodeMirror = this; 43426 updateGutters(this); 43427 themeChanged(this); 43428 if (options.lineWrapping) 43429 { this.display.wrapper.className += " CodeMirror-wrap"; } 43430 initScrollbars(this); 43431 43432 this.state = { 43433 keyMaps: [], // stores maps added by addKeyMap 43434 overlays: [], // highlighting overlays, as added by addOverlay 43435 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info 43436 overwrite: false, 43437 delayingBlurEvent: false, 43438 focused: false, 43439 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode 43440 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll 43441 selectingText: false, 43442 draggingText: false, 43443 highlight: new Delayed(), // stores highlight worker timeout 43444 keySeq: null, // Unfinished key sequence 43445 specialChars: null 43446 }; 43447 43448 if (options.autofocus && !mobile) { display.input.focus(); } 43449 43450 // Override magic textarea content restore that IE sometimes does 43451 // on our hidden textarea on reload 43452 if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); } 43453 43454 registerEventHandlers(this); 43455 ensureGlobalHandlers(); 43456 43457 startOperation(this); 43458 this.curOp.forceUpdate = true; 43459 attachDoc(this, doc); 43460 43461 if ((options.autofocus && !mobile) || this.hasFocus()) 43462 { setTimeout(bind(onFocus, this), 20); } 43463 else 43464 { onBlur(this); } 43465 43466 for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) 43467 { optionHandlers[opt](this$1, options[opt], Init); } } 43468 maybeUpdateLineNumberWidth(this); 43469 if (options.finishInit) { options.finishInit(this); } 43470 for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1); } 43471 endOperation(this); 43472 // Suppress optimizelegibility in Webkit, since it breaks text 43473 // measuring on line wrapping boundaries. 43474 if (webkit && options.lineWrapping && 43475 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") 43476 { display.lineDiv.style.textRendering = "auto"; } 43477} 43478 43479// The default configuration options. 43480CodeMirror$1.defaults = defaults; 43481// Functions to run when options are changed. 43482CodeMirror$1.optionHandlers = optionHandlers; 43483 43484// Attach the necessary event handlers when initializing the editor 43485function registerEventHandlers(cm) { 43486 var d = cm.display; 43487 on(d.scroller, "mousedown", operation(cm, onMouseDown)); 43488 // Older IE's will not fire a second mousedown for a double click 43489 if (ie && ie_version < 11) 43490 { on(d.scroller, "dblclick", operation(cm, function (e) { 43491 if (signalDOMEvent(cm, e)) { return } 43492 var pos = posFromMouse(cm, e); 43493 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } 43494 e_preventDefault(e); 43495 var word = cm.findWordAt(pos); 43496 extendSelection(cm.doc, word.anchor, word.head); 43497 })); } 43498 else 43499 { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); } 43500 // Some browsers fire contextmenu *after* opening the menu, at 43501 // which point we can't mess with it anymore. Context menu is 43502 // handled in onMouseDown for these browsers. 43503 if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }); } 43504 43505 // Used to suppress mouse event handling when a touch happens 43506 var touchFinished, prevTouch = {end: 0}; 43507 function finishTouch() { 43508 if (d.activeTouch) { 43509 touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000); 43510 prevTouch = d.activeTouch; 43511 prevTouch.end = +new Date; 43512 } 43513 } 43514 function isMouseLikeTouchEvent(e) { 43515 if (e.touches.length != 1) { return false } 43516 var touch = e.touches[0]; 43517 return touch.radiusX <= 1 && touch.radiusY <= 1 43518 } 43519 function farAway(touch, other) { 43520 if (other.left == null) { return true } 43521 var dx = other.left - touch.left, dy = other.top - touch.top; 43522 return dx * dx + dy * dy > 20 * 20 43523 } 43524 on(d.scroller, "touchstart", function (e) { 43525 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { 43526 d.input.ensurePolled(); 43527 clearTimeout(touchFinished); 43528 var now = +new Date; 43529 d.activeTouch = {start: now, moved: false, 43530 prev: now - prevTouch.end <= 300 ? prevTouch : null}; 43531 if (e.touches.length == 1) { 43532 d.activeTouch.left = e.touches[0].pageX; 43533 d.activeTouch.top = e.touches[0].pageY; 43534 } 43535 } 43536 }); 43537 on(d.scroller, "touchmove", function () { 43538 if (d.activeTouch) { d.activeTouch.moved = true; } 43539 }); 43540 on(d.scroller, "touchend", function (e) { 43541 var touch = d.activeTouch; 43542 if (touch && !eventInWidget(d, e) && touch.left != null && 43543 !touch.moved && new Date - touch.start < 300) { 43544 var pos = cm.coordsChar(d.activeTouch, "page"), range; 43545 if (!touch.prev || farAway(touch, touch.prev)) // Single tap 43546 { range = new Range(pos, pos); } 43547 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap 43548 { range = cm.findWordAt(pos); } 43549 else // Triple tap 43550 { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); } 43551 cm.setSelection(range.anchor, range.head); 43552 cm.focus(); 43553 e_preventDefault(e); 43554 } 43555 finishTouch(); 43556 }); 43557 on(d.scroller, "touchcancel", finishTouch); 43558 43559 // Sync scrolling between fake scrollbars and real scrollable 43560 // area, ensure viewport is updated when scrolling. 43561 on(d.scroller, "scroll", function () { 43562 if (d.scroller.clientHeight) { 43563 updateScrollTop(cm, d.scroller.scrollTop); 43564 setScrollLeft(cm, d.scroller.scrollLeft, true); 43565 signal(cm, "scroll", cm); 43566 } 43567 }); 43568 43569 // Listen to wheel events in order to try and update the viewport on time. 43570 on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }); 43571 on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }); 43572 43573 // Prevent wrapper from ever scrolling 43574 on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); 43575 43576 d.dragFunctions = { 43577 enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }}, 43578 over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, 43579 start: function (e) { return onDragStart(cm, e); }, 43580 drop: operation(cm, onDrop), 43581 leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} 43582 }; 43583 43584 var inp = d.input.getField(); 43585 on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }); 43586 on(inp, "keydown", operation(cm, onKeyDown)); 43587 on(inp, "keypress", operation(cm, onKeyPress)); 43588 on(inp, "focus", function (e) { return onFocus(cm, e); }); 43589 on(inp, "blur", function (e) { return onBlur(cm, e); }); 43590} 43591 43592var initHooks = []; 43593CodeMirror$1.defineInitHook = function (f) { return initHooks.push(f); }; 43594 43595// Indent the given line. The how parameter can be "smart", 43596// "add"/null, "subtract", or "prev". When aggressive is false 43597// (typically set to true for forced single-line indents), empty 43598// lines are not indented, and places where the mode returns Pass 43599// are left alone. 43600function indentLine(cm, n, how, aggressive) { 43601 var doc = cm.doc, state; 43602 if (how == null) { how = "add"; } 43603 if (how == "smart") { 43604 // Fall back to "prev" when the mode doesn't have an indentation 43605 // method. 43606 if (!doc.mode.indent) { how = "prev"; } 43607 else { state = getContextBefore(cm, n).state; } 43608 } 43609 43610 var tabSize = cm.options.tabSize; 43611 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); 43612 if (line.stateAfter) { line.stateAfter = null; } 43613 var curSpaceString = line.text.match(/^\s*/)[0], indentation; 43614 if (!aggressive && !/\S/.test(line.text)) { 43615 indentation = 0; 43616 how = "not"; 43617 } else if (how == "smart") { 43618 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); 43619 if (indentation == Pass || indentation > 150) { 43620 if (!aggressive) { return } 43621 how = "prev"; 43622 } 43623 } 43624 if (how == "prev") { 43625 if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); } 43626 else { indentation = 0; } 43627 } else if (how == "add") { 43628 indentation = curSpace + cm.options.indentUnit; 43629 } else if (how == "subtract") { 43630 indentation = curSpace - cm.options.indentUnit; 43631 } else if (typeof how == "number") { 43632 indentation = curSpace + how; 43633 } 43634 indentation = Math.max(0, indentation); 43635 43636 var indentString = "", pos = 0; 43637 if (cm.options.indentWithTabs) 43638 { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} } 43639 if (pos < indentation) { indentString += spaceStr(indentation - pos); } 43640 43641 if (indentString != curSpaceString) { 43642 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); 43643 line.stateAfter = null; 43644 return true 43645 } else { 43646 // Ensure that, if the cursor was in the whitespace at the start 43647 // of the line, it is moved to the end of that space. 43648 for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { 43649 var range = doc.sel.ranges[i$1]; 43650 if (range.head.line == n && range.head.ch < curSpaceString.length) { 43651 var pos$1 = Pos(n, curSpaceString.length); 43652 replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)); 43653 break 43654 } 43655 } 43656 } 43657} 43658 43659// This will be set to a {lineWise: bool, text: [string]} object, so 43660// that, when pasting, we know what kind of selections the copied 43661// text was made out of. 43662var lastCopied = null; 43663 43664function setLastCopied(newLastCopied) { 43665 lastCopied = newLastCopied; 43666} 43667 43668function applyTextInput(cm, inserted, deleted, sel, origin) { 43669 var doc = cm.doc; 43670 cm.display.shift = false; 43671 if (!sel) { sel = doc.sel; } 43672 43673 var paste = cm.state.pasteIncoming || origin == "paste"; 43674 var textLines = splitLinesAuto(inserted), multiPaste = null; 43675 // When pasting N lines into N selections, insert one line per selection 43676 if (paste && sel.ranges.length > 1) { 43677 if (lastCopied && lastCopied.text.join("\n") == inserted) { 43678 if (sel.ranges.length % lastCopied.text.length == 0) { 43679 multiPaste = []; 43680 for (var i = 0; i < lastCopied.text.length; i++) 43681 { multiPaste.push(doc.splitLines(lastCopied.text[i])); } 43682 } 43683 } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { 43684 multiPaste = map(textLines, function (l) { return [l]; }); 43685 } 43686 } 43687 43688 var updateInput; 43689 // Normal behavior is to insert the new text into every selection 43690 for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { 43691 var range$$1 = sel.ranges[i$1]; 43692 var from = range$$1.from(), to = range$$1.to(); 43693 if (range$$1.empty()) { 43694 if (deleted && deleted > 0) // Handle deletion 43695 { from = Pos(from.line, from.ch - deleted); } 43696 else if (cm.state.overwrite && !paste) // Handle overwrite 43697 { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); } 43698 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) 43699 { from = to = Pos(from.line, 0); } 43700 } 43701 updateInput = cm.curOp.updateInput; 43702 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, 43703 origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")}; 43704 makeChange(cm.doc, changeEvent); 43705 signalLater(cm, "inputRead", cm, changeEvent); 43706 } 43707 if (inserted && !paste) 43708 { triggerElectric(cm, inserted); } 43709 43710 ensureCursorVisible(cm); 43711 cm.curOp.updateInput = updateInput; 43712 cm.curOp.typing = true; 43713 cm.state.pasteIncoming = cm.state.cutIncoming = false; 43714} 43715 43716function handlePaste(e, cm) { 43717 var pasted = e.clipboardData && e.clipboardData.getData("Text"); 43718 if (pasted) { 43719 e.preventDefault(); 43720 if (!cm.isReadOnly() && !cm.options.disableInput) 43721 { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }); } 43722 return true 43723 } 43724} 43725 43726function triggerElectric(cm, inserted) { 43727 // When an 'electric' character is inserted, immediately trigger a reindent 43728 if (!cm.options.electricChars || !cm.options.smartIndent) { return } 43729 var sel = cm.doc.sel; 43730 43731 for (var i = sel.ranges.length - 1; i >= 0; i--) { 43732 var range$$1 = sel.ranges[i]; 43733 if (range$$1.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range$$1.head.line)) { continue } 43734 var mode = cm.getModeAt(range$$1.head); 43735 var indented = false; 43736 if (mode.electricChars) { 43737 for (var j = 0; j < mode.electricChars.length; j++) 43738 { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { 43739 indented = indentLine(cm, range$$1.head.line, "smart"); 43740 break 43741 } } 43742 } else if (mode.electricInput) { 43743 if (mode.electricInput.test(getLine(cm.doc, range$$1.head.line).text.slice(0, range$$1.head.ch))) 43744 { indented = indentLine(cm, range$$1.head.line, "smart"); } 43745 } 43746 if (indented) { signalLater(cm, "electricInput", cm, range$$1.head.line); } 43747 } 43748} 43749 43750function copyableRanges(cm) { 43751 var text = [], ranges = []; 43752 for (var i = 0; i < cm.doc.sel.ranges.length; i++) { 43753 var line = cm.doc.sel.ranges[i].head.line; 43754 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; 43755 ranges.push(lineRange); 43756 text.push(cm.getRange(lineRange.anchor, lineRange.head)); 43757 } 43758 return {text: text, ranges: ranges} 43759} 43760 43761function disableBrowserMagic(field, spellcheck) { 43762 field.setAttribute("autocorrect", "off"); 43763 field.setAttribute("autocapitalize", "off"); 43764 field.setAttribute("spellcheck", !!spellcheck); 43765} 43766 43767function hiddenTextarea() { 43768 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none"); 43769 var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); 43770 // The textarea is kept positioned near the cursor to prevent the 43771 // fact that it'll be scrolled into view on input from scrolling 43772 // our fake cursor out of view. On webkit, when wrap=off, paste is 43773 // very slow. So make the area wide instead. 43774 if (webkit) { te.style.width = "1000px"; } 43775 else { te.setAttribute("wrap", "off"); } 43776 // If border: 0; -- iOS fails to open keyboard (issue #1287) 43777 if (ios) { te.style.border = "1px solid black"; } 43778 disableBrowserMagic(te); 43779 return div 43780} 43781 43782// The publicly visible API. Note that methodOp(f) means 43783// 'wrap f in an operation, performed on its `this` parameter'. 43784 43785// This is not the complete set of editor methods. Most of the 43786// methods defined on the Doc type are also injected into 43787// CodeMirror.prototype, for backwards compatibility and 43788// convenience. 43789 43790var addEditorMethods = function(CodeMirror) { 43791 var optionHandlers = CodeMirror.optionHandlers; 43792 43793 var helpers = CodeMirror.helpers = {}; 43794 43795 CodeMirror.prototype = { 43796 constructor: CodeMirror, 43797 focus: function(){window.focus(); this.display.input.focus();}, 43798 43799 setOption: function(option, value) { 43800 var options = this.options, old = options[option]; 43801 if (options[option] == value && option != "mode") { return } 43802 options[option] = value; 43803 if (optionHandlers.hasOwnProperty(option)) 43804 { operation(this, optionHandlers[option])(this, value, old); } 43805 signal(this, "optionChange", this, option); 43806 }, 43807 43808 getOption: function(option) {return this.options[option]}, 43809 getDoc: function() {return this.doc}, 43810 43811 addKeyMap: function(map$$1, bottom) { 43812 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map$$1)); 43813 }, 43814 removeKeyMap: function(map$$1) { 43815 var maps = this.state.keyMaps; 43816 for (var i = 0; i < maps.length; ++i) 43817 { if (maps[i] == map$$1 || maps[i].name == map$$1) { 43818 maps.splice(i, 1); 43819 return true 43820 } } 43821 }, 43822 43823 addOverlay: methodOp(function(spec, options) { 43824 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); 43825 if (mode.startState) { throw new Error("Overlays may not be stateful.") } 43826 insertSorted(this.state.overlays, 43827 {mode: mode, modeSpec: spec, opaque: options && options.opaque, 43828 priority: (options && options.priority) || 0}, 43829 function (overlay) { return overlay.priority; }); 43830 this.state.modeGen++; 43831 regChange(this); 43832 }), 43833 removeOverlay: methodOp(function(spec) { 43834 var this$1 = this; 43835 43836 var overlays = this.state.overlays; 43837 for (var i = 0; i < overlays.length; ++i) { 43838 var cur = overlays[i].modeSpec; 43839 if (cur == spec || typeof spec == "string" && cur.name == spec) { 43840 overlays.splice(i, 1); 43841 this$1.state.modeGen++; 43842 regChange(this$1); 43843 return 43844 } 43845 } 43846 }), 43847 43848 indentLine: methodOp(function(n, dir, aggressive) { 43849 if (typeof dir != "string" && typeof dir != "number") { 43850 if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev"; } 43851 else { dir = dir ? "add" : "subtract"; } 43852 } 43853 if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); } 43854 }), 43855 indentSelection: methodOp(function(how) { 43856 var this$1 = this; 43857 43858 var ranges = this.doc.sel.ranges, end = -1; 43859 for (var i = 0; i < ranges.length; i++) { 43860 var range$$1 = ranges[i]; 43861 if (!range$$1.empty()) { 43862 var from = range$$1.from(), to = range$$1.to(); 43863 var start = Math.max(end, from.line); 43864 end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; 43865 for (var j = start; j < end; ++j) 43866 { indentLine(this$1, j, how); } 43867 var newRanges = this$1.doc.sel.ranges; 43868 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) 43869 { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); } 43870 } else if (range$$1.head.line > end) { 43871 indentLine(this$1, range$$1.head.line, how, true); 43872 end = range$$1.head.line; 43873 if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1); } 43874 } 43875 } 43876 }), 43877 43878 // Fetch the parser token for a given character. Useful for hacks 43879 // that want to inspect the mode state (say, for completion). 43880 getTokenAt: function(pos, precise) { 43881 return takeToken(this, pos, precise) 43882 }, 43883 43884 getLineTokens: function(line, precise) { 43885 return takeToken(this, Pos(line), precise, true) 43886 }, 43887 43888 getTokenTypeAt: function(pos) { 43889 pos = clipPos(this.doc, pos); 43890 var styles = getLineStyles(this, getLine(this.doc, pos.line)); 43891 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; 43892 var type; 43893 if (ch == 0) { type = styles[2]; } 43894 else { for (;;) { 43895 var mid = (before + after) >> 1; 43896 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; } 43897 else if (styles[mid * 2 + 1] < ch) { before = mid + 1; } 43898 else { type = styles[mid * 2 + 2]; break } 43899 } } 43900 var cut = type ? type.indexOf("overlay ") : -1; 43901 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) 43902 }, 43903 43904 getModeAt: function(pos) { 43905 var mode = this.doc.mode; 43906 if (!mode.innerMode) { return mode } 43907 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode 43908 }, 43909 43910 getHelper: function(pos, type) { 43911 return this.getHelpers(pos, type)[0] 43912 }, 43913 43914 getHelpers: function(pos, type) { 43915 var this$1 = this; 43916 43917 var found = []; 43918 if (!helpers.hasOwnProperty(type)) { return found } 43919 var help = helpers[type], mode = this.getModeAt(pos); 43920 if (typeof mode[type] == "string") { 43921 if (help[mode[type]]) { found.push(help[mode[type]]); } 43922 } else if (mode[type]) { 43923 for (var i = 0; i < mode[type].length; i++) { 43924 var val = help[mode[type][i]]; 43925 if (val) { found.push(val); } 43926 } 43927 } else if (mode.helperType && help[mode.helperType]) { 43928 found.push(help[mode.helperType]); 43929 } else if (help[mode.name]) { 43930 found.push(help[mode.name]); 43931 } 43932 for (var i$1 = 0; i$1 < help._global.length; i$1++) { 43933 var cur = help._global[i$1]; 43934 if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) 43935 { found.push(cur.val); } 43936 } 43937 return found 43938 }, 43939 43940 getStateAfter: function(line, precise) { 43941 var doc = this.doc; 43942 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); 43943 return getContextBefore(this, line + 1, precise).state 43944 }, 43945 43946 cursorCoords: function(start, mode) { 43947 var pos, range$$1 = this.doc.sel.primary(); 43948 if (start == null) { pos = range$$1.head; } 43949 else if (typeof start == "object") { pos = clipPos(this.doc, start); } 43950 else { pos = start ? range$$1.from() : range$$1.to(); } 43951 return cursorCoords(this, pos, mode || "page") 43952 }, 43953 43954 charCoords: function(pos, mode) { 43955 return charCoords(this, clipPos(this.doc, pos), mode || "page") 43956 }, 43957 43958 coordsChar: function(coords, mode) { 43959 coords = fromCoordSystem(this, coords, mode || "page"); 43960 return coordsChar(this, coords.left, coords.top) 43961 }, 43962 43963 lineAtHeight: function(height, mode) { 43964 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; 43965 return lineAtHeight(this.doc, height + this.display.viewOffset) 43966 }, 43967 heightAtLine: function(line, mode, includeWidgets) { 43968 var end = false, lineObj; 43969 if (typeof line == "number") { 43970 var last = this.doc.first + this.doc.size - 1; 43971 if (line < this.doc.first) { line = this.doc.first; } 43972 else if (line > last) { line = last; end = true; } 43973 lineObj = getLine(this.doc, line); 43974 } else { 43975 lineObj = line; 43976 } 43977 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + 43978 (end ? this.doc.height - heightAtLine(lineObj) : 0) 43979 }, 43980 43981 defaultTextHeight: function() { return textHeight(this.display) }, 43982 defaultCharWidth: function() { return charWidth(this.display) }, 43983 43984 getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, 43985 43986 addWidget: function(pos, node, scroll, vert, horiz) { 43987 var display = this.display; 43988 pos = cursorCoords(this, clipPos(this.doc, pos)); 43989 var top = pos.bottom, left = pos.left; 43990 node.style.position = "absolute"; 43991 node.setAttribute("cm-ignore-events", "true"); 43992 this.display.input.setUneditable(node); 43993 display.sizer.appendChild(node); 43994 if (vert == "over") { 43995 top = pos.top; 43996 } else if (vert == "above" || vert == "near") { 43997 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), 43998 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); 43999 // Default to positioning above (if specified and possible); otherwise default to positioning below 44000 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) 44001 { top = pos.top - node.offsetHeight; } 44002 else if (pos.bottom + node.offsetHeight <= vspace) 44003 { top = pos.bottom; } 44004 if (left + node.offsetWidth > hspace) 44005 { left = hspace - node.offsetWidth; } 44006 } 44007 node.style.top = top + "px"; 44008 node.style.left = node.style.right = ""; 44009 if (horiz == "right") { 44010 left = display.sizer.clientWidth - node.offsetWidth; 44011 node.style.right = "0px"; 44012 } else { 44013 if (horiz == "left") { left = 0; } 44014 else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; } 44015 node.style.left = left + "px"; 44016 } 44017 if (scroll) 44018 { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); } 44019 }, 44020 44021 triggerOnKeyDown: methodOp(onKeyDown), 44022 triggerOnKeyPress: methodOp(onKeyPress), 44023 triggerOnKeyUp: onKeyUp, 44024 triggerOnMouseDown: methodOp(onMouseDown), 44025 44026 execCommand: function(cmd) { 44027 if (commands.hasOwnProperty(cmd)) 44028 { return commands[cmd].call(null, this) } 44029 }, 44030 44031 triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), 44032 44033 findPosH: function(from, amount, unit, visually) { 44034 var this$1 = this; 44035 44036 var dir = 1; 44037 if (amount < 0) { dir = -1; amount = -amount; } 44038 var cur = clipPos(this.doc, from); 44039 for (var i = 0; i < amount; ++i) { 44040 cur = findPosH(this$1.doc, cur, dir, unit, visually); 44041 if (cur.hitSide) { break } 44042 } 44043 return cur 44044 }, 44045 44046 moveH: methodOp(function(dir, unit) { 44047 var this$1 = this; 44048 44049 this.extendSelectionsBy(function (range$$1) { 44050 if (this$1.display.shift || this$1.doc.extend || range$$1.empty()) 44051 { return findPosH(this$1.doc, range$$1.head, dir, unit, this$1.options.rtlMoveVisually) } 44052 else 44053 { return dir < 0 ? range$$1.from() : range$$1.to() } 44054 }, sel_move); 44055 }), 44056 44057 deleteH: methodOp(function(dir, unit) { 44058 var sel = this.doc.sel, doc = this.doc; 44059 if (sel.somethingSelected()) 44060 { doc.replaceSelection("", null, "+delete"); } 44061 else 44062 { deleteNearSelection(this, function (range$$1) { 44063 var other = findPosH(doc, range$$1.head, dir, unit, false); 44064 return dir < 0 ? {from: other, to: range$$1.head} : {from: range$$1.head, to: other} 44065 }); } 44066 }), 44067 44068 findPosV: function(from, amount, unit, goalColumn) { 44069 var this$1 = this; 44070 44071 var dir = 1, x = goalColumn; 44072 if (amount < 0) { dir = -1; amount = -amount; } 44073 var cur = clipPos(this.doc, from); 44074 for (var i = 0; i < amount; ++i) { 44075 var coords = cursorCoords(this$1, cur, "div"); 44076 if (x == null) { x = coords.left; } 44077 else { coords.left = x; } 44078 cur = findPosV(this$1, coords, dir, unit); 44079 if (cur.hitSide) { break } 44080 } 44081 return cur 44082 }, 44083 44084 moveV: methodOp(function(dir, unit) { 44085 var this$1 = this; 44086 44087 var doc = this.doc, goals = []; 44088 var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected(); 44089 doc.extendSelectionsBy(function (range$$1) { 44090 if (collapse) 44091 { return dir < 0 ? range$$1.from() : range$$1.to() } 44092 var headPos = cursorCoords(this$1, range$$1.head, "div"); 44093 if (range$$1.goalColumn != null) { headPos.left = range$$1.goalColumn; } 44094 goals.push(headPos.left); 44095 var pos = findPosV(this$1, headPos, dir, unit); 44096 if (unit == "page" && range$$1 == doc.sel.primary()) 44097 { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top); } 44098 return pos 44099 }, sel_move); 44100 if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) 44101 { doc.sel.ranges[i].goalColumn = goals[i]; } } 44102 }), 44103 44104 // Find the word at the given position (as returned by coordsChar). 44105 findWordAt: function(pos) { 44106 var doc = this.doc, line = getLine(doc, pos.line).text; 44107 var start = pos.ch, end = pos.ch; 44108 if (line) { 44109 var helper = this.getHelper(pos, "wordChars"); 44110 if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end; } 44111 var startChar = line.charAt(start); 44112 var check = isWordChar(startChar, helper) 44113 ? function (ch) { return isWordChar(ch, helper); } 44114 : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } 44115 : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); }; 44116 while (start > 0 && check(line.charAt(start - 1))) { --start; } 44117 while (end < line.length && check(line.charAt(end))) { ++end; } 44118 } 44119 return new Range(Pos(pos.line, start), Pos(pos.line, end)) 44120 }, 44121 44122 toggleOverwrite: function(value) { 44123 if (value != null && value == this.state.overwrite) { return } 44124 if (this.state.overwrite = !this.state.overwrite) 44125 { addClass(this.display.cursorDiv, "CodeMirror-overwrite"); } 44126 else 44127 { rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); } 44128 44129 signal(this, "overwriteToggle", this, this.state.overwrite); 44130 }, 44131 hasFocus: function() { return this.display.input.getField() == activeElt() }, 44132 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, 44133 44134 scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }), 44135 getScrollInfo: function() { 44136 var scroller = this.display.scroller; 44137 return {left: scroller.scrollLeft, top: scroller.scrollTop, 44138 height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, 44139 width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, 44140 clientHeight: displayHeight(this), clientWidth: displayWidth(this)} 44141 }, 44142 44143 scrollIntoView: methodOp(function(range$$1, margin) { 44144 if (range$$1 == null) { 44145 range$$1 = {from: this.doc.sel.primary().head, to: null}; 44146 if (margin == null) { margin = this.options.cursorScrollMargin; } 44147 } else if (typeof range$$1 == "number") { 44148 range$$1 = {from: Pos(range$$1, 0), to: null}; 44149 } else if (range$$1.from == null) { 44150 range$$1 = {from: range$$1, to: null}; 44151 } 44152 if (!range$$1.to) { range$$1.to = range$$1.from; } 44153 range$$1.margin = margin || 0; 44154 44155 if (range$$1.from.line != null) { 44156 scrollToRange(this, range$$1); 44157 } else { 44158 scrollToCoordsRange(this, range$$1.from, range$$1.to, range$$1.margin); 44159 } 44160 }), 44161 44162 setSize: methodOp(function(width, height) { 44163 var this$1 = this; 44164 44165 var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; }; 44166 if (width != null) { this.display.wrapper.style.width = interpret(width); } 44167 if (height != null) { this.display.wrapper.style.height = interpret(height); } 44168 if (this.options.lineWrapping) { clearLineMeasurementCache(this); } 44169 var lineNo$$1 = this.display.viewFrom; 44170 this.doc.iter(lineNo$$1, this.display.viewTo, function (line) { 44171 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) 44172 { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo$$1, "widget"); break } } } 44173 ++lineNo$$1; 44174 }); 44175 this.curOp.forceUpdate = true; 44176 signal(this, "refresh", this); 44177 }), 44178 44179 operation: function(f){return runInOp(this, f)}, 44180 startOperation: function(){return startOperation(this)}, 44181 endOperation: function(){return endOperation(this)}, 44182 44183 refresh: methodOp(function() { 44184 var oldHeight = this.display.cachedTextHeight; 44185 regChange(this); 44186 this.curOp.forceUpdate = true; 44187 clearCaches(this); 44188 scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop); 44189 updateGutterSpace(this); 44190 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) 44191 { estimateLineHeights(this); } 44192 signal(this, "refresh", this); 44193 }), 44194 44195 swapDoc: methodOp(function(doc) { 44196 var old = this.doc; 44197 old.cm = null; 44198 attachDoc(this, doc); 44199 clearCaches(this); 44200 this.display.input.reset(); 44201 scrollToCoords(this, doc.scrollLeft, doc.scrollTop); 44202 this.curOp.forceScroll = true; 44203 signalLater(this, "swapDoc", this, old); 44204 return old 44205 }), 44206 44207 getInputField: function(){return this.display.input.getField()}, 44208 getWrapperElement: function(){return this.display.wrapper}, 44209 getScrollerElement: function(){return this.display.scroller}, 44210 getGutterElement: function(){return this.display.gutters} 44211 }; 44212 eventMixin(CodeMirror); 44213 44214 CodeMirror.registerHelper = function(type, name, value) { 44215 if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; } 44216 helpers[type][name] = value; 44217 }; 44218 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { 44219 CodeMirror.registerHelper(type, name, value); 44220 helpers[type]._global.push({pred: predicate, val: value}); 44221 }; 44222}; 44223 44224// Used for horizontal relative motion. Dir is -1 or 1 (left or 44225// right), unit can be "char", "column" (like char, but doesn't 44226// cross line boundaries), "word" (across next word), or "group" (to 44227// the start of next group of word or non-word-non-whitespace 44228// chars). The visually param controls whether, in right-to-left 44229// text, direction 1 means to move towards the next index in the 44230// string, or towards the character to the right of the current 44231// position. The resulting position will have a hitSide=true 44232// property if it reached the end of the document. 44233function findPosH(doc, pos, dir, unit, visually) { 44234 var oldPos = pos; 44235 var origDir = dir; 44236 var lineObj = getLine(doc, pos.line); 44237 function findNextLine() { 44238 var l = pos.line + dir; 44239 if (l < doc.first || l >= doc.first + doc.size) { return false } 44240 pos = new Pos(l, pos.ch, pos.sticky); 44241 return lineObj = getLine(doc, l) 44242 } 44243 function moveOnce(boundToLine) { 44244 var next; 44245 if (visually) { 44246 next = moveVisually(doc.cm, lineObj, pos, dir); 44247 } else { 44248 next = moveLogically(lineObj, pos, dir); 44249 } 44250 if (next == null) { 44251 if (!boundToLine && findNextLine()) 44252 { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir); } 44253 else 44254 { return false } 44255 } else { 44256 pos = next; 44257 } 44258 return true 44259 } 44260 44261 if (unit == "char") { 44262 moveOnce(); 44263 } else if (unit == "column") { 44264 moveOnce(true); 44265 } else if (unit == "word" || unit == "group") { 44266 var sawType = null, group = unit == "group"; 44267 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); 44268 for (var first = true;; first = false) { 44269 if (dir < 0 && !moveOnce(!first)) { break } 44270 var cur = lineObj.text.charAt(pos.ch) || "\n"; 44271 var type = isWordChar(cur, helper) ? "w" 44272 : group && cur == "\n" ? "n" 44273 : !group || /\s/.test(cur) ? null 44274 : "p"; 44275 if (group && !first && !type) { type = "s"; } 44276 if (sawType && sawType != type) { 44277 if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after";} 44278 break 44279 } 44280 44281 if (type) { sawType = type; } 44282 if (dir > 0 && !moveOnce(!first)) { break } 44283 } 44284 } 44285 var result = skipAtomic(doc, pos, oldPos, origDir, true); 44286 if (equalCursorPos(oldPos, result)) { result.hitSide = true; } 44287 return result 44288} 44289 44290// For relative vertical movement. Dir may be -1 or 1. Unit can be 44291// "page" or "line". The resulting position will have a hitSide=true 44292// property if it reached the end of the document. 44293function findPosV(cm, pos, dir, unit) { 44294 var doc = cm.doc, x = pos.left, y; 44295 if (unit == "page") { 44296 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); 44297 var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3); 44298 y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount; 44299 44300 } else if (unit == "line") { 44301 y = dir > 0 ? pos.bottom + 3 : pos.top - 3; 44302 } 44303 var target; 44304 for (;;) { 44305 target = coordsChar(cm, x, y); 44306 if (!target.outside) { break } 44307 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } 44308 y += dir * 5; 44309 } 44310 return target 44311} 44312 44313// CONTENTEDITABLE INPUT STYLE 44314 44315var ContentEditableInput = function(cm) { 44316 this.cm = cm; 44317 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; 44318 this.polling = new Delayed(); 44319 this.composing = null; 44320 this.gracePeriod = false; 44321 this.readDOMTimeout = null; 44322}; 44323 44324ContentEditableInput.prototype.init = function (display) { 44325 var this$1 = this; 44326 44327 var input = this, cm = input.cm; 44328 var div = input.div = display.lineDiv; 44329 disableBrowserMagic(div, cm.options.spellcheck); 44330 44331 on(div, "paste", function (e) { 44332 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 44333 // IE doesn't fire input events, so we schedule a read for the pasted content in this way 44334 if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); } 44335 }); 44336 44337 on(div, "compositionstart", function (e) { 44338 this$1.composing = {data: e.data, done: false}; 44339 }); 44340 on(div, "compositionupdate", function (e) { 44341 if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; } 44342 }); 44343 on(div, "compositionend", function (e) { 44344 if (this$1.composing) { 44345 if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); } 44346 this$1.composing.done = true; 44347 } 44348 }); 44349 44350 on(div, "touchstart", function () { return input.forceCompositionEnd(); }); 44351 44352 on(div, "input", function () { 44353 if (!this$1.composing) { this$1.readFromDOMSoon(); } 44354 }); 44355 44356 function onCopyCut(e) { 44357 if (signalDOMEvent(cm, e)) { return } 44358 if (cm.somethingSelected()) { 44359 setLastCopied({lineWise: false, text: cm.getSelections()}); 44360 if (e.type == "cut") { cm.replaceSelection("", null, "cut"); } 44361 } else if (!cm.options.lineWiseCopyCut) { 44362 return 44363 } else { 44364 var ranges = copyableRanges(cm); 44365 setLastCopied({lineWise: true, text: ranges.text}); 44366 if (e.type == "cut") { 44367 cm.operation(function () { 44368 cm.setSelections(ranges.ranges, 0, sel_dontScroll); 44369 cm.replaceSelection("", null, "cut"); 44370 }); 44371 } 44372 } 44373 if (e.clipboardData) { 44374 e.clipboardData.clearData(); 44375 var content = lastCopied.text.join("\n"); 44376 // iOS exposes the clipboard API, but seems to discard content inserted into it 44377 e.clipboardData.setData("Text", content); 44378 if (e.clipboardData.getData("Text") == content) { 44379 e.preventDefault(); 44380 return 44381 } 44382 } 44383 // Old-fashioned briefly-focus-a-textarea hack 44384 var kludge = hiddenTextarea(), te = kludge.firstChild; 44385 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); 44386 te.value = lastCopied.text.join("\n"); 44387 var hadFocus = document.activeElement; 44388 selectInput(te); 44389 setTimeout(function () { 44390 cm.display.lineSpace.removeChild(kludge); 44391 hadFocus.focus(); 44392 if (hadFocus == div) { input.showPrimarySelection(); } 44393 }, 50); 44394 } 44395 on(div, "copy", onCopyCut); 44396 on(div, "cut", onCopyCut); 44397}; 44398 44399ContentEditableInput.prototype.prepareSelection = function () { 44400 var result = prepareSelection(this.cm, false); 44401 result.focus = this.cm.state.focused; 44402 return result 44403}; 44404 44405ContentEditableInput.prototype.showSelection = function (info, takeFocus) { 44406 if (!info || !this.cm.display.view.length) { return } 44407 if (info.focus || takeFocus) { this.showPrimarySelection(); } 44408 this.showMultipleSelections(info); 44409}; 44410 44411ContentEditableInput.prototype.showPrimarySelection = function () { 44412 var sel = window.getSelection(), cm = this.cm, prim = cm.doc.sel.primary(); 44413 var from = prim.from(), to = prim.to(); 44414 44415 if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { 44416 sel.removeAllRanges(); 44417 return 44418 } 44419 44420 var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); 44421 var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset); 44422 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && 44423 cmp(minPos(curAnchor, curFocus), from) == 0 && 44424 cmp(maxPos(curAnchor, curFocus), to) == 0) 44425 { return } 44426 44427 var view = cm.display.view; 44428 var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || 44429 {node: view[0].measure.map[2], offset: 0}; 44430 var end = to.line < cm.display.viewTo && posToDOM(cm, to); 44431 if (!end) { 44432 var measure = view[view.length - 1].measure; 44433 var map$$1 = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; 44434 end = {node: map$$1[map$$1.length - 1], offset: map$$1[map$$1.length - 2] - map$$1[map$$1.length - 3]}; 44435 } 44436 44437 if (!start || !end) { 44438 sel.removeAllRanges(); 44439 return 44440 } 44441 44442 var old = sel.rangeCount && sel.getRangeAt(0), rng; 44443 try { rng = range(start.node, start.offset, end.offset, end.node); } 44444 catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible 44445 if (rng) { 44446 if (!gecko && cm.state.focused) { 44447 sel.collapse(start.node, start.offset); 44448 if (!rng.collapsed) { 44449 sel.removeAllRanges(); 44450 sel.addRange(rng); 44451 } 44452 } else { 44453 sel.removeAllRanges(); 44454 sel.addRange(rng); 44455 } 44456 if (old && sel.anchorNode == null) { sel.addRange(old); } 44457 else if (gecko) { this.startGracePeriod(); } 44458 } 44459 this.rememberSelection(); 44460}; 44461 44462ContentEditableInput.prototype.startGracePeriod = function () { 44463 var this$1 = this; 44464 44465 clearTimeout(this.gracePeriod); 44466 this.gracePeriod = setTimeout(function () { 44467 this$1.gracePeriod = false; 44468 if (this$1.selectionChanged()) 44469 { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); } 44470 }, 20); 44471}; 44472 44473ContentEditableInput.prototype.showMultipleSelections = function (info) { 44474 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); 44475 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); 44476}; 44477 44478ContentEditableInput.prototype.rememberSelection = function () { 44479 var sel = window.getSelection(); 44480 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; 44481 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; 44482}; 44483 44484ContentEditableInput.prototype.selectionInEditor = function () { 44485 var sel = window.getSelection(); 44486 if (!sel.rangeCount) { return false } 44487 var node = sel.getRangeAt(0).commonAncestorContainer; 44488 return contains(this.div, node) 44489}; 44490 44491ContentEditableInput.prototype.focus = function () { 44492 if (this.cm.options.readOnly != "nocursor") { 44493 if (!this.selectionInEditor()) 44494 { this.showSelection(this.prepareSelection(), true); } 44495 this.div.focus(); 44496 } 44497}; 44498ContentEditableInput.prototype.blur = function () { this.div.blur(); }; 44499ContentEditableInput.prototype.getField = function () { return this.div }; 44500 44501ContentEditableInput.prototype.supportsTouch = function () { return true }; 44502 44503ContentEditableInput.prototype.receivedFocus = function () { 44504 var input = this; 44505 if (this.selectionInEditor()) 44506 { this.pollSelection(); } 44507 else 44508 { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); } 44509 44510 function poll() { 44511 if (input.cm.state.focused) { 44512 input.pollSelection(); 44513 input.polling.set(input.cm.options.pollInterval, poll); 44514 } 44515 } 44516 this.polling.set(this.cm.options.pollInterval, poll); 44517}; 44518 44519ContentEditableInput.prototype.selectionChanged = function () { 44520 var sel = window.getSelection(); 44521 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || 44522 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset 44523}; 44524 44525ContentEditableInput.prototype.pollSelection = function () { 44526 if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } 44527 var sel = window.getSelection(), cm = this.cm; 44528 // On Android Chrome (version 56, at least), backspacing into an 44529 // uneditable block element will put the cursor in that element, 44530 // and then, because it's not editable, hide the virtual keyboard. 44531 // Because Android doesn't allow us to actually detect backspace 44532 // presses in a sane way, this code checks for when that happens 44533 // and simulates a backspace press in this case. 44534 if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { 44535 this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}); 44536 this.blur(); 44537 this.focus(); 44538 return 44539 } 44540 if (this.composing) { return } 44541 this.rememberSelection(); 44542 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); 44543 var head = domToPos(cm, sel.focusNode, sel.focusOffset); 44544 if (anchor && head) { runInOp(cm, function () { 44545 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); 44546 if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; } 44547 }); } 44548}; 44549 44550ContentEditableInput.prototype.pollContent = function () { 44551 if (this.readDOMTimeout != null) { 44552 clearTimeout(this.readDOMTimeout); 44553 this.readDOMTimeout = null; 44554 } 44555 44556 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); 44557 var from = sel.from(), to = sel.to(); 44558 if (from.ch == 0 && from.line > cm.firstLine()) 44559 { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); } 44560 if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) 44561 { to = Pos(to.line + 1, 0); } 44562 if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } 44563 44564 var fromIndex, fromLine, fromNode; 44565 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { 44566 fromLine = lineNo(display.view[0].line); 44567 fromNode = display.view[0].node; 44568 } else { 44569 fromLine = lineNo(display.view[fromIndex].line); 44570 fromNode = display.view[fromIndex - 1].node.nextSibling; 44571 } 44572 var toIndex = findViewIndex(cm, to.line); 44573 var toLine, toNode; 44574 if (toIndex == display.view.length - 1) { 44575 toLine = display.viewTo - 1; 44576 toNode = display.lineDiv.lastChild; 44577 } else { 44578 toLine = lineNo(display.view[toIndex + 1].line) - 1; 44579 toNode = display.view[toIndex + 1].node.previousSibling; 44580 } 44581 44582 if (!fromNode) { return false } 44583 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); 44584 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); 44585 while (newText.length > 1 && oldText.length > 1) { 44586 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } 44587 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } 44588 else { break } 44589 } 44590 44591 var cutFront = 0, cutEnd = 0; 44592 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); 44593 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) 44594 { ++cutFront; } 44595 var newBot = lst(newText), oldBot = lst(oldText); 44596 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), 44597 oldBot.length - (oldText.length == 1 ? cutFront : 0)); 44598 while (cutEnd < maxCutEnd && 44599 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) 44600 { ++cutEnd; } 44601 // Try to move start of change to start of selection if ambiguous 44602 if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { 44603 while (cutFront && cutFront > from.ch && 44604 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { 44605 cutFront--; 44606 cutEnd++; 44607 } 44608 } 44609 44610 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, ""); 44611 newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, ""); 44612 44613 var chFrom = Pos(fromLine, cutFront); 44614 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); 44615 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { 44616 replaceRange(cm.doc, newText, chFrom, chTo, "+input"); 44617 return true 44618 } 44619}; 44620 44621ContentEditableInput.prototype.ensurePolled = function () { 44622 this.forceCompositionEnd(); 44623}; 44624ContentEditableInput.prototype.reset = function () { 44625 this.forceCompositionEnd(); 44626}; 44627ContentEditableInput.prototype.forceCompositionEnd = function () { 44628 if (!this.composing) { return } 44629 clearTimeout(this.readDOMTimeout); 44630 this.composing = null; 44631 this.updateFromDOM(); 44632 this.div.blur(); 44633 this.div.focus(); 44634}; 44635ContentEditableInput.prototype.readFromDOMSoon = function () { 44636 var this$1 = this; 44637 44638 if (this.readDOMTimeout != null) { return } 44639 this.readDOMTimeout = setTimeout(function () { 44640 this$1.readDOMTimeout = null; 44641 if (this$1.composing) { 44642 if (this$1.composing.done) { this$1.composing = null; } 44643 else { return } 44644 } 44645 this$1.updateFromDOM(); 44646 }, 80); 44647}; 44648 44649ContentEditableInput.prototype.updateFromDOM = function () { 44650 var this$1 = this; 44651 44652 if (this.cm.isReadOnly() || !this.pollContent()) 44653 { runInOp(this.cm, function () { return regChange(this$1.cm); }); } 44654}; 44655 44656ContentEditableInput.prototype.setUneditable = function (node) { 44657 node.contentEditable = "false"; 44658}; 44659 44660ContentEditableInput.prototype.onKeyPress = function (e) { 44661 if (e.charCode == 0 || this.composing) { return } 44662 e.preventDefault(); 44663 if (!this.cm.isReadOnly()) 44664 { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); } 44665}; 44666 44667ContentEditableInput.prototype.readOnlyChanged = function (val) { 44668 this.div.contentEditable = String(val != "nocursor"); 44669}; 44670 44671ContentEditableInput.prototype.onContextMenu = function () {}; 44672ContentEditableInput.prototype.resetPosition = function () {}; 44673 44674ContentEditableInput.prototype.needsContentAttribute = true; 44675 44676function posToDOM(cm, pos) { 44677 var view = findViewForLine(cm, pos.line); 44678 if (!view || view.hidden) { return null } 44679 var line = getLine(cm.doc, pos.line); 44680 var info = mapFromLineView(view, line, pos.line); 44681 44682 var order = getOrder(line, cm.doc.direction), side = "left"; 44683 if (order) { 44684 var partPos = getBidiPartAt(order, pos.ch); 44685 side = partPos % 2 ? "right" : "left"; 44686 } 44687 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); 44688 result.offset = result.collapse == "right" ? result.end : result.start; 44689 return result 44690} 44691 44692function isInGutter(node) { 44693 for (var scan = node; scan; scan = scan.parentNode) 44694 { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } 44695 return false 44696} 44697 44698function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } 44699 44700function domTextBetween(cm, from, to, fromLine, toLine) { 44701 var text = "", closing = false, lineSep = cm.doc.lineSeparator(); 44702 function recognizeMarker(id) { return function (marker) { return marker.id == id; } } 44703 function close() { 44704 if (closing) { 44705 text += lineSep; 44706 closing = false; 44707 } 44708 } 44709 function addText(str) { 44710 if (str) { 44711 close(); 44712 text += str; 44713 } 44714 } 44715 function walk(node) { 44716 if (node.nodeType == 1) { 44717 var cmText = node.getAttribute("cm-text"); 44718 if (cmText != null) { 44719 addText(cmText || node.textContent.replace(/\u200b/g, "")); 44720 return 44721 } 44722 var markerID = node.getAttribute("cm-marker"), range$$1; 44723 if (markerID) { 44724 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); 44725 if (found.length && (range$$1 = found[0].find(0))) 44726 { addText(getBetween(cm.doc, range$$1.from, range$$1.to).join(lineSep)); } 44727 return 44728 } 44729 if (node.getAttribute("contenteditable") == "false") { return } 44730 var isBlock = /^(pre|div|p)$/i.test(node.nodeName); 44731 if (isBlock) { close(); } 44732 for (var i = 0; i < node.childNodes.length; i++) 44733 { walk(node.childNodes[i]); } 44734 if (isBlock) { closing = true; } 44735 } else if (node.nodeType == 3) { 44736 addText(node.nodeValue); 44737 } 44738 } 44739 for (;;) { 44740 walk(from); 44741 if (from == to) { break } 44742 from = from.nextSibling; 44743 } 44744 return text 44745} 44746 44747function domToPos(cm, node, offset) { 44748 var lineNode; 44749 if (node == cm.display.lineDiv) { 44750 lineNode = cm.display.lineDiv.childNodes[offset]; 44751 if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } 44752 node = null; offset = 0; 44753 } else { 44754 for (lineNode = node;; lineNode = lineNode.parentNode) { 44755 if (!lineNode || lineNode == cm.display.lineDiv) { return null } 44756 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } 44757 } 44758 } 44759 for (var i = 0; i < cm.display.view.length; i++) { 44760 var lineView = cm.display.view[i]; 44761 if (lineView.node == lineNode) 44762 { return locateNodeInLineView(lineView, node, offset) } 44763 } 44764} 44765 44766function locateNodeInLineView(lineView, node, offset) { 44767 var wrapper = lineView.text.firstChild, bad = false; 44768 if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } 44769 if (node == wrapper) { 44770 bad = true; 44771 node = wrapper.childNodes[offset]; 44772 offset = 0; 44773 if (!node) { 44774 var line = lineView.rest ? lst(lineView.rest) : lineView.line; 44775 return badPos(Pos(lineNo(line), line.text.length), bad) 44776 } 44777 } 44778 44779 var textNode = node.nodeType == 3 ? node : null, topNode = node; 44780 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { 44781 textNode = node.firstChild; 44782 if (offset) { offset = textNode.nodeValue.length; } 44783 } 44784 while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; } 44785 var measure = lineView.measure, maps = measure.maps; 44786 44787 function find(textNode, topNode, offset) { 44788 for (var i = -1; i < (maps ? maps.length : 0); i++) { 44789 var map$$1 = i < 0 ? measure.map : maps[i]; 44790 for (var j = 0; j < map$$1.length; j += 3) { 44791 var curNode = map$$1[j + 2]; 44792 if (curNode == textNode || curNode == topNode) { 44793 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); 44794 var ch = map$$1[j] + offset; 44795 if (offset < 0 || curNode != textNode) { ch = map$$1[j + (offset ? 1 : 0)]; } 44796 return Pos(line, ch) 44797 } 44798 } 44799 } 44800 } 44801 var found = find(textNode, topNode, offset); 44802 if (found) { return badPos(found, bad) } 44803 44804 // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems 44805 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { 44806 found = find(after, after.firstChild, 0); 44807 if (found) 44808 { return badPos(Pos(found.line, found.ch - dist), bad) } 44809 else 44810 { dist += after.textContent.length; } 44811 } 44812 for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { 44813 found = find(before, before.firstChild, -1); 44814 if (found) 44815 { return badPos(Pos(found.line, found.ch + dist$1), bad) } 44816 else 44817 { dist$1 += before.textContent.length; } 44818 } 44819} 44820 44821// TEXTAREA INPUT STYLE 44822 44823var TextareaInput = function(cm) { 44824 this.cm = cm; 44825 // See input.poll and input.reset 44826 this.prevInput = ""; 44827 44828 // Flag that indicates whether we expect input to appear real soon 44829 // now (after some event like 'keypress' or 'input') and are 44830 // polling intensively. 44831 this.pollingFast = false; 44832 // Self-resetting timeout for the poller 44833 this.polling = new Delayed(); 44834 // Used to work around IE issue with selection being forgotten when focus moves away from textarea 44835 this.hasSelection = false; 44836 this.composing = null; 44837}; 44838 44839TextareaInput.prototype.init = function (display) { 44840 var this$1 = this; 44841 44842 var input = this, cm = this.cm; 44843 this.createField(display); 44844 var te = this.textarea; 44845 44846 display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild); 44847 44848 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) 44849 if (ios) { te.style.width = "0px"; } 44850 44851 on(te, "input", function () { 44852 if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; } 44853 input.poll(); 44854 }); 44855 44856 on(te, "paste", function (e) { 44857 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 44858 44859 cm.state.pasteIncoming = true; 44860 input.fastPoll(); 44861 }); 44862 44863 function prepareCopyCut(e) { 44864 if (signalDOMEvent(cm, e)) { return } 44865 if (cm.somethingSelected()) { 44866 setLastCopied({lineWise: false, text: cm.getSelections()}); 44867 } else if (!cm.options.lineWiseCopyCut) { 44868 return 44869 } else { 44870 var ranges = copyableRanges(cm); 44871 setLastCopied({lineWise: true, text: ranges.text}); 44872 if (e.type == "cut") { 44873 cm.setSelections(ranges.ranges, null, sel_dontScroll); 44874 } else { 44875 input.prevInput = ""; 44876 te.value = ranges.text.join("\n"); 44877 selectInput(te); 44878 } 44879 } 44880 if (e.type == "cut") { cm.state.cutIncoming = true; } 44881 } 44882 on(te, "cut", prepareCopyCut); 44883 on(te, "copy", prepareCopyCut); 44884 44885 on(display.scroller, "paste", function (e) { 44886 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } 44887 cm.state.pasteIncoming = true; 44888 input.focus(); 44889 }); 44890 44891 // Prevent normal selection in the editor (we handle our own) 44892 on(display.lineSpace, "selectstart", function (e) { 44893 if (!eventInWidget(display, e)) { e_preventDefault(e); } 44894 }); 44895 44896 on(te, "compositionstart", function () { 44897 var start = cm.getCursor("from"); 44898 if (input.composing) { input.composing.range.clear(); } 44899 input.composing = { 44900 start: start, 44901 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) 44902 }; 44903 }); 44904 on(te, "compositionend", function () { 44905 if (input.composing) { 44906 input.poll(); 44907 input.composing.range.clear(); 44908 input.composing = null; 44909 } 44910 }); 44911}; 44912 44913TextareaInput.prototype.createField = function (_display) { 44914 // Wraps and hides input textarea 44915 this.wrapper = hiddenTextarea(); 44916 // The semihidden textarea that is focused when the editor is 44917 // focused, and receives input. 44918 this.textarea = this.wrapper.firstChild; 44919}; 44920 44921TextareaInput.prototype.prepareSelection = function () { 44922 // Redraw the selection and/or cursor 44923 var cm = this.cm, display = cm.display, doc = cm.doc; 44924 var result = prepareSelection(cm); 44925 44926 // Move the hidden textarea near the cursor to prevent scrolling artifacts 44927 if (cm.options.moveInputWithCursor) { 44928 var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); 44929 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); 44930 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, 44931 headPos.top + lineOff.top - wrapOff.top)); 44932 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, 44933 headPos.left + lineOff.left - wrapOff.left)); 44934 } 44935 44936 return result 44937}; 44938 44939TextareaInput.prototype.showSelection = function (drawn) { 44940 var cm = this.cm, display = cm.display; 44941 removeChildrenAndAdd(display.cursorDiv, drawn.cursors); 44942 removeChildrenAndAdd(display.selectionDiv, drawn.selection); 44943 if (drawn.teTop != null) { 44944 this.wrapper.style.top = drawn.teTop + "px"; 44945 this.wrapper.style.left = drawn.teLeft + "px"; 44946 } 44947}; 44948 44949// Reset the input to correspond to the selection (or to be empty, 44950// when not typing and nothing is selected) 44951TextareaInput.prototype.reset = function (typing) { 44952 if (this.contextMenuPending || this.composing) { return } 44953 var cm = this.cm; 44954 if (cm.somethingSelected()) { 44955 this.prevInput = ""; 44956 var content = cm.getSelection(); 44957 this.textarea.value = content; 44958 if (cm.state.focused) { selectInput(this.textarea); } 44959 if (ie && ie_version >= 9) { this.hasSelection = content; } 44960 } else if (!typing) { 44961 this.prevInput = this.textarea.value = ""; 44962 if (ie && ie_version >= 9) { this.hasSelection = null; } 44963 } 44964}; 44965 44966TextareaInput.prototype.getField = function () { return this.textarea }; 44967 44968TextareaInput.prototype.supportsTouch = function () { return false }; 44969 44970TextareaInput.prototype.focus = function () { 44971 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { 44972 try { this.textarea.focus(); } 44973 catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM 44974 } 44975}; 44976 44977TextareaInput.prototype.blur = function () { this.textarea.blur(); }; 44978 44979TextareaInput.prototype.resetPosition = function () { 44980 this.wrapper.style.top = this.wrapper.style.left = 0; 44981}; 44982 44983TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); }; 44984 44985// Poll for input changes, using the normal rate of polling. This 44986// runs as long as the editor is focused. 44987TextareaInput.prototype.slowPoll = function () { 44988 var this$1 = this; 44989 44990 if (this.pollingFast) { return } 44991 this.polling.set(this.cm.options.pollInterval, function () { 44992 this$1.poll(); 44993 if (this$1.cm.state.focused) { this$1.slowPoll(); } 44994 }); 44995}; 44996 44997// When an event has just come in that is likely to add or change 44998// something in the input textarea, we poll faster, to ensure that 44999// the change appears on the screen quickly. 45000TextareaInput.prototype.fastPoll = function () { 45001 var missed = false, input = this; 45002 input.pollingFast = true; 45003 function p() { 45004 var changed = input.poll(); 45005 if (!changed && !missed) {missed = true; input.polling.set(60, p);} 45006 else {input.pollingFast = false; input.slowPoll();} 45007 } 45008 input.polling.set(20, p); 45009}; 45010 45011// Read input from the textarea, and update the document to match. 45012// When something is selected, it is present in the textarea, and 45013// selected (unless it is huge, in which case a placeholder is 45014// used). When nothing is selected, the cursor sits after previously 45015// seen text (can be empty), which is stored in prevInput (we must 45016// not reset the textarea when typing, because that breaks IME). 45017TextareaInput.prototype.poll = function () { 45018 var this$1 = this; 45019 45020 var cm = this.cm, input = this.textarea, prevInput = this.prevInput; 45021 // Since this is called a *lot*, try to bail out as cheaply as 45022 // possible when it is clear that nothing happened. hasSelection 45023 // will be the case when there is a lot of text in the textarea, 45024 // in which case reading its value would be expensive. 45025 if (this.contextMenuPending || !cm.state.focused || 45026 (hasSelection(input) && !prevInput && !this.composing) || 45027 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) 45028 { return false } 45029 45030 var text = input.value; 45031 // If nothing changed, bail. 45032 if (text == prevInput && !cm.somethingSelected()) { return false } 45033 // Work around nonsensical selection resetting in IE9/10, and 45034 // inexplicable appearance of private area unicode characters on 45035 // some key combos in Mac (#2689). 45036 if (ie && ie_version >= 9 && this.hasSelection === text || 45037 mac && /[\uf700-\uf7ff]/.test(text)) { 45038 cm.display.input.reset(); 45039 return false 45040 } 45041 45042 if (cm.doc.sel == cm.display.selForContextMenu) { 45043 var first = text.charCodeAt(0); 45044 if (first == 0x200b && !prevInput) { prevInput = "\u200b"; } 45045 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } 45046 } 45047 // Find the part of the input that is actually new 45048 var same = 0, l = Math.min(prevInput.length, text.length); 45049 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; } 45050 45051 runInOp(cm, function () { 45052 applyTextInput(cm, text.slice(same), prevInput.length - same, 45053 null, this$1.composing ? "*compose" : null); 45054 45055 // Don't leave long text in the textarea, since it makes further polling slow 45056 if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = ""; } 45057 else { this$1.prevInput = text; } 45058 45059 if (this$1.composing) { 45060 this$1.composing.range.clear(); 45061 this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), 45062 {className: "CodeMirror-composing"}); 45063 } 45064 }); 45065 return true 45066}; 45067 45068TextareaInput.prototype.ensurePolled = function () { 45069 if (this.pollingFast && this.poll()) { this.pollingFast = false; } 45070}; 45071 45072TextareaInput.prototype.onKeyPress = function () { 45073 if (ie && ie_version >= 9) { this.hasSelection = null; } 45074 this.fastPoll(); 45075}; 45076 45077TextareaInput.prototype.onContextMenu = function (e) { 45078 var input = this, cm = input.cm, display = cm.display, te = input.textarea; 45079 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; 45080 if (!pos || presto) { return } // Opera is difficult. 45081 45082 // Reset the current text selection only if the click is done outside of the selection 45083 // and 'resetSelectionOnContextMenu' option is true. 45084 var reset = cm.options.resetSelectionOnContextMenu; 45085 if (reset && cm.doc.sel.contains(pos) == -1) 45086 { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); } 45087 45088 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText; 45089 input.wrapper.style.cssText = "position: absolute"; 45090 var wrapperBox = input.wrapper.getBoundingClientRect(); 45091 te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; 45092 var oldScrollY; 45093 if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712) 45094 display.input.focus(); 45095 if (webkit) { window.scrollTo(null, oldScrollY); } 45096 display.input.reset(); 45097 // Adds "Select all" to context menu in FF 45098 if (!cm.somethingSelected()) { te.value = input.prevInput = " "; } 45099 input.contextMenuPending = true; 45100 display.selForContextMenu = cm.doc.sel; 45101 clearTimeout(display.detectingSelectAll); 45102 45103 // Select-all will be greyed out if there's nothing to select, so 45104 // this adds a zero-width space so that we can later check whether 45105 // it got selected. 45106 function prepareSelectAllHack() { 45107 if (te.selectionStart != null) { 45108 var selected = cm.somethingSelected(); 45109 var extval = "\u200b" + (selected ? te.value : ""); 45110 te.value = "\u21da"; // Used to catch context-menu undo 45111 te.value = extval; 45112 input.prevInput = selected ? "" : "\u200b"; 45113 te.selectionStart = 1; te.selectionEnd = extval.length; 45114 // Re-set this, in case some other handler touched the 45115 // selection in the meantime. 45116 display.selForContextMenu = cm.doc.sel; 45117 } 45118 } 45119 function rehide() { 45120 input.contextMenuPending = false; 45121 input.wrapper.style.cssText = oldWrapperCSS; 45122 te.style.cssText = oldCSS; 45123 if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); } 45124 45125 // Try to detect the user choosing select-all 45126 if (te.selectionStart != null) { 45127 if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); } 45128 var i = 0, poll = function () { 45129 if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && 45130 te.selectionEnd > 0 && input.prevInput == "\u200b") { 45131 operation(cm, selectAll)(cm); 45132 } else if (i++ < 10) { 45133 display.detectingSelectAll = setTimeout(poll, 500); 45134 } else { 45135 display.selForContextMenu = null; 45136 display.input.reset(); 45137 } 45138 }; 45139 display.detectingSelectAll = setTimeout(poll, 200); 45140 } 45141 } 45142 45143 if (ie && ie_version >= 9) { prepareSelectAllHack(); } 45144 if (captureRightClick) { 45145 e_stop(e); 45146 var mouseup = function () { 45147 off(window, "mouseup", mouseup); 45148 setTimeout(rehide, 20); 45149 }; 45150 on(window, "mouseup", mouseup); 45151 } else { 45152 setTimeout(rehide, 50); 45153 } 45154}; 45155 45156TextareaInput.prototype.readOnlyChanged = function (val) { 45157 if (!val) { this.reset(); } 45158 this.textarea.disabled = val == "nocursor"; 45159}; 45160 45161TextareaInput.prototype.setUneditable = function () {}; 45162 45163TextareaInput.prototype.needsContentAttribute = false; 45164 45165function fromTextArea(textarea, options) { 45166 options = options ? copyObj(options) : {}; 45167 options.value = textarea.value; 45168 if (!options.tabindex && textarea.tabIndex) 45169 { options.tabindex = textarea.tabIndex; } 45170 if (!options.placeholder && textarea.placeholder) 45171 { options.placeholder = textarea.placeholder; } 45172 // Set autofocus to true if this textarea is focused, or if it has 45173 // autofocus and no other element is focused. 45174 if (options.autofocus == null) { 45175 var hasFocus = activeElt(); 45176 options.autofocus = hasFocus == textarea || 45177 textarea.getAttribute("autofocus") != null && hasFocus == document.body; 45178 } 45179 45180 function save() {textarea.value = cm.getValue();} 45181 45182 var realSubmit; 45183 if (textarea.form) { 45184 on(textarea.form, "submit", save); 45185 // Deplorable hack to make the submit method do the right thing. 45186 if (!options.leaveSubmitMethodAlone) { 45187 var form = textarea.form; 45188 realSubmit = form.submit; 45189 try { 45190 var wrappedSubmit = form.submit = function () { 45191 save(); 45192 form.submit = realSubmit; 45193 form.submit(); 45194 form.submit = wrappedSubmit; 45195 }; 45196 } catch(e) {} 45197 } 45198 } 45199 45200 options.finishInit = function (cm) { 45201 cm.save = save; 45202 cm.getTextArea = function () { return textarea; }; 45203 cm.toTextArea = function () { 45204 cm.toTextArea = isNaN; // Prevent this from being ran twice 45205 save(); 45206 textarea.parentNode.removeChild(cm.getWrapperElement()); 45207 textarea.style.display = ""; 45208 if (textarea.form) { 45209 off(textarea.form, "submit", save); 45210 if (typeof textarea.form.submit == "function") 45211 { textarea.form.submit = realSubmit; } 45212 } 45213 }; 45214 }; 45215 45216 textarea.style.display = "none"; 45217 var cm = CodeMirror$1(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, 45218 options); 45219 return cm 45220} 45221 45222function addLegacyProps(CodeMirror) { 45223 CodeMirror.off = off; 45224 CodeMirror.on = on; 45225 CodeMirror.wheelEventPixels = wheelEventPixels; 45226 CodeMirror.Doc = Doc; 45227 CodeMirror.splitLines = splitLinesAuto; 45228 CodeMirror.countColumn = countColumn; 45229 CodeMirror.findColumn = findColumn; 45230 CodeMirror.isWordChar = isWordCharBasic; 45231 CodeMirror.Pass = Pass; 45232 CodeMirror.signal = signal; 45233 CodeMirror.Line = Line; 45234 CodeMirror.changeEnd = changeEnd; 45235 CodeMirror.scrollbarModel = scrollbarModel; 45236 CodeMirror.Pos = Pos; 45237 CodeMirror.cmpPos = cmp; 45238 CodeMirror.modes = modes; 45239 CodeMirror.mimeModes = mimeModes; 45240 CodeMirror.resolveMode = resolveMode; 45241 CodeMirror.getMode = getMode; 45242 CodeMirror.modeExtensions = modeExtensions; 45243 CodeMirror.extendMode = extendMode; 45244 CodeMirror.copyState = copyState; 45245 CodeMirror.startState = startState; 45246 CodeMirror.innerMode = innerMode; 45247 CodeMirror.commands = commands; 45248 CodeMirror.keyMap = keyMap; 45249 CodeMirror.keyName = keyName; 45250 CodeMirror.isModifierKey = isModifierKey; 45251 CodeMirror.lookupKey = lookupKey; 45252 CodeMirror.normalizeKeyMap = normalizeKeyMap; 45253 CodeMirror.StringStream = StringStream; 45254 CodeMirror.SharedTextMarker = SharedTextMarker; 45255 CodeMirror.TextMarker = TextMarker; 45256 CodeMirror.LineWidget = LineWidget; 45257 CodeMirror.e_preventDefault = e_preventDefault; 45258 CodeMirror.e_stopPropagation = e_stopPropagation; 45259 CodeMirror.e_stop = e_stop; 45260 CodeMirror.addClass = addClass; 45261 CodeMirror.contains = contains; 45262 CodeMirror.rmClass = rmClass; 45263 CodeMirror.keyNames = keyNames; 45264} 45265 45266// EDITOR CONSTRUCTOR 45267 45268defineOptions(CodeMirror$1); 45269 45270addEditorMethods(CodeMirror$1); 45271 45272// Set up methods on CodeMirror's prototype to redirect to the editor's document. 45273var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); 45274for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
45275 { CodeMirror$1.prototype[prop] = (function(method) { 45276 return function() {return method.apply(this.doc, arguments)} 45277 })(Doc.prototype[prop]); } } 45278 45279eventMixin(Doc); 45280 45281// INPUT HANDLING 45282 45283CodeMirror$1.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; 45284 45285// MODE DEFINITION AND QUERYING 45286 45287// Extra arguments are stored as the mode's dependencies, which is 45288// used by (legacy) mechanisms like loadmode.js to automatically 45289// load a mode. (Preferred mechanism is the require/define calls.) 45290CodeMirror$1.defineMode = function(name/*, mode, â¦*/) { 45291 if (!CodeMirror$1.defaults.mode && name != "null") { CodeMirror$1.defaults.mode = name; } 45292 defineMode.apply(this, arguments); 45293}; 45294 45295CodeMirror$1.defineMIME = defineMIME; 45296 45297// Minimal default mode. 45298CodeMirror$1.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }); 45299CodeMirror$1.defineMIME("text/plain", "null"); 45300 45301// EXTENSIONS 45302 45303CodeMirror$1.defineExtension = function (name, func) { 45304 CodeMirror$1.prototype[name] = func; 45305}; 45306CodeMirror$1.defineDocExtension = function (name, func) { 45307 Doc.prototype[name] = func; 45308}; 45309 45310CodeMirror$1.fromTextArea = fromTextArea; 45311 45312addLegacyProps(CodeMirror$1); 45313 45314CodeMirror$1.version = "5.37.0"; 45315 45316return CodeMirror$1; 45317 45318}))); 45319 45320},{}],7:[function(require,module,exports){ 45321// CodeMirror, copyright (c) by Marijn Haverbeke and others 45322// Distributed under an MIT license: http://codemirror.net/LICENSE 45323 45324(function(mod) { 45325 if (typeof exports == "object" && typeof module == "object") // CommonJS 45326 mod(require("../../lib/codemirror")); 45327 else if (typeof define == "function" && define.amd) // AMD 45328 define(["../../lib/codemirror"], mod); 45329 else // Plain browser env 45330 mod(CodeMirror); 45331})(function(CodeMirror) { 45332"use strict"; 45333 45334CodeMirror.defineMode("css", function(config, parserConfig) { 45335 var inline = parserConfig.inline 45336 if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css"); 45337 45338 var indentUnit = config.indentUnit, 45339 tokenHooks = parserConfig.tokenHooks, 45340 documentTypes = parserConfig.documentTypes || {}, 45341 mediaTypes = parserConfig.mediaTypes || {}, 45342 mediaFeatures = parserConfig.mediaFeatures || {}, 45343 mediaValueKeywords = parserConfig.mediaValueKeywords || {}, 45344 propertyKeywords = parserConfig.propertyKeywords || {}, 45345 nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {}, 45346 fontProperties = parserConfig.fontProperties || {}, 45347 counterDescriptors = parserConfig.counterDescriptors || {}, 45348 colorKeywords = parserConfig.colorKeywords || {}, 45349 valueKeywords = parserConfig.valueKeywords || {}, 45350 allowNested = parserConfig.allowNested, 45351 lineComment = parserConfig.lineComment,
vendor: 16,574 bytes, lines 45352-45799
45352 supportsAtComponent = parserConfig.supportsAtComponent === true; 45353 45354 var type, override; 45355 function ret(style, tp) { type = tp; return style; } 45356 45357 // Tokenizers 45358 45359 function tokenBase(stream, state) { 45360 var ch = stream.next(); 45361 if (tokenHooks[ch]) { 45362 var result = tokenHooks[ch](stream, state); 45363 if (result !== false) return result; 45364 } 45365 if (ch == "@") { 45366 stream.eatWhile(/[\w\\\-]/); 45367 return ret("def", stream.current()); 45368 } else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) { 45369 return ret(null, "compare"); 45370 } else if (ch == "\"" || ch == "'") { 45371 state.tokenize = tokenString(ch); 45372 return state.tokenize(stream, state); 45373 } else if (ch == "#") { 45374 stream.eatWhile(/[\w\\\-]/); 45375 return ret("atom", "hash"); 45376 } else if (ch == "!") { 45377 stream.match(/^\s*\w*/); 45378 return ret("keyword", "important"); 45379 } else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) { 45380 stream.eatWhile(/[\w.%]/); 45381 return ret("number", "unit"); 45382 } else if (ch === "-") { 45383 if (/[\d.]/.test(stream.peek())) { 45384 stream.eatWhile(/[\w.%]/); 45385 return ret("number", "unit"); 45386 } else if (stream.match(/^-[\w\\\-]+/)) { 45387 stream.eatWhile(/[\w\\\-]/); 45388 if (stream.match(/^\s*:/, false)) 45389 return ret("variable-2", "variable-definition"); 45390 return ret("variable-2", "variable"); 45391 } else if (stream.match(/^\w+-/)) { 45392 return ret("meta", "meta"); 45393 } 45394 } else if (/[,+>*\/]/.test(ch)) { 45395 return ret(null, "select-op"); 45396 } else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) { 45397 return ret("qualifier", "qualifier"); 45398 } else if (/[:;{}\[\]\(\)]/.test(ch)) { 45399 return ret(null, ch); 45400 } else if (((ch == "u" || ch == "U") && stream.match(/rl(-prefix)?\(/i)) || 45401 ((ch == "d" || ch == "D") && stream.match("omain(", true, true)) || 45402 ((ch == "r" || ch == "R") && stream.match("egexp(", true, true))) { 45403 stream.backUp(1); 45404 state.tokenize = tokenParenthesized; 45405 return ret("property", "word"); 45406 } else if (/[\w\\\-]/.test(ch)) { 45407 stream.eatWhile(/[\w\\\-]/); 45408 return ret("property", "word"); 45409 } else { 45410 return ret(null, null); 45411 } 45412 } 45413 45414 function tokenString(quote) { 45415 return function(stream, state) { 45416 var escaped = false, ch; 45417 while ((ch = stream.next()) != null) { 45418 if (ch == quote && !escaped) { 45419 if (quote == ")") stream.backUp(1); 45420 break; 45421 } 45422 escaped = !escaped && ch == "\\"; 45423 } 45424 if (ch == quote || !escaped && quote != ")") state.tokenize = null; 45425 return ret("string", "string"); 45426 }; 45427 } 45428 45429 function tokenParenthesized(stream, state) { 45430 stream.next(); // Must be '(' 45431 if (!stream.match(/\s*[\"\')]/, false)) 45432 state.tokenize = tokenString(")"); 45433 else 45434 state.tokenize = null; 45435 return ret(null, "("); 45436 } 45437 45438 // Context management 45439 45440 function Context(type, indent, prev) { 45441 this.type = type; 45442 this.indent = indent; 45443 this.prev = prev; 45444 } 45445 45446 function pushContext(state, stream, type, indent) { 45447 state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context); 45448 return type; 45449 } 45450 45451 function popContext(state) { 45452 if (state.context.prev) 45453 state.context = state.context.prev; 45454 return state.context.type; 45455 } 45456 45457 function pass(type, stream, state) { 45458 return states[state.context.type](type, stream, state); 45459 } 45460 function popAndPass(type, stream, state, n) { 45461 for (var i = n || 1; i > 0; i--) 45462 state.context = state.context.prev; 45463 return pass(type, stream, state); 45464 } 45465 45466 // Parser 45467 45468 function wordAsValue(stream) { 45469 var word = stream.current().toLowerCase(); 45470 if (valueKeywords.hasOwnProperty(word)) 45471 override = "atom"; 45472 else if (colorKeywords.hasOwnProperty(word)) 45473 override = "keyword"; 45474 else 45475 override = "variable"; 45476 } 45477 45478 var states = {}; 45479 45480 states.top = function(type, stream, state) { 45481 if (type == "{") { 45482 return pushContext(state, stream, "block"); 45483 } else if (type == "}" && state.context.prev) { 45484 return popContext(state); 45485 } else if (supportsAtComponent && /@component/i.test(type)) { 45486 return pushContext(state, stream, "atComponentBlock"); 45487 } else if (/^@(-moz-)?document$/i.test(type)) { 45488 return pushContext(state, stream, "documentTypes"); 45489 } else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) { 45490 return pushContext(state, stream, "atBlock"); 45491 } else if (/^@(font-face|counter-style)/i.test(type)) { 45492 state.stateArg = type; 45493 return "restricted_atBlock_before"; 45494 } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) { 45495 return "keyframes"; 45496 } else if (type && type.charAt(0) == "@") { 45497 return pushContext(state, stream, "at"); 45498 } else if (type == "hash") { 45499 override = "builtin"; 45500 } else if (type == "word") { 45501 override = "tag"; 45502 } else if (type == "variable-definition") { 45503 return "maybeprop"; 45504 } else if (type == "interpolation") { 45505 return pushContext(state, stream, "interpolation"); 45506 } else if (type == ":") { 45507 return "pseudo"; 45508 } else if (allowNested && type == "(") { 45509 return pushContext(state, stream, "parens"); 45510 } 45511 return state.context.type; 45512 }; 45513 45514 states.block = function(type, stream, state) { 45515 if (type == "word") { 45516 var word = stream.current().toLowerCase(); 45517 if (propertyKeywords.hasOwnProperty(word)) { 45518 override = "property"; 45519 return "maybeprop"; 45520 } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) { 45521 override = "string-2"; 45522 return "maybeprop"; 45523 } else if (allowNested) { 45524 override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag"; 45525 return "block"; 45526 } else { 45527 override += " error"; 45528 return "maybeprop"; 45529 } 45530 } else if (type == "meta") { 45531 return "block"; 45532 } else if (!allowNested && (type == "hash" || type == "qualifier")) { 45533 override = "error"; 45534 return "block"; 45535 } else { 45536 return states.top(type, stream, state); 45537 } 45538 }; 45539 45540 states.maybeprop = function(type, stream, state) { 45541 if (type == ":") return pushContext(state, stream, "prop"); 45542 return pass(type, stream, state); 45543 }; 45544 45545 states.prop = function(type, stream, state) { 45546 if (type == ";") return popContext(state); 45547 if (type == "{" && allowNested) return pushContext(state, stream, "propBlock"); 45548 if (type == "}" || type == "{") return popAndPass(type, stream, state); 45549 if (type == "(") return pushContext(state, stream, "parens"); 45550 45551 if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) { 45552 override += " error"; 45553 } else if (type == "word") { 45554 wordAsValue(stream); 45555 } else if (type == "interpolation") { 45556 return pushContext(state, stream, "interpolation"); 45557 } 45558 return "prop"; 45559 }; 45560 45561 states.propBlock = function(type, _stream, state) { 45562 if (type == "}") return popContext(state); 45563 if (type == "word") { override = "property"; return "maybeprop"; } 45564 return state.context.type; 45565 }; 45566 45567 states.parens = function(type, stream, state) { 45568 if (type == "{" || type == "}") return popAndPass(type, stream, state); 45569 if (type == ")") return popContext(state); 45570 if (type == "(") return pushContext(state, stream, "parens"); 45571 if (type == "interpolation") return pushContext(state, stream, "interpolation"); 45572 if (type == "word") wordAsValue(stream); 45573 return "parens"; 45574 }; 45575 45576 states.pseudo = function(type, stream, state) { 45577 if (type == "meta") return "pseudo"; 45578 45579 if (type == "word") { 45580 override = "variable-3"; 45581 return state.context.type; 45582 } 45583 return pass(type, stream, state); 45584 }; 45585 45586 states.documentTypes = function(type, stream, state) { 45587 if (type == "word" && documentTypes.hasOwnProperty(stream.current())) { 45588 override = "tag"; 45589 return state.context.type; 45590 } else { 45591 return states.atBlock(type, stream, state); 45592 } 45593 }; 45594 45595 states.atBlock = function(type, stream, state) { 45596 if (type == "(") return pushContext(state, stream, "atBlock_parens"); 45597 if (type == "}" || type == ";") return popAndPass(type, stream, state); 45598 if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top"); 45599 45600 if (type == "interpolation") return pushContext(state, stream, "interpolation"); 45601 45602 if (type == "word") { 45603 var word = stream.current().toLowerCase(); 45604 if (word == "only" || word == "not" || word == "and" || word == "or") 45605 override = "keyword"; 45606 else if (mediaTypes.hasOwnProperty(word)) 45607 override = "attribute"; 45608 else if (mediaFeatures.hasOwnProperty(word)) 45609 override = "property"; 45610 else if (mediaValueKeywords.hasOwnProperty(word)) 45611 override = "keyword"; 45612 else if (propertyKeywords.hasOwnProperty(word)) 45613 override = "property"; 45614 else if (nonStandardPropertyKeywords.hasOwnProperty(word)) 45615 override = "string-2"; 45616 else if (valueKeywords.hasOwnProperty(word)) 45617 override = "atom"; 45618 else if (colorKeywords.hasOwnProperty(word)) 45619 override = "keyword"; 45620 else 45621 override = "error"; 45622 } 45623 return state.context.type; 45624 }; 45625 45626 states.atComponentBlock = function(type, stream, state) { 45627 if (type == "}") 45628 return popAndPass(type, stream, state); 45629 if (type == "{") 45630 return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false); 45631 if (type == "word") 45632 override = "error"; 45633 return state.context.type; 45634 }; 45635 45636 states.atBlock_parens = function(type, stream, state) { 45637 if (type == ")") return popContext(state); 45638 if (type == "{" || type == "}") return popAndPass(type, stream, state, 2); 45639 return states.atBlock(type, stream, state); 45640 }; 45641 45642 states.restricted_atBlock_before = function(type, stream, state) { 45643 if (type == "{") 45644 return pushContext(state, stream, "restricted_atBlock"); 45645 if (type == "word" && state.stateArg == "@counter-style") { 45646 override = "variable"; 45647 return "restricted_atBlock_before"; 45648 } 45649 return pass(type, stream, state); 45650 }; 45651 45652 states.restricted_atBlock = function(type, stream, state) { 45653 if (type == "}") { 45654 state.stateArg = null; 45655 return popContext(state); 45656 } 45657 if (type == "word") { 45658 if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) || 45659 (state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase()))) 45660 override = "error"; 45661 else 45662 override = "property"; 45663 return "maybeprop"; 45664 } 45665 return "restricted_atBlock"; 45666 }; 45667 45668 states.keyframes = function(type, stream, state) { 45669 if (type == "word") { override = "variable"; return "keyframes"; } 45670 if (type == "{") return pushContext(state, stream, "top"); 45671 return pass(type, stream, state); 45672 }; 45673 45674 states.at = function(type, stream, state) { 45675 if (type == ";") return popContext(state); 45676 if (type == "{" || type == "}") return popAndPass(type, stream, state); 45677 if (type == "word") override = "tag"; 45678 else if (type == "hash") override = "builtin"; 45679 return "at"; 45680 }; 45681 45682 states.interpolation = function(type, stream, state) { 45683 if (type == "}") return popContext(state); 45684 if (type == "{" || type == ";") return popAndPass(type, stream, state); 45685 if (type == "word") override = "variable"; 45686 else if (type != "variable" && type != "(" && type != ")") override = "error"; 45687 return "interpolation"; 45688 }; 45689 45690 return { 45691 startState: function(base) { 45692 return {tokenize: null, 45693 state: inline ? "block" : "top", 45694 stateArg: null, 45695 context: new Context(inline ? "block" : "top", base || 0, null)}; 45696 }, 45697 45698 token: function(stream, state) { 45699 if (!state.tokenize && stream.eatSpace()) return null; 45700 var style = (state.tokenize || tokenBase)(stream, state); 45701 if (style && typeof style == "object") { 45702 type = style[1]; 45703 style = style[0]; 45704 } 45705 override = style; 45706 if (type != "comment") 45707 state.state = states[state.state](type, stream, state); 45708 return override; 45709 }, 45710 45711 indent: function(state, textAfter) { 45712 var cx = state.context, ch = textAfter && textAfter.charAt(0); 45713 var indent = cx.indent; 45714 if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev; 45715 if (cx.prev) { 45716 if (ch == "}" && (cx.type == "block" || cx.type == "top" || 45717 cx.type == "interpolation" || cx.type == "restricted_atBlock")) { 45718 // Resume indentation from parent context. 45719 cx = cx.prev; 45720 indent = cx.indent; 45721 } else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") || 45722 ch == "{" && (cx.type == "at" || cx.type == "atBlock")) { 45723 // Dedent relative to current context. 45724 indent = Math.max(0, cx.indent - indentUnit); 45725 } 45726 } 45727 return indent; 45728 }, 45729 45730 electricChars: "}", 45731 blockCommentStart: "/*", 45732 blockCommentEnd: "*/", 45733 blockCommentContinue: " * ", 45734 lineComment: lineComment, 45735 fold: "brace" 45736 }; 45737}); 45738 45739 function keySet(array) { 45740 var keys = {}; 45741 for (var i = 0; i < array.length; ++i) { 45742 keys[array[i].toLowerCase()] = true; 45743 } 45744 return keys; 45745 } 45746 45747 var documentTypes_ = [ 45748 "domain", "regexp", "url", "url-prefix" 45749 ], documentTypes = keySet(documentTypes_); 45750 45751 var mediaTypes_ = [ 45752 "all", "aural", "braille", "handheld", "print", "projection", "screen", 45753 "tty", "tv", "embossed" 45754 ], mediaTypes = keySet(mediaTypes_); 45755 45756 var mediaFeatures_ = [ 45757 "width", "min-width", "max-width", "height", "min-height", "max-height", 45758 "device-width", "min-device-width", "max-device-width", "device-height", 45759 "min-device-height", "max-device-height", "aspect-ratio", 45760 "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio", 45761 "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color", 45762 "max-color", "color-index", "min-color-index", "max-color-index", 45763 "monochrome", "min-monochrome", "max-monochrome", "resolution", 45764 "min-resolution", "max-resolution", "scan", "grid", "orientation", 45765 "device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio", 45766 "pointer", "any-pointer", "hover", "any-hover" 45767 ], mediaFeatures = keySet(mediaFeatures_); 45768 45769 var mediaValueKeywords_ = [ 45770 "landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover", 45771 "interlace", "progressive" 45772 ], mediaValueKeywords = keySet(mediaValueKeywords_); 45773 45774 var propertyKeywords_ = [ 45775 "align-content", "align-items", "align-self", "alignment-adjust", 45776 "alignment-baseline", "anchor-point", "animation", "animation-delay", 45777 "animation-direction", "animation-duration", "animation-fill-mode", 45778 "animation-iteration-count", "animation-name", "animation-play-state", 45779 "animation-timing-function", "appearance", "azimuth", "backface-visibility", 45780 "background", "background-attachment", "background-blend-mode", "background-clip", 45781 "background-color", "background-image", "background-origin", "background-position", 45782 "background-repeat", "background-size", "baseline-shift", "binding", 45783 "bleed", "bookmark-label", "bookmark-level", "bookmark-state", 45784 "bookmark-target", "border", "border-bottom", "border-bottom-color", 45785 "border-bottom-left-radius", "border-bottom-right-radius", 45786 "border-bottom-style", "border-bottom-width", "border-collapse", 45787 "border-color", "border-image", "border-image-outset", 45788 "border-image-repeat", "border-image-slice", "border-image-source", 45789 "border-image-width", "border-left", "border-left-color", 45790 "border-left-style", "border-left-width", "border-radius", "border-right", 45791 "border-right-color", "border-right-style", "border-right-width", 45792 "border-spacing", "border-style", "border-top", "border-top-color", 45793 "border-top-left-radius", "border-top-right-radius", "border-top-style", 45794 "border-top-width", "border-width", "bottom", "box-decoration-break", 45795 "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", 45796 "caption-side", "caret-color", "clear", "clip", "color", "color-profile", "column-count", 45797 "column-fill", "column-gap", "column-rule", "column-rule-color", 45798 "column-rule-style", "column-rule-width", "column-span", "column-width", 45799 "columns", "content", "counter-increment", "counter-reset", "crop", "cue",
45800 "cue-after", "cue-before", "cursor", "direction", "display", 45801 "dominant-baseline", "drop-initial-after-adjust", 45802 "drop-initial-after-align", "drop-initial-before-adjust", 45803 "drop-initial-before-align", "drop-initial-size", "drop-initial-value", 45804 "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis", 45805 "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", 45806 "float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings", 45807 "font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust", 45808 "font-stretch", "font-style", "font-synthesis", "font-variant", 45809 "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", 45810 "font-variant-ligatures", "font-variant-numeric", "font-variant-position", 45811 "font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow", 45812 "grid-auto-rows", "grid-column", "grid-column-end", "grid-column-gap", 45813 "grid-column-start", "grid-gap", "grid-row", "grid-row-end", "grid-row-gap", 45814 "grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns", 45815 "grid-template-rows", "hanging-punctuation", "height", "hyphens", 45816 "icon", "image-orientation", "image-rendering", "image-resolution", 45817 "inline-box-align", "justify-content", "justify-items", "justify-self", "left", "letter-spacing", 45818 "line-break", "line-height", "line-stacking", "line-stacking-ruby", 45819 "line-stacking-shift", "line-stacking-strategy", "list-style", 45820 "list-style-image", "list-style-position", "list-style-type", "margin", 45821 "margin-bottom", "margin-left", "margin-right", "margin-top", 45822 "marks", "marquee-direction", "marquee-loop", 45823 "marquee-play-count", "marquee-speed", "marquee-style", "max-height", 45824 "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", 45825 "nav-left", "nav-right", "nav-up", "object-fit", "object-position", 45826 "opacity", "order", "orphans", "outline", 45827 "outline-color", "outline-offset", "outline-style", "outline-width", 45828 "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", 45829 "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", 45830 "page", "page-break-after", "page-break-before", "page-break-inside", 45831 "page-policy", "pause", "pause-after", "pause-before", "perspective", 45832 "perspective-origin", "pitch", "pitch-range", "place-content", "place-items", "place-self", "play-during", "position", 45833 "presentation-level", "punctuation-trim", "quotes", "region-break-after", 45834 "region-break-before", "region-break-inside", "region-fragment", 45835 "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness", 45836 "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang", 45837 "ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin", 45838 "shape-outside", "size", "speak", "speak-as", "speak-header", 45839 "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", 45840 "tab-size", "table-layout", "target", "target-name", "target-new", 45841 "target-position", "text-align", "text-align-last", "text-decoration", 45842 "text-decoration-color", "text-decoration-line", "text-decoration-skip", 45843 "text-decoration-style", "text-emphasis", "text-emphasis-color", 45844 "text-emphasis-position", "text-emphasis-style", "text-height", 45845 "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow", 45846 "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position", 45847 "text-wrap", "top", "transform", "transform-origin", "transform-style", 45848 "transition", "transition-delay", "transition-duration", 45849 "transition-property", "transition-timing-function", "unicode-bidi", 45850 "user-select", "vertical-align", "visibility", "voice-balance", "voice-duration", 45851 "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", 45852 "voice-volume", "volume", "white-space", "widows", "width", "will-change", "word-break", 45853 "word-spacing", "word-wrap", "z-index", 45854 // SVG-specific 45855 "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color", 45856 "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events", 45857 "color-interpolation", "color-interpolation-filters", 45858 "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering", 45859 "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke", 45860 "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", 45861 "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering", 45862 "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal", 45863 "glyph-orientation-vertical", "text-anchor", "writing-mode" 45864 ], propertyKeywords = keySet(propertyKeywords_); 45865 45866 var nonStandardPropertyKeywords_ = [ 45867 "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color", 45868 "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color", 45869 "scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside", 45870 "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", 45871 "searchfield-results-decoration", "zoom" 45872 ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_); 45873 45874 var fontProperties_ = [
45875 "font-family", "src", "unicode-range", "font-variant", "font-feature-settings", 45876 "font-stretch", "font-weight", "font-style" 45877 ], fontProperties = keySet(fontProperties_); 45878 45879 var counterDescriptors_ = [ 45880 "additive-symbols", "fallback", "negative", "pad", "prefix", "range", 45881 "speak-as", "suffix", "symbols", "system" 45882 ], counterDescriptors = keySet(counterDescriptors_); 45883 45884 var colorKeywords_ = [ 45885 "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", 45886 "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", 45887 "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", 45888 "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", 45889 "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen", 45890 "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", 45891 "darkslateblue", "darkslategray", "darkturquoise", "darkviolet", 45892 "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick", 45893 "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", 45894 "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew", 45895 "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", 45896 "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", 45897 "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink", 45898 "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", 45899 "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", 45900 "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", 45901 "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", 45902 "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", 45903 "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", 45904 "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", 45905 "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", 45906 "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", 45907 "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", 45908 "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan", 45909 "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", 45910 "whitesmoke", "yellow", "yellowgreen" 45911 ], colorKeywords = keySet(colorKeywords_); 45912 45913 var valueKeywords_ = [ 45914 "above", "absolute", "activeborder", "additive", "activecaption", "afar", 45915 "after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate", 45916 "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", 45917 "arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page", 45918 "avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary", 45919 "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", 45920 "both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel", 45921 "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian", 45922 "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", 45923 "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch", 45924 "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", 45925 "col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse", 45926 "compact", "condensed", "contain", "content", "contents", 45927 "content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop", 45928 "cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal", 45929 "decimal-leading-zero", "default", "default-button", "dense", "destination-atop", 45930 "destination-in", "destination-out", "destination-over", "devanagari", "difference", 45931 "disc", "discard", "disclosure-closed", "disclosure-open", "document", 45932 "dot-dash", "dot-dot-dash", 45933 "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", 45934 "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
45935 "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", 45936 "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", 45937 "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", 45938 "ethiopic-halehame-gez", "ethiopic-halehame-om-et", 45939 "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", 45940 "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", 45941 "ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed", 45942 "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes", 45943 "forwards", "from", "geometricPrecision", "georgian", "graytext", "grid", "groove", 45944 "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew", 45945 "help", "hidden", "hide", "higher", "highlight", "highlighttext", 45946 "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore", 45947 "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", 45948 "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", 45949 "inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert", 45950 "italic", "japanese-formal", "japanese-informal", "justify", "kannada", 45951 "katakana", "katakana-iroha", "keep-all", "khmer", 45952 "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal", 45953 "landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten", 45954 "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem", 45955 "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", 45956 "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", 45957 "lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d", 45958 "media-controls-background", "media-current-time-display", 45959 "media-fullscreen-button", "media-mute-button", "media-play-button", 45960 "media-return-to-realtime-button", "media-rewind-button", 45961 "media-seek-back-button", "media-seek-forward-button", "media-slider", 45962 "media-sliderthumb", "media-time-remaining-display", "media-volume-slider", 45963 "media-volume-slider-container", "media-volume-sliderthumb", "medium", 45964 "menu", "menulist", "menulist-button", "menulist-text", 45965 "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", 45966 "mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize", 45967 "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", 45968 "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", 45969 "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote", 45970 "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", 45971 "outside", "outside-shape", "overlay", "overline", "padding", "padding-box", 45972 "painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter", 45973 "pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", 45974 "progress", "push-button", "radial-gradient", "radio", "read-only", 45975 "read-write", "read-write-plaintext-only", "rectangle", "region", 45976 "relative", "repeat", "repeating-linear-gradient", 45977 "repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse", 45978 "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY", 45979 "rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running", 45980 "s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen", 45981 "scroll", "scrollbar", "scroll-position", "se-resize", "searchfield", 45982 "searchfield-cancel-button", "searchfield-decoration", 45983 "searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end", 45984 "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", 45985 "simp-chinese-formal", "simp-chinese-informal", "single", 45986 "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
vendor: 19,176 bytes, lines 45987-46502
45987 "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", 45988 "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali", 45989 "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square", 45990 "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", 45991 "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table", 45992 "table-caption", "table-cell", "table-column", "table-column-group", 45993 "table-footer-group", "table-header-group", "table-row", "table-row-group", 45994 "tamil", 45995 "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", 45996 "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", 45997 "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", 45998 "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", 45999 "trad-chinese-formal", "trad-chinese-informal", "transform", 46000 "translate", "translate3d", "translateX", "translateY", "translateZ", 46001 "transparent", "ultra-condensed", "ultra-expanded", "underline", "unset", "up", 46002 "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", 46003 "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", 46004 "var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", 46005 "visibleStroke", "visual", "w-resize", "wait", "wave", "wider", 46006 "window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor", 46007 "xx-large", "xx-small" 46008 ], valueKeywords = keySet(valueKeywords_); 46009 46010 var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_) 46011 .concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_) 46012 .concat(valueKeywords_); 46013 CodeMirror.registerHelper("hintWords", "css", allWords); 46014 46015 function tokenCComment(stream, state) { 46016 var maybeEnd = false, ch; 46017 while ((ch = stream.next()) != null) { 46018 if (maybeEnd && ch == "/") { 46019 state.tokenize = null; 46020 break; 46021 } 46022 maybeEnd = (ch == "*"); 46023 } 46024 return ["comment", "comment"]; 46025 } 46026 46027 CodeMirror.defineMIME("text/css", { 46028 documentTypes: documentTypes, 46029 mediaTypes: mediaTypes, 46030 mediaFeatures: mediaFeatures, 46031 mediaValueKeywords: mediaValueKeywords, 46032 propertyKeywords: propertyKeywords, 46033 nonStandardPropertyKeywords: nonStandardPropertyKeywords, 46034 fontProperties: fontProperties, 46035 counterDescriptors: counterDescriptors, 46036 colorKeywords: colorKeywords, 46037 valueKeywords: valueKeywords, 46038 tokenHooks: { 46039 "/": function(stream, state) { 46040 if (!stream.eat("*")) return false; 46041 state.tokenize = tokenCComment; 46042 return tokenCComment(stream, state); 46043 } 46044 }, 46045 name: "css" 46046 }); 46047 46048 CodeMirror.defineMIME("text/x-scss", { 46049 mediaTypes: mediaTypes, 46050 mediaFeatures: mediaFeatures, 46051 mediaValueKeywords: mediaValueKeywords, 46052 propertyKeywords: propertyKeywords, 46053 nonStandardPropertyKeywords: nonStandardPropertyKeywords, 46054 colorKeywords: colorKeywords, 46055 valueKeywords: valueKeywords, 46056 fontProperties: fontProperties, 46057 allowNested: true, 46058 lineComment: "//", 46059 tokenHooks: { 46060 "/": function(stream, state) { 46061 if (stream.eat("/")) { 46062 stream.skipToEnd(); 46063 return ["comment", "comment"]; 46064 } else if (stream.eat("*")) { 46065 state.tokenize = tokenCComment; 46066 return tokenCComment(stream, state); 46067 } else { 46068 return ["operator", "operator"]; 46069 } 46070 }, 46071 ":": function(stream) { 46072 if (stream.match(/\s*\{/, false)) 46073 return [null, null] 46074 return false; 46075 }, 46076 "$": function(stream) { 46077 stream.match(/^[\w-]+/); 46078 if (stream.match(/^\s*:/, false)) 46079 return ["variable-2", "variable-definition"]; 46080 return ["variable-2", "variable"]; 46081 }, 46082 "#": function(stream) { 46083 if (!stream.eat("{")) return false; 46084 return [null, "interpolation"]; 46085 } 46086 }, 46087 name: "css", 46088 helperType: "scss" 46089 }); 46090 46091 CodeMirror.defineMIME("text/x-less", { 46092 mediaTypes: mediaTypes, 46093 mediaFeatures: mediaFeatures, 46094 mediaValueKeywords: mediaValueKeywords, 46095 propertyKeywords: propertyKeywords, 46096 nonStandardPropertyKeywords: nonStandardPropertyKeywords, 46097 colorKeywords: colorKeywords, 46098 valueKeywords: valueKeywords, 46099 fontProperties: fontProperties, 46100 allowNested: true, 46101 lineComment: "//", 46102 tokenHooks: { 46103 "/": function(stream, state) { 46104 if (stream.eat("/")) { 46105 stream.skipToEnd(); 46106 return ["comment", "comment"]; 46107 } else if (stream.eat("*")) { 46108 state.tokenize = tokenCComment; 46109 return tokenCComment(stream, state); 46110 } else { 46111 return ["operator", "operator"]; 46112 } 46113 }, 46114 "@": function(stream) { 46115 if (stream.eat("{")) return [null, "interpolation"]; 46116 if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i, false)) return false; 46117 stream.eatWhile(/[\w\\\-]/); 46118 if (stream.match(/^\s*:/, false)) 46119 return ["variable-2", "variable-definition"]; 46120 return ["variable-2", "variable"]; 46121 }, 46122 "&": function() { 46123 return ["atom", "atom"]; 46124 } 46125 }, 46126 name: "css", 46127 helperType: "less" 46128 }); 46129 46130 CodeMirror.defineMIME("text/x-gss", { 46131 documentTypes: documentTypes, 46132 mediaTypes: mediaTypes, 46133 mediaFeatures: mediaFeatures, 46134 propertyKeywords: propertyKeywords, 46135 nonStandardPropertyKeywords: nonStandardPropertyKeywords, 46136 fontProperties: fontProperties, 46137 counterDescriptors: counterDescriptors, 46138 colorKeywords: colorKeywords, 46139 valueKeywords: valueKeywords, 46140 supportsAtComponent: true, 46141 tokenHooks: { 46142 "/": function(stream, state) { 46143 if (!stream.eat("*")) return false; 46144 state.tokenize = tokenCComment; 46145 return tokenCComment(stream, state); 46146 } 46147 }, 46148 name: "css", 46149 helperType: "gss" 46150 }); 46151 46152}); 46153 46154},{"../../lib/codemirror":6}],8:[function(require,module,exports){ 46155// CodeMirror, copyright (c) by Marijn Haverbeke and others 46156// Distributed under an MIT license: http://codemirror.net/LICENSE 46157 46158(function(mod) { 46159 if (typeof exports == "object" && typeof module == "object") // CommonJS 46160 mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css")); 46161 else if (typeof define == "function" && define.amd) // AMD 46162 define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod); 46163 else // Plain browser env 46164 mod(CodeMirror); 46165})(function(CodeMirror) { 46166 "use strict"; 46167 46168 var defaultTags = { 46169 script: [ 46170 ["lang", /(javascript|babel)/i, "javascript"], 46171 ["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"], 46172 ["type", /./, "text/plain"], 46173 [null, null, "javascript"] 46174 ], 46175 style: [ 46176 ["lang", /^css$/i, "css"], 46177 ["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"], 46178 ["type", /./, "text/plain"], 46179 [null, null, "css"] 46180 ] 46181 }; 46182 46183 function maybeBackup(stream, pat, style) { 46184 var cur = stream.current(), close = cur.search(pat); 46185 if (close > -1) { 46186 stream.backUp(cur.length - close); 46187 } else if (cur.match(/<\/?$/)) { 46188 stream.backUp(cur.length); 46189 if (!stream.match(pat, false)) stream.match(cur); 46190 } 46191 return style; 46192 } 46193 46194 var attrRegexpCache = {}; 46195 function getAttrRegexp(attr) { 46196 var regexp = attrRegexpCache[attr]; 46197 if (regexp) return regexp; 46198 return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*"); 46199 } 46200 46201 function getAttrValue(text, attr) { 46202 var match = text.match(getAttrRegexp(attr)) 46203 return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : "" 46204 } 46205 46206 function getTagRegexp(tagName, anchored) { 46207 return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i"); 46208 } 46209 46210 function addTags(from, to) { 46211 for (var tag in from) { 46212 var dest = to[tag] || (to[tag] = []); 46213 var source = from[tag]; 46214 for (var i = source.length - 1; i >= 0; i--) 46215 dest.unshift(source[i]) 46216 } 46217 } 46218 46219 function findMatchingMode(tagInfo, tagText) { 46220 for (var i = 0; i < tagInfo.length; i++) { 46221 var spec = tagInfo[i]; 46222 if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2]; 46223 } 46224 } 46225 46226 CodeMirror.defineMode("htmlmixed", function (config, parserConfig) { 46227 var htmlMode = CodeMirror.getMode(config, { 46228 name: "xml", 46229 htmlMode: true, 46230 multilineTagIndentFactor: parserConfig.multilineTagIndentFactor, 46231 multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag 46232 }); 46233 46234 var tags = {}; 46235 var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes; 46236 addTags(defaultTags, tags); 46237 if (configTags) addTags(configTags, tags); 46238 if (configScript) for (var i = configScript.length - 1; i >= 0; i--) 46239 tags.script.unshift(["type", configScript[i].matches, configScript[i].mode]) 46240 46241 function html(stream, state) { 46242 var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName 46243 if (tag && !/[<>\s\/]/.test(stream.current()) && 46244 (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) && 46245 tags.hasOwnProperty(tagName)) { 46246 state.inTag = tagName + " " 46247 } else if (state.inTag && tag && />$/.test(stream.current())) { 46248 var inTag = /^([\S]+) (.*)/.exec(state.inTag) 46249 state.inTag = null 46250 var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2]) 46251 var mode = CodeMirror.getMode(config, modeSpec) 46252 var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false); 46253 state.token = function (stream, state) { 46254 if (stream.match(endTagA, false)) { 46255 state.token = html; 46256 state.localState = state.localMode = null; 46257 return null; 46258 } 46259 return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState)); 46260 }; 46261 state.localMode = mode; 46262 state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "")); 46263 } else if (state.inTag) { 46264 state.inTag += stream.current() 46265 if (stream.eol()) state.inTag += " " 46266 } 46267 return style; 46268 }; 46269 46270 return { 46271 startState: function () { 46272 var state = CodeMirror.startState(htmlMode); 46273 return {token: html, inTag: null, localMode: null, localState: null, htmlState: state}; 46274 }, 46275 46276 copyState: function (state) { 46277 var local; 46278 if (state.localState) { 46279 local = CodeMirror.copyState(state.localMode, state.localState); 46280 } 46281 return {token: state.token, inTag: state.inTag, 46282 localMode: state.localMode, localState: local, 46283 htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; 46284 }, 46285 46286 token: function (stream, state) { 46287 return state.token(stream, state); 46288 }, 46289 46290 indent: function (state, textAfter, line) { 46291 if (!state.localMode || /^\s*<\//.test(textAfter)) 46292 return htmlMode.indent(state.htmlState, textAfter); 46293 else if (state.localMode.indent) 46294 return state.localMode.indent(state.localState, textAfter, line); 46295 else 46296 return CodeMirror.Pass; 46297 }, 46298 46299 innerMode: function (state) { 46300 return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode}; 46301 } 46302 }; 46303 }, "xml", "javascript", "css"); 46304 46305 CodeMirror.defineMIME("text/html", "htmlmixed"); 46306}); 46307 46308},{"../../lib/codemirror":6,"../css/css":7,"../javascript/javascript":9,"../xml/xml":10}],9:[function(require,module,exports){ 46309// CodeMirror, copyright (c) by Marijn Haverbeke and others 46310// Distributed under an MIT license: http://codemirror.net/LICENSE 46311 46312(function(mod) { 46313 if (typeof exports == "object" && typeof module == "object") // CommonJS 46314 mod(require("../../lib/codemirror")); 46315 else if (typeof define == "function" && define.amd) // AMD 46316 define(["../../lib/codemirror"], mod); 46317 else // Plain browser env 46318 mod(CodeMirror); 46319})(function(CodeMirror) { 46320"use strict"; 46321 46322CodeMirror.defineMode("javascript", function(config, parserConfig) { 46323 var indentUnit = config.indentUnit; 46324 var statementIndent = parserConfig.statementIndent; 46325 var jsonldMode = parserConfig.jsonld; 46326 var jsonMode = parserConfig.json || jsonldMode; 46327 var isTS = parserConfig.typescript; 46328 var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; 46329 46330 // Tokenizer 46331 46332 var keywords = function(){ 46333 function kw(type) {return {type: type, style: "keyword"};} 46334 var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d"); 46335 var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 46336 46337 return { 46338 "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 46339 "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C, 46340 "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"), 46341 "function": kw("function"), "catch": kw("catch"), 46342 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 46343 "in": operator, "typeof": operator, "instanceof": operator, 46344 "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, 46345 "this": kw("this"), "class": kw("class"), "super": kw("atom"), 46346 "yield": C, "export": kw("export"), "import": kw("import"), "extends": C, 46347 "await": C 46348 }; 46349 }(); 46350 46351 var isOperatorChar = /[+\-*&%=<>!?|~^@]/; 46352 var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; 46353 46354 function readRegexp(stream) { 46355 var escaped = false, next, inSet = false; 46356 while ((next = stream.next()) != null) { 46357 if (!escaped) { 46358 if (next == "/" && !inSet) return; 46359 if (next == "[") inSet = true; 46360 else if (inSet && next == "]") inSet = false; 46361 } 46362 escaped = !escaped && next == "\\"; 46363 } 46364 } 46365 46366 // Used as scratch variables to communicate multiple values without 46367 // consing up tons of objects. 46368 var type, content; 46369 function ret(tp, style, cont) { 46370 type = tp; content = cont; 46371 return style; 46372 } 46373 function tokenBase(stream, state) { 46374 var ch = stream.next(); 46375 if (ch == '"' || ch == "'") { 46376 state.tokenize = tokenString(ch); 46377 return state.tokenize(stream, state); 46378 } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { 46379 return ret("number", "number"); 46380 } else if (ch == "." && stream.match("..")) { 46381 return ret("spread", "meta"); 46382 } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 46383 return ret(ch); 46384 } else if (ch == "=" && stream.eat(">")) { 46385 return ret("=>", "operator"); 46386 } else if (ch == "0" && stream.eat(/x/i)) { 46387 stream.eatWhile(/[\da-f]/i); 46388 return ret("number", "number"); 46389 } else if (ch == "0" && stream.eat(/o/i)) { 46390 stream.eatWhile(/[0-7]/i); 46391 return ret("number", "number"); 46392 } else if (ch == "0" && stream.eat(/b/i)) { 46393 stream.eatWhile(/[01]/i); 46394 return ret("number", "number"); 46395 } else if (/\d/.test(ch)) { 46396 stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 46397 return ret("number", "number"); 46398 } else if (ch == "/") { 46399 if (stream.eat("*")) { 46400 state.tokenize = tokenComment; 46401 return tokenComment(stream, state); 46402 } else if (stream.eat("/")) { 46403 stream.skipToEnd(); 46404 return ret("comment", "comment"); 46405 } else if (expressionAllowed(stream, state, 1)) { 46406 readRegexp(stream); 46407 stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); 46408 return ret("regexp", "string-2"); 46409 } else { 46410 stream.eat("="); 46411 return ret("operator", "operator", stream.current()); 46412 } 46413 } else if (ch == "`") { 46414 state.tokenize = tokenQuasi; 46415 return tokenQuasi(stream, state); 46416 } else if (ch == "#") { 46417 stream.skipToEnd(); 46418 return ret("error", "error"); 46419 } else if (isOperatorChar.test(ch)) { 46420 if (ch != ">" || !state.lexical || state.lexical.type != ">") { 46421 if (stream.eat("=")) { 46422 if (ch == "!" || ch == "=") stream.eat("=") 46423 } else if (/[<>*+\-]/.test(ch)) { 46424 stream.eat(ch) 46425 if (ch == ">") stream.eat(ch) 46426 } 46427 } 46428 return ret("operator", "operator", stream.current()); 46429 } else if (wordRE.test(ch)) { 46430 stream.eatWhile(wordRE); 46431 var word = stream.current() 46432 if (state.lastType != ".") { 46433 if (keywords.propertyIsEnumerable(word)) { 46434 var kw = keywords[word] 46435 return ret(kw.type, kw.style, word) 46436 } 46437 if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false)) 46438 return ret("async", "keyword", word) 46439 } 46440 return ret("variable", "variable", word) 46441 } 46442 } 46443 46444 function tokenString(quote) { 46445 return function(stream, state) { 46446 var escaped = false, next; 46447 if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ 46448 state.tokenize = tokenBase; 46449 return ret("jsonld-keyword", "meta"); 46450 } 46451 while ((next = stream.next()) != null) { 46452 if (next == quote && !escaped) break; 46453 escaped = !escaped && next == "\\"; 46454 } 46455 if (!escaped) state.tokenize = tokenBase; 46456 return ret("string", "string"); 46457 }; 46458 } 46459 46460 function tokenComment(stream, state) { 46461 var maybeEnd = false, ch; 46462 while (ch = stream.next()) { 46463 if (ch == "/" && maybeEnd) { 46464 state.tokenize = tokenBase; 46465 break; 46466 } 46467 maybeEnd = (ch == "*"); 46468 } 46469 return ret("comment", "comment"); 46470 } 46471 46472 function tokenQuasi(stream, state) { 46473 var escaped = false, next; 46474 while ((next = stream.next()) != null) { 46475 if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { 46476 state.tokenize = tokenBase; 46477 break; 46478 } 46479 escaped = !escaped && next == "\\"; 46480 } 46481 return ret("quasi", "string-2", stream.current()); 46482 } 46483 46484 var brackets = "([{}])"; 46485 // This is a crude lookahead trick to try and notice that we're 46486 // parsing the argument patterns for a fat-arrow function before we 46487 // actually hit the arrow token. It only works if the arrow is on 46488 // the same line as the arguments and there's no strange noise 46489 // (comments) in between. Fallback is to only notice when we hit the 46490 // arrow, and not declare the arguments as locals for the arrow 46491 // body. 46492 function findFatArrow(stream, state) { 46493 if (state.fatArrowAt) state.fatArrowAt = null; 46494 var arrow = stream.string.indexOf("=>", stream.start); 46495 if (arrow < 0) return; 46496 46497 if (isTS) { // Try to skip TypeScript return type declarations after the arguments 46498 var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow)) 46499 if (m) arrow = m.index 46500 } 46501 46502 var depth = 0, sawSomething = false;
46503 for (var pos = arrow - 1; pos >= 0; --pos) { 46504 var ch = stream.string.charAt(pos); 46505 var bracket = brackets.indexOf(ch); 46506 if (bracket >= 0 && bracket < 3) { 46507 if (!depth) { ++pos; break; } 46508 if (--depth == 0) { if (ch == "(") sawSomething = true; break; } 46509 } else if (bracket >= 3 && bracket < 6) { 46510 ++depth; 46511 } else if (wordRE.test(ch)) { 46512 sawSomething = true; 46513 } else if (/["'\/]/.test(ch)) { 46514 return; 46515 } else if (sawSomething && !depth) { 46516 ++pos; 46517 break; 46518 } 46519 } 46520 if (sawSomething && !depth) state.fatArrowAt = pos; 46521 } 46522 46523 // Parser 46524 46525 var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; 46526 46527 function JSLexical(indented, column, type, align, prev, info) { 46528 this.indented = indented; 46529 this.column = column; 46530 this.type = type; 46531 this.prev = prev; 46532 this.info = info; 46533 if (align != null) this.align = align; 46534 } 46535 46536 function inScope(state, varname) { 46537 for (var v = state.localVars; v; v = v.next) 46538 if (v.name == varname) return true; 46539 for (var cx = state.context; cx; cx = cx.prev) { 46540 for (var v = cx.vars; v; v = v.next) 46541 if (v.name == varname) return true; 46542 } 46543 } 46544 46545 function parseJS(state, style, type, content, stream) { 46546 var cc = state.cc; 46547 // Communicate our context to the combinators. 46548 // (Less wasteful than consing up a hundred closures on every call.) 46549 cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; 46550 46551 if (!state.lexical.hasOwnProperty("align")) 46552 state.lexical.align = true; 46553 46554 while(true) { 46555 var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 46556 if (combinator(type, content)) { 46557 while(cc.length && cc[cc.length - 1].lex) 46558 cc.pop()(); 46559 if (cx.marked) return cx.marked; 46560 if (type == "variable" && inScope(state, content)) return "variable-2"; 46561 return style; 46562 } 46563 } 46564 } 46565 46566 // Combinator utils 46567 46568 var cx = {state: null, column: null, marked: null, cc: null}; 46569 function pass() { 46570 for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 46571 } 46572 function cont() { 46573 pass.apply(null, arguments); 46574 return true; 46575 } 46576 function register(varname) { 46577 function inList(list) { 46578 for (var v = list; v; v = v.next) 46579 if (v.name == varname) return true; 46580 return false; 46581 } 46582 var state = cx.state; 46583 cx.marked = "def"; 46584 if (state.context) { 46585 if (inList(state.localVars)) return; 46586 state.localVars = {name: varname, next: state.localVars}; 46587 } else { 46588 if (inList(state.globalVars)) return; 46589 if (parserConfig.globalVars) 46590 state.globalVars = {name: varname, next: state.globalVars}; 46591 } 46592 } 46593 46594 function isModifier(name) { 46595 return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly" 46596 } 46597 46598 // Combinators 46599 46600 var defaultVars = {name: "this", next: {name: "arguments"}}; 46601 function pushcontext() { 46602 cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 46603 cx.state.localVars = defaultVars; 46604 } 46605 function popcontext() { 46606 cx.state.localVars = cx.state.context.vars; 46607 cx.state.context = cx.state.context.prev; 46608 } 46609 function pushlex(type, info) { 46610 var result = function() { 46611 var state = cx.state, indent = state.indented; 46612 if (state.lexical.type == "stat") indent = state.lexical.indented; 46613 else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) 46614 indent = outer.indented; 46615 state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); 46616 }; 46617 result.lex = true; 46618 return result; 46619 } 46620 function poplex() { 46621 var state = cx.state; 46622 if (state.lexical.prev) { 46623 if (state.lexical.type == ")") 46624 state.indented = state.lexical.indented; 46625 state.lexical = state.lexical.prev; 46626 } 46627 } 46628 poplex.lex = true; 46629 46630 function expect(wanted) { 46631 function exp(type) { 46632 if (type == wanted) return cont(); 46633 else if (wanted == ";") return pass(); 46634 else return cont(exp); 46635 }; 46636 return exp; 46637 } 46638
vendor: 37,501 bytes, lines 46639-47624
46639 function statement(type, value) { 46640 if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); 46641 if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex); 46642 if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 46643 if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex); 46644 if (type == "debugger") return cont(expect(";")); 46645 if (type == "{") return cont(pushlex("}"), block, poplex); 46646 if (type == ";") return cont(); 46647 if (type == "if") { 46648 if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) 46649 cx.state.cc.pop()(); 46650 return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse); 46651 } 46652 if (type == "function") return cont(functiondef); 46653 if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); 46654 if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), className, poplex); } 46655 if (type == "variable") { 46656 if (isTS && value == "declare") { 46657 cx.marked = "keyword" 46658 return cont(statement) 46659 } else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) { 46660 cx.marked = "keyword" 46661 if (value == "enum") return cont(enumdef); 46662 else if (value == "type") return cont(typeexpr, expect("operator"), typeexpr, expect(";")); 46663 else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex) 46664 } else if (isTS && value == "namespace") { 46665 cx.marked = "keyword" 46666 return cont(pushlex("form"), expression, block, poplex) 46667 } else if (isTS && value == "abstract") { 46668 cx.marked = "keyword" 46669 return cont(statement) 46670 } else { 46671 return cont(pushlex("stat"), maybelabel); 46672 } 46673 } 46674 if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), 46675 block, poplex, poplex); 46676 if (type == "case") return cont(expression, expect(":")); 46677 if (type == "default") return cont(expect(":")); 46678 if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 46679 statement, poplex, popcontext); 46680 if (type == "export") return cont(pushlex("stat"), afterExport, poplex); 46681 if (type == "import") return cont(pushlex("stat"), afterImport, poplex); 46682 if (type == "async") return cont(statement) 46683 if (value == "@") return cont(expression, statement) 46684 return pass(pushlex("stat"), expression, expect(";"), poplex); 46685 } 46686 function expression(type, value) { 46687 return expressionInner(type, value, false); 46688 } 46689 function expressionNoComma(type, value) { 46690 return expressionInner(type, value, true); 46691 } 46692 function parenExpr(type) { 46693 if (type != "(") return pass() 46694 return cont(pushlex(")"), expression, expect(")"), poplex) 46695 } 46696 function expressionInner(type, value, noComma) { 46697 if (cx.state.fatArrowAt == cx.stream.start) { 46698 var body = noComma ? arrowBodyNoComma : arrowBody; 46699 if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext); 46700 else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); 46701 } 46702 46703 var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; 46704 if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); 46705 if (type == "function") return cont(functiondef, maybeop); 46706 if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); } 46707 if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression); 46708 if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); 46709 if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); 46710 if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); 46711 if (type == "{") return contCommasep(objprop, "}", null, maybeop); 46712 if (type == "quasi") return pass(quasi, maybeop); 46713 if (type == "new") return cont(maybeTarget(noComma)); 46714 if (type == "import") return cont(expression); 46715 return cont(); 46716 } 46717 function maybeexpression(type) { 46718 if (type.match(/[;\}\)\],]/)) return pass(); 46719 return pass(expression); 46720 } 46721 46722 function maybeoperatorComma(type, value) { 46723 if (type == ",") return cont(expression); 46724 return maybeoperatorNoComma(type, value, false); 46725 } 46726 function maybeoperatorNoComma(type, value, noComma) { 46727 var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; 46728 var expr = noComma == false ? expression : expressionNoComma; 46729 if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); 46730 if (type == "operator") { 46731 if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me); 46732 if (isTS && value == "<" && cx.stream.match(/^([^>]|<.*?>)*>\s*\(/, false)) 46733 return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me); 46734 if (value == "?") return cont(expression, expect(":"), expr); 46735 return cont(expr); 46736 } 46737 if (type == "quasi") { return pass(quasi, me); } 46738 if (type == ";") return; 46739 if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); 46740 if (type == ".") return cont(property, me); 46741 if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); 46742 if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) } 46743 if (type == "regexp") { 46744 cx.state.lastType = cx.marked = "operator" 46745 cx.stream.backUp(cx.stream.pos - cx.stream.start - 1) 46746 return cont(expr) 46747 } 46748 } 46749 function quasi(type, value) { 46750 if (type != "quasi") return pass(); 46751 if (value.slice(value.length - 2) != "${") return cont(quasi); 46752 return cont(expression, continueQuasi); 46753 } 46754 function continueQuasi(type) { 46755 if (type == "}") { 46756 cx.marked = "string-2"; 46757 cx.state.tokenize = tokenQuasi; 46758 return cont(quasi); 46759 } 46760 } 46761 function arrowBody(type) { 46762 findFatArrow(cx.stream, cx.state); 46763 return pass(type == "{" ? statement : expression); 46764 } 46765 function arrowBodyNoComma(type) { 46766 findFatArrow(cx.stream, cx.state); 46767 return pass(type == "{" ? statement : expressionNoComma); 46768 } 46769 function maybeTarget(noComma) { 46770 return function(type) { 46771 if (type == ".") return cont(noComma ? targetNoComma : target); 46772 else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma) 46773 else return pass(noComma ? expressionNoComma : expression); 46774 }; 46775 } 46776 function target(_, value) { 46777 if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } 46778 } 46779 function targetNoComma(_, value) { 46780 if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } 46781 } 46782 function maybelabel(type) { 46783 if (type == ":") return cont(poplex, statement); 46784 return pass(maybeoperatorComma, expect(";"), poplex); 46785 } 46786 function property(type) { 46787 if (type == "variable") {cx.marked = "property"; return cont();} 46788 } 46789 function objprop(type, value) { 46790 if (type == "async") { 46791 cx.marked = "property"; 46792 return cont(objprop); 46793 } else if (type == "variable" || cx.style == "keyword") { 46794 cx.marked = "property"; 46795 if (value == "get" || value == "set") return cont(getterSetter); 46796 var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params 46797 if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false))) 46798 cx.state.fatArrowAt = cx.stream.pos + m[0].length 46799 return cont(afterprop); 46800 } else if (type == "number" || type == "string") { 46801 cx.marked = jsonldMode ? "property" : (cx.style + " property"); 46802 return cont(afterprop); 46803 } else if (type == "jsonld-keyword") { 46804 return cont(afterprop); 46805 } else if (isTS && isModifier(value)) { 46806 cx.marked = "keyword" 46807 return cont(objprop) 46808 } else if (type == "[") { 46809 return cont(expression, maybetype, expect("]"), afterprop); 46810 } else if (type == "spread") { 46811 return cont(expressionNoComma, afterprop); 46812 } else if (value == "*") { 46813 cx.marked = "keyword"; 46814 return cont(objprop); 46815 } else if (type == ":") { 46816 return pass(afterprop) 46817 } 46818 } 46819 function getterSetter(type) { 46820 if (type != "variable") return pass(afterprop); 46821 cx.marked = "property"; 46822 return cont(functiondef); 46823 } 46824 function afterprop(type) { 46825 if (type == ":") return cont(expressionNoComma); 46826 if (type == "(") return pass(functiondef); 46827 } 46828 function commasep(what, end, sep) { 46829 function proceed(type, value) { 46830 if (sep ? sep.indexOf(type) > -1 : type == ",") { 46831 var lex = cx.state.lexical; 46832 if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; 46833 return cont(function(type, value) { 46834 if (type == end || value == end) return pass() 46835 return pass(what) 46836 }, proceed); 46837 } 46838 if (type == end || value == end) return cont(); 46839 return cont(expect(end)); 46840 } 46841 return function(type, value) { 46842 if (type == end || value == end) return cont(); 46843 return pass(what, proceed); 46844 }; 46845 } 46846 function contCommasep(what, end, info) { 46847 for (var i = 3; i < arguments.length; i++) 46848 cx.cc.push(arguments[i]); 46849 return cont(pushlex(end, info), commasep(what, end), poplex); 46850 } 46851 function block(type) { 46852 if (type == "}") return cont(); 46853 return pass(statement, block); 46854 } 46855 function maybetype(type, value) { 46856 if (isTS) { 46857 if (type == ":") return cont(typeexpr); 46858 if (value == "?") return cont(maybetype); 46859 } 46860 } 46861 function mayberettype(type) { 46862 if (isTS && type == ":") { 46863 if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr) 46864 else return cont(typeexpr) 46865 } 46866 } 46867 function isKW(_, value) { 46868 if (value == "is") { 46869 cx.marked = "keyword" 46870 return cont() 46871 } 46872 } 46873 function typeexpr(type, value) { 46874 if (value == "keyof" || value == "typeof") { 46875 cx.marked = "keyword" 46876 return cont(value == "keyof" ? typeexpr : expressionNoComma) 46877 } 46878 if (type == "variable" || value == "void") { 46879 cx.marked = "type" 46880 return cont(afterType) 46881 } 46882 if (type == "string" || type == "number" || type == "atom") return cont(afterType); 46883 if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType) 46884 if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType) 46885 if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType) 46886 if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr) 46887 } 46888 function maybeReturnType(type) { 46889 if (type == "=>") return cont(typeexpr) 46890 } 46891 function typeprop(type, value) { 46892 if (type == "variable" || cx.style == "keyword") { 46893 cx.marked = "property" 46894 return cont(typeprop) 46895 } else if (value == "?") { 46896 return cont(typeprop) 46897 } else if (type == ":") { 46898 return cont(typeexpr) 46899 } else if (type == "[") { 46900 return cont(expression, maybetype, expect("]"), typeprop) 46901 } 46902 } 46903 function typearg(type, value) { 46904 if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg) 46905 if (type == ":") return cont(typeexpr) 46906 return pass(typeexpr) 46907 } 46908 function afterType(type, value) { 46909 if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType) 46910 if (value == "|" || type == "." || value == "&") return cont(typeexpr) 46911 if (type == "[") return cont(expect("]"), afterType) 46912 if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) } 46913 } 46914 function maybeTypeArgs(_, value) { 46915 if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType) 46916 } 46917 function typeparam() { 46918 return pass(typeexpr, maybeTypeDefault) 46919 } 46920 function maybeTypeDefault(_, value) { 46921 if (value == "=") return cont(typeexpr) 46922 } 46923 function vardef(_, value) { 46924 if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)} 46925 return pass(pattern, maybetype, maybeAssign, vardefCont); 46926 } 46927 function pattern(type, value) { 46928 if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) } 46929 if (type == "variable") { register(value); return cont(); } 46930 if (type == "spread") return cont(pattern); 46931 if (type == "[") return contCommasep(pattern, "]"); 46932 if (type == "{") return contCommasep(proppattern, "}"); 46933 } 46934 function proppattern(type, value) { 46935 if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { 46936 register(value); 46937 return cont(maybeAssign); 46938 } 46939 if (type == "variable") cx.marked = "property"; 46940 if (type == "spread") return cont(pattern); 46941 if (type == "}") return pass(); 46942 return cont(expect(":"), pattern, maybeAssign); 46943 } 46944 function maybeAssign(_type, value) { 46945 if (value == "=") return cont(expressionNoComma); 46946 } 46947 function vardefCont(type) { 46948 if (type == ",") return cont(vardef); 46949 } 46950 function maybeelse(type, value) { 46951 if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); 46952 } 46953 function forspec(type, value) { 46954 if (value == "await") return cont(forspec); 46955 if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); 46956 } 46957 function forspec1(type) { 46958 if (type == "var") return cont(vardef, expect(";"), forspec2); 46959 if (type == ";") return cont(forspec2); 46960 if (type == "variable") return cont(formaybeinof); 46961 return pass(expression, expect(";"), forspec2); 46962 } 46963 function formaybeinof(_type, value) { 46964 if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 46965 return cont(maybeoperatorComma, forspec2); 46966 } 46967 function forspec2(type, value) { 46968 if (type == ";") return cont(forspec3); 46969 if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 46970 return pass(expression, expect(";"), forspec3); 46971 } 46972 function forspec3(type) { 46973 if (type != ")") cont(expression); 46974 } 46975 function functiondef(type, value) { 46976 if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} 46977 if (type == "variable") {register(value); return cont(functiondef);} 46978 if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext); 46979 if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef) 46980 } 46981 function funarg(type, value) { 46982 if (value == "@") cont(expression, funarg) 46983 if (type == "spread") return cont(funarg); 46984 if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); } 46985 return pass(pattern, maybetype, maybeAssign); 46986 } 46987 function classExpression(type, value) { 46988 // Class expressions may have an optional name. 46989 if (type == "variable") return className(type, value); 46990 return classNameAfter(type, value); 46991 } 46992 function className(type, value) { 46993 if (type == "variable") {register(value); return cont(classNameAfter);} 46994 } 46995 function classNameAfter(type, value) { 46996 if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter) 46997 if (value == "extends" || value == "implements" || (isTS && type == ",")) { 46998 if (value == "implements") cx.marked = "keyword"; 46999 return cont(isTS ? typeexpr : expression, classNameAfter); 47000 } 47001 if (type == "{") return cont(pushlex("}"), classBody, poplex); 47002 } 47003 function classBody(type, value) { 47004 if (type == "async" || 47005 (type == "variable" && 47006 (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) && 47007 cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) { 47008 cx.marked = "keyword"; 47009 return cont(classBody); 47010 } 47011 if (type == "variable" || cx.style == "keyword") { 47012 cx.marked = "property"; 47013 return cont(isTS ? classfield : functiondef, classBody); 47014 } 47015 if (type == "[") 47016 return cont(expression, maybetype, expect("]"), isTS ? classfield : functiondef, classBody) 47017 if (value == "*") { 47018 cx.marked = "keyword"; 47019 return cont(classBody); 47020 } 47021 if (type == ";") return cont(classBody); 47022 if (type == "}") return cont(); 47023 if (value == "@") return cont(expression, classBody) 47024 } 47025 function classfield(type, value) { 47026 if (value == "?") return cont(classfield) 47027 if (type == ":") return cont(typeexpr, maybeAssign) 47028 if (value == "=") return cont(expressionNoComma) 47029 return pass(functiondef) 47030 } 47031 function afterExport(type, value) { 47032 if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } 47033 if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } 47034 if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";")); 47035 return pass(statement); 47036 } 47037 function exportField(type, value) { 47038 if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); } 47039 if (type == "variable") return pass(expressionNoComma, exportField); 47040 } 47041 function afterImport(type) { 47042 if (type == "string") return cont(); 47043 if (type == "(") return pass(expression); 47044 return pass(importSpec, maybeMoreImports, maybeFrom); 47045 } 47046 function importSpec(type, value) { 47047 if (type == "{") return contCommasep(importSpec, "}"); 47048 if (type == "variable") register(value); 47049 if (value == "*") cx.marked = "keyword"; 47050 return cont(maybeAs); 47051 } 47052 function maybeMoreImports(type) { 47053 if (type == ",") return cont(importSpec, maybeMoreImports) 47054 } 47055 function maybeAs(_type, value) { 47056 if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } 47057 } 47058 function maybeFrom(_type, value) { 47059 if (value == "from") { cx.marked = "keyword"; return cont(expression); } 47060 } 47061 function arrayLiteral(type) { 47062 if (type == "]") return cont(); 47063 return pass(commasep(expressionNoComma, "]")); 47064 } 47065 function enumdef() { 47066 return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex) 47067 } 47068 function enummember() { 47069 return pass(pattern, maybeAssign); 47070 } 47071 47072 function isContinuedStatement(state, textAfter) { 47073 return state.lastType == "operator" || state.lastType == "," || 47074 isOperatorChar.test(textAfter.charAt(0)) || 47075 /[,.]/.test(textAfter.charAt(0)); 47076 } 47077 47078 function expressionAllowed(stream, state, backUp) { 47079 return state.tokenize == tokenBase && 47080 /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) || 47081 (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) 47082 } 47083 47084 // Interface 47085 47086 return { 47087 startState: function(basecolumn) { 47088 var state = { 47089 tokenize: tokenBase, 47090 lastType: "sof", 47091 cc: [], 47092 lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 47093 localVars: parserConfig.localVars, 47094 context: parserConfig.localVars && {vars: parserConfig.localVars}, 47095 indented: basecolumn || 0 47096 }; 47097 if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") 47098 state.globalVars = parserConfig.globalVars; 47099 return state; 47100 }, 47101 47102 token: function(stream, state) { 47103 if (stream.sol()) { 47104 if (!state.lexical.hasOwnProperty("align")) 47105 state.lexical.align = false; 47106 state.indented = stream.indentation(); 47107 findFatArrow(stream, state); 47108 } 47109 if (state.tokenize != tokenComment && stream.eatSpace()) return null; 47110 var style = state.tokenize(stream, state); 47111 if (type == "comment") return style; 47112 state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; 47113 return parseJS(state, style, type, content, stream); 47114 }, 47115 47116 indent: function(state, textAfter) { 47117 if (state.tokenize == tokenComment) return CodeMirror.Pass; 47118 if (state.tokenize != tokenBase) return 0; 47119 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top 47120 // Kludge to prevent 'maybelse' from blocking lexical scope pops 47121 if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { 47122 var c = state.cc[i]; 47123 if (c == poplex) lexical = lexical.prev; 47124 else if (c != maybeelse) break; 47125 } 47126 while ((lexical.type == "stat" || lexical.type == "form") && 47127 (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) && 47128 (top == maybeoperatorComma || top == maybeoperatorNoComma) && 47129 !/^[,\.=+\-*:?[\(]/.test(textAfter)))) 47130 lexical = lexical.prev; 47131 if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") 47132 lexical = lexical.prev; 47133 var type = lexical.type, closing = firstChar == type; 47134 47135 if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); 47136 else if (type == "form" && firstChar == "{") return lexical.indented; 47137 else if (type == "form") return lexical.indented + indentUnit; 47138 else if (type == "stat") 47139 return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); 47140 else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) 47141 return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 47142 else if (lexical.align) return lexical.column + (closing ? 0 : 1); 47143 else return lexical.indented + (closing ? 0 : indentUnit); 47144 }, 47145 47146 electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, 47147 blockCommentStart: jsonMode ? null : "/*", 47148 blockCommentEnd: jsonMode ? null : "*/", 47149 blockCommentContinue: jsonMode ? null : " * ", 47150 lineComment: jsonMode ? null : "//", 47151 fold: "brace", 47152 closeBrackets: "()[]{}''\"\"``", 47153 47154 helperType: jsonMode ? "json" : "javascript", 47155 jsonldMode: jsonldMode, 47156 jsonMode: jsonMode, 47157 47158 expressionAllowed: expressionAllowed, 47159 47160 skipExpression: function(state) { 47161 var top = state.cc[state.cc.length - 1] 47162 if (top == expression || top == expressionNoComma) state.cc.pop() 47163 } 47164 }; 47165}); 47166 47167CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); 47168 47169CodeMirror.defineMIME("text/javascript", "javascript"); 47170CodeMirror.defineMIME("text/ecmascript", "javascript"); 47171CodeMirror.defineMIME("application/javascript", "javascript"); 47172CodeMirror.defineMIME("application/x-javascript", "javascript"); 47173CodeMirror.defineMIME("application/ecmascript", "javascript"); 47174CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 47175CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); 47176CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); 47177CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 47178CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 47179 47180}); 47181 47182},{"../../lib/codemirror":6}],10:[function(require,module,exports){ 47183// CodeMirror, copyright (c) by Marijn Haverbeke and others 47184// Distributed under an MIT license: http://codemirror.net/LICENSE 47185 47186(function(mod) { 47187 if (typeof exports == "object" && typeof module == "object") // CommonJS 47188 mod(require("../../lib/codemirror")); 47189 else if (typeof define == "function" && define.amd) // AMD 47190 define(["../../lib/codemirror"], mod); 47191 else // Plain browser env 47192 mod(CodeMirror); 47193})(function(CodeMirror) { 47194"use strict"; 47195 47196var htmlConfig = { 47197 autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, 47198 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, 47199 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, 47200 'track': true, 'wbr': true, 'menuitem': true}, 47201 implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, 47202 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, 47203 'th': true, 'tr': true}, 47204 contextGrabbers: { 47205 'dd': {'dd': true, 'dt': true}, 47206 'dt': {'dd': true, 'dt': true}, 47207 'li': {'li': true}, 47208 'option': {'option': true, 'optgroup': true}, 47209 'optgroup': {'optgroup': true}, 47210 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, 47211 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, 47212 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 47213 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, 47214 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, 47215 'rp': {'rp': true, 'rt': true}, 47216 'rt': {'rp': true, 'rt': true}, 47217 'tbody': {'tbody': true, 'tfoot': true}, 47218 'td': {'td': true, 'th': true}, 47219 'tfoot': {'tbody': true}, 47220 'th': {'td': true, 'th': true}, 47221 'thead': {'tbody': true, 'tfoot': true}, 47222 'tr': {'tr': true} 47223 }, 47224 doNotIndent: {"pre": true}, 47225 allowUnquoted: true, 47226 allowMissing: true, 47227 caseFold: true 47228} 47229 47230var xmlConfig = { 47231 autoSelfClosers: {}, 47232 implicitlyClosed: {}, 47233 contextGrabbers: {}, 47234 doNotIndent: {}, 47235 allowUnquoted: false, 47236 allowMissing: false, 47237 allowMissingTagName: false, 47238 caseFold: false 47239} 47240 47241CodeMirror.defineMode("xml", function(editorConf, config_) { 47242 var indentUnit = editorConf.indentUnit 47243 var config = {} 47244 var defaults = config_.htmlMode ? htmlConfig : xmlConfig 47245 for (var prop in defaults) config[prop] = defaults[prop] 47246 for (var prop in config_) config[prop] = config_[prop] 47247 47248 // Return variables for tokenizers 47249 var type, setStyle; 47250 47251 function inText(stream, state) { 47252 function chain(parser) { 47253 state.tokenize = parser; 47254 return parser(stream, state); 47255 } 47256 47257 var ch = stream.next(); 47258 if (ch == "<") { 47259 if (stream.eat("!")) { 47260 if (stream.eat("[")) { 47261 if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); 47262 else return null; 47263 } else if (stream.match("--")) { 47264 return chain(inBlock("comment", "-->")); 47265 } else if (stream.match("DOCTYPE", true, true)) { 47266 stream.eatWhile(/[\w\._\-]/); 47267 return chain(doctype(1)); 47268 } else { 47269 return null; 47270 } 47271 } else if (stream.eat("?")) { 47272 stream.eatWhile(/[\w\._\-]/); 47273 state.tokenize = inBlock("meta", "?>"); 47274 return "meta"; 47275 } else { 47276 type = stream.eat("/") ? "closeTag" : "openTag"; 47277 state.tokenize = inTag; 47278 return "tag bracket"; 47279 } 47280 } else if (ch == "&") { 47281 var ok; 47282 if (stream.eat("#")) { 47283 if (stream.eat("x")) { 47284 ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); 47285 } else { 47286 ok = stream.eatWhile(/[\d]/) && stream.eat(";"); 47287 } 47288 } else { 47289 ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); 47290 } 47291 return ok ? "atom" : "error"; 47292 } else { 47293 stream.eatWhile(/[^&<]/); 47294 return null; 47295 } 47296 } 47297 inText.isInText = true; 47298 47299 function inTag(stream, state) { 47300 var ch = stream.next(); 47301 if (ch == ">" || (ch == "/" && stream.eat(">"))) { 47302 state.tokenize = inText; 47303 type = ch == ">" ? "endTag" : "selfcloseTag"; 47304 return "tag bracket"; 47305 } else if (ch == "=") { 47306 type = "equals"; 47307 return null; 47308 } else if (ch == "<") { 47309 state.tokenize = inText; 47310 state.state = baseState; 47311 state.tagName = state.tagStart = null; 47312 var next = state.tokenize(stream, state); 47313 return next ? next + " tag error" : "tag error"; 47314 } else if (/[\'\"]/.test(ch)) { 47315 state.tokenize = inAttribute(ch); 47316 state.stringStartCol = stream.column(); 47317 return state.tokenize(stream, state); 47318 } else { 47319 stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/); 47320 return "word"; 47321 } 47322 } 47323 47324 function inAttribute(quote) { 47325 var closure = function(stream, state) { 47326 while (!stream.eol()) { 47327 if (stream.next() == quote) { 47328 state.tokenize = inTag; 47329 break; 47330 } 47331 } 47332 return "string"; 47333 }; 47334 closure.isInAttribute = true; 47335 return closure; 47336 } 47337 47338 function inBlock(style, terminator) { 47339 return function(stream, state) { 47340 while (!stream.eol()) { 47341 if (stream.match(terminator)) { 47342 state.tokenize = inText; 47343 break; 47344 } 47345 stream.next(); 47346 } 47347 return style; 47348 }; 47349 } 47350 function doctype(depth) { 47351 return function(stream, state) { 47352 var ch; 47353 while ((ch = stream.next()) != null) { 47354 if (ch == "<") { 47355 state.tokenize = doctype(depth + 1); 47356 return state.tokenize(stream, state); 47357 } else if (ch == ">") { 47358 if (depth == 1) { 47359 state.tokenize = inText; 47360 break; 47361 } else { 47362 state.tokenize = doctype(depth - 1); 47363 return state.tokenize(stream, state); 47364 } 47365 } 47366 } 47367 return "meta"; 47368 }; 47369 } 47370 47371 function Context(state, tagName, startOfLine) { 47372 this.prev = state.context; 47373 this.tagName = tagName; 47374 this.indent = state.indented; 47375 this.startOfLine = startOfLine; 47376 if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) 47377 this.noIndent = true; 47378 } 47379 function popContext(state) { 47380 if (state.context) state.context = state.context.prev; 47381 } 47382 function maybePopContext(state, nextTagName) { 47383 var parentTagName; 47384 while (true) { 47385 if (!state.context) { 47386 return; 47387 } 47388 parentTagName = state.context.tagName; 47389 if (!config.contextGrabbers.hasOwnProperty(parentTagName) || 47390 !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { 47391 return; 47392 } 47393 popContext(state); 47394 } 47395 } 47396 47397 function baseState(type, stream, state) { 47398 if (type == "openTag") { 47399 state.tagStart = stream.column(); 47400 return tagNameState; 47401 } else if (type == "closeTag") { 47402 return closeTagNameState; 47403 } else { 47404 return baseState; 47405 } 47406 } 47407 function tagNameState(type, stream, state) { 47408 if (type == "word") { 47409 state.tagName = stream.current(); 47410 setStyle = "tag"; 47411 return attrState; 47412 } else if (config.allowMissingTagName && type == "endTag") { 47413 setStyle = "tag bracket"; 47414 return attrState(type, stream, state); 47415 } else { 47416 setStyle = "error"; 47417 return tagNameState; 47418 } 47419 } 47420 function closeTagNameState(type, stream, state) { 47421 if (type == "word") { 47422 var tagName = stream.current(); 47423 if (state.context && state.context.tagName != tagName && 47424 config.implicitlyClosed.hasOwnProperty(state.context.tagName)) 47425 popContext(state); 47426 if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) { 47427 setStyle = "tag"; 47428 return closeState; 47429 } else { 47430 setStyle = "tag error"; 47431 return closeStateErr; 47432 } 47433 } else if (config.allowMissingTagName && type == "endTag") { 47434 setStyle = "tag bracket"; 47435 return closeState(type, stream, state); 47436 } else { 47437 setStyle = "error"; 47438 return closeStateErr; 47439 } 47440 } 47441 47442 function closeState(type, _stream, state) { 47443 if (type != "endTag") { 47444 setStyle = "error"; 47445 return closeState; 47446 } 47447 popContext(state); 47448 return baseState; 47449 } 47450 function closeStateErr(type, stream, state) { 47451 setStyle = "error"; 47452 return closeState(type, stream, state); 47453 } 47454 47455 function attrState(type, _stream, state) { 47456 if (type == "word") { 47457 setStyle = "attribute"; 47458 return attrEqState; 47459 } else if (type == "endTag" || type == "selfcloseTag") { 47460 var tagName = state.tagName, tagStart = state.tagStart; 47461 state.tagName = state.tagStart = null; 47462 if (type == "selfcloseTag" || 47463 config.autoSelfClosers.hasOwnProperty(tagName)) { 47464 maybePopContext(state, tagName); 47465 } else { 47466 maybePopContext(state, tagName); 47467 state.context = new Context(state, tagName, tagStart == state.indented); 47468 } 47469 return baseState; 47470 } 47471 setStyle = "error"; 47472 return attrState; 47473 } 47474 function attrEqState(type, stream, state) { 47475 if (type == "equals") return attrValueState; 47476 if (!config.allowMissing) setStyle = "error"; 47477 return attrState(type, stream, state); 47478 } 47479 function attrValueState(type, stream, state) { 47480 if (type == "string") return attrContinuedState; 47481 if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;} 47482 setStyle = "error"; 47483 return attrState(type, stream, state); 47484 } 47485 function attrContinuedState(type, stream, state) { 47486 if (type == "string") return attrContinuedState; 47487 return attrState(type, stream, state); 47488 } 47489 47490 return { 47491 startState: function(baseIndent) { 47492 var state = {tokenize: inText, 47493 state: baseState, 47494 indented: baseIndent || 0, 47495 tagName: null, tagStart: null, 47496 context: null} 47497 if (baseIndent != null) state.baseIndent = baseIndent 47498 return state 47499 }, 47500 47501 token: function(stream, state) { 47502 if (!state.tagName && stream.sol()) 47503 state.indented = stream.indentation(); 47504 47505 if (stream.eatSpace()) return null; 47506 type = null; 47507 var style = state.tokenize(stream, state); 47508 if ((style || type) && style != "comment") { 47509 setStyle = null; 47510 state.state = state.state(type || style, stream, state); 47511 if (setStyle) 47512 style = setStyle == "error" ? style + " error" : setStyle; 47513 } 47514 return style; 47515 }, 47516 47517 indent: function(state, textAfter, fullLine) { 47518 var context = state.context; 47519 // Indent multi-line strings (e.g. css). 47520 if (state.tokenize.isInAttribute) { 47521 if (state.tagStart == state.indented) 47522 return state.stringStartCol + 1; 47523 else 47524 return state.indented + indentUnit; 47525 } 47526 if (context && context.noIndent) return CodeMirror.Pass; 47527 if (state.tokenize != inTag && state.tokenize != inText) 47528 return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; 47529 // Indent the starts of attribute names. 47530 if (state.tagName) { 47531 if (config.multilineTagIndentPastTag !== false) 47532 return state.tagStart + state.tagName.length + 2; 47533 else 47534 return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1); 47535 } 47536 if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0; 47537 var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter); 47538 if (tagAfter && tagAfter[1]) { // Closing tag spotted 47539 while (context) { 47540 if (context.tagName == tagAfter[2]) { 47541 context = context.prev; 47542 break; 47543 } else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) { 47544 context = context.prev; 47545 } else { 47546 break; 47547 } 47548 } 47549 } else if (tagAfter) { // Opening tag spotted 47550 while (context) { 47551 var grabbers = config.contextGrabbers[context.tagName]; 47552 if (grabbers && grabbers.hasOwnProperty(tagAfter[2])) 47553 context = context.prev; 47554 else 47555 break; 47556 } 47557 } 47558 while (context && context.prev && !context.startOfLine) 47559 context = context.prev; 47560 if (context) return context.indent + indentUnit; 47561 else return state.baseIndent || 0; 47562 }, 47563 47564 electricInput: /<\/[\s\w:]+>$/, 47565 blockCommentStart: "<!--", 47566 blockCommentEnd: "-->", 47567 47568 configuration: config.htmlMode ? "html" : "xml", 47569 helperType: config.htmlMode ? "html" : "xml", 47570 47571 skipAttribute: function(state) { 47572 if (state.state == attrValueState) 47573 state.state = attrState 47574 } 47575 }; 47576}); 47577 47578CodeMirror.defineMIME("text/xml", "xml"); 47579CodeMirror.defineMIME("application/xml", "xml"); 47580if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) 47581 CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); 47582 47583}); 47584 47585},{"../../lib/codemirror":6}],11:[function(require,module,exports){ 47586(function (global){ 47587/** 47588 * marked - a markdown parser 47589 * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 47590 * https://github.com/markedjs/marked 47591 */ 47592 47593;(function(root) { 47594'use strict'; 47595 47596/** 47597 * Block-Level Grammar 47598 */ 47599 47600var block = { 47601 newline: /^\n+/, 47602 code: /^( {4}[^\n]+\n*)+/, 47603 fences: noop, 47604 hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, 47605 heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, 47606 nptable: noop, 47607 blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, 47608 list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 47609 html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, 47610 def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, 47611 table: noop, 47612 lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 47613 paragraph: /^([^\n]+(?:\n?(?!hr|heading|lheading| {0,3}>|tag)[^\n]+)+)/, 47614 text: /^[^\n]+/ 47615}; 47616 47617block._label = /(?:\\[\[\]]|[^\[\]])+/; 47618block._title = /(?:"(?:\\"|[^"]|"[^"\n]*")*"|'\n?(?:[^'\n]+\n?)*'|\([^()]*\))/; 47619block.def = edit(block.def) 47620 .replace('label', block._label) 47621 .replace('title', block._title) 47622 .getRegex(); 47623 47624block.bullet = /(?:[*+-]|\d+\.)/;
47625block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; 47626block.item = edit(block.item, 'gm') 47627 .replace(/bull/g, block.bullet) 47628 .getRegex(); 47629 47630block.list = edit(block.list) 47631 .replace(/bull/g, block.bullet) 47632 .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') 47633 .replace('def', '\\n+(?=' + block.def.source + ')') 47634 .getRegex(); 47635 47636block._tag = '(?!(?:' 47637 + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' 47638 + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' 47639 + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b'; 47640 47641block.html = edit(block.html) 47642 .replace('comment', /<!--[\s\S]*?-->/) 47643 .replace('closed', /<(tag)[\s\S]+?<\/\1>/) 47644 .replace('closing', /<tag(?:"[^"]*"|'[^']*'|\s[^'"\/>\s]*)*?\/?>/) 47645 .replace(/tag/g, block._tag) 47646 .getRegex(); 47647 47648block.paragraph = edit(block.paragraph) 47649 .replace('hr', block.hr) 47650 .replace('heading', block.heading) 47651 .replace('lheading', block.lheading) 47652 .replace('tag', '<' + block._tag) 47653 .getRegex(); 47654 47655block.blockquote = edit(block.blockquote) 47656 .replace('paragraph', block.paragraph) 47657 .getRegex(); 47658 47659/** 47660 * Normal Block Grammar 47661 */ 47662 47663block.normal = merge({}, block); 47664 47665/** 47666 * GFM Block Grammar 47667 */ 47668 47669block.gfm = merge({}, block.normal, { 47670 fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, 47671 paragraph: /^/, 47672 heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ 47673}); 47674 47675block.gfm.paragraph = edit(block.paragraph) 47676 .replace('(?!', '(?!' 47677 + block.gfm.fences.source.replace('\\1', '\\2') + '|' 47678 + block.list.source.replace('\\1', '\\3') + '|') 47679 .getRegex(); 47680 47681/** 47682 * GFM + Tables Block Grammar 47683 */ 47684 47685block.tables = merge({}, block.gfm, { 47686 nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, 47687 table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ 47688}); 47689 47690/** 47691 * Block Lexer 47692 */ 47693 47694function Lexer(options) { 47695 this.tokens = []; 47696 this.tokens.links = {}; 47697 this.options = options || marked.defaults; 47698 this.rules = block.normal; 47699 47700 if (this.options.gfm) { 47701 if (this.options.tables) { 47702 this.rules = block.tables; 47703 } else { 47704 this.rules = block.gfm; 47705 } 47706 } 47707} 47708 47709/** 47710 * Expose Block Rules 47711 */ 47712 47713Lexer.rules = block; 47714 47715/** 47716 * Static Lex Method 47717 */ 47718 47719Lexer.lex = function(src, options) { 47720 var lexer = new Lexer(options); 47721 return lexer.lex(src); 47722}; 47723 47724/** 47725 * Preprocessing 47726 */ 47727 47728Lexer.prototype.lex = function(src) { 47729 src = src 47730 .replace(/\r\n|\r/g, '\n') 47731 .replace(/\t/g, ' ') 47732 .replace(/\u00a0/g, ' ') 47733 .replace(/\u2424/g, '\n'); 47734 47735 return this.token(src, true); 47736}; 47737 47738/** 47739 * Lexing 47740 */ 47741 47742Lexer.prototype.token = function(src, top) { 47743 src = src.replace(/^ +$/gm, ''); 47744 var next, 47745 loose, 47746 cap, 47747 bull, 47748 b, 47749 item, 47750 space, 47751 i, 47752 tag, 47753 l, 47754 isordered; 47755 47756 while (src) { 47757 // newline 47758 if (cap = this.rules.newline.exec(src)) { 47759 src = src.substring(cap[0].length); 47760 if (cap[0].length > 1) { 47761 this.tokens.push({ 47762 type: 'space' 47763 }); 47764 } 47765 } 47766 47767 // code 47768 if (cap = this.rules.code.exec(src)) { 47769 src = src.substring(cap[0].length); 47770 cap = cap[0].replace(/^ {4}/gm, ''); 47771 this.tokens.push({ 47772 type: 'code', 47773 text: !this.options.pedantic 47774 ? cap.replace(/\n+$/, '') 47775 : cap 47776 }); 47777 continue; 47778 } 47779 47780 // fences (gfm) 47781 if (cap = this.rules.fences.exec(src)) { 47782 src = src.substring(cap[0].length); 47783 this.tokens.push({ 47784 type: 'code', 47785 lang: cap[2], 47786 text: cap[3] || '' 47787 }); 47788 continue; 47789 } 47790 47791 // heading 47792 if (cap = this.rules.heading.exec(src)) { 47793 src = src.substring(cap[0].length); 47794 this.tokens.push({ 47795 type: 'heading', 47796 depth: cap[1].length, 47797 text: cap[2] 47798 }); 47799 continue; 47800 } 47801 47802 // table no leading pipe (gfm) 47803 if (top && (cap = this.rules.nptable.exec(src))) { 47804 src = src.substring(cap[0].length); 47805 47806 item = { 47807 type: 'table', 47808 header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 47809 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 47810 cells: cap[3].replace(/\n$/, '').split('\n') 47811 }; 47812 47813 for (i = 0; i < item.align.length; i++) { 47814 if (/^ *-+: *$/.test(item.align[i])) { 47815 item.align[i] = 'right'; 47816 } else if (/^ *:-+: *$/.test(item.align[i])) { 47817 item.align[i] = 'center'; 47818 } else if (/^ *:-+ *$/.test(item.align[i])) { 47819 item.align[i] = 'left'; 47820 } else { 47821 item.align[i] = null; 47822 } 47823 } 47824 47825 for (i = 0; i < item.cells.length; i++) { 47826 item.cells[i] = item.cells[i].split(/ *\| */); 47827 } 47828 47829 this.tokens.push(item); 47830 47831 continue; 47832 } 47833 47834 // hr 47835 if (cap = this.rules.hr.exec(src)) { 47836 src = src.substring(cap[0].length); 47837 this.tokens.push({ 47838 type: 'hr' 47839 }); 47840 continue; 47841 } 47842 47843 // blockquote 47844 if (cap = this.rules.blockquote.exec(src)) { 47845 src = src.substring(cap[0].length); 47846 47847 this.tokens.push({ 47848 type: 'blockquote_start' 47849 }); 47850 47851 cap = cap[0].replace(/^ *> ?/gm, ''); 47852 47853 // Pass `top` to keep the current 47854 // "toplevel" state. This is exactly 47855 // how markdown.pl works. 47856 this.token(cap, top); 47857 47858 this.tokens.push({ 47859 type: 'blockquote_end' 47860 }); 47861 47862 continue; 47863 } 47864 47865 // list 47866 if (cap = this.rules.list.exec(src)) { 47867 src = src.substring(cap[0].length); 47868 bull = cap[2]; 47869 isordered = bull.length > 1; 47870 47871 this.tokens.push({ 47872 type: 'list_start', 47873 ordered: isordered, 47874 start: isordered ? +bull : '' 47875 }); 47876 47877 // Get each top-level item. 47878 cap = cap[0].match(this.rules.item); 47879 47880 next = false;
47881 l = cap.length; 47882 i = 0; 47883 47884 for (; i < l; i++) { 47885 item = cap[i]; 47886 47887 // Remove the list item's bullet 47888 // so it is seen as the next token. 47889 space = item.length; 47890 item = item.replace(/^ *([*+-]|\d+\.) +/, ''); 47891 47892 // Outdent whatever the 47893 // list item contains. Hacky. 47894 if (~item.indexOf('\n ')) { 47895 space -= item.length; 47896 item = !this.options.pedantic 47897 ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') 47898 : item.replace(/^ {1,4}/gm, ''); 47899 } 47900 47901 // Determine whether the next list item belongs here. 47902 // Backpedal if it does not belong in this list. 47903 if (this.options.smartLists && i !== l - 1) { 47904 b = block.bullet.exec(cap[i + 1])[0]; 47905 if (bull !== b && !(bull.length > 1 && b.length > 1)) { 47906 src = cap.slice(i + 1).join('\n') + src; 47907 i = l - 1; 47908 } 47909 } 47910 47911 // Determine whether item is loose or not. 47912 // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 47913 // for discount behavior. 47914 loose = next || /\n\n(?!\s*$)/.test(item); 47915 if (i !== l - 1) { 47916 next = item.charAt(item.length - 1) === '\n'; 47917 if (!loose) loose = next; 47918 } 47919 47920 this.tokens.push({ 47921 type: loose 47922 ? 'loose_item_start' 47923 : 'list_item_start' 47924 }); 47925 47926 // Recurse. 47927 this.token(item, false); 47928 47929 this.tokens.push({ 47930 type: 'list_item_end' 47931 }); 47932 } 47933 47934 this.tokens.push({ 47935 type: 'list_end' 47936 }); 47937 47938 continue; 47939 } 47940 47941 // html 47942 if (cap = this.rules.html.exec(src)) { 47943 src = src.substring(cap[0].length); 47944 this.tokens.push({ 47945 type: this.options.sanitize 47946 ? 'paragraph' 47947 : 'html', 47948 pre: !this.options.sanitizer 47949 && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), 47950 text: cap[0] 47951 }); 47952 continue; 47953 } 47954 47955 // def 47956 if (top && (cap = this.rules.def.exec(src))) { 47957 src = src.substring(cap[0].length); 47958 if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); 47959 tag = cap[1].toLowerCase(); 47960 if (!this.tokens.links[tag]) { 47961 this.tokens.links[tag] = { 47962 href: cap[2], 47963 title: cap[3] 47964 }; 47965 } 47966 continue; 47967 } 47968 47969 // table (gfm) 47970 if (top && (cap = this.rules.table.exec(src))) { 47971 src = src.substring(cap[0].length); 47972 47973 item = { 47974 type: 'table', 47975 header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 47976 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 47977 cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') 47978 }; 47979 47980 for (i = 0; i < item.align.length; i++) { 47981 if (/^ *-+: *$/.test(item.align[i])) { 47982 item.align[i] = 'right'; 47983 } else if (/^ *:-+: *$/.test(item.align[i])) { 47984 item.align[i] = 'center'; 47985 } else if (/^ *:-+ *$/.test(item.align[i])) { 47986 item.align[i] = 'left'; 47987 } else { 47988 item.align[i] = null; 47989 } 47990 } 47991 47992 for (i = 0; i < item.cells.length; i++) { 47993 item.cells[i] = item.cells[i] 47994 .replace(/^ *\| *| *\| *$/g, '') 47995 .split(/ *\| */); 47996 } 47997 47998 this.tokens.push(item); 47999 48000 continue; 48001 } 48002 48003 // lheading 48004 if (cap = this.rules.lheading.exec(src)) { 48005 src = src.substring(cap[0].length); 48006 this.tokens.push({ 48007 type: 'heading', 48008 depth: cap[2] === '=' ? 1 : 2, 48009 text: cap[1] 48010 }); 48011 continue; 48012 } 48013 48014 // top-level paragraph 48015 if (top && (cap = this.rules.paragraph.exec(src))) { 48016 src = src.substring(cap[0].length); 48017 this.tokens.push({ 48018 type: 'paragraph', 48019 text: cap[1].charAt(cap[1].length - 1) === '\n' 48020 ? cap[1].slice(0, -1) 48021 : cap[1] 48022 }); 48023 continue; 48024 } 48025 48026 // text 48027 if (cap = this.rules.text.exec(src)) { 48028 // Top-level should never reach here. 48029 src = src.substring(cap[0].length); 48030 this.tokens.push({ 48031 type: 'text', 48032 text: cap[0] 48033 }); 48034 continue; 48035 } 48036 48037 if (src) {
48038 throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); 48039 } 48040 } 48041 48042 return this.tokens; 48043}; 48044 48045/** 48046 * Inline-Level Grammar 48047 */ 48048 48049var inline = { 48050 escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, 48051 autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, 48052 url: noop, 48053 tag: /^<!--[\s\S]*?-->|^<\/?[a-zA-Z0-9\-]+(?:"[^"]*"|'[^']*'|\s[^<'">\/\s]*)*?\/?>/, 48054 link: /^!?\[(inside)\]\(href\)/, 48055 reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, 48056 nolink: /^!?\[((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\]/, 48057 strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, 48058 em: /^_([^\s_](?:[^_]|__)+?[^\s_])_\b|^\*((?:\*\*|[^*])+?)\*(?!\*)/, 48059 code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/, 48060 br: /^ {2,}\n(?!\s*$)/, 48061 del: noop, 48062 text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/ 48063}; 48064 48065inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; 48066inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; 48067 48068inline.autolink = edit(inline.autolink) 48069 .replace('scheme', inline._scheme) 48070 .replace('email', inline._email) 48071 .getRegex() 48072 48073inline._inside = /(?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]]|\](?=[^\[]*\]))*/; 48074inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/; 48075 48076inline.link = edit(inline.link) 48077 .replace('inside', inline._inside) 48078 .replace('href', inline._href) 48079 .getRegex(); 48080 48081inline.reflink = edit(inline.reflink) 48082 .replace('inside', inline._inside) 48083 .getRegex(); 48084 48085/** 48086 * Normal Inline Grammar 48087 */ 48088 48089inline.normal = merge({}, inline); 48090 48091/** 48092 * Pedantic Inline Grammar 48093 */ 48094 48095inline.pedantic = merge({}, inline.normal, { 48096 strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 48097 em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ 48098}); 48099 48100/** 48101 * GFM Inline Grammar 48102 */ 48103 48104inline.gfm = merge({}, inline.normal, { 48105 escape: edit(inline.escape).replace('])', '~|])').getRegex(), 48106 url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/) 48107 .replace('email', inline._email) 48108 .getRegex(), 48109 _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, 48110 del: /^~~(?=\S)([\s\S]*?\S)~~/, 48111 text: edit(inline.text) 48112 .replace(']|', '~]|') 48113 .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|') 48114 .getRegex() 48115}); 48116 48117/** 48118 * GFM + Line Breaks Inline Grammar 48119 */ 48120 48121inline.breaks = merge({}, inline.gfm, { 48122 br: edit(inline.br).replace('{2,}', '*').getRegex(), 48123 text: edit(inline.gfm.text).replace('{2,}', '*').getRegex() 48124}); 48125 48126/** 48127 * Inline Lexer & Compiler 48128 */ 48129 48130function InlineLexer(links, options) { 48131 this.options = options || marked.defaults; 48132 this.links = links; 48133 this.rules = inline.normal; 48134 this.renderer = this.options.renderer || new Renderer(); 48135 this.renderer.options = this.options; 48136 48137 if (!this.links) { 48138 throw new Error('Tokens array requires a `links` property.'); 48139 } 48140 48141 if (this.options.gfm) { 48142 if (this.options.breaks) { 48143 this.rules = inline.breaks; 48144 } else { 48145 this.rules = inline.gfm; 48146 } 48147 } else if (this.options.pedantic) { 48148 this.rules = inline.pedantic; 48149 } 48150} 48151 48152/** 48153 * Expose Inline Rules 48154 */ 48155 48156InlineLexer.rules = inline; 48157 48158/** 48159 * Static Lexing/Compiling Method 48160 */ 48161 48162InlineLexer.output = function(src, links, options) { 48163 var inline = new InlineLexer(links, options); 48164 return inline.output(src); 48165}; 48166 48167/** 48168 * Lexing/Compiling 48169 */ 48170 48171InlineLexer.prototype.output = function(src) { 48172 var out = '', 48173 link, 48174 text, 48175 href, 48176 cap; 48177 48178 while (src) { 48179 // escape 48180 if (cap = this.rules.escape.exec(src)) { 48181 src = src.substring(cap[0].length); 48182 out += cap[1]; 48183 continue; 48184 } 48185 48186 // autolink 48187 if (cap = this.rules.autolink.exec(src)) { 48188 src = src.substring(cap[0].length); 48189 if (cap[2] === '@') { 48190 text = escape(this.mangle(cap[1])); 48191 href = 'mailto:' + text; 48192 } else { 48193 text = escape(cap[1]); 48194 href = text; 48195 } 48196 out += this.renderer.link(href, null, text); 48197 continue; 48198 } 48199 48200 // url (gfm) 48201 if (!this.inLink && (cap = this.rules.url.exec(src))) { 48202 cap[0] = this.rules._backpedal.exec(cap[0])[0]; 48203 src = src.substring(cap[0].length); 48204 if (cap[2] === '@') { 48205 text = escape(cap[0]); 48206 href = 'mailto:' + text; 48207 } else { 48208 text = escape(cap[0]); 48209 if (cap[1] === 'www.') { 48210 href = 'http://' + text; 48211 } else { 48212 href = text; 48213 } 48214 } 48215 out += this.renderer.link(href, null, text); 48216 continue; 48217 } 48218 48219 // tag 48220 if (cap = this.rules.tag.exec(src)) { 48221 if (!this.inLink && /^<a /i.test(cap[0])) { 48222 this.inLink = true; 48223 } else if (this.inLink && /^<\/a>/i.test(cap[0])) { 48224 this.inLink = false;
48225 } 48226 src = src.substring(cap[0].length); 48227 out += this.options.sanitize 48228 ? this.options.sanitizer 48229 ? this.options.sanitizer(cap[0]) 48230 : escape(cap[0]) 48231 : cap[0] 48232 continue; 48233 } 48234 48235 // link 48236 if (cap = this.rules.link.exec(src)) { 48237 src = src.substring(cap[0].length); 48238 this.inLink = true; 48239 out += this.outputLink(cap, { 48240 href: cap[2], 48241 title: cap[3] 48242 }); 48243 this.inLink = false; 48244 continue; 48245 } 48246 48247 // reflink, nolink 48248 if ((cap = this.rules.reflink.exec(src)) 48249 || (cap = this.rules.nolink.exec(src))) { 48250 src = src.substring(cap[0].length); 48251 link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 48252 link = this.links[link.toLowerCase()]; 48253 if (!link || !link.href) { 48254 out += cap[0].charAt(0); 48255 src = cap[0].substring(1) + src; 48256 continue; 48257 } 48258 this.inLink = true; 48259 out += this.outputLink(cap, link); 48260 this.inLink = false; 48261 continue; 48262 } 48263 48264 // strong 48265 if (cap = this.rules.strong.exec(src)) { 48266 src = src.substring(cap[0].length); 48267 out += this.renderer.strong(this.output(cap[2] || cap[1])); 48268 continue; 48269 } 48270 48271 // em 48272 if (cap = this.rules.em.exec(src)) { 48273 src = src.substring(cap[0].length); 48274 out += this.renderer.em(this.output(cap[2] || cap[1])); 48275 continue; 48276 } 48277 48278 // code 48279 if (cap = this.rules.code.exec(src)) { 48280 src = src.substring(cap[0].length); 48281 out += this.renderer.codespan(escape(cap[2].trim(), true)); 48282 continue; 48283 } 48284 48285 // br 48286 if (cap = this.rules.br.exec(src)) { 48287 src = src.substring(cap[0].length); 48288 out += this.renderer.br(); 48289 continue; 48290 } 48291 48292 // del (gfm) 48293 if (cap = this.rules.del.exec(src)) { 48294 src = src.substring(cap[0].length); 48295 out += this.renderer.del(this.output(cap[1])); 48296 continue; 48297 } 48298 48299 // text 48300 if (cap = this.rules.text.exec(src)) { 48301 src = src.substring(cap[0].length); 48302 out += this.renderer.text(escape(this.smartypants(cap[0]))); 48303 continue; 48304 } 48305 48306 if (src) { 48307 throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); 48308 } 48309 } 48310 48311 return out; 48312}; 48313 48314/** 48315 * Compile Link 48316 */ 48317 48318InlineLexer.prototype.outputLink = function(cap, link) { 48319 var href = escape(link.href), 48320 title = link.title ? escape(link.title) : null; 48321 48322 return cap[0].charAt(0) !== '!' 48323 ? this.renderer.link(href, title, this.output(cap[1])) 48324 : this.renderer.image(href, title, escape(cap[1])); 48325}; 48326 48327/** 48328 * Smartypants Transformations 48329 */ 48330 48331InlineLexer.prototype.smartypants = function(text) { 48332 if (!this.options.smartypants) return text; 48333 return text 48334 // em-dashes 48335 .replace(/---/g, '\u2014') 48336 // en-dashes 48337 .replace(/--/g, '\u2013') 48338 // opening singles 48339 .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 48340 // closing singles & apostrophes 48341 .replace(/'/g, '\u2019') 48342 // opening doubles 48343 .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 48344 // closing doubles 48345 .replace(/"/g, '\u201d') 48346 // ellipses 48347 .replace(/\.{3}/g, '\u2026'); 48348}; 48349 48350/** 48351 * Mangle Links 48352 */ 48353 48354InlineLexer.prototype.mangle = function(text) { 48355 if (!this.options.mangle) return text; 48356 var out = '', 48357 l = text.length, 48358 i = 0, 48359 ch; 48360 48361 for (; i < l; i++) { 48362 ch = text.charCodeAt(i); 48363 if (Math.random() > 0.5) { 48364 ch = 'x' + ch.toString(16); 48365 } 48366 out += '&#' + ch + ';'; 48367 } 48368 48369 return out; 48370}; 48371 48372/** 48373 * Renderer 48374 */ 48375 48376function Renderer(options) { 48377 this.options = options || {}; 48378} 48379 48380Renderer.prototype.code = function(code, lang, escaped) { 48381 if (this.options.highlight) { 48382 var out = this.options.highlight(code, lang); 48383 if (out != null && out !== code) { 48384 escaped = true; 48385 code = out; 48386 } 48387 } 48388 48389 if (!lang) { 48390 return '<pre><code>' 48391 + (escaped ? code : escape(code, true)) 48392 + '\n</code></pre>'; 48393 } 48394 48395 return '<pre><code class="' 48396 + this.options.langPrefix 48397 + escape(lang, true) 48398 + '">' 48399 + (escaped ? code : escape(code, true)) 48400 + '\n</code></pre>\n'; 48401}; 48402 48403Renderer.prototype.blockquote = function(quote) { 48404 return '<blockquote>\n' + quote + '</blockquote>\n'; 48405}; 48406 48407Renderer.prototype.html = function(html) { 48408 return html; 48409}; 48410 48411Renderer.prototype.heading = function(text, level, raw) { 48412 return '<h' 48413 + level 48414 + ' id="' 48415 + this.options.headerPrefix 48416 + raw.toLowerCase().replace(/[^\w]+/g, '-') 48417 + '">' 48418 + text 48419 + '</h' 48420 + level 48421 + '>\n'; 48422}; 48423 48424Renderer.prototype.hr = function() { 48425 return this.options.xhtml ? '<hr/>\n' : '<hr>\n'; 48426}; 48427 48428Renderer.prototype.list = function(body, ordered, start) { 48429 var type = ordered ? 'ol' : 'ul', 48430 startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; 48431 return '<' + type + startatt + '>\n' + body + '</' + type + '>\n'; 48432}; 48433 48434Renderer.prototype.listitem = function(text) { 48435 return '<li>
48435' + text + '</li>\n'; 48436}; 48437 48438Renderer.prototype.paragraph = function(text) { 48439 return '<p>' + text + '</p>\n'; 48440}; 48441 48442Renderer.prototype.table = function(header, body) { 48443 return '<table>\n' 48444 + '<thead>\n' 48445 + header 48446 + '</thead>\n' 48447 + '<tbody>\n' 48448 + body 48449 + '</tbody>\n' 48450 + '</table>\n'; 48451}; 48452 48453Renderer.prototype.tablerow = function(content) { 48454 return '<tr>\n' + content + '</tr>\n'; 48455}; 48456 48457Renderer.prototype.tablecell = function(content, flags) { 48458 var type = flags.header ? 'th' : 'td'; 48459 var tag = flags.align 48460 ? '<' + type + ' style="text-align:' + flags.align + '">' 48461 : '<' + type + '>'; 48462 return tag + content + '</' + type + '>\n'; 48463}; 48464 48465// span level renderer 48466Renderer.prototype.strong = function(text) { 48467 return '<strong>' + text + '</strong>'; 48468}; 48469 48470Renderer.prototype.em = function(text) { 48471 return '<em>' + text + '</em>'; 48472}; 48473 48474Renderer.prototype.codespan = function(text) { 48475 return '<code>' + text + '</code>'; 48476}; 48477 48478Renderer.prototype.br = function() { 48479 return this.options.xhtml ? '<br/>' : '<br>'; 48480}; 48481 48482Renderer.prototype.del = function(text) { 48483 return '<del>' + text + '</del>'; 48484}; 48485 48486Renderer.prototype.link = function(href, title, text) { 48487 if (this.options.sanitize) { 48488 try { 48489 var prot = decodeURIComponent(unescape(href)) 48490 .replace(/[^\w:]/g, '') 48491 .toLowerCase(); 48492 } catch (e) { 48493 return text; 48494 } 48495 if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { 48496 return text; 48497 } 48498 } 48499 if (this.options.baseUrl && !originIndependentUrl.test(href)) { 48500 href = resolveUrl(this.options.baseUrl, href); 48501 } 48502 var out = '<a href="' + href + '"'; 48503 if (title) { 48504 out += ' title="' + title + '"'; 48505 } 48506 out += '>
48506' + text + '</a>'; 48507 return out; 48508}; 48509 48510Renderer.prototype.image = function(href, title, text) { 48511 if (this.options.baseUrl && !originIndependentUrl.test(href)) { 48512 href = resolveUrl(this.options.baseUrl, href); 48513 } 48514 var out = '<img src="' + href + '" alt="' + text + '"'; 48515 if (title) { 48516 out += ' title="' + title + '"'; 48517 } 48518 out += this.options.xhtml ? '/>' : '>'; 48519 return out; 48520}; 48521 48522Renderer.prototype.text = function(text) { 48523 return text; 48524}; 48525 48526/** 48527 * TextRenderer 48528 * returns only the textual part of the token 48529 */ 48530 48531function TextRenderer() {} 48532 48533// no need for block level renderers 48534 48535TextRenderer.prototype.strong = 48536TextRenderer.prototype.em = 48537TextRenderer.prototype.codespan = 48538TextRenderer.prototype.del = 48539TextRenderer.prototype.text = function (text) { 48540 return text; 48541} 48542 48543TextRenderer.prototype.link = 48544TextRenderer.prototype.image = function(href, title, text) { 48545 return '' + text; 48546} 48547 48548TextRenderer.prototype.br = function() { 48549 return ''; 48550} 48551 48552/** 48553 * Parsing & Compiling 48554 */ 48555 48556function Parser(options) { 48557 this.tokens = []; 48558 this.token = null; 48559 this.options = options || marked.defaults; 48560 this.options.renderer = this.options.renderer || new Renderer(); 48561 this.renderer = this.options.renderer; 48562 this.renderer.options = this.options; 48563} 48564 48565/** 48566 * Static Parse Method 48567 */ 48568 48569Parser.parse = function(src, options) { 48570 var parser = new Parser(options); 48571 return parser.parse(src); 48572}; 48573 48574/** 48575 * Parse Loop 48576 */ 48577 48578Parser.prototype.parse = function(src) { 48579 this.inline = new InlineLexer(src.links, this.options); 48580 // use an InlineLexer with a TextRenderer to extract pure text 48581 this.inlineText = new InlineLexer( 48582 src.links, 48583 merge({}, this.options, {renderer: new TextRenderer()}) 48584 ); 48585 this.tokens = src.reverse(); 48586 48587 var out = ''; 48588 while (this.next()) { 48589 out += this.tok(); 48590 } 48591 48592 return out; 48593}; 48594 48595/** 48596 * Next Token 48597 */ 48598 48599Parser.prototype.next = function() { 48600 return this.token = this.tokens.pop(); 48601}; 48602 48603/** 48604 * Preview Next Token 48605 */ 48606 48607Parser.prototype.peek = function() { 48608 return this.tokens[this.tokens.length - 1] || 0; 48609}; 48610 48611/** 48612 * Parse Text Tokens 48613 */ 48614 48615Parser.prototype.parseText = function() { 48616 var body = this.token.text; 48617 48618 while (this.peek().type === 'text') { 48619 body += '\n' + this.next().text; 48620 } 48621 48622 return this.inline.output(body); 48623}; 48624 48625/** 48626 * Parse Current Token 48627 */ 48628 48629Parser.prototype.tok = function() { 48630 switch (this.token.type) { 48631 case 'space': { 48632 return ''; 48633 } 48634 case 'hr': { 48635 return this.renderer.hr(); 48636 } 48637 case 'heading': { 48638 return this.renderer.heading( 48639 this.inline.output(this.token.text), 48640 this.token.depth, 48641 unescape(this.inlineText.output(this.token.text))); 48642 } 48643 case 'code': { 48644 return this.renderer.code(this.token.text, 48645 this.token.lang, 48646 this.token.escaped); 48647 } 48648 case 'table': { 48649 var header = '', 48650 body = '', 48651 i, 48652 row, 48653 cell, 48654 j; 48655 48656 // header 48657 cell = ''; 48658 for (i = 0; i < this.token.header.length; i++) { 48659 cell += this.renderer.tablecell( 48660 this.inline.output(this.token.header[i]), 48661 { header: true, align: this.token.align[i] } 48662 ); 48663 } 48664 header += this.renderer.tablerow(cell); 48665 48666 for (i = 0; i < this.token.cells.length; i++) { 48667 row = this.token.cells[i]; 48668 48669 cell = ''; 48670 for (j = 0; j < row.length; j++) { 48671 cell += this.renderer.tablecell( 48672 this.inline.output(row[j]), 48673 { header: false, align: this.token.align[j] } 48674 ); 48675 } 48676 48677 body += this.renderer.tablerow(cell); 48678 } 48679 return this.renderer.table(header, body); 48680 } 48681 case 'blockquote_start': { 48682 body = ''; 48683 48684 while (this.next().type !== 'blockquote_end') { 48685 body += this.tok(); 48686 } 48687 48688 return this.renderer.blockquote(body); 48689 } 48690 case 'list_start': { 48691 body = ''; 48692 var ordered = this.token.ordered, 48693 start = this.token.start; 48694 48695 while (this.next().type !== 'list_end') { 48696 body += this.tok(); 48697 } 48698 48699 return this.renderer.list(body, ordered, start); 48700 } 48701 case 'list_item_start': { 48702 body = ''; 48703 48704 while (this.next().type !== 'list_item_end') { 48705 body += this.token.type === 'text' 48706 ? this.parseText() 48707 : this.tok(); 48708 } 48709 48710 return this.renderer.listitem(body); 48711 } 48712 case 'loose_item_start': { 48713 body = ''; 48714 48715 while (this.next().type !== 'list_item_end') { 48716 body += this.tok(); 48717 } 48718 48719 return this.renderer.listitem(body); 48720 } 48721 case 'html': { 48722 var html = !this.token.pre && !this.options.pedantic 48723 ? this.inline.output(this.token.text) 48724 : this.token.text; 48725 return this.renderer.html(html); 48726 } 48727 case 'paragraph': {
48728 return this.renderer.paragraph(this.inline.output(this.token.text)); 48729 } 48730 case 'text': { 48731 return this.renderer.paragraph(this.parseText()); 48732 } 48733 } 48734}; 48735 48736/** 48737 * Helpers 48738 */ 48739 48740function escape(html, encode) { 48741 return html 48742 .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') 48743 .replace(/</g, '<') 48744 .replace(/>/g, '>') 48745 .replace(/"/g, '"') 48746 .replace(/'/g, '''); 48747} 48748 48749function unescape(html) { 48750 // explicitly match decimal, hex, and named HTML entities 48751 return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { 48752 n = n.toLowerCase(); 48753 if (n === 'colon') return ':'; 48754 if (n.charAt(0) === '#') { 48755 return n.charAt(1) === 'x' 48756 ? String.fromCharCode(parseInt(n.substring(2), 16)) 48757 : String.fromCharCode(+n.substring(1)); 48758 } 48759 return ''; 48760 }); 48761} 48762 48763function edit(regex, opt) { 48764 regex = regex.source; 48765 opt = opt || ''; 48766 return { 48767 replace: function(name, val) { 48768 val = val.source || val; 48769 val = val.replace(/(^|[^\[])\^/g, '$1'); 48770 regex = regex.replace(name, val); 48771 return this; 48772 }, 48773 getRegex: function() { 48774 return new RegExp(regex, opt); 48775 } 48776 }; 48777} 48778 48779function resolveUrl(base, href) { 48780 if (!baseUrls[' ' + base]) { 48781 // we can ignore everything in base after the last slash of its path component, 48782 // but we might need to add _that_ 48783 // https://tools.ietf.org/html/rfc3986#section-3 48784 if (/^[^:]+:\/*[^/]*$/.test(base)) { 48785 baseUrls[' ' + base] = base + '/'; 48786 } else { 48787 baseUrls[' ' + base] = base.replace(/[^/]*$/, ''); 48788 } 48789 } 48790 base = baseUrls[' ' + base]; 48791 48792 if (href.slice(0, 2) === '//') { 48793 return base.replace(/:[\s\S]*/, ':') + href; 48794 } else if (href.charAt(0) === '/') { 48795 return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href; 48796 } else { 48797 return base + href; 48798 } 48799} 48800var baseUrls = {}; 48801var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; 48802 48803function noop() {} 48804noop.exec = noop; 48805 48806function merge(obj) { 48807 var i = 1, 48808 target, 48809 key; 48810 48811 for (; i < arguments.length; i++) { 48812 target = arguments[i]; 48813 for (key in target) { 48814 if (Object.prototype.hasOwnProperty.call(target, key)) { 48815 obj[key] = target[key]; 48816 } 48817 } 48818 } 48819 48820 return obj; 48821} 48822 48823/** 48824 * Marked 48825 */ 48826 48827function marked(src, opt, callback) { 48828 // throw error in case of non string input 48829 if (typeof src === 'undefined' || src === null) { 48830 throw new Error('marked(): input parameter is undefined or null'); 48831 } 48832 if (typeof src !== 'string') { 48833 throw new Error('marked(): input parameter is of type ' 48834 + Object.prototype.toString.call(src) + ', string expected'); 48835 } 48836 48837 if (callback || typeof opt === 'function') { 48838 if (!callback) { 48839 callback = opt; 48840 opt = null; 48841 } 48842 48843 opt = merge({}, marked.defaults, opt || {}); 48844 48845 var highlight = opt.highlight, 48846 tokens, 48847 pending, 48848 i = 0; 48849 48850 try { 48851 tokens = Lexer.lex(src, opt) 48852 } catch (e) { 48853 return callback(e); 48854 } 48855 48856 pending = tokens.length; 48857 48858 var done = function(err) { 48859 if (err) { 48860 opt.highlight = highlight; 48861 return callback(err); 48862 } 48863 48864 var out; 48865 48866 try { 48867 out = Parser.parse(tokens, opt); 48868 } catch (e) { 48869 err = e; 48870 } 48871 48872 opt.highlight = highlight; 48873 48874 return err 48875 ? callback(err) 48876 : callback(null, out); 48877 }; 48878 48879 if (!highlight || highlight.length < 3) { 48880 return done(); 48881 } 48882 48883 delete opt.highlight; 48884 48885 if (!pending) return done(); 48886 48887 for (; i < tokens.length; i++) { 48888 (function(token) { 48889 if (token.type !== 'code') { 48890 return --pending || done(); 48891 } 48892 return highlight(token.text, token.lang, function(err, code) { 48893 if (err) return done(err); 48894 if (code == null || code === token.text) { 48895 return --pending || done(); 48896 } 48897 token.text = code; 48898 token.escaped = true; 48899 --pending || done(); 48900 }); 48901 })(tokens[i]); 48902 } 48903 48904 return; 48905 } 48906 try { 48907 if (opt) opt = merge({}, marked.defaults, opt); 48908 return Parser.parse(Lexer.lex(src, opt), opt); 48909 } catch (e) { 48910 e.message += '\nPlease report this to https://github.com/markedjs/marked.'; 48911 if ((opt || marked.defaults).silent) { 48912 return '<p>An error occurred:</p><pre>' 48913 + escape(e.message + '', true) 48914 + '</pre>'; 48915 } 48916 throw e; 48917 } 48918} 48919 48920/** 48921 * Options 48922 */ 48923 48924marked.options = 48925marked.setOptions = function(opt) {
48926 merge(marked.defaults, opt); 48927 return marked; 48928}; 48929 48930marked.defaults = { 48931 gfm: true, 48932 tables: true, 48933 breaks: false, 48934 pedantic: false, 48935 sanitize: false, 48936 sanitizer: null, 48937 mangle: true, 48938 smartLists: false, 48939 silent: false, 48940 highlight: null, 48941 langPrefix: 'lang-', 48942 smartypants: false, 48943 headerPrefix: '', 48944 renderer: new Renderer(), 48945 xhtml: false, 48946 baseUrl: null 48947}; 48948 48949/** 48950 * Expose 48951 */ 48952 48953marked.Parser = Parser; 48954marked.parser = Parser.parse; 48955 48956marked.Renderer = Renderer; 48957marked.TextRenderer = TextRenderer; 48958 48959marked.Lexer = Lexer; 48960marked.lexer = Lexer.lex; 48961 48962marked.InlineLexer = InlineLexer; 48963marked.inlineLexer = InlineLexer.output; 48964 48965marked.parse = marked; 48966 48967if (typeof module !== 'undefined' && typeof exports === 'object') { 48968 module.exports = marked; 48969} else if (typeof define === 'function' && define.amd) { 48970 define(function() { return marked; }); 48971} else { 48972 root.marked = marked; 48973} 48974})(this || (typeof window !== 'undefined' ? window : global)); 48975 48976}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 48977},{}],12:[function(require,module,exports){ 48978/** 48979 * UI-Router Extras: Sticky states, Future States, Deep State Redirect, Transition promise 48980 * Monolithic build (all modules) 48981 * @version 0.1.3 48982 * @link http://christopherthielen.github.io/ui-router-extras/ 48983 * @license MIT License, http://www.opensource.org/licenses/MIT 48984 */ 48985(function (root, factory) { 48986 'use strict'; 48987 48988 if (typeof define === 'function' && define.amd) { 48989 define(['angular'], function (angular) { 48990 factory(angular); 48991 }); 48992 } else if (typeof exports === 'object') { 48993 factory(require('angular')); 48994 } else { 48995 factory(root.angular); 48996 } 48997}(this, function (angular, undefined) { 48998 var mod_core = angular.module("ct.ui.router.extras.core", [ "ui.router" ]); 48999 49000var internalStates = {}, stateRegisteredCallbacks = []; 49001mod_core.config([ '$stateProvider', '$injector', function ($stateProvider, $injector) { 49002 // Decorate any state attribute in order to get access to the internal state representation. 49003 $stateProvider.decorator('parent', function (state, parentFn) { 49004 // Capture each internal UI-Router state representations as opposed to the user-defined state object. 49005 // The internal state is, e.g., the state returned by $state.$current as opposed to $state.current 49006 internalStates[state.self.name] = state; 49007 // Add an accessor for the internal state from the user defined state 49008 state.self.$$state = function () { 49009 return internalStates[state.self.name]; 49010 }; 49011 49012 angular.forEach(stateRegisteredCallbacks, function(callback) { callback(state); }); 49013 return parentFn(state); 49014 }); 49015}]); 49016 49017var DEBUG = false; 49018 49019var forEach = angular.forEach; 49020var extend = angular.extend; 49021var isArray = angular.isArray; 49022 49023var map = function (collection, callback) { 49024 "use strict"; 49025 var result = []; 49026 forEach(collection, function (item, index) { 49027 result.push(callback(item, index)); 49028 }); 49029 return result; 49030}; 49031 49032var keys = function (collection) { 49033 "use strict"; 49034 return map(collection, function (collection, key) { 49035 return key; 49036 }); 49037}; 49038 49039var filter = function (collection, callback) { 49040 "use strict"; 49041 var result = []; 49042 forEach(collection, function (item, index) { 49043 if (callback(item, index)) { 49044 result.push(item); 49045 } 49046 }); 49047 return result; 49048}; 49049 49050var filterObj = function (collection, callback) { 49051 "use strict"; 49052 var result = {}; 49053 forEach(collection, function (item, index) { 49054 if (callback(item, index)) { 49055 result[index] = item; 49056 } 49057 }); 49058 return result; 49059}; 49060 49061// Duplicates code in UI-Router common.js 49062function ancestors(first, second) { 49063 var path = []; 49064 49065 for (var n in first.path) { 49066 if (first.path[n] !== second.path[n]) break; 49067 path.push(first.path[n]); 49068 } 49069 return path; 49070} 49071 49072// Duplicates code in UI-Router common.js 49073function objectKeys(object) { 49074 if (Object.keys) { 49075 return Object.keys(object); 49076 } 49077 var result = []; 49078 49079 angular.forEach(object, function (val, key) { 49080 result.push(key); 49081 }); 49082 return result; 49083} 49084 49085/** 49086 * like objectKeys, but includes keys from prototype chain.
49087 * @param object the object whose prototypal keys will be returned 49088 * @param ignoreKeys an array of keys to ignore 49089 */ 49090// Duplicates code in UI-Router common.js 49091function protoKeys(object, ignoreKeys) { 49092 var result = []; 49093 for (var key in object) { 49094 if (!ignoreKeys || ignoreKeys.indexOf(key) === -1) 49095 result.push(key); 49096 } 49097 return result; 49098} 49099 49100// Duplicates code in UI-Router common.js 49101function arraySearch(array, value) { 49102 if (Array.prototype.indexOf) { 49103 return array.indexOf(value, Number(arguments[2]) || 0); 49104 } 49105 var len = array.length >>> 0, from = Number(arguments[2]) || 0; 49106 from = (from < 0) ? Math.ceil(from) : Math.floor(from); 49107 49108 if (from < 0) from += len; 49109 49110 for (; from < len; from++) { 49111 if (from in array && array[from] === value) return from; 49112 } 49113 return -1; 49114} 49115 49116// Duplicates code in UI-Router common.js 49117// Added compatibility code (isArray check) to support both 0.2.x and 0.3.x series of UI-Router. 49118function inheritParams(currentParams, newParams, $current, $to) { 49119 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = []; 49120 49121 for (var i in parents) { 49122 if (!parents[i].params) continue; 49123 // This test allows compatibility with 0.2.x and 0.3.x (optional and object params) 49124 parentParams = isArray(parents[i].params) ? parents[i].params : objectKeys(parents[i].params);
49125 if (!parentParams.length) continue; 49126 49127 for (var j in parentParams) { 49128 if (arraySearch(inheritList, parentParams[j]) >= 0) continue; 49129 inheritList.push(parentParams[j]); 49130 inherited[parentParams[j]] = currentParams[parentParams[j]]; 49131 } 49132 } 49133 return extend({}, inherited, newParams); 49134} 49135 49136function inherit(parent, extra) { 49137 return extend(new (extend(function () { }, {prototype: parent}))(), extra); 49138} 49139 49140function onStateRegistered(callback) { stateRegisteredCallbacks.push(callback); } 49141 49142mod_core.provider("uirextras_core", function() { 49143 var core = { 49144 internalStates: internalStates, 49145 onStateRegistered: onStateRegistered, 49146 forEach: forEach, 49147 extend: extend, 49148 isArray: isArray, 49149 map: map, 49150 keys: keys, 49151 filter: filter, 49152 filterObj: filterObj, 49153 ancestors: ancestors, 49154 objectKeys: objectKeys, 49155 protoKeys: protoKeys, 49156 arraySearch: arraySearch, 49157 inheritParams: inheritParams, 49158 inherit: inherit 49159 }; 49160 49161 angular.extend(this, core); 49162 49163 this.$get = function() { 49164 return core; 49165 }; 49166}); 49167 49168 49169var ignoreDsr; 49170function resetIgnoreDsr() { 49171 ignoreDsr = undefined; 49172} 49173 49174// Decorate $state.transitionTo to gain access to the last transition.options variable. 49175// This is used to process the options.ignoreDsr option 49176angular.module('ct.ui.router.extras.dsr', [ 'ct.ui.router.extras.core' ]).config([ "$provide", function ($provide) { 49177 var $state_transitionTo; 49178 $provide.decorator("$state", ['$delegate', '$q', function ($state, $q) { 49179 $state_transitionTo = $state.transitionTo; 49180 $state.transitionTo = function (to, toParams, options) { 49181 if (options && options.ignoreDsr) { 49182 ignoreDsr = options.ignoreDsr; 49183 } 49184 49185 return $state_transitionTo.apply($state, arguments).then( 49186 function (result) { 49187 resetIgnoreDsr(); 49188 return result; 49189 }, 49190 function (err) { 49191 resetIgnoreDsr(); 49192 return $q.reject(err); 49193 } 49194 ); 49195 }; 49196 return $state; 49197 }]); 49198}]); 49199 49200angular.module('ct.ui.router.extras.dsr').service("$deepStateRedirect", [ '$rootScope', '$state', '$injector', function ($rootScope, $state, $injector) { 49201 var lastSubstate = {}; 49202 var deepStateRedirectsByName = {}; 49203 49204 var REDIRECT = "Redirect", ANCESTOR_REDIRECT = "AncestorRedirect"; 49205 49206 function computeDeepStateStatus(state) { 49207 var name = state.name; 49208 if (deepStateRedirectsByName.hasOwnProperty(name)) 49209 return deepStateRedirectsByName[name]; 49210 recordDeepStateRedirectStatus(name); 49211 } 49212 49213 function getConfig(state) { 49214 var declaration = state.deepStateRedirect || state.dsr; 49215 if (!declaration) return { dsr: false }; 49216 var dsrCfg = { dsr: true }; 49217 49218 if (angular.isFunction(declaration)) { 49219 dsrCfg.fn = declaration; 49220 } else if (angular.isObject(declaration)) { 49221 dsrCfg = angular.extend(dsrCfg, declaration); 49222 } 49223 49224 if (angular.isString(dsrCfg['default'])) { 49225 dsrCfg['default'] = { state: dsrCfg['default'] }; 49226 } 49227 49228 if (!dsrCfg.fn) { 49229 dsrCfg.fn = [ '$dsr$', function($dsr$) { 49230 return $dsr$.redirect.state != $dsr$.to.state; 49231 } ]; 49232 } 49233 return dsrCfg; 49234 } 49235 49236 function recordDeepStateRedirectStatus(stateName) { 49237 var state = $state.get(stateName); 49238 if (!state) return false; 49239 var cfg = getConfig(state); 49240 if (cfg.dsr) { 49241 deepStateRedirectsByName[state.name] = REDIRECT; 49242 if (lastSubstate[stateName] === undefined) 49243 lastSubstate[stateName] = {}; 49244 } 49245 49246 var parent = state.$$state && state.$$state().parent; 49247 if (parent) { 49248 var parentStatus = recordDeepStateRedirectStatus(parent.self.name); 49249 if (parentStatus && deepStateRedirectsByName[state.name] === undefined) { 49250 deepStateRedirectsByName[state.name] = ANCESTOR_REDIRECT; 49251 } 49252 } 49253 return deepStateRedirectsByName[state.name] || false; 49254 } 49255 49256 function getMatchParams(params, dsrParams) { 49257 if (dsrParams === true) dsrParams = Object.keys(params); 49258 if (dsrParams === null || dsrParams === undefined) dsrParams = []; 49259 49260 var matchParams = {}; 49261 angular.forEach(dsrParams.sort(), function(name) { matchParams[name] = params[name]; }); 49262 return matchParams; 49263 } 49264
49265 function getParamsString(params, dsrParams) { 49266 var matchParams = getMatchParams(params, dsrParams); 49267 function safeString(input) { return !input ? input : input.toString(); } 49268 var paramsToString = {}; 49269 angular.forEach(matchParams, function(val, name) { paramsToString[name] = safeString(val); }); 49270 return angular.toJson(paramsToString); 49271 } 49272 49273 $rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams) { 49274 var cfg = getConfig(toState); 49275 if (ignoreDsr || (computeDeepStateStatus(toState) !== REDIRECT) && !cfg['default']) return; 49276 // We're changing directly to one of the redirect (tab) states. 49277 // Get the DSR key for this state by calculating the DSRParams option 49278 var key = getParamsString(toParams, cfg.params); 49279 var redirect = lastSubstate[toState.name][key] || cfg['default']; 49280 if (!redirect) return; 49281 49282 // we have a last substate recorded 49283 var $dsr$ = { redirect: { state: redirect.state, params: redirect.params}, to: { state: toState.name, params: toParams } }; 49284 var result = $injector.invoke(cfg.fn, toState, { $dsr$: $dsr$ }); 49285 if (!result) return; 49286 if (result.state) redirect = result; 49287 event.preventDefault(); 49288 var redirectParams = getMatchParams(toParams, cfg.params); 49289 $state.go(redirect.state, angular.extend(redirectParams, redirect.params)); 49290 }); 49291 49292 $rootScope.$on("$stateChangeSuccess", function (event, toState, toParams, fromState, fromParams) { 49293 var deepStateStatus = computeDeepStateStatus(toState); 49294 if (deepStateStatus) { 49295 var name = toState.name; 49296 angular.forEach(lastSubstate, function (redirect, dsrState) { 49297 // update Last-SubState¶ms for each DSR that this transition matches. 49298 var cfg = getConfig($state.get(dsrState)); 49299 var key = getParamsString(toParams, cfg.params); 49300 if (toState.$$state().includes[dsrState]) { 49301 lastSubstate[dsrState][key] = { state: name, params: angular.copy(toParams) }; 49302 } 49303 }); 49304 } 49305 }); 49306 49307 return { 49308 getRedirect: function(dsrState, params) { 49309 var state = $state.get(dsrState); 49310 computeDeepStateStatus(state) 49311 var cfg = getConfig(state); 49312 var key = getParamsString(params, cfg.params); 49313 var redirect = lastSubstate[state.name]; 49314 if (redirect && redirect[key]) { 49315 redirect = redirect[key]; 49316 } else { 49317 redirect = cfg['default']; 49318 } 49319 return redirect; 49320 }, 49321 reset: function(stateOrName, params) { 49322 if (!stateOrName) { 49323 angular.forEach(lastSubstate, function(redirect, dsrState) { lastSubstate[dsrState] = {}; }); 49324 } else { 49325 var state = $state.get(stateOrName); 49326 if (!state) throw new Error("Unknown state: " + stateOrName); 49327 if (lastSubstate[state.name]) { 49328 if (params) { 49329 var key = getParamsString(params, getConfig(state).params); 49330 delete lastSubstate[state.name][key]; 49331 } else { 49332 lastSubstate[state.name] = {}; 49333 } 49334 } 49335 } 49336 } 49337 }; 49338}]); 49339 49340angular.module('ct.ui.router.extras.dsr').run(['$deepStateRedirect', function ($deepStateRedirect) { 49341 // Make sure $deepStateRedirect is instantiated 49342}]); 49343 49344angular.module("ct.ui.router.extras.sticky", [ 'ct.ui.router.extras.core' ]); 49345
49346var mod_sticky = angular.module("ct.ui.router.extras.sticky"); 49347 49348$StickyStateProvider.$inject = [ '$stateProvider', 'uirextras_coreProvider' ]; 49349function $StickyStateProvider($stateProvider, uirextras_coreProvider) { 49350 var core = uirextras_coreProvider; 49351 var inheritParams = core.inheritParams; 49352 var objectKeys = core.objectKeys; 49353 var protoKeys = core.protoKeys; 49354 var forEach = core.forEach; 49355 var map = core.map; 49356 49357 // Holds all the states which are inactivated. Inactivated states can be either sticky states, or descendants of sticky states. 49358 var inactiveStates = {}; // state.name -> (state) 49359 var stickyStates = {}; // state.name -> true 49360 var $state; 49361 var DEBUG = false; 49362 49363 // Called by $stateProvider.registerState(); 49364 // registers a sticky state with $stickyStateProvider 49365 this.registerStickyState = function (state) { 49366 stickyStates[state.name] = state; 49367 // console.log("Registered sticky state: ", state); 49368 }; 49369 49370 this.enableDebug = this.debugMode = function (enabled) { 49371 if (angular.isDefined(enabled)) 49372 DEBUG = enabled; 49373 return DEBUG; 49374 }; 49375 49376 this.$get = [ '$rootScope', '$state', '$stateParams', '$injector', '$log', 49377 function ($rootScope, $state, $stateParams, $injector, $log) { 49378 // Each inactive states is either a sticky state, or a child of a sticky state. 49379 // This function finds the closest ancestor sticky state, then find that state's parent. 49380 // Map all inactive states to their closest parent-to-sticky state. 49381 function mapInactives() { 49382 var mappedStates = {}; 49383 angular.forEach(inactiveStates, function (state, name) { 49384 var stickyAncestors = getStickyStateStack(state); 49385 for (var i = 0; i < stickyAncestors.length; i++) { 49386 var parent = stickyAncestors[i].parent; 49387 mappedStates[parent.name] = mappedStates[parent.name] || []; 49388 mappedStates[parent.name].push(state); 49389 } 49390 if (mappedStates['']) { 49391 // This is necessary to compute Transition.inactives when there are sticky states are children to root state. 49392 mappedStates['__inactives'] = mappedStates['']; // jshint ignore:line 49393 } 49394 }); 49395 return mappedStates; 49396 } 49397 49398 function mapInactivesByImmediateParent() { 49399 var inactivesByAllParents ={}; 49400 forEach(inactiveStates, function(state) { 49401 forEach(state.path, function(ancestor) { 49402 if (ancestor === state) return; 49403 inactivesByAllParents[ancestor.name] = inactivesByAllParents[ancestor.name] || []; 49404 inactivesByAllParents[ancestor.name].push(state); 49405 }); 49406 }); 49407 return inactivesByAllParents; 49408 } 49409 49410 // Given a state, returns all ancestor states which are sticky. 49411 // Walks up the view's state's ancestry tree and locates each ancestor state which is marked as sticky. 49412 // Returns an array populated with only those ancestor sticky states. 49413 function getStickyStateStack(state) { 49414 var stack = []; 49415 if (!state) return stack; 49416 do { 49417 if (state.sticky) stack.push(state); 49418 state = state.parent; 49419 } while (state); 49420 stack.reverse(); 49421 return stack; 49422 } 49423 49424 // Returns a sticky transition type necessary to enter the state. 49425 // Transition can be: reactivate, reload, or enter 49426 49427 // Note: if a state is being reactivated but params dont match, we treat 49428 // it as a Exit/Enter, thus the special "reload" transition. 49429 // If a parent inactivated state has "reload" transition type, then 49430 // all descendant states must also be exit/entered, thus the first line of this function. 49431 function getEnterTransition(state, stateParams, reloadStateTree, ancestorReloaded) { 49432 if (ancestorReloaded) return "reload"; 49433 var inactiveState = inactiveStates[state.self.name]; 49434 if (!inactiveState) return "enter"; 49435 if (state.self === reloadStateTree) return "reload"; 49436 var paramsMatch = paramsEqualForState(state.ownParams, stateParams, inactiveState.locals.globals.$stateParams); 49437 return paramsMatch ? "reactivate" : "reload"; 49438 } 49439 49440 // Given a state and (optional) stateParams, returns the inactivated state from the inactive sticky
49440state registry. 49441 function getInactivatedState(state, stateParams) { 49442 var inactiveState = inactiveStates[state.name]; 49443 if (!inactiveState) return null; 49444 if (!stateParams) return inactiveState; 49445 var paramsMatch = paramsEqualForState(state.ownParams, stateParams, inactiveState.locals.globals.$stateParams); 49446 return paramsMatch ? inactiveState : null; 49447 } 49448 49449 function paramsEqualForState(ownParams, stateParams, stateParams2) { 49450 if (typeof ownParams.$$equals === 'function') 49451 return ownParams.$$equals(stateParams, stateParams2); 49452 return equalForKeys(stateParams, stateParams2, ownParams); 49453 } 49454 49455 // Duplicates logic in $state.transitionTo, primarily to find the pivot state (i.e., the "keep" value) 49456 function equalForKeys(a, b, keys) { 49457 if (!angular.isArray(keys) && angular.isObject(keys)) { 49458 keys = protoKeys(keys, ["$$keys", "$$values", "$$equals", "$$validates", "$$new", "$$parent"]); 49459 } 49460 if (!keys) { 49461 keys = []; 49462 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility 49463 } 49464 49465 for (var i = 0; i < keys.length; i++) { 49466 var k = keys[i]; 49467 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized 49468 } 49469 return true; 49470 } 49471 49472 function calcTreeChanges(transition) { 49473 var fromPath = transition.fromState.path; 49474 var toPath = transition.toState.path; 49475 var toParams = transition.toParams; 49476 var keep = 0, state = toPath[keep]; 49477 49478 if (transition.options && transition.options.inherit) { 49479 toParams = transition.toParams = 49480 inheritParams($stateParams, toParams || {}, $state.$current, transition.toState); 49481 } 49482 49483 while (state && state === fromPath[keep] && paramsEqualForState(state.ownParams, toParams, transition.fromParams)) { 49484 // We're "keeping" this state. bump keep var and get the next state in toPath for the next iteration. 49485 state = toPath[++keep]; 49486 } 49487 49488 return { 49489 keep: keep, 49490 retained: fromPath.slice(0, keep), 49491 exiting: fromPath.slice(keep), 49492 entering: toPath.slice(keep) 49493 }; 49494 } 49495 49496 function sortByStateDepth(a,b) { 49497 return a.name.split(".").length - b.name.split(".").length; 49498 } 49499 49500 var stickySupport = { 49501 getInactiveStates: function () { 49502 return map(inactiveStates, angular.identity).sort(sortByStateDepth); 49503 }, 49504 getInactiveStatesByParent: function () { 49505 return mapInactives(); 49506 }, 49507 // Main API for $stickyState, used by $state. 49508 // Processes a potential transition, returns an object with the following attributes: 49509 // { 49510 // keep: The number of states being "kept" 49511 // inactives: Array of all states which will be inactive if the transition is completed. 49512 // reactivatingStates: Array of all states which will be reactivated if the transition is completed. 49513 // orphans: Array of previously inactive states, which are being orphaned by the transition 49514 // Note: Transitioning directly to an inactive state with inactive children will reactivate the state, but exit all the inactive children. 49515 // enter: Enter transition type for all added states. This is a parallel array to "toStates" array in $state.transitionTo. 49516 // exit: Exit transition type for all removed states. This is a parallel array to "fromStates" array in $state.transitionTo. 49517 // } 49518 processTransition: function (transition) { 49519 var treeChanges = calcTreeChanges(transition); 49520 var currentInactives = stickySupport.getInactiveStates(); 49521 var futureInactives, exitingTypes, enteringTypes; 49522 var keep = treeChanges.keep; 49523 49524
49525 ///////////////////////////////////////// 49526 // helper functions 49527 function notIn(array) { return function (elem) { return array.indexOf(elem) === -1; }; } 49528 function flattenReduce(memo, list) { return memo.concat(list); } 49529 function uniqReduce(memo, orphan) { if (notIn(memo)(orphan)) memo.push(orphan); return memo; } 49530 function prop(attr) { return function(obj) { return obj[attr]; } } 49531 function typeIs(type) { return function(obj) { return obj.type === type; } } 49532 function isChildOf(state) { return function(other) { return other.parent === state; }; } 49533 var notEntering = notIn(treeChanges.entering); 49534 function notSticky(state) { return !state.sticky; } 49535 //////////////////////////////////// 49536 49537 49538 // Calculate the "exit" transition types for states being exited in fromPath 49539 // Exit types will be either "inactivate" or "exit" 49540 // Two things must be satisfied in order to inactivate the "exiting" states (instead of exit them): 49541 // - The first element of the exiting path must be sticky 49542 // - We must be entering any sibling state of the sticky (we can check this using entering.length) 49543 var shouldInactivate = treeChanges.exiting[0] && treeChanges.exiting[0].sticky && treeChanges.entering.length > 0; 49544 exitingTypes = treeChanges.exiting.map(function (state) { 49545 var stateRentering = treeChanges.entering.indexOf(state) !== -1; 49546 var type = shouldInactivate && !stateRentering ? "inactivate" : "exit"; 49547 return { type: type, state: state }; 49548 }); 49549 49550 49551 // Calculate the "enter" transition types for states being entered in toPath 49552 // Enter types will be either "enter", "reactivate", or "reload" where: 49553 // enter: full resolve, no special logic 49554 // reactivate: use previous locals 49555 // reload: like 'enter', except exit the inactive state before entering it. 49556 var reloaded = transition.options && !!transition.options.reload; 49557 enteringTypes = treeChanges.entering.map(function(state) { 49558 var type = getEnterTransition(state, transition.toParams, transition.reloadStateTree, reloaded); 49559 reloaded = reloaded || type === 'reload'; 49560 return { type: type, state: state }; 49561 }); 49562 49563 // Find all the "orphaned" states. those states that are : 49564 // - are siblings of the entering states 49565 // - previously inactive 49566 // - are not being reactivated (entered) 49567 // - are not sticky 49568 // unioned with: 49569 // - children of the toState 49570 // - previously inactive 49571 // 49572 // Given: 49573 // - states A (sticky: true), B, A.foo, A.bar 49574 // - A.foo is currently inactive 49575 // - B is currently active 49576 // Orphan case 1) 49577 // - Transition to A.bar orphans the inactive state A.foo; it should be exited 49578 // Orphan case 2) 49579 // - Transition directly to A orphans the inactive state A.foo; it should be exited 49580 // 49581 // Given: 49582 // - states A (sticky: true), B, A.foo (sticky), A.bar 49583 // - A.foo is currently inactive 49584 // - B is currently active 49585 // Orphan case 3) 49586 // - Transition directly to A orphans the inactive sticky state A.foo; it should be exited 49587 // Note: transition from B to A.bar does not orphan A.foo 49588 // Note 2: each orphaned state might be the parent of a larger inactive subtree. 49589 var orphanedRoots = treeChanges.entering 49590 // For each entering state in the path, find all sibling states which are currently inactive 49591 .map(function (entering) { return currentInactives.filter(isChildOf(entering.parent)); }) 49592 // Flatten nested arrays. Now we have an array of inactive states that are children of the ones being entered. 49593 .reduce(flattenReduce, []) 49594 // Consider "orphaned": only those children that are themselves not currently being entered 49595 .filter(notEntering) 49596 // Consider "orphaned": only those children that are not themselves sticky states. 49597 .filter(notSticky) 49598 // Finally, union that set with any inactive children of the "to state" 49599 .concat(currentInactives.filter(isChildOf(transition.toState))); 49600 49601 var currentInactivesByParent = mapInactivesByImmediateParent(); 49602 var allOrphans = orphanedRoots 49603 .map(function(root) { return currentInactivesByParent[root.name] }) 49604 .filter(angular.isDefined) 49605 .reduce(flattenReduce, []) 49606 .concat(orphanedRoots) 49607 // Sort by depth to exit orphans in proper order 49608 .sort(sortByStateDepth); 49609 49610 // Add them to the list of states being exited. 49611 var exitOrOrphaned = exitingTypes 49612 .filter(typeIs("exit")) 49613 .map(prop("state")) 49614 .concat(allOrphans); 49615 49616 // Now calculate the states that will be inactive if this transition succeeds.
49617 // We have already pushed the transitionType == "inactivate" states to 'inactives'. 49618 // Second, add all the existing inactive states 49619 futureInactives = currentInactives 49620 .filter(notIn(exitOrOrphaned)) 49621 .filter(notIn(treeChanges.entering)) 49622 .concat(exitingTypes.filter(typeIs("inactivate")).map(prop("state"))) 49623 .sort(sortByStateDepth); 49624 49625 return { 49626 keep: keep, 49627 enter: new Array(keep).concat(enteringTypes.map(prop("type"))), 49628 exit: new Array(keep).concat(exitingTypes.map(prop("type"))), 49629 inactives: futureInactives, 49630 reactivatingStates: enteringTypes.filter(typeIs("reactivate")).map(prop("state")), 49631 orphans: allOrphans 49632 }; 49633 }, 49634 49635 // Adds a state to the inactivated sticky state registry. 49636 stateInactivated: function (state) { 49637 // Keep locals around. 49638 inactiveStates[state.self.name] = state; 49639 // Notify states they are being Inactivated (i.e., a different 49640 // sticky state tree is now active). 49641 state.self.status = 'inactive'; 49642 if (state.self.onInactivate) 49643 $injector.invoke(state.self.onInactivate, state.self, state.locals.globals); 49644 }, 49645 49646 // Removes a previously inactivated state from the inactive sticky state registry 49647 stateReactivated: function (state) { 49648 if (inactiveStates[state.self.name]) { 49649 delete inactiveStates[state.self.name]; 49650 } 49651 state.self.status = 'entered'; 49652// if (state.locals == null || state.locals.globals == null) debugger; 49653 if (state.self.onReactivate) 49654 $injector.invoke(state.self.onReactivate, state.self, state.locals.globals); 49655 }, 49656 49657 // Exits all inactivated descendant substates when the ancestor state is exited. 49658 // When transitionTo is exiting a state, this function is called with the state being exited. It checks the 49659 // registry of inactivated states for descendants of the exited state and also exits those descendants. It then 49660 // removes the locals and de-registers the state from the inactivated registry. 49661 stateExiting: function (exiting, exitQueue, onExit) { 49662 var exitingNames = {}; 49663 angular.forEach(exitQueue, function (state) { 49664 exitingNames[state.self.name] = true; 49665 }); 49666 49667 angular.forEach(inactiveStates, function (inactiveExiting, name) { 49668 // TODO: Might need to run the inactivations in the proper depth-first order? 49669 if (!exitingNames[name] && inactiveExiting.includes[exiting.name]) { 49670 if (DEBUG) $log.debug("Exiting " + name + " because it's a substate of " + exiting.name + " and wasn't found in ", exitingNames); 49671 if (inactiveExiting.self.onExit) 49672 $injector.invoke(inactiveExiting.self.onExit, inactiveExiting.self, inactiveExiting.locals.globals); 49673 angular.forEach(inactiveExiting.locals, function(localval, key) { 49674 delete inactivePseudoState.locals[key]; 49675 }); 49676 inactiveExiting.locals = null; 49677 inactiveExiting.self.status = 'exited'; 49678 delete inactiveStates[name]; 49679 } 49680 }); 49681 49682 if (onExit) 49683 $injector.invoke(onExit, exiting.self, exiting.locals.globals); 49684 exiting.locals = null; 49685 exiting.self.status = 'exited'; 49686 delete inactiveStates[exiting.self.name]; 49687 }, 49688 49689 // Removes a previously inactivated state from the inactive sticky state registry 49690 stateEntering: function (entering, params, onEnter, updateParams) { 49691 var inactivatedState = getInactivatedState(entering); 49692 if (inactivatedState && (updateParams || !getInactivatedState(entering, params))) { 49693 var savedLocals = entering.locals; 49694 this.stateExiting(inactivatedState); 49695 entering.locals = savedLocals; 49696 } 49697 entering.self.status = 'entered'; 49698 49699 if (onEnter) 49700 $injector.invoke(onEnter, entering.self, entering.locals.globals); 49701 }, 49702 reset: function reset(inactiveState, params) { 49703 function resetOne(state) { stickySupport.reset(state); } 49704 if (inactiveState === "*") { 49705 angular.forEach(stickySupport.getInactiveStates(), resetOne); 49706 return true; 49707 } 49708 var state = $state.get(inactiveState); 49709 if (!state) return false;
49710 var exiting = getInactivatedState(state, params); 49711 if (!exiting) return false; 49712 stickySupport.stateExiting(exiting); 49713 $rootScope.$broadcast("$viewContentLoading"); 49714 return true; 49715 } 49716 }; 49717 49718 return stickySupport; 49719 }]; 49720} 49721 49722mod_sticky.provider("$stickyState", $StickyStateProvider); 49723 49724/** 49725 * Sticky States makes entire state trees "sticky". Sticky state trees are retained until their parent state is 49726 * exited. This can be useful to allow multiple modules, peers to each other, each module having its own independent 49727 * state tree. The peer modules can be activated and inactivated without any loss of their internal context, including 49728 * DOM content such as unvalidated/partially filled in forms, and even scroll position. 49729 * 49730 * DOM content is retained by declaring a named ui-view in the parent state, and filling it in with a named view from the 49731 * sticky state. 49732 * 49733 * Technical overview: 49734 * 49735 * ---PATHS--- 49736 * UI-Router uses state paths to manage entering and exiting of individual states. Each state "A.B.C.X" has its own path, starting 49737 * from the root state ("") and ending at the state "X". The path is composed the final state "X"'s ancestors, e.g., 49738 * [ "", "A", "B", "C", "X" ]. 49739 * 49740 * When a transition is processed, the previous path (fromState.path) is compared with the requested destination path 49741 * (toState.path). All states that the from and to paths have in common are "kept" during the transition. The last 49742 * "kept" element in the path is the "pivot". 49743 * 49744 * ---VIEWS--- 49745 * A View in UI-Router consists of a controller and a template. Each view belongs to one state, and a state can have many 49746 * views. Each view plugs into a ui-view element in the DOM of one of the parent state's view(s). 49747 * 49748 * View context is managed in UI-Router using a 'state locals' concept. When a state's views are fully loaded, those views 49749 * are placed on the states 'locals' object. Each locals object prototypally inherits from its parent state's locals object. 49750 * This means that state "A.B.C.X"'s locals object also has all of state "A.B.C"'s locals as well as those from "A.B" and "A". 49751 * The root state ("") defines no views, but it is included in the protypal inheritance chain. 49752 * 49753 * The locals object is used by the ui-view directive to load the template, render the content, create the child scope, 49754 * initialize the controller, etc. The ui-view directives caches the locals in a closure variable. If the locals are 49755 * identical (===), then the ui-view directive exits early, and does no rendering. 49756 * 49757 * In stock UI-Router, when a state is exited, that state's locals object is deleted and those views are cleaned up by 49758 * the ui-view directive shortly. 49759 * 49760 * ---Sticky States--- 49761 * UI-Router Extras keeps views for inactive states live, even when UI-Router thinks it has exited them. It does this 49762 * by creating a pseudo state called "__inactives" that is the parent of the root state. It also then defines a locals 49763 * object on the "__inactives" state, which the root state protoypally inherits from. By doing this, views for inactive 49764 * states are accessible through locals object's protoypal inheritance chain from any state in the system. 49765 * 49766 * ---Transitions--- 49767 * UI-Router Extras decorates the $state.transitionTo function. While a transition is in progress, the toState and 49768 * fromState internal state representations are modified in order to coerce stock UI-Router's transitionTo() into performing 49769 * the appropriate operations. When the transition promise is completed, the original toState and fromState values are 49770 * restored. 49771 * 49772 * Stock UI-Router's $state.transitionTo function uses toState.path and fromState.path to manage entering and exiting 49773 * states. UI-Router Extras takes advantage of those internal implementation details and prepares a toState.path and 49774 * fromState.path which coerces UI-Router into entering and exiting the correct states, or more importantly, not entering 49775 * and not exiting inactive or sticky states. It also replaces state.self.onEnter and state.self.onExit for elements in 49776 * the paths when they are being inactivated or reactivated. 49777 */ 49778 49779 49780 49781// ------------------------ Sticky State module-level variables ----------------------------------------------- 49782var _StickyState; // internal reference to $stickyStateProvider 49783var internalStates = {}; // Map { statename -> InternalStateObj } holds internal representation of all states 49784var root, // Root state, internal representation 49785 pendingTransitions = [], // One transition may supersede another. This holds references to all pending transitions 49786 pendingRestore, // The restore function from the superseded transition 49787 inactivePseudoState, // This pseudo state holds all the inactive states' locals (resolved state data, such as views etc) 49788 reactivatingLocals = { }, // This is a prent locals to the inactivePseudoState locals, used to hold locals for states being reactivated 49789 versionHeuristics = { // Heuristics used to guess the current UI-Router Version 49790 hasParamSet: false 49791 }; 49792 49793// Creates a blank surrogate state 49794function SurrogateState(type) { 49795 return { 49796 resolve: { }, 49797 locals: { 49798 globals: root && root.locals && root.locals.globals 49799 }, 49800 views: { }, 49801 self: { }, 49802 params: { }, 49803 ownParams: ( versionHeuristics.hasParamSet ? { $$equals: function() { return true; } } : []), 49804 surrogateType: type 49805 }; 49806} 49807 49808// ------------------------ Sticky State registration and initialization code ---------------------------------- 49809// Grab a copy of the $stickyState service for use by the transition management code 49810angular.module("ct.ui.router.extras.sticky").run(["$stickyState", function ($stickyState) { 49811 _StickyState = $stickyState; 49812}]); 49813 49814angular.module("ct.ui.router.extras.sticky").config( 49815 [ "$provide", "$stateProvider", '$stickyStateProvider', '$urlMatcherFactoryProvi
49815der', 'uirextras_coreProvider', 49816 function ($provide, $stateProvider, $stickyStateProvider, $urlMatcherFactoryProvider, uirextras_coreProvider) { 49817 var core = uirextras_coreProvider; 49818 var internalStates = core.internalStates; 49819 var inherit = core.inherit; 49820 var inheritParams = core.inheritParams; 49821 var forEach = core.forEach; 49822 var map = core.map; 49823 var filterObj = core.filterObj; 49824 49825 versionHeuristics.hasParamSet = !!$urlMatcherFactoryProvider.ParamSet; 49826 // inactivePseudoState (__inactives) holds all the inactive locals which includes resolved states data, i.e., views, scope, etc 49827 inactivePseudoState = angular.extend(new SurrogateState("__inactives"), { self: { name: '__inactives' } }); 49828 // Reset other module scoped variables. This is to primarily to flush any previous state during karma runs. 49829 root = pendingRestore = undefined; 49830 pendingTransitions = []; 49831 49832 uirextras_coreProvider.onStateRegistered(function(state) { 49833 // Register the ones marked as "sticky" 49834 if (state.self.sticky === true) { 49835 $stickyStateProvider.registerStickyState(state.self); 49836 } 49837 }); 49838 49839 var $state_transitionTo; // internal reference to the real $state.transitionTo function 49840 // Decorate the $state service, so we can decorate the $state.transitionTo() function with sticky state stuff. 49841 $provide.decorator("$state", ['$delegate', '$log', '$q', function ($state, $log, $q) { 49842 // Note: this code gets run only on the first state that is decorated 49843 root = $state.$current; 49844 internalStates[""] = root; 49845 root.parent = inactivePseudoState; // Make inactivePsuedoState the parent of root. "wat" 49846 inactivePseudoState.parent = undefined; // Make inactivePsuedoState the real root. 49847 // Add another locals bucket, as a parent to inactivatePseudoState locals. 49848 // This is for temporary storage of locals of states being reactivated while a transition is pending 49849 // This is necessary in some cases where $viewContentLoading is triggered before the $state.$current is updated to the toState. 49850 inactivePseudoState.locals = inherit(reactivatingLocals, inactivePseudoState.locals); 49851 root.locals = inherit(inactivePseudoState.locals, root.locals); // make root locals extend the __inactives locals. 49852 delete inactivePseudoState.locals.globals; 49853 49854 // Hold on to the real $state.transitionTo in a module-scope variable. 49855 $state_transitionTo = $state.transitionTo; 49856 49857 // ------------------------ Decorated transitionTo implementation begins here --------------------------- 49858 $state.transitionTo = function (to, toParams, options) { 49859 var DEBUG = $stickyStateProvider.debugMode(); 49860 // TODO: Move this to module.run? 49861 // TODO: I'd rather have root.locals prototypally inherit from inactivePseudoState.locals 49862 // Link root.locals and inactives.locals. Do this at runtime, after root.locals has been set. 49863 if (!inactivePseudoState.locals) 49864 inactivePseudoState.locals = root.locals; 49865 var idx = pendingTransitions.length; 49866 if (pendingRestore) { 49867 pendingRestore(); 49868 if (DEBUG) { 49869 $log.debug("Restored paths from pending transition"); 49870 } 49871 } 49872 49873 var fromState = $state.$current, fromParams = $state.params; 49874 var rel = options && options.relative || $state.$current; // Not sure if/when $state.$current is appropriate here. 49875 var toStateSelf = $state.get(to, rel); // exposes findState relative path functionality, returns state.self 49876 var savedToStatePath, savedFromStatePath, stickyTransitions; 49877 var reactivated = [], exited = [], terminalReactivatedState; 49878 toParams = toParams || {}; 49879 arguments[1] = toParams; 49880 49881 var noop = function () { 49882 }; 49883 // Sticky states works by modifying the internal state objects of toState and fromState, especially their .path(s). 49884 // The restore() function is a closure scoped function that restores those states' definitions to their original values. 49885 var restore = function () { 49886 if (savedToStatePath) { 49887 toState.path = savedToStatePath; 49888 savedToStatePath = null; 49889 } 49890 49891 if (savedFromStatePath) { 49892 fromState.path = savedFromStatePath; 49893 savedFromStatePath = null; 49894 } 49895 49896 angular.forEach(restore.restoreFunctions, function (restoreFunction) { 49897 restoreFunction(); 49898 }); 49899 // Restore is done, now set the restore function to noop in case it gets called again. 49900 restore = noop; 49901 // pendingRestore keeps track of a transition that is in progress. It allows the decorated transitionTo 49902 // method to be re-entrant (for example, when superceding a transition, i.e., redirect). The decorated 49903 // transitionTo checks right away if there is a pending transition in progress and restores the paths 49904 // if so using pendingRestore. 49905 pendingRestore = null; 49906 pendingTransitions.splice(idx, 1); // Remove this transition from the list 49907 }; 49908 49909 // All decorated transitions have their toState.path and fromState.path replaced. Surrogate states also make 49910 // additional changes to the states definition before handing the transition off to UI-Router. In particular, 49911 // certain types of surrogate states modify the state.self object's onEnter or onExit callbacks. 49912 // Those surrogate states must then register additional restore steps using restore.addRestoreFunction(fn) 49913 restore.restoreFunctions = []; 49914 restore.addRestoreFunction = function addRestoreFunction(fn) { 49915 this.restoreFunctions.push(fn); 49916 }; 49917 49918 49919 // --------------------- Surrogate State Functions ------------------------ 49920 // During a transition, the .path arrays in toState and fromState are replaced. Individual path elements 49921 // (states) which aren't being "kept" are replaced with surrogate elements (states). This section of the code 49922 // has factory functions for all the different types of surrogate states. 49923 49924 49925 function stateReactivatedSurrogatePhase1(state) { 49926 var surrogate = angular.extend(new SurrogateState("reactivate_phase1"), { locals: state.locals }); 49927 surrogate.self = angular.extend({}, state.self); 49928 return surrogate; 49929 } 49930 49931 function stateReactivatedSurrogatePhase2(state) { 49932 var surrogate = angular.extend(new SurrogateState("reactivate_phase2"), state); 49933 var oldOnEnter = surrogate.self.onEnter; 49934 surrogate.resolve = {}; // Don't re-resolve when reactivating states (fixes issue #22) 49935 // TODO: Not 100% sure if this is necessary. I think resolveState will load the views if I don't do this. 49936 surrogate.views = {}; // Don't re-activate controllers when reactivating states (fixes issue #22) 49937 surrogate.self.onEnter = function () { 49938 // ui-router sets locals on the surrogate to a blank locals (because we gave it nothing to resolve) 49939 // Re-set it back to the already loaded state.locals here. 49940 surrogate.locals = state.locals; 49941 _StickyState.stateReactivated(state); 49942 }; 49943 restore.addRestoreFunction(function () { 49944 state.self.onEnter = oldOnEnter; 49945 }); 49946 return surrogate; 49947 } 49948 49949 function stateInactivatedSurrogate(state) { 49950 var surrogate = new SurrogateState("inactivate"); 49951 surrogate.self = state.self; 49952 var oldOnExit = state.self.onExit; 49953 surrogate.self.onExit = function () { 49954 _StickyState.stateInactivated(state); 49955 }; 49956 restore.addRestoreFunction(function () { 49957 state.self.onExit = oldOnExit; 49958 }); 49959 return surrogate; 49960 } 49961 49962 function stateEnteredSurrogate(state, toParams) { 49963 var oldOnEnter = state.self.onEnter; 49964 state.self.onEnter = function () { 49965 _StickyState.stateEntering(state, toParams, oldOnEnter); 49966 }; 49967 restore.addRestoreFunction(function () { 49968 state.self.onEnter = oldOnEnter; 49969 }); 49970 49971 return state; 49972 } 49973 49974 // TODO: This may be completely unnecessary now that we're using $$uirouterextrasreload temp param 49975 function stateUpdateParamsSurrogate(state, toParams) { 49976 var oldOnEnter = state.self.onEnter; 49977 state.self.onEnter = function () { 49978 _StickyState.stateEntering(state, toParams, oldOnEnter, true); 49979 }; 49980 restore.addRestoreFunction(function () { 49981 state.self.onEnter = oldOnEnter; 49982 }); 49983 49984 return state; 49985 } 49986 49987 function stateExitedSurrogate(state) { 49988 var oldOnExit = state.self.onExit; 49989 state.self.onExit = function () { 49990 _StickyState.stateExiting(state, exited, oldOnExit); 49991 }; 49992 restore.addRestoreFunction(function () { 49993 state.self.onExit = oldOnExit; 49994 }); 49995 49996 return state; 49997 } 49998 49999 50000 // --------------------- decorated .transitionTo() logic starts here ------------------------ 50001 if (toStateSelf) { 50002 var toState = internalStates[toStateSelf.name]; // have the state, now grab the internal state representation 50003 if (toState) { 50004 // Save the toState and fromState paths to be restored using restore(
50004) 50005 savedToStatePath = toState.path; 50006 savedFromStatePath = fromState.path; 50007 50008 // Try to resolve options.reload to a state. If so, we'll reload only up to the given state. 50009 var reload = options && options.reload || false; 50010 var reloadStateTree = reload && (reload === true ? savedToStatePath[0].self : $state.get(reload, rel)); 50011 // If options.reload is a string or a state, we want to handle reload ourselves and not 50012 // let ui-router reload the entire toPath. 50013 if (options && reload && reload !== true) 50014 delete options.reload; 50015 50016 var currentTransition = { 50017 toState: toState, 50018 toParams: toParams || {}, 50019 fromState: fromState, 50020 fromParams: fromParams || {}, 50021 options: options, 50022 reloadStateTree: reloadStateTree 50023 }; 50024 50025 pendingTransitions.push(currentTransition); // TODO: See if a list of pending transitions is necessary. 50026 pendingRestore = restore; 50027 50028 // If we're reloading from a state and below, temporarily add a param to the top of the state tree 50029 // being reloaded, and add a param value to the transition. This will cause the "has params changed 50030 // for state" check to return true, and the states will be reloaded. 50031 if (reloadStateTree) { 50032 currentTransition.toParams.$$uirouterextrasreload = Math.random(); 50033 var params = reloadStateTree.$$state().params; 50034 var ownParams = reloadStateTree.$$state().ownParams; 50035 50036 if (versionHeuristics.hasParamSet) { 50037 var tempParam = new $urlMatcherFactoryProvider.Param('$$uirouterextrasreload'); 50038 params.$$uirouterextrasreload = ownParams.$$uirouterextrasreload = tempParam; 50039 restore.restoreFunctions.push(function() { 50040 delete params.$$uirouterextrasreload; 50041 delete ownParams.$$uirouterextrasreload; 50042 }); 50043 } else { 50044 params.push('$$uirouterextrasreload'); 50045 ownParams.push('$$uirouterextrasreload'); 50046 restore.restoreFunctions.push(function() { 50047 params.length = params.length -1; 50048 ownParams.length = ownParams.length -1; 50049 }); 50050 } 50051 } 50052 50053 // $StickyStateProvider.processTransition analyzes the states involved in the pending transition. It 50054 // returns an object that tells us: 50055 // 1) if we're involved in a sticky-type transition 50056 // 2) what types of exit transitions will occur for each "exited" path element 50057 // 3) what types of enter transitions will occur for each "entered" path element 50058 // 4) which states will be inactive if the transition succeeds. 50059 stickyTransitions = _StickyState.processTransition(currentTransition); 50060 50061 if (DEBUG) debugTransition($log, currentTransition, stickyTransitions); 50062 50063 // Begin processing of surrogate to and from paths. 50064 var surrogateToPath = toState.path.slice(0, stickyTransitions.keep); 50065 var surrogateFromPath = fromState.path.slice(0, stickyTransitions.keep); 50066 50067 // Clear out and reload inactivePseudoState.locals each time transitionTo is called 50068 angular.forEach(inactivePseudoState.locals, function (local, name) { 50069 if (name.indexOf("@") != -1) delete inactivePseudoState.locals[name]; 50070 }); 50071 50072 var saveViewsToLocals = function (targetObj) { 50073 return function(view, name) { 50074 if (name.indexOf("@") !== -1) { // Only grab this state's "view" locals 50075 targetObj[name] = view; // Add all inactive views not already included. 50076 } 50077 } 50078 }; 50079 50080 // For each state that will be inactive when the transition is complete, place its view-locals on the 50081 // __inactives pseudostate's .locals. This allows the ui-view directive to access them and 50082 // render the inactive views.
50083 forEach(stickyTransitions.inactives, function(state) { 50084 forEach(state.locals, saveViewsToLocals(inactivePseudoState.locals)) 50085 }); 50086 50087 // For each state that will be reactivated during the transition, place its view-locals on a separate 50088 // locals object (prototypal parent of __inactives.locals, and remove them when the transition is complete. 50089 // This is necessary when we a transition will reactivate one state, but enter a second. 50090 // Gory details: 50091 // - the entering of a new state causes $view.load() to fire $viewContentLoading while the transition is 50092 // still in process 50093 // - all ui-view(s) check if they should re-render themselves in response to this event. 50094 // - ui-view checks if previousLocals is equal to currentLocals 50095 // - it uses $state.$current.locals[myViewName] for previousLocals 50096 // - Because the transition is not completed, $state.$current is set to the from state, and 50097 // the ui-view for a reactivated state cannot find its previous locals. 50098 forEach(stickyTransitions.reactivatingStates, function(state) { 50099 forEach(state.locals, saveViewsToLocals(reactivatingLocals)); 50100 }); 50101 50102 // When the transition is complete, remove the copies of the view locals from reactivatingLocals. 50103 restore.addRestoreFunction(function clearReactivatingLocals() { 50104 forEach(reactivatingLocals, function (val, viewname) { 50105 delete reactivatingLocals[viewname]; 50106 }) 50107 }); 50108 50109 // Find all the states the transition will be entering. For each entered state, check entered-state-transition-type 50110 // Depending on the entered-state transition type, place the proper surrogate state on the surrogate toPath. 50111 angular.forEach(stickyTransitions.enter, function (value, idx) { 50112 var surrogate; 50113 var enteringState = toState.path[idx]; 50114 if (value === "reactivate") { 50115 // Reactivated states require TWO surrogates. The "phase 1 reactivated surrogates" are added to both 50116 // to.path and from.path, and as such, are considered to be "kept" by UI-Router. 50117 // This is required to get UI-Router to add the surrogate locals to the protoypal locals object 50118 surrogate = stateReactivatedSurrogatePhase1(enteringState); 50119 surrogateToPath.push(surrogate); 50120 surrogateFromPath.push(surrogate); // so toPath[i] === fromPath[i] 50121 50122 // The "phase 2 reactivated surrogate" is added to the END of the .path, after all the phase 1 50123 // surrogates have been added. 50124 reactivated.push(stateReactivatedSurrogatePhase2(enteringState)); 50125 terminalReactivatedState = enteringState; 50126 } else if (value === "reload") { 50127 // If the state params have been changed, we need to exit any inactive states and re-enter them. 50128 surrogateToPath.push(stateUpdateParamsSurrogate(enteringState)); 50129 terminalReactivatedState = enteringState; 50130 } else if (value === "enter") { 50131 // Standard enter transition. We still wrap it in a surrogate. 50132 surrogateToPath.push(stateEnteredSurrogate(enteringState)); 50133 } 50134 }); 50135 50136 // Find all the states the transition will be exiting. For each exited state, check the exited-state-transition-type. 50137 // Depending on the exited-state transition type, place a surrogate state on the surrogate fromPath. 50138 angular.forEach(stickyTransitions.exit, function (value, idx) { 50139 var exiting = fromState.path[idx]; 50140 if (value === "inactivate") { 50141 surrogateFromPath.push(stateInactivatedSurrogate(exiting)); 50142 exited.push(exiting); 50143 } else if (value === "exit") { 50144 surrogateFromPath.push(stateExitedSurrogate(exiting)); 50145 exited.push(exiting); 50146 } 50147 }); 50148 50149 // Add surrogate states for reactivated to ToPath again (phase 2), this time without a matching FromPath entry 50150 // This is to get ui-router to call the surrogate's onEnter callback. 50151 if (reactivated.length) { 50152 angular.forEach(reactivated, function (surrogate) { 50153 surrogateToPath.push(surrogate); 50154 }); 50155 } 50156 50157 // We may transition directly to an inactivated state, reactivating it. In this case, we should 50158 // exit all of that state's inactivated children. 50159 var orphans = stickyTransitions.orphans; 50160 // Add surrogate exited states for all orphaned descendants of the Deepest Reactivated State 50161 surrogateFromPath = surrogateFromPath.concat(map(orphans, function (exiting) { 50162 return stateExitedSurrogate(exiting); 50163 })); 50164 exited = exited.concat(orphans); 50165 50166 // Replace the .path variables. toState.path and fromState.path are now ready for a sticky transition. 50167 fromState.path = surrogateFromPath; 50168 toState.path = surrogateToPath; 50169 50170 var pathMessage = function (state) { 50171 return (state.surrogateType ? state.surrogateType + ":" : "") + state.self.name; 50172 }; 50173 if (DEBUG) $log.debug("SurrogateFromPath: ", map(surrogateFromPath, pathMessage)); 50174 if (DEBUG) $log.debug("SurrogateToPath: ", map(surrogateToPath, pathMessage)); 50175 } 50176 } 50177 50178 // toState and fromState are all set up; now run stock UI-Router's $state.transitionTo(). 50179 var transitionPromise = $state_transitionTo.apply($state, arguments); 50180 50181 // Add post-transition promise handlers, then return the promise to the original caller. 50182 return transitionPromise.then(function transitionSuccess(state) { 50183 // First, restore toState and fromState to their original values.
50184 restore(); 50185 if (DEBUG) debugViewsAfterSuccess($log, internalStates[state.name], $state); 50186 50187 state.status = 'active'; // TODO: This status is used in statevis.js, and almost certainly belongs elsewhere. 50188 50189 return state; 50190 }, function transitionFailed(err) { 50191 restore(); 50192 if (DEBUG && 50193 err.message !== "transition prevented" && 50194 err.message !== "transition aborted" && 50195 err.message !== "transition superseded") { 50196 $log.debug("transition failed", err); 50197 $log.debug(err.stack); 50198 } 50199 return $q.reject(err); 50200 }); 50201 }; 50202 return $state; 50203 }]); 50204 50205 50206 50207 function debugTransition($log, currentTransition, stickyTransition) { 50208 function message(path, index, state) { 50209 return (path[index] ? path[index].toUpperCase() + ": " + state.self.name : "(" + state.self.name + ")"); 50210 } 50211 50212 var inactiveLogVar = map(stickyTransition.inactives, function (state) { 50213 return state.self.name; 50214 }); 50215 var enterLogVar = map(currentTransition.toState.path, function (state, index) { 50216 return message(stickyTransition.enter, index, state); 50217 }); 50218 var exitLogVar = map(currentTransition.fromState.path, function (state, index) { 50219 return message(stickyTransition.exit, index, state); 50220 }); 50221 50222 var transitionMessage = currentTransition.fromState.self.name + ": " + 50223 angular.toJson(currentTransition.fromParams) + ": " + 50224 " -> " + 50225 currentTransition.toState.self.name + ": " + 50226 angular.toJson(currentTransition.toParams); 50227 50228 $log.debug("------------------------------------------------------"); 50229 $log.debug(" Current transition: ", transitionMessage); 50230 $log.debug("Before transition, inactives are: : ", map(_StickyState.getInactiveStates(), function (s) { 50231 return s.self.name; 50232 })); 50233 $log.debug("After transition, inactives will be: ", inactiveLogVar); 50234 $log.debug("Transition will exit: ", exitLogVar); 50235 $log.debug("Transition will enter: ", enterLogVar); 50236 } 50237 50238 function debugViewsAfterSuccess($log, currentState, $state) { 50239 $log.debug("Current state: " + currentState.self.name + ", inactive states: ", map(_StickyState.getInactiveStates(), function (s) { 50240 return s.self.name; 50241 })); 50242 50243 var statesOnly = function (local, name) { 50244 return name != 'globals' && name != 'resolve'; 50245 }; 50246 50247 var viewsForState = function (state) { 50248 var viewLocals = filterObj(state.locals, statesOnly); 50249 50250 if (!Object.keys(viewLocals).length) { 50251 viewLocals[''] = { $$state: { name: null } }; 50252 } 50253 50254 return map(viewLocals, function(local, name) { 50255 return { 50256 localsFor: state.self.name ? state.self.name : "(root)", 50257 uiViewName: name || null, 50258 filledByState: local.$$state.name 50259 }; 50260 }); 50261 }; 50262 50263 var viewsByState = viewsForState(currentState); 50264 var parent = currentState.parent; 50265 while (parent && parent !== currentState) { 50266 viewsByState = viewsByState.concat(viewsForState(parent)); 50267 currentState = parent; 50268 parent = currentState.parent; 50269 } 50270 50271 $log.debug("Views active on each state:"); 50272 console.table(viewsByState.reverse()); 50273 } 50274 } 50275 ] 50276); 50277 50278(function(angular, undefined) { 50279 var app = angular.module('ct.ui.router.extras.future', [ 'ct.ui.router.extras.core' ]); 50280 50281 _futureStateProvider.$inject = [ '$stateProvider', '$urlRouterProvider', '$urlMatcherFactoryProvider', 'uirextras_coreProvider' ]; 50282 function _futureStateProvider($stateProvider, $urlRouterProvider, $urlMatcherFactory, uirextras_coreProvider) { 50283 var core = uirextras_coreProvider; 50284 var internalStates = core.internalStates; 50285 var stateFactories = {}, futureStates = {}; 50286 var lazyloadInProgress = false, resolveFunctions = [], initPromise, initDone = false;
50287 var provider = this; 50288 50289 // This function registers a promiseFn, to be resolved before the url/state matching code 50290 // will reject a route. The promiseFn is injected/executed using the runtime $injector. 50291 // The function should return a promise. 50292 // When all registered promises are resolved, then the route is re-sync'ed. 50293 50294 // Example: function($http) { 50295 // return $http.get('//server.com/api/DynamicFutureStates').then(function(data) { 50296 // angular.forEach(data.futureStates, function(fstate) { $futureStateProvider.futureState(fstate); }); 50297 // }; 50298 // } 50299 this.addResolve = function (promiseFn) { 50300 resolveFunctions.push(promiseFn); 50301 }; 50302 50303 // Register a state factory function for a particular future-state type. This factory, given a future-state object, 50304 // should create a ui-router state. 50305 // The factory function is injected/executed using the runtime $injector. The future-state is injected as 'futureState'. 50306 50307 // Example: 50308 // $futureStateProvider.stateFactory('test', function(futureState) { 50309 // return { 50310 // name: futureState.stateName, 50311 // url: futureState.urlFragment, 50312 // template: '<h3>Future State Template</h3>', 50313 // controller: function() { 50314 // console.log("Entered state " + futureState.stateName); 50315 // } 50316 // } 50317 // }); 50318 this.stateFactory = function (futureStateType, factory) { 50319 stateFactories[futureStateType] = factory; 50320 }; 50321 50322 this.futureState = function (futureState) { 50323 if (futureState.stateName) // backwards compat for now 50324 futureState.name = futureState.stateName; 50325 if (futureState.urlPrefix) // backwards compat for now 50326 futureState.url = "^" + futureState.urlPrefix; 50327 50328 futureStates[futureState.name] = futureState; 50329 var parentMatcher, parentName = futureState.name.split(/\./).slice(0, -1).join("."), 50330 realParent = findState(futureState.parent || parentName); 50331 if (realParent) { 50332 parentMatcher = realParent.url || realParent.navigable && realParent.navigable.url; 50333 } else if (parentName === "") { 50334 parentMatcher = $urlMatcherFactory.compile(""); 50335 } else { 50336 var futureParent = findState((futureState.parent || parentName), true); 50337 if (!futureParent) throw new Error("Couldn't determine parent state of future state. FutureState:" + angular.toJson(futureState)); 50338 var pattern; 50339 if (futureParent.urlMatcher) { 50340 pattern = futureParent.urlMatcher.source.replace(/\*rest$/, ""); 50341 } 50342 else { 50343 // if the futureParent doesn't have a urlMatcher, then we are still 50344 // starting from the beginning of the path 50345 pattern = ""; 50346 } 50347 parentMatcher = $urlMatcherFactory.compile(pattern); 50348 futureState.parentFutureState = futureParent; 50349 } 50350 if (futureState.url) { 50351 futureState.urlMatcher = futureState.url.charAt(0) === "^" ? 50352 $urlMatcherFactory.compile(futureState.url.substring(1) + "*rest") : 50353 parentMatcher.concat(futureState.url + "*rest"); 50354 } 50355 }; 50356 50357 this.get = function () { 50358 return angular.extend({}, futureStates); 50359 }; 50360 50361 function findState(stateOrName, findFutureState) { 50362 var statename = angular.isObject(stateOrName) ? stateOrName.name : stateOrName; 50363 return !findFutureState ? internalStates[statename] : futureStates[statename]; 50364 } 50365 50366 /* options is an object with at least a name or url attribute */ 50367 function findFutureState($state, options) { 50368 if (options.name) { 50369 var nameComponents = options.name.split(/\./); 50370 if (options.name.charAt(0) === '.') 50371 nameComponents[0] = $state.current.name; 50372 while (nameComponents.length) { 50373 var stateName = nameComponents.join("."); 50374 if ($state.get(stateName, { relative: $state.current })) 50375 return null; // State is already defined; nothing to do 50376 if (futureStates[stateName]) 50377 return futureStates[stateName]; 50378 nameComponents.pop(); 50379 } 50380 } 50381 50382 if (options.url) { 50383 var matches = []; 50384 for(var future in futureStates) { 50385 var matcher = futureStates[future].urlMatcher; 50386 if (matcher && matcher.exec(options.url)) { 50387 matches.push(futureStates[future]); 50388 } 50389 } 50390 // Find most specific by ignoring matching parents from matches 50391 var copy = matches.slice(0); 50392 for (var i = matches.length - 1; i >= 0; i--) { 50393 for (var j = 0; j < copy.length; j++) { 50394 if (matches[i] === copy[j].parentFutureState) matches.splice(i, 1); 50395 } 50396 } 50397 return matches[0]; 50398 } 50399 } 50400 50401 function lazyLoadState($injector, futureState) { 50402 lazyloadInProgress = true; 50403 var $q = $injector.get("$q"); 50404 if (!futureState) { 50405 var deferred = $q.defer(); 50406 deferred.reject("No lazyState passed in " + futureState); 50407 return deferred.promise; 50408 } 50409 50410 var parentPromises = $q.when([]), parentFuture = futureState.parentFutureState; 50411 if (parentFuture && futureStates[parentFuture.name]) { 50412 parentPromises = lazyLoadState($injector, futureStates[parentFuture.name]); 50413 } 50414 50415 var type = futureState.type; 50416 var factory = stateFactories[type]; 50417 if (!factory) throw Error("No state factory for futureState.type: " + (futureState && futureState.type)); 50418 50419 var failedLoadPolicy = factory.$options && factory.$options.failedLazyLoadPolicy || "remove"; 50420 function deregisterFutureState() { delete(futureStates[futureState.name]); } 50421 function errorHandler(err) { 50422 if (failedLoadPolicy === "remove") deregisterFutureState(); 50423 return $q.reject(err); 50424 } 50425 50426 return parentPromises.then(function(array) { 50427 var factoryPromise = $injector.invoke(factory, factory, { futureState: futureState }); 50428 50429 return factoryPromise.then(function(fullState) { 50430 deregisterFutureState(); // Success; remove future state 50431 if (fullState) { array.push(fullState); } // Pass a chain of realized states back 50432 return array; 50433 }); 50434 }).catch(errorHandler) 50435 } 50436 50437 var otherwiseFunc = [ '$log', '$location', 50438 function otherwiseFunc($log, $location) { 50439 //$log.debug("Unable to map " + $location.path()); 50440 }]; 50441 50442 function futureState_otherwise($injector, $location) { 50443 var resyncing = false;
50444 50445 var lazyLoadMissingState = 50446 ['$rootScope', '$urlRouter', '$state', 50447 function lazyLoadMissingState($rootScope, $urlRouter, $state) { 50448 function resync() { 50449 resyncing = true; $urlRouter.sync(); resyncing = false; 50450 } 50451 if (!initDone) { 50452 // Asynchronously load state definitions, then resync URL 50453 initPromise().then(resync); 50454 initDone = true; 50455 return; 50456 } 50457 50458 var futureState = findFutureState($state, { url: $location.path() }); 50459 if (!futureState) { 50460 return $injector.invoke(otherwiseFunc); 50461 } 50462 50463 // Config loaded. Asynchronously lazy-load state definition from URL fragment, if mapped. 50464 lazyLoadState($injector, futureState).then(function lazyLoadedStateCallback(states) { 50465 states.forEach(function (state) { 50466 if (state && (!$state.get(state) || (state.name && !$state.get(state.name)))) 50467 $stateProvider.state(state); 50468 }); 50469 lazyloadInProgress = false; 50470 resync(); 50471 }, function lazyLoadStateAborted() { 50472 lazyloadInProgress = false; 50473 resync(); 50474 }); 50475 }]; 50476 if (lazyloadInProgress) return; 50477 50478 var nextFn = resyncing ? otherwiseFunc : lazyLoadMissingState; 50479 return $injector.invoke(nextFn); 50480 } 50481 50482 $urlRouterProvider.otherwise(futureState_otherwise); 50483 50484 $urlRouterProvider.otherwise = function(rule) { 50485 if (angular.isString(rule)) { 50486 var redirect = rule; 50487 rule = function () { return redirect; }; 50488 } 50489 else if (!angular.isFunction(rule)) throw new Error("'rule' must be a function"); 50490 otherwiseFunc = ['$injector', '$location', rule]; 50491 return $urlRouterProvider; 50492 }; 50493 50494 var serviceObject = { 50495 getResolvePromise: function () { 50496 return initPromise(); 50497 } 50498 }; 50499 50500 // Used in .run() block to init 50501 this.$get = [ '$injector', '$state', '$q', '$rootScope', '$urlRouter', '$timeout', '$log', 50502 function futureStateProvider_get($injector, $state, $q, $rootScope, $urlRouter, $timeout, $log) { 50503 function init() { 50504 $rootScope.$on("$stateNotFound", function futureState_notFound(event, unfoundState, fromState, fromParams) { 50505 if (lazyloadInProgress) return; 50506 //$log.debug("event, unfoundState, fromState, fromParams", event, unfoundState, fromState, fromParams); 50507 50508 var futureState = findFutureState($state, { name: unfoundState.to }); 50509 if (!futureState) return; 50510 50511 event.preventDefault(); 50512 var promise = lazyLoadState($injector, futureState); 50513 promise.then(function (states) { 50514 states.forEach(function (state) { 50515 if (state && (!$state.get(state) || (state.name && !$state.get(state.name)))) 50516 $stateProvider.state(state); 50517 }); 50518 $state.go(unfoundState.to, unfoundState.toParams, unfoundState.options); 50519 lazyloadInProgress = false; 50520 }, function (error) { 50521 console.log("failed to lazy load state ", error); 50522 if (fromState.name) $state.go(fromState, fromParams); 50523 lazyloadInProgress = false; 50524 }); 50525 }); 50526 50527 // Do this better. Want to load remote config once, before everything else 50528 if (!initPromise) { 50529 var promises = []; 50530 angular.forEach(resolveFunctions, function (promiseFn) { 50531 promises.push($injector.invoke(promiseFn)); 50532 }); 50533 initPromise = function () { 50534 return $q.all(promises); 50535 }; 50536 } 50537 50538 // TODO: analyze this. I'm calling $urlRouter.sync() in two places for retry-initial-transition. 50539 // TODO: I should only need to do this once. Pick the better place and remove the extra resync. 50540 initPromise().then(function retryInitialState() { 50541 $timeout(function () { 50542 if ($state.transition) { 50543 $state.transition.then(retryInitialState, retryInitialState); 50544 } else { 50545 $urlRouter.sync(); 50546 } 50547 }); 50548 }); 50549 } 50550 50551 init(); 50552 50553 serviceObject.state = $stateProvider.state; 50554 serviceObject.futureState = provider.futureState; 50555 serviceObject.get = provider.get; 50556 50557 return serviceObject; 50558 } 50559 ]; 50560 } 50561 50562 app.provider('$futureState', _futureStateProvider); 50563 50564 var statesAddedQueue = { 50565 state: function(state) { 50566 if (statesAddedQueue.$rootScope) 50567 statesAddedQueue.$rootScope.$broadcast("$stateAdded", state); 50568 }, 50569 itsNowRuntimeOhWhatAHappyDay: function($rootScope) { 50570 statesAddedQueue.$rootScope = $rootScope; 50571 }, 50572 $rootScope: undefined 50573 }; 50574
50575 app.config([ '$stateProvider', function($stateProvider) { 50576 // decorate $stateProvider.state so we can broadcast when a real state was added 50577 var realStateFn = $stateProvider.state; 50578 $stateProvider.state = function state_announce() { 50579 var val = realStateFn.apply($stateProvider, arguments); 50580 50581 var state = angular.isObject(arguments[0]) ? arguments[0] : arguments[1]; 50582 statesAddedQueue.state(state); 50583 return val; 50584 }; 50585 }]); 50586 50587 // inject $futureState so the service gets initialized via $get(); 50588 app.run(['$futureState', function ($futureState, $rootScope) { 50589 statesAddedQueue.itsNowRuntimeOhWhatAHappyDay($rootScope); 50590 } ]); 50591 50592})(angular); 50593 50594angular.module('ct.ui.router.extras.previous', [ 'ct.ui.router.extras.core', 'ct.ui.router.extras.transition' ]).service("$previousState", 50595 [ '$rootScope', '$state', '$q', 50596 function ($rootScope, $state, $q) { 50597 var previous = null, lastPrevious = null, memos = {}; 50598 50599 $rootScope.$on("$transitionStart", function(evt, $transition$) { 50600 var from = $transition$.from; 50601 // Check if the fromState is navigable before tracking it. 50602 // Root state doesn't get decorated with $$state(). Doh. 50603 var fromState = from.state && from.state.$$state && from.state.$$state(); 50604 function commit() { lastPrevious = null; } 50605 function revert() { previous = lastPrevious; } 50606 if (fromState) { 50607 lastPrevious = previous; 50608 previous = $transition$.from; 50609 50610 $transition$.promise.then(commit)['catch'](revert); 50611 } 50612 }); 50613 50614 var $previousState = { 50615 get: function (memoName) { 50616 return memoName ? memos[memoName] : previous; 50617 }, 50618 set: function (memoName, previousState, previousParams) { 50619 memos[memoName] = { state: $state.get(previousState), params: previousParams }; 50620 }, 50621 go: function (memoName, options) { 50622 var to = $previousState.get(memoName); 50623 if (!to) { 50624 return $q.reject(new Error('no previous state ' + (memoName ? 'for memo: ' + memoName : ''))); 50625 } 50626 return $state.go(to.state, to.params, options); 50627 }, 50628 memo: function (memoName, defaultStateName, defaultStateParams) { 50629 memos[memoName] = previous || { state: $state.get(defaultStateName), params: defaultStateParams }; 50630 }, 50631 forget: function (memoName) { 50632 if (memoName) { 50633 delete memos[memoName]; 50634 } else { 50635 previous = undefined; 50636 } 50637 } 50638 }; 50639 50640 return $previousState; 50641 } 50642 ] 50643); 50644 50645angular.module('ct.ui.router.extras.previous').run(['$previousState', function ($previousState) { 50646 // Inject $previousState so it can register $rootScope events 50647}]); 50648 50649 50650angular.module("ct.ui.router.extras.transition", [ 'ct.ui.router.extras.core' ]).config( [ "$provide", function ($provide) { 50651 // Decorate the $state service, so we can replace $state.transitionTo() 50652 $provide.decorator("$state", ['$delegate', '$rootScope', '$q', '$injector', 50653 function ($state, $rootScope, $q, $injector) { 50654 // Keep an internal reference to the real $state.transitionTo function 50655 var $state_transitionTo = $state.transitionTo; 50656 // $state.transitionTo can be re-entered. Keep track of re-entrant stack 50657 var transitionDepth = -1; 50658 var tDataStack = []; 50659 var restoreFnStack = []; 50660 50661 // This function decorates the $injector, adding { $transition$: tData } to invoke() and instantiate() locals. 50662 // It returns a function that restores $injector to its previous state. 50663 function decorateInjector(tData) { 50664 var oldinvoke = $injector.invoke; 50665 var oldinstantiate = $injector.instantiate; 50666 $injector.invoke = function (fn, self, locals) { 50667 return oldinvoke(fn, self, angular.extend({$transition$: tData}, locals)); 50668 }; 50669 $injector.instantiate = function (fn, locals) { 50670 return oldinstantiate(fn, angular.extend({$transition$: tData}, locals)); 50671 }; 50672 50673 return function restoreItems() { 50674 $injector.invoke = oldinvoke; 50675 $injector.instantiate = oldinstantiate; 50676 }; 50677 } 50678 50679 function popStack() { 50680 restoreFnStack.pop()(); 50681 tDataStack.pop(); 50682 transitionDepth--; 50683 } 50684 50685 // This promise callback (for when the real transitionTo is successful) runs the restore function for the 50686 // current stack level, then broadcasts the $transitionSuccess event. 50687 function transitionSuccess(deferred, tSuccess) { 50688 return function successFn(data) { 50689 popStack(); 50690 $rootScope.$broadcast("$transitionSuccess", tSuccess); 50691 deferred.resolve(data); // $transition$ deferred 50692 return data; 50693 }; 50694 } 50695 50696 // This promise callback (for when the real transitionTo fails) runs the restore function for the 50697 // current stack level, then broadcasts the $transitionError event. 50698 function transitionFailure(deferred, tFail) { 50699 return function failureFn(error) { 50700 popStack(); 50701 $rootScope.$broadcast("$transitionError", tFail, error); 50702 deferred.reject(error); // $transition$ deferred 50703 return $q.reject(error); 50704 }; 50705 } 50706 50707 // Decorate $state.transitionTo.
50708 $state.transitionTo = function (to, toParams, options) { 50709 // Create a deferred/promise which can be used earlier than UI-Router's transition promise. 50710 var deferred = $q.defer(); 50711 // Place the promise in a transition data, and place it on the stack to be used in $stateChangeStart 50712 var tData = tDataStack[++transitionDepth] = { 50713 promise: deferred.promise 50714 }; 50715 // placeholder restoreFn in case transitionTo doesn't reach $stateChangeStart (state not found, etc) 50716 restoreFnStack[transitionDepth] = function() { }; 50717 // Invoke the real $state.transitionTo 50718 var tPromise = $state_transitionTo.apply($state, arguments); 50719 50720 // insert our promise callbacks into the chain. 50721 return tPromise.then(transitionSuccess(deferred, tData), transitionFailure(deferred, tData)); 50722 }; 50723 50724 // This event is handled synchronously in transitionTo call stack 50725 $rootScope.$on("$stateChangeStart", function (evt, toState, toParams, fromState, fromParams) { 50726 if (transitionDepth >= tDataStack.length) return; 50727 var depth = transitionDepth; 50728 // To/From is now normalized by ui-router. Add this information to the transition data object. 50729 var tData = angular.extend(tDataStack[depth], { 50730 to: { state: toState, params: toParams }, 50731 from: { state: fromState, params: fromParams } 50732 }); 50733 50734 var restoreFn = decorateInjector(tData); 50735 restoreFnStack[depth] = restoreFn; 50736 $rootScope.$broadcast("$transitionStart", tData); 50737 } 50738 ); 50739 50740 return $state; 50741 }]); 50742 } 50743 ] 50744); 50745 50746// statevis requires d3. 50747(function () { 50748 "use strict"; 50749 var app = angular.module("ct.ui.router.extras.statevis", [ 'ct.ui.router.extras.core', 'ct.ui.router.extras.sticky' ]); 50750 50751 app.directive('stateVis', [ '$state', '$timeout', '$interval', stateVisDirective ]); 50752 50753 /** 50754 * This directive gets all the current states using $state.get() and displays them in a tree using D3 lib. 50755 * It then listens for state events and updates the tree. 50756 * 50757 * Usage: 50758 * <state-vis height="1000px" width="1000px"></state-vis> 50759 */ 50760 function stateVisDirective($state, $timeout, $interval) { 50761 return { 50762 scope: { 50763 width: '@', 50764 height: '@' 50765 }, 50766 restrict: 'AE', 50767 template: '<svg></svg>', 50768 link: function (_scope, _elem, _attrs) { 50769 var stateMap = {}; 50770 var width = _scope.width || 400, 50771 height = _scope.height || 400; 50772 50773 var tree = d3.layout.tree() 50774 .size([width - 20, height - 20]) 50775 .separation(function (a, b) { 50776 return a.parent == b.parent ? 10 : 25; 50777 }); 50778 50779 var root = $state.get().filter(function (state) { return state.name === ""; })[0]; 50780 var nodes = tree(root); 50781 50782 root.parent = root; 50783 root.px = root.x = width / 2; 50784 root.py = root.y = height / 2; 50785 50786 var activeNode = { }; 50787 activeNode.px = activeNode.x = root.px; 50788 activeNode.py = activeNode.y = root.py; 50789 50790 var diagonal = d3.svg.diagonal(); 50791 50792 var svg = d3.select(_elem.find("svg")[0]) 50793 .attr("width", width) 50794 .attr("height", height) 50795 .append("g") 50796 .attr("transform", "translate(10, 10)"); 50797 50798 var node = svg.selectAll(".node"), 50799 link = svg.selectAll(".link"), 50800 active = svg.selectAll(".active") 50801 ; 50802 50803 var updateInterval = 200, 50804 transLength = 200, 50805 timer = setInterval(update, updateInterval); 50806 50807 function addStates(data) { 50808 // *********** Convert flat data into a nice tree *************** 50809 data = data.map(function (node) { 50810 return node.name === "" ? root : angular.copy(node); 50811 }); 50812 angular.extend(stateMap, data.reduce(function (map, node) { 50813 map[node.name] = node; 50814 return map; 50815 }, {})); 50816 50817 data.forEach(function (node) { 50818 // add to parent 50819 var parentName = node.name.split(/\./).slice(0, -1).join("."); 50820 var parent = node.name != parentName && stateMap[parentName]; 50821 if (parent) { 50822 (parent.children || (parent.children = [])).push(node); // create child array if it doesn't exist 50823 node.px = parent.px; 50824 node.py = parent.py; 50825 nodes.push(node); 50826 } 50827 }); 50828 } 50829 50830 $interval(function () { 50831 _scope.states = $state.get(); 50832 angular.forEach(nodes, function (n) { 50833 var s = $state.get(n.name); 50834 if (s) { 50835 n.status = s.status || 'exited'; 50836 } 50837 }); 50838// _scope.futureStates = $futureState.get(); 50839 }, 250); 50840 50841 _scope.$watchCollection("states", function (newval, oldval) { 50842 var oldstates = (oldval || []).map(function (s) { return s.name; }); 50843 addStates((newval || []).filter(function(state) { return oldstates.indexOf(state.name) == -1; } )); 50844// addStates(_.reject(newval, function (state) { return _.contains(oldstates, state.name); })); 50845 }); 50846 50847// addStates($state.get()); 50848 update(updateInterval); 50849
50850 function update() { 50851 // Recompute the layout and data join. 50852 node = node.data(tree.nodes(root), function (d) { return d.name; }); 50853 link = link.data(tree.links(nodes), function (d) { return d.target.name; }); 50854 active = active.data(activeNode); 50855 50856 nodes.forEach(function (d) { d.y = d.depth * 70; }); 50857 50858 // Add entering nodes in the parentâs old position. 50859 var nodeEnter = node.enter(); 50860 50861 function stateName(node) { 50862 var name = node.name.split(".").pop(); 50863 if (node.sticky) { name += " (STICKY)"; } 50864 if (node.deepStateRedirect) { name += " (DSR)"; } 50865 return name; 50866 } 50867 50868 active.enter() 50869 .append("circle") 50870 .attr("class", "active") 50871 .attr("r", 13) 50872 .attr("cx", function (d) { return d.parent.px || 100; }) 50873 .attr("cy", function (d) { return d.parent.py || 100; }) 50874 ; 50875 50876 nodeEnter.append("circle") 50877 .attr("class", "node") 50878 .attr("r", 9) 50879 .attr("cx", function (d) { return d.parent.px; }) 50880 .attr("cy", function (d) { return d.parent.py; }); 50881 50882 nodeEnter.append("text") 50883 .attr("class", "label") 50884 .attr("x", function (d) { return d.parent.px; }) 50885 .attr("y", function (d) { return d.parent.py; }) 50886 .attr("text-anchor", function (d) { return "middle"; }) 50887 .text(stateName) 50888 .style("fill-opacity", 1); 50889 50890 50891 // Add entering links in the parentâs old position. 50892 link.enter().insert("path", ".node") 50893 .attr("class", "link") 50894 .attr("d", function (d) { 50895 var o = {x: d.source.px, y: d.source.py}; 50896 return diagonal({source: o, target: o}); 50897 }); 50898 50899 // Transition nodes and links to their new positions. 50900 var t = svg.transition() 50901 .duration(transLength); 50902 50903 t.selectAll(".link") 50904 .attr("d", diagonal); 50905 50906 /* jshint -W093 */ 50907 var circleColors = { entered: '#AF0', exited: '#777', active: '#0f0', inactive: '#55F', future: '#009' }; 50908 t.selectAll(".node") 50909 .attr("cx", function (d) { return d.px = d.x; }) 50910 .attr("cy", function (d) { return d.py = d.y; }) 50911 .attr("r", function (d) { return d.status === 'active' ? 15 : 10; }) 50912 .style("fill", function (d) { return circleColors[d.status] || "#FFF"; }); 50913 50914 t.selectAll(".label") 50915 .attr("x", function (d) { return d.px = d.x; }) 50916 .attr("y", function (d) { return d.py = d.y - 15; }) 50917 .attr("transform", function (d) { return "rotate(-25 " + d.x + " " + d.y + ")"; }) 50918 ; 50919 50920 t.selectAll(".active") 50921 .attr("x", function (d) { return d.px = d.x; }) 50922 .attr("y", function (d) { return d.py = d.y - 15; }); 50923 } 50924 } 50925 }; 50926 } 50927})(); 50928 50929 50930angular.module("ct.ui.router.extras", 50931 [ 50932 'ct.ui.router.extras.core', 50933 'ct.ui.router.extras.dsr', 50934 'ct.ui.router.extras.future', 50935 'ct.ui.router.extras.previous', 50936 'ct.ui.router.extras.statevis', 50937 'ct.ui.router.extras.sticky', 50938 'ct.ui.router.extras.transition' 50939 ]); 50940 50941 50942})); 50943},{"angular":5}],13:[function(require,module,exports){ 50944require('angular'); 50945require('angular-ui-router'); 50946require('ui-router-extras'); 50947require('angular-marked'); 50948window.CodeMirror = require('codemirror'); 50949require('codemirror/mode/css/css.js'); 50950require('codemirror/mode/javascript/javascript.js'); 50951require('codemirror/mode/xml/xml.js'); 50952require('codemirror/mode/htmlmixed/htmlmixed.js'); 50953},{"angular":5,"angular-marked":1,"angular-ui-router":3,"codemirror":6,"codemirror/mode/css/css.js":7,"codemirror/mode/htmlmixed/htmlmixed.js":8,"codemirror/mode/javascript/javascript.js":9,"codemirror/mode/xml/xml.js":10,"ui-router-extras":12}]},{},[13]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.