PageRenderTime 139ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 2ms

/WadaBarHybrid/js0/contrib/angular-1.1.1.js

https://bitbucket.org/yamert/wada
JavaScript | 14652 lines | 8034 code | 1111 blank | 5507 comment | 1107 complexity | f99b7dad1ce87d1075920de68ee09981 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. /**
  2. * @license AngularJS v1.1.1
  3. * (c) 2010-2012 Google, Inc. http://angularjs.org
  4. * License: MIT
  5. */
  6. (function(window, document, undefined) {
  7. 'use strict';
  8. ////////////////////////////////////
  9. /**
  10. * @ngdoc function
  11. * @name angular.lowercase
  12. * @function
  13. *
  14. * @description Converts the specified string to lowercase.
  15. * @param {string} string String to be converted to lowercase.
  16. * @returns {string} Lowercased string.
  17. */
  18. var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
  19. /**
  20. * @ngdoc function
  21. * @name angular.uppercase
  22. * @function
  23. *
  24. * @description Converts the specified string to uppercase.
  25. * @param {string} string String to be converted to uppercase.
  26. * @returns {string} Uppercased string.
  27. */
  28. var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};
  29. var manualLowercase = function(s) {
  30. return isString(s)
  31. ? s.replace(/[A-Z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) | 32);})
  32. : s;
  33. };
  34. var manualUppercase = function(s) {
  35. return isString(s)
  36. ? s.replace(/[a-z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) & ~32);})
  37. : s;
  38. };
  39. // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
  40. // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
  41. // with correct but slower alternatives.
  42. if ('i' !== 'I'.toLowerCase()) {
  43. lowercase = manualLowercase;
  44. uppercase = manualUppercase;
  45. }
  46. function fromCharCode(code) {return String.fromCharCode(code);}
  47. var Error = window.Error,
  48. /** holds major version number for IE or NaN for real browsers */
  49. msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]),
  50. jqLite, // delay binding since jQuery could be loaded after us.
  51. jQuery, // delay binding
  52. slice = [].slice,
  53. push = [].push,
  54. toString = Object.prototype.toString,
  55. /** @name angular */
  56. angular = window.angular || (window.angular = {}),
  57. angularModule,
  58. nodeName_,
  59. uid = ['0', '0', '0'];
  60. /**
  61. * @ngdoc function
  62. * @name angular.forEach
  63. * @function
  64. *
  65. * @description
  66. * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
  67. * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
  68. * is the value of an object property or an array element and `key` is the object property key or
  69. * array element index. Specifying a `context` for the function is optional.
  70. *
  71. * Note: this function was previously known as `angular.foreach`.
  72. *
  73. <pre>
  74. var values = {name: 'misko', gender: 'male'};
  75. var log = [];
  76. angular.forEach(values, function(value, key){
  77. this.push(key + ': ' + value);
  78. }, log);
  79. expect(log).toEqual(['name: misko', 'gender:male']);
  80. </pre>
  81. *
  82. * @param {Object|Array} obj Object to iterate over.
  83. * @param {Function} iterator Iterator function.
  84. * @param {Object=} context Object to become context (`this`) for the iterator function.
  85. * @returns {Object|Array} Reference to `obj`.
  86. */
  87. function forEach(obj, iterator, context) {
  88. var key;
  89. if (obj) {
  90. if (isFunction(obj)){
  91. for (key in obj) {
  92. if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
  93. iterator.call(context, obj[key], key);
  94. }
  95. }
  96. } else if (obj.forEach && obj.forEach !== forEach) {
  97. obj.forEach(iterator, context);
  98. } else if (isObject(obj) && isNumber(obj.length)) {
  99. for (key = 0; key < obj.length; key++)
  100. iterator.call(context, obj[key], key);
  101. } else {
  102. for (key in obj) {
  103. if (obj.hasOwnProperty(key)) {
  104. iterator.call(context, obj[key], key);
  105. }
  106. }
  107. }
  108. }
  109. return obj;
  110. }
  111. function sortedKeys(obj) {
  112. var keys = [];
  113. for (var key in obj) {
  114. if (obj.hasOwnProperty(key)) {
  115. keys.push(key);
  116. }
  117. }
  118. return keys.sort();
  119. }
  120. function forEachSorted(obj, iterator, context) {
  121. var keys = sortedKeys(obj);
  122. for ( var i = 0; i < keys.length; i++) {
  123. iterator.call(context, obj[keys[i]], keys[i]);
  124. }
  125. return keys;
  126. }
  127. /**
  128. * when using forEach the params are value, key, but it is often useful to have key, value.
  129. * @param {function(string, *)} iteratorFn
  130. * @returns {function(*, string)}
  131. */
  132. function reverseParams(iteratorFn) {
  133. return function(value, key) { iteratorFn(key, value) };
  134. }
  135. /**
  136. * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
  137. * characters such as '012ABC'. The reason why we are not using simply a number counter is that
  138. * the number string gets longer over time, and it can also overflow, where as the the nextId
  139. * will grow much slower, it is a string, and it will never overflow.
  140. *
  141. * @returns an unique alpha-numeric string
  142. */
  143. function nextUid() {
  144. var index = uid.length;
  145. var digit;
  146. while(index) {
  147. index--;
  148. digit = uid[index].charCodeAt(0);
  149. if (digit == 57 /*'9'*/) {
  150. uid[index] = 'A';
  151. return uid.join('');
  152. }
  153. if (digit == 90 /*'Z'*/) {
  154. uid[index] = '0';
  155. } else {
  156. uid[index] = String.fromCharCode(digit + 1);
  157. return uid.join('');
  158. }
  159. }
  160. uid.unshift('0');
  161. return uid.join('');
  162. }
  163. /**
  164. * @ngdoc function
  165. * @name angular.extend
  166. * @function
  167. *
  168. * @description
  169. * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
  170. * to `dst`. You can specify multiple `src` objects.
  171. *
  172. * @param {Object} dst Destination object.
  173. * @param {...Object} src Source object(s).
  174. */
  175. function extend(dst) {
  176. forEach(arguments, function(obj){
  177. if (obj !== dst) {
  178. forEach(obj, function(value, key){
  179. dst[key] = value;
  180. });
  181. }
  182. });
  183. return dst;
  184. }
  185. function int(str) {
  186. return parseInt(str, 10);
  187. }
  188. function inherit(parent, extra) {
  189. return extend(new (extend(function() {}, {prototype:parent}))(), extra);
  190. }
  191. /**
  192. * @ngdoc function
  193. * @name angular.noop
  194. * @function
  195. *
  196. * @description
  197. * A function that performs no operations. This function can be useful when writing code in the
  198. * functional style.
  199. <pre>
  200. function foo(callback) {
  201. var result = calculateResult();
  202. (callback || angular.noop)(result);
  203. }
  204. </pre>
  205. */
  206. function noop() {}
  207. noop.$inject = [];
  208. /**
  209. * @ngdoc function
  210. * @name angular.identity
  211. * @function
  212. *
  213. * @description
  214. * A function that returns its first argument. This function is useful when writing code in the
  215. * functional style.
  216. *
  217. <pre>
  218. function transformer(transformationFn, value) {
  219. return (transformationFn || identity)(value);
  220. };
  221. </pre>
  222. */
  223. function identity($) {return $;}
  224. identity.$inject = [];
  225. function valueFn(value) {return function() {return value;};}
  226. /**
  227. * @ngdoc function
  228. * @name angular.isUndefined
  229. * @function
  230. *
  231. * @description
  232. * Determines if a reference is undefined.
  233. *
  234. * @param {*} value Reference to check.
  235. * @returns {boolean} True if `value` is undefined.
  236. */
  237. function isUndefined(value){return typeof value == 'undefined';}
  238. /**
  239. * @ngdoc function
  240. * @name angular.isDefined
  241. * @function
  242. *
  243. * @description
  244. * Determines if a reference is defined.
  245. *
  246. * @param {*} value Reference to check.
  247. * @returns {boolean} True if `value` is defined.
  248. */
  249. function isDefined(value){return typeof value != 'undefined';}
  250. /**
  251. * @ngdoc function
  252. * @name angular.isObject
  253. * @function
  254. *
  255. * @description
  256. * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
  257. * considered to be objects.
  258. *
  259. * @param {*} value Reference to check.
  260. * @returns {boolean} True if `value` is an `Object` but not `null`.
  261. */
  262. function isObject(value){return value != null && typeof value == 'object';}
  263. /**
  264. * @ngdoc function
  265. * @name angular.isString
  266. * @function
  267. *
  268. * @description
  269. * Determines if a reference is a `String`.
  270. *
  271. * @param {*} value Reference to check.
  272. * @returns {boolean} True if `value` is a `String`.
  273. */
  274. function isString(value){return typeof value == 'string';}
  275. /**
  276. * @ngdoc function
  277. * @name angular.isNumber
  278. * @function
  279. *
  280. * @description
  281. * Determines if a reference is a `Number`.
  282. *
  283. * @param {*} value Reference to check.
  284. * @returns {boolean} True if `value` is a `Number`.
  285. */
  286. function isNumber(value){return typeof value == 'number';}
  287. /**
  288. * @ngdoc function
  289. * @name angular.isDate
  290. * @function
  291. *
  292. * @description
  293. * Determines if a value is a date.
  294. *
  295. * @param {*} value Reference to check.
  296. * @returns {boolean} True if `value` is a `Date`.
  297. */
  298. function isDate(value){
  299. return toString.apply(value) == '[object Date]';
  300. }
  301. /**
  302. * @ngdoc function
  303. * @name angular.isArray
  304. * @function
  305. *
  306. * @description
  307. * Determines if a reference is an `Array`.
  308. *
  309. * @param {*} value Reference to check.
  310. * @returns {boolean} True if `value` is an `Array`.
  311. */
  312. function isArray(value) {
  313. return toString.apply(value) == '[object Array]';
  314. }
  315. /**
  316. * @ngdoc function
  317. * @name angular.isFunction
  318. * @function
  319. *
  320. * @description
  321. * Determines if a reference is a `Function`.
  322. *
  323. * @param {*} value Reference to check.
  324. * @returns {boolean} True if `value` is a `Function`.
  325. */
  326. function isFunction(value){return typeof value == 'function';}
  327. /**
  328. * Checks if `obj` is a window object.
  329. *
  330. * @private
  331. * @param {*} obj Object to check
  332. * @returns {boolean} True if `obj` is a window obj.
  333. */
  334. function isWindow(obj) {
  335. return obj && obj.document && obj.location && obj.alert && obj.setInterval;
  336. }
  337. function isScope(obj) {
  338. return obj && obj.$evalAsync && obj.$watch;
  339. }
  340. function isFile(obj) {
  341. return toString.apply(obj) === '[object File]';
  342. }
  343. function isBoolean(value) {
  344. return typeof value == 'boolean';
  345. }
  346. function trim(value) {
  347. return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value;
  348. }
  349. /**
  350. * @ngdoc function
  351. * @name angular.isElement
  352. * @function
  353. *
  354. * @description
  355. * Determines if a reference is a DOM element (or wrapped jQuery element).
  356. *
  357. * @param {*} value Reference to check.
  358. * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
  359. */
  360. function isElement(node) {
  361. return node &&
  362. (node.nodeName // we are a direct element
  363. || (node.bind && node.find)); // we have a bind and find method part of jQuery API
  364. }
  365. /**
  366. * @param str 'key1,key2,...'
  367. * @returns {object} in the form of {key1:true, key2:true, ...}
  368. */
  369. function makeMap(str){
  370. var obj = {}, items = str.split(","), i;
  371. for ( i = 0; i < items.length; i++ )
  372. obj[ items[i] ] = true;
  373. return obj;
  374. }
  375. if (msie < 9) {
  376. nodeName_ = function(element) {
  377. element = element.nodeName ? element : element[0];
  378. return (element.scopeName && element.scopeName != 'HTML')
  379. ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName;
  380. };
  381. } else {
  382. nodeName_ = function(element) {
  383. return element.nodeName ? element.nodeName : element[0].nodeName;
  384. };
  385. }
  386. function map(obj, iterator, context) {
  387. var results = [];
  388. forEach(obj, function(value, index, list) {
  389. results.push(iterator.call(context, value, index, list));
  390. });
  391. return results;
  392. }
  393. /**
  394. * @description
  395. * Determines the number of elements in an array, the number of properties an object has, or
  396. * the length of a string.
  397. *
  398. * Note: This function is used to augment the Object type in Angular expressions. See
  399. * {@link angular.Object} for more information about Angular arrays.
  400. *
  401. * @param {Object|Array|string} obj Object, array, or string to inspect.
  402. * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object
  403. * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array.
  404. */
  405. function size(obj, ownPropsOnly) {
  406. var size = 0, key;
  407. if (isArray(obj) || isString(obj)) {
  408. return obj.length;
  409. } else if (isObject(obj)){
  410. for (key in obj)
  411. if (!ownPropsOnly || obj.hasOwnProperty(key))
  412. size++;
  413. }
  414. return size;
  415. }
  416. function includes(array, obj) {
  417. return indexOf(array, obj) != -1;
  418. }
  419. function indexOf(array, obj) {
  420. if (array.indexOf) return array.indexOf(obj);
  421. for ( var i = 0; i < array.length; i++) {
  422. if (obj === array[i]) return i;
  423. }
  424. return -1;
  425. }
  426. function arrayRemove(array, value) {
  427. var index = indexOf(array, value);
  428. if (index >=0)
  429. array.splice(index, 1);
  430. return value;
  431. }
  432. function isLeafNode (node) {
  433. if (node) {
  434. switch (node.nodeName) {
  435. case "OPTION":
  436. case "PRE":
  437. case "TITLE":
  438. return true;
  439. }
  440. }
  441. return false;
  442. }
  443. /**
  444. * @ngdoc function
  445. * @name angular.copy
  446. * @function
  447. *
  448. * @description
  449. * Creates a deep copy of `source`, which should be an object or an array.
  450. *
  451. * * If no destination is supplied, a copy of the object or array is created.
  452. * * If a destination is provided, all of its elements (for array) or properties (for objects)
  453. * are deleted and then all elements/properties from the source are copied to it.
  454. * * If `source` is not an object or array, `source` is returned.
  455. *
  456. * Note: this function is used to augment the Object type in Angular expressions. See
  457. * {@link ng.$filter} for more information about Angular arrays.
  458. *
  459. * @param {*} source The source that will be used to make a copy.
  460. * Can be any type, including primitives, `null`, and `undefined`.
  461. * @param {(Object|Array)=} destination Destination into which the source is copied. If
  462. * provided, must be of the same type as `source`.
  463. * @returns {*} The copy or updated `destination`, if `destination` was specified.
  464. */
  465. function copy(source, destination){
  466. if (isWindow(source) || isScope(source)) throw Error("Can't copy Window or Scope");
  467. if (!destination) {
  468. destination = source;
  469. if (source) {
  470. if (isArray(source)) {
  471. destination = copy(source, []);
  472. } else if (isDate(source)) {
  473. destination = new Date(source.getTime());
  474. } else if (isObject(source)) {
  475. destination = copy(source, {});
  476. }
  477. }
  478. } else {
  479. if (source === destination) throw Error("Can't copy equivalent objects or arrays");
  480. if (isArray(source)) {
  481. while(destination.length) {
  482. destination.pop();
  483. }
  484. for ( var i = 0; i < source.length; i++) {
  485. destination.push(copy(source[i]));
  486. }
  487. } else {
  488. forEach(destination, function(value, key){
  489. delete destination[key];
  490. });
  491. for ( var key in source) {
  492. destination[key] = copy(source[key]);
  493. }
  494. }
  495. }
  496. return destination;
  497. }
  498. /**
  499. * Create a shallow copy of an object
  500. */
  501. function shallowCopy(src, dst) {
  502. dst = dst || {};
  503. for(var key in src) {
  504. if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
  505. dst[key] = src[key];
  506. }
  507. }
  508. return dst;
  509. }
  510. /**
  511. * @ngdoc function
  512. * @name angular.equals
  513. * @function
  514. *
  515. * @description
  516. * Determines if two objects or two values are equivalent. Supports value types, arrays and
  517. * objects.
  518. *
  519. * Two objects or values are considered equivalent if at least one of the following is true:
  520. *
  521. * * Both objects or values pass `===` comparison.
  522. * * Both objects or values are of the same type and all of their properties pass `===` comparison.
  523. * * Both values are NaN. (In JavasScript, NaN == NaN => false. But we consider two NaN as equal)
  524. *
  525. * During a property comparision, properties of `function` type and properties with names
  526. * that begin with `$` are ignored.
  527. *
  528. * Scope and DOMWindow objects are being compared only be identify (`===`).
  529. *
  530. * @param {*} o1 Object or value to compare.
  531. * @param {*} o2 Object or value to compare.
  532. * @returns {boolean} True if arguments are equal.
  533. */
  534. function equals(o1, o2) {
  535. if (o1 === o2) return true;
  536. if (o1 === null || o2 === null) return false;
  537. if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
  538. var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
  539. if (t1 == t2) {
  540. if (t1 == 'object') {
  541. if (isArray(o1)) {
  542. if ((length = o1.length) == o2.length) {
  543. for(key=0; key<length; key++) {
  544. if (!equals(o1[key], o2[key])) return false;
  545. }
  546. return true;
  547. }
  548. } else if (isDate(o1)) {
  549. return isDate(o2) && o1.getTime() == o2.getTime();
  550. } else {
  551. if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2)) return false;
  552. keySet = {};
  553. for(key in o1) {
  554. if (key.charAt(0) !== '$' && !isFunction(o1[key]) && !equals(o1[key], o2[key])) {
  555. return false;
  556. }
  557. keySet[key] = true;
  558. }
  559. for(key in o2) {
  560. if (!keySet[key] && key.charAt(0) !== '$' && !isFunction(o2[key])) return false;
  561. }
  562. return true;
  563. }
  564. }
  565. }
  566. return false;
  567. }
  568. function concat(array1, array2, index) {
  569. return array1.concat(slice.call(array2, index));
  570. }
  571. function sliceArgs(args, startIndex) {
  572. return slice.call(args, startIndex || 0);
  573. }
  574. /**
  575. * @ngdoc function
  576. * @name angular.bind
  577. * @function
  578. *
  579. * @description
  580. * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
  581. * `fn`). You can supply optional `args` that are are prebound to the function. This feature is also
  582. * known as [function currying](http://en.wikipedia.org/wiki/Currying).
  583. *
  584. * @param {Object} self Context which `fn` should be evaluated in.
  585. * @param {function()} fn Function to be bound.
  586. * @param {...*} args Optional arguments to be prebound to the `fn` function call.
  587. * @returns {function()} Function that wraps the `fn` with all the specified bindings.
  588. */
  589. function bind(self, fn) {
  590. var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
  591. if (isFunction(fn) && !(fn instanceof RegExp)) {
  592. return curryArgs.length
  593. ? function() {
  594. return arguments.length
  595. ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
  596. : fn.apply(self, curryArgs);
  597. }
  598. : function() {
  599. return arguments.length
  600. ? fn.apply(self, arguments)
  601. : fn.call(self);
  602. };
  603. } else {
  604. // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
  605. return fn;
  606. }
  607. }
  608. function toJsonReplacer(key, value) {
  609. var val = value;
  610. if (/^\$+/.test(key)) {
  611. val = undefined;
  612. } else if (isWindow(value)) {
  613. val = '$WINDOW';
  614. } else if (value && document === value) {
  615. val = '$DOCUMENT';
  616. } else if (isScope(value)) {
  617. val = '$SCOPE';
  618. }
  619. return val;
  620. }
  621. /**
  622. * @ngdoc function
  623. * @name angular.toJson
  624. * @function
  625. *
  626. * @description
  627. * Serializes input into a JSON-formatted string.
  628. *
  629. * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
  630. * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
  631. * @returns {string} Jsonified string representing `obj`.
  632. */
  633. function toJson(obj, pretty) {
  634. return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
  635. }
  636. /**
  637. * @ngdoc function
  638. * @name angular.fromJson
  639. * @function
  640. *
  641. * @description
  642. * Deserializes a JSON string.
  643. *
  644. * @param {string} json JSON string to deserialize.
  645. * @returns {Object|Array|Date|string|number} Deserialized thingy.
  646. */
  647. function fromJson(json) {
  648. return isString(json)
  649. ? JSON.parse(json)
  650. : json;
  651. }
  652. function toBoolean(value) {
  653. if (value && value.length !== 0) {
  654. var v = lowercase("" + value);
  655. value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
  656. } else {
  657. value = false;
  658. }
  659. return value;
  660. }
  661. /**
  662. * @returns {string} Returns the string representation of the element.
  663. */
  664. function startingTag(element) {
  665. element = jqLite(element).clone();
  666. try {
  667. // turns out IE does not let you set .html() on elements which
  668. // are not allowed to have children. So we just ignore it.
  669. element.html('');
  670. } catch(e) {}
  671. return jqLite('<div>').append(element).html().
  672. match(/^(<[^>]+>)/)[1].
  673. replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
  674. }
  675. /////////////////////////////////////////////////
  676. /**
  677. * Parses an escaped url query string into key-value pairs.
  678. * @returns Object.<(string|boolean)>
  679. */
  680. function parseKeyValue(/**string*/keyValue) {
  681. var obj = {}, key_value, key;
  682. forEach((keyValue || "").split('&'), function(keyValue){
  683. if (keyValue) {
  684. key_value = keyValue.split('=');
  685. key = decodeURIComponent(key_value[0]);
  686. obj[key] = isDefined(key_value[1]) ? decodeURIComponent(key_value[1]) : true;
  687. }
  688. });
  689. return obj;
  690. }
  691. function toKeyValue(obj) {
  692. var parts = [];
  693. forEach(obj, function(value, key) {
  694. parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true)));
  695. });
  696. return parts.length ? parts.join('&') : '';
  697. }
  698. /**
  699. * We need our custom method because encodeURIComponent is too agressive and doesn't follow
  700. * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
  701. * segments:
  702. * segment = *pchar
  703. * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
  704. * pct-encoded = "%" HEXDIG HEXDIG
  705. * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  706. * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
  707. * / "*" / "+" / "," / ";" / "="
  708. */
  709. function encodeUriSegment(val) {
  710. return encodeUriQuery(val, true).
  711. replace(/%26/gi, '&').
  712. replace(/%3D/gi, '=').
  713. replace(/%2B/gi, '+');
  714. }
  715. /**
  716. * This method is intended for encoding *key* or *value* parts of query component. We need a custom
  717. * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
  718. * encoded per http://tools.ietf.org/html/rfc3986:
  719. * query = *( pchar / "/" / "?" )
  720. * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
  721. * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  722. * pct-encoded = "%" HEXDIG HEXDIG
  723. * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
  724. * / "*" / "+" / "," / ";" / "="
  725. */
  726. function encodeUriQuery(val, pctEncodeSpaces) {
  727. return encodeURIComponent(val).
  728. replace(/%40/gi, '@').
  729. replace(/%3A/gi, ':').
  730. replace(/%24/g, '$').
  731. replace(/%2C/gi, ',').
  732. replace((pctEncodeSpaces ? null : /%20/g), '+');
  733. }
  734. /**
  735. * @ngdoc directive
  736. * @name ng.directive:ngApp
  737. *
  738. * @element ANY
  739. * @param {angular.Module} ngApp an optional application
  740. * {@link angular.module module} name to load.
  741. *
  742. * @description
  743. *
  744. * Use this directive to auto-bootstrap on application. Only
  745. * one directive can be used per HTML document. The directive
  746. * designates the root of the application and is typically placed
  747. * ot the root of the page.
  748. *
  749. * In the example below if the `ngApp` directive would not be placed
  750. * on the `html` element then the document would not be compiled
  751. * and the `{{ 1+2 }}` would not be resolved to `3`.
  752. *
  753. * `ngApp` is the easiest way to bootstrap an application.
  754. *
  755. <doc:example>
  756. <doc:source>
  757. I can add: 1 + 2 = {{ 1+2 }}
  758. </doc:source>
  759. </doc:example>
  760. *
  761. */
  762. function angularInit(element, bootstrap) {
  763. var elements = [element],
  764. appElement,
  765. module,
  766. names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
  767. NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
  768. function append(element) {
  769. element && elements.push(element);
  770. }
  771. forEach(names, function(name) {
  772. names[name] = true;
  773. append(document.getElementById(name));
  774. name = name.replace(':', '\\:');
  775. if (element.querySelectorAll) {
  776. forEach(element.querySelectorAll('.' + name), append);
  777. forEach(element.querySelectorAll('.' + name + '\\:'), append);
  778. forEach(element.querySelectorAll('[' + name + ']'), append);
  779. }
  780. });
  781. forEach(elements, function(element) {
  782. if (!appElement) {
  783. var className = ' ' + element.className + ' ';
  784. var match = NG_APP_CLASS_REGEXP.exec(className);
  785. if (match) {
  786. appElement = element;
  787. module = (match[2] || '').replace(/\s+/g, ',');
  788. } else {
  789. forEach(element.attributes, function(attr) {
  790. if (!appElement && names[attr.name]) {
  791. appElement = element;
  792. module = attr.value;
  793. }
  794. });
  795. }
  796. }
  797. });
  798. if (appElement) {
  799. bootstrap(appElement, module ? [module] : []);
  800. }
  801. }
  802. /**
  803. * @ngdoc function
  804. * @name angular.bootstrap
  805. * @description
  806. * Use this function to manually start up angular application.
  807. *
  808. * See: {@link guide/bootstrap Bootstrap}
  809. *
  810. * @param {Element} element DOM element which is the root of angular application.
  811. * @param {Array<String|Function>=} modules an array of module declarations. See: {@link angular.module modules}
  812. * @returns {AUTO.$injector} Returns the newly created injector for this app.
  813. */
  814. function bootstrap(element, modules) {
  815. element = jqLite(element);
  816. modules = modules || [];
  817. modules.unshift(['$provide', function($provide) {
  818. $provide.value('$rootElement', element);
  819. }]);
  820. modules.unshift('ng');
  821. var injector = createInjector(modules);
  822. injector.invoke(
  823. ['$rootScope', '$rootElement', '$compile', '$injector', function(scope, element, compile, injector){
  824. scope.$apply(function() {
  825. element.data('$injector', injector);
  826. compile(element)(scope);
  827. });
  828. }]
  829. );
  830. return injector;
  831. }
  832. var SNAKE_CASE_REGEXP = /[A-Z]/g;
  833. function snake_case(name, separator){
  834. separator = separator || '_';
  835. return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
  836. return (pos ? separator : '') + letter.toLowerCase();
  837. });
  838. }
  839. function bindJQuery() {
  840. // bind to jQuery if present;
  841. jQuery = window.jQuery;
  842. // reset to jQuery or default to us.
  843. if (jQuery) {
  844. jqLite = jQuery;
  845. extend(jQuery.fn, {
  846. scope: JQLitePrototype.scope,
  847. controller: JQLitePrototype.controller,
  848. injector: JQLitePrototype.injector,
  849. inheritedData: JQLitePrototype.inheritedData
  850. });
  851. JQLitePatchJQueryRemove('remove', true);
  852. JQLitePatchJQueryRemove('empty');
  853. JQLitePatchJQueryRemove('html');
  854. } else {
  855. jqLite = JQLite;
  856. }
  857. angular.element = jqLite;
  858. }
  859. /**
  860. * throw error of the argument is falsy.
  861. */
  862. function assertArg(arg, name, reason) {
  863. if (!arg) {
  864. throw new Error("Argument '" + (name || '?') + "' is " + (reason || "required"));
  865. }
  866. return arg;
  867. }
  868. function assertArgFn(arg, name, acceptArrayAnnotation) {
  869. if (acceptArrayAnnotation && isArray(arg)) {
  870. arg = arg[arg.length - 1];
  871. }
  872. assertArg(isFunction(arg), name, 'not a function, got ' +
  873. (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
  874. return arg;
  875. }
  876. /**
  877. * @ngdoc interface
  878. * @name angular.Module
  879. * @description
  880. *
  881. * Interface for configuring angular {@link angular.module modules}.
  882. */
  883. function setupModuleLoader(window) {
  884. function ensure(obj, name, factory) {
  885. return obj[name] || (obj[name] = factory());
  886. }
  887. return ensure(ensure(window, 'angular', Object), 'module', function() {
  888. /** @type {Object.<string, angular.Module>} */
  889. var modules = {};
  890. /**
  891. * @ngdoc function
  892. * @name angular.module
  893. * @description
  894. *
  895. * The `angular.module` is a global place for creating and registering Angular modules. All
  896. * modules (angular core or 3rd party) that should be available to an application must be
  897. * registered using this mechanism.
  898. *
  899. *
  900. * # Module
  901. *
  902. * A module is a collocation of services, directives, filters, and configuration information. Module
  903. * is used to configure the {@link AUTO.$injector $injector}.
  904. *
  905. * <pre>
  906. * // Create a new module
  907. * var myModule = angular.module('myModule', []);
  908. *
  909. * // register a new service
  910. * myModule.value('appName', 'MyCoolApp');
  911. *
  912. * // configure existing services inside initialization blocks.
  913. * myModule.config(function($locationProvider) {
  914. * // Configure existing providers
  915. * $locationProvider.hashPrefix('!');
  916. * });
  917. * </pre>
  918. *
  919. * Then you can create an injector and load your modules like this:
  920. *
  921. * <pre>
  922. * var injector = angular.injector(['ng', 'MyModule'])
  923. * </pre>
  924. *
  925. * However it's more likely that you'll just use
  926. * {@link ng.directive:ngApp ngApp} or
  927. * {@link angular.bootstrap} to simplify this process for you.
  928. *
  929. * @param {!string} name The name of the module to create or retrieve.
  930. * @param {Array.<string>=} requires If specified then new module is being created. If unspecified then the
  931. * the module is being retrieved for further configuration.
  932. * @param {Function} configFn Optional configuration function for the module. Same as
  933. * {@link angular.Module#config Module#config()}.
  934. * @returns {module} new module with the {@link angular.Module} api.
  935. */
  936. return function module(name, requires, configFn) {
  937. if (requires && modules.hasOwnProperty(name)) {
  938. modules[name] = null;
  939. }
  940. return ensure(modules, name, function() {
  941. if (!requires) {
  942. throw Error('No module: ' + name);
  943. }
  944. /** @type {!Array.<Array.<*>>} */
  945. var invokeQueue = [];
  946. /** @type {!Array.<Function>} */
  947. var runBlocks = [];
  948. var config = invokeLater('$injector', 'invoke');
  949. /** @type {angular.Module} */
  950. var moduleInstance = {
  951. // Private state
  952. _invokeQueue: invokeQueue,
  953. _runBlocks: runBlocks,
  954. /**
  955. * @ngdoc property
  956. * @name angular.Module#requires
  957. * @propertyOf angular.Module
  958. * @returns {Array.<string>} List of module names which must be loaded before this module.
  959. * @description
  960. * Holds the list of modules which the injector will load before the current module is loaded.
  961. */
  962. requires: requires,
  963. /**
  964. * @ngdoc property
  965. * @name angular.Module#name
  966. * @propertyOf angular.Module
  967. * @returns {string} Name of the module.
  968. * @description
  969. */
  970. name: name,
  971. /**
  972. * @ngdoc method
  973. * @name angular.Module#provider
  974. * @methodOf angular.Module
  975. * @param {string} name service name
  976. * @param {Function} providerType Construction function for creating new instance of the service.
  977. * @description
  978. * See {@link AUTO.$provide#provider $provide.provider()}.
  979. */
  980. provider: invokeLater('$provide', 'provider'),
  981. /**
  982. * @ngdoc method
  983. * @name angular.Module#factory
  984. * @methodOf angular.Module
  985. * @param {string} name service name
  986. * @param {Function} providerFunction Function for creating new instance of the service.
  987. * @description
  988. * See {@link AUTO.$provide#factory $provide.factory()}.
  989. */
  990. factory: invokeLater('$provide', 'factory'),
  991. /**
  992. * @ngdoc method
  993. * @name angular.Module#service
  994. * @methodOf angular.Module
  995. * @param {string} name service name
  996. * @param {Function} constructor A constructor function that will be instantiated.
  997. * @description
  998. * See {@link AUTO.$provide#service $provide.service()}.
  999. */
  1000. service: invokeLater('$provide', 'service'),
  1001. /**
  1002. * @ngdoc method
  1003. * @name angular.Module#value
  1004. * @methodOf angular.Module
  1005. * @param {string} name service name
  1006. * @param {*} object Service instance object.
  1007. * @description
  1008. * See {@link AUTO.$provide#value $provide.value()}.
  1009. */
  1010. value: invokeLater('$provide', 'value'),
  1011. /**
  1012. * @ngdoc method
  1013. * @name angular.Module#constant
  1014. * @methodOf angular.Module
  1015. * @param {string} name constant name
  1016. * @param {*} object Constant value.
  1017. * @description
  1018. * Because the constant are fixed, they get applied before other provide methods.
  1019. * See {@link AUTO.$provide#constant $provide.constant()}.
  1020. */
  1021. constant: invokeLater('$provide', 'constant', 'unshift'),
  1022. /**
  1023. * @ngdoc method
  1024. * @name angular.Module#filter
  1025. * @methodOf angular.Module
  1026. * @param {string} name Filter name.
  1027. * @param {Function} filterFactory Factory function for creating new instance of filter.
  1028. * @description
  1029. * See {@link ng.$filterProvider#register $filterProvider.register()}.
  1030. */
  1031. filter: invokeLater('$filterProvider', 'register'),
  1032. /**
  1033. * @ngdoc method
  1034. * @name angular.Module#controller
  1035. * @methodOf angular.Module
  1036. * @param {string} name Controller name.
  1037. * @param {Function} constructor Controller constructor function.
  1038. * @description
  1039. * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
  1040. */
  1041. controller: invokeLater('$controllerProvider', 'register'),
  1042. /**
  1043. * @ngdoc method
  1044. * @name angular.Module#directive
  1045. * @methodOf angular.Module
  1046. * @param {string} name directive name
  1047. * @param {Function} directiveFactory Factory function for creating new instance of
  1048. * directives.
  1049. * @description
  1050. * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
  1051. */
  1052. directive: invokeLater('$compileProvider', 'directive'),
  1053. /**
  1054. * @ngdoc method
  1055. * @name angular.Module#config
  1056. * @methodOf angular.Module
  1057. * @param {Function} configFn Execute this function on module load. Useful for service
  1058. * configuration.
  1059. * @description
  1060. * Use this method to register work which needs to be performed on module loading.
  1061. */
  1062. config: config,
  1063. /**
  1064. * @ngdoc method
  1065. * @name angular.Module#run
  1066. * @methodOf angular.Module
  1067. * @param {Function} initializationFn Execute this function after injector creation.
  1068. * Useful for application initialization.
  1069. * @description
  1070. * Use this method to register work which should be performed when the injector is done
  1071. * loading all modules.
  1072. */
  1073. run: function(block) {
  1074. runBlocks.push(block);
  1075. return this;
  1076. }
  1077. };
  1078. if (configFn) {
  1079. config(configFn);
  1080. }
  1081. return moduleInstance;
  1082. /**
  1083. * @param {string} provider
  1084. * @param {string} method
  1085. * @param {String=} insertMethod
  1086. * @returns {angular.Module}
  1087. */
  1088. function invokeLater(provider, method, insertMethod) {
  1089. return function() {
  1090. invokeQueue[insertMethod || 'push']([provider, method, arguments]);
  1091. return moduleInstance;
  1092. }
  1093. }
  1094. });
  1095. };
  1096. });
  1097. }
  1098. /**
  1099. * @ngdoc property
  1100. * @name angular.version
  1101. * @description
  1102. * An object that contains information about the current AngularJS version. This object has the
  1103. * following properties:
  1104. *
  1105. * - `full` – `{string}` – Full version string, such as "0.9.18".
  1106. * - `major` – `{number}` – Major version number, such as "0".
  1107. * - `minor` – `{number}` – Minor version number, such as "9".
  1108. * - `dot` – `{number}` – Dot version number, such as "18".
  1109. * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
  1110. */
  1111. var version = {
  1112. full: '1.1.1', // all of these placeholder strings will be replaced by rake's
  1113. major: 1, // compile task
  1114. minor: 1,
  1115. dot: 1,
  1116. codeName: 'pathological-kerning'
  1117. };
  1118. function publishExternalAPI(angular){
  1119. extend(angular, {
  1120. 'bootstrap': bootstrap,
  1121. 'copy': copy,
  1122. 'extend': extend,
  1123. 'equals': equals,
  1124. 'element': jqLite,
  1125. 'forEach': forEach,
  1126. 'injector': createInjector,
  1127. 'noop':noop,
  1128. 'bind':bind,
  1129. 'toJson': toJson,
  1130. 'fromJson': fromJson,
  1131. 'identity':identity,
  1132. 'isUndefined': isUndefined,
  1133. 'isDefined': isDefined,
  1134. 'isString': isString,
  1135. 'isFunction': isFunction,
  1136. 'isObject': isObject,
  1137. 'isNumber': isNumber,
  1138. 'isElement': isElement,
  1139. 'isArray': isArray,
  1140. 'version': version,
  1141. 'isDate': isDate,
  1142. 'lowercase': lowercase,
  1143. 'uppercase': uppercase,
  1144. 'callbacks': {counter: 0}
  1145. });
  1146. angularModule = setupModuleLoader(window);
  1147. try {
  1148. angularModule('ngLocale');
  1149. } catch (e) {
  1150. angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
  1151. }
  1152. angularModule('ng', ['ngLocale'], ['$provide',
  1153. function ngModule($provide) {
  1154. $provide.provider('$compile', $CompileProvider).
  1155. directive({
  1156. a: htmlAnchorDirective,
  1157. input: inputDirective,
  1158. textarea: inputDirective,
  1159. form: formDirective,
  1160. script: scriptDirective,
  1161. select: selectDirective,
  1162. style: styleDirective,
  1163. option: optionDirective,
  1164. ngBind: ngBindDirective,
  1165. ngBindHtmlUnsafe: ngBindHtmlUnsafeDirective,
  1166. ngBindTemplate: ngBindTemplateDirective,
  1167. ngClass: ngClassDirective,
  1168. ngClassEven: ngClassEvenDirective,
  1169. ngClassOdd: ngClassOddDirective,
  1170. ngCsp: ngCspDirective,
  1171. ngCloak: ngCloakDirective,
  1172. ngController: ngControllerDirective,
  1173. ngForm: ngFormDirective,
  1174. ngHide: ngHideDirective,
  1175. ngInclude: ngIncludeDirective,
  1176. ngInit: ngInitDirective,
  1177. ngNonBindable: ngNonBindableDirective,
  1178. ngPluralize: ngPluralizeDirective,
  1179. ngRepeat: ngRepeatDirective,
  1180. ngShow: ngShowDirective,
  1181. ngSubmit: ngSubmitDirective,
  1182. ngStyle: ngStyleDirective,
  1183. ngSwitch: ngSwitchDirective,
  1184. ngSwitchWhen: ngSwitchWhenDirective,
  1185. ngSwitchDefault: ngSwitchDefaultDirective,
  1186. ngOptions: ngOptionsDirective,
  1187. ngView: ngViewDirective,
  1188. ngTransclude: ngTranscludeDirective,
  1189. ngModel: ngModelDirective,
  1190. ngList: ngListDirective,
  1191. ngChange: ngChangeDirective,
  1192. required: requiredDirective,
  1193. ngRequired: requiredDirective,
  1194. ngValue: ngValueDirective
  1195. }).
  1196. directive(ngAttributeAliasDirectives).
  1197. directive(ngEventDirectives);
  1198. $provide.provider({
  1199. $anchorScroll: $AnchorScrollProvider,
  1200. $browser: $BrowserProvider,
  1201. $cacheFactory: $CacheFactoryProvider,
  1202. $controller: $ControllerProvider,
  1203. $document: $DocumentProvider,
  1204. $exceptionHandler: $ExceptionHandlerProvider,
  1205. $filter: $FilterProvider,
  1206. $interpolate: $InterpolateProvider,
  1207. $http: $HttpProvider,
  1208. $httpBackend: $HttpBackendProvider,
  1209. $location: $LocationProvider,
  1210. $log: $LogProvider,
  1211. $parse: $ParseProvider,
  1212. $route: $RouteProvider,
  1213. $routeParams: $RouteParamsProvider,
  1214. $rootScope: $RootScopeProvider,
  1215. $q: $QProvider,
  1216. $sniffer: $SnifferProvider,
  1217. $templateCache: $TemplateCacheProvider,
  1218. $timeout: $TimeoutProvider,
  1219. $window: $WindowProvider
  1220. });
  1221. }
  1222. ]);
  1223. }
  1224. //////////////////////////////////
  1225. //JQLite
  1226. //////////////////////////////////
  1227. /**
  1228. * @ngdoc function
  1229. * @name angular.element
  1230. * @function
  1231. *
  1232. * @description
  1233. * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
  1234. * `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if
  1235. * jQuery is available, or a function that wraps the element or string in Angular's jQuery lite
  1236. * implementation (commonly referred to as jqLite).
  1237. *
  1238. * Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded`
  1239. * event fired.
  1240. *
  1241. * jqLite is a tiny, API-compatible subset of jQuery that allows
  1242. * Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality
  1243. * within a very small footprint, so only a subset of the jQuery API - methods, arguments and
  1244. * invocation styles - are supported.
  1245. *
  1246. * Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never
  1247. * raw DOM references.
  1248. *
  1249. * ## Angular's jQuery lite provides the following methods:
  1250. *
  1251. * - [addClass()](http://api.jquery.com/addClass/)
  1252. * - [after()](http://api.jquery.com/after/)
  1253. * - [append()](http://api.jquery.com/append/)
  1254. * - [attr()](http://api.jquery.com/attr/)
  1255. * - [bind()](http://api.jquery.com/bind/)
  1256. * - [children()](http://api.jquery.com/children/)
  1257. * - [clone()](http://api.jquery.com/clone/)
  1258. * - [contents()](http://api.jquery.com/contents/)
  1259. * - [css()](http://api.jquery.com/css/)
  1260. * - [data()](http://api.jquery.com/data/)
  1261. * - [eq()](http://api.jquery.com/eq/)
  1262. * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name.
  1263. * - [hasClass()](http://api.jquery.com/hasClass/)
  1264. * - [html()](http://api.jquery.com/html/)
  1265. * - [next()](http://api.jquery.com/next/)
  1266. * - [parent()](http://api.jquery.com/parent/)
  1267. * - [prepend()](http://api.jquery.com/prepend/)
  1268. * - [prop()](http://api.jquery.com/prop/)
  1269. * - [ready()](http://api.jquery.com/ready/)
  1270. * - [remove()](http://api.jquery.com/remove/)
  1271. * - [removeAttr()](http://api.jquery.com/removeAttr/)
  1272. * - [removeClass()](http://api.jquery.com/removeClass/)
  1273. * - [removeData()](http://api.jquery.com/removeData/)
  1274. * - [replaceWith()](http://api.jquery.com/replaceWith/)
  1275. * - [text()](http://api.jquery.com/text/)
  1276. * - [toggleClass()](http://api.jquery.com/toggleClass/)
  1277. * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Doesn't pass native event objects to handlers.
  1278. * - [unbind()](http://api.jquery.com/unbind/)
  1279. * - [val()](http://api.jquery.com/val/)
  1280. * - [wrap()](http://api.jquery.com/wrap/)
  1281. *
  1282. * ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
  1283. *
  1284. * - `controller(name)` - retrieves the controller of the current element or its parent. By default
  1285. * retrieves controller associated with the `ngController` directive. If `name` is provided as
  1286. * camelCase directive name, then the controller for this directive will be retrieved (e.g.
  1287. * `'ngModel'`).
  1288. * - `injector()` - retrieves the injector of the current element or its parent.
  1289. * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current
  1290. * element or its parent.
  1291. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
  1292. * parent element is reached.
  1293. *
  1294. * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
  1295. * @returns {Object} jQuery object.
  1296. */
  1297. var jqCache = JQLite.cache = {},
  1298. jqName = JQLite.expando = 'ng-' + new Date().getTime(),
  1299. jqId = 1,
  1300. addEventListenerFn = (window.document.addEventListener
  1301. ? function(element, type, fn) {element.addEventListener(type, fn, false);}
  1302. : function(element, type, fn) {element.attachEvent('on' + type, fn);}),
  1303. removeEventListenerFn = (window.document.removeEventListener
  1304. ? function(element, type, fn) {element.removeEventListener(type, fn, false); }
  1305. : function(element, type, fn) {element.detachEvent('on' + type, fn); });
  1306. function jqNextId() { return ++jqId; }
  1307. var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
  1308. var MOZ_HACK_REGEXP = /^moz([A-Z])/;
  1309. /**
  1310. * Converts snake_case to camelCase.
  1311. * Also there is special case for Moz prefix starting with upper case letter.
  1312. * @param name Name to normalize
  1313. */
  1314. function camelCase(name) {
  1315. return name.
  1316. replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
  1317. return offset ? letter.toUpperCase() : letter;
  1318. }).
  1319. replace(MOZ_HACK_REGEXP, 'Moz$1');
  1320. }
  1321. /////////////////////////////////////////////
  1322. // jQuery mutation patch
  1323. //
  1324. // In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
  1325. // $destroy event on all DOM nodes being removed.
  1326. //
  1327. /////////////////////////////////////////////
  1328. function JQLitePatchJQueryRemove(name, dispatchThis) {
  1329. var originalJqFn = jQuery.fn[name];
  1330. originalJqFn = originalJqFn.$original || originalJqFn;
  1331. removePatch.$original = originalJqFn;
  1332. jQuery.fn[name] = removePatch;
  1333. function removePatch() {
  1334. var list = [this],
  1335. fireEvent = dispatchThis,
  1336. set, setIndex, setLength,
  1337. element, childIndex, childLength, children,
  1338. fns, events;
  1339. while(list.length) {
  1340. set = list.shift();
  1341. for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
  1342. element = jqLite(set[setIndex]);
  1343. if (fireEvent) {
  1344. element.triggerHandler('$destroy');
  1345. } else {
  1346. fireEvent = !fireEvent;
  1347. }
  1348. for(childIndex = 0, childLength = (children = element.children()).length;
  1349. childIndex < childLength;
  1350. childIndex++) {
  1351. list.push(jQuery(children[childIndex]));
  1352. }
  1353. }
  1354. }
  1355. return originalJqFn.apply(this, arguments);
  1356. }
  1357. }
  1358. /////////////////////////////////////////////
  1359. function JQLite(element) {
  1360. if (element instanceof JQLite) {
  1361. return element;
  1362. }
  1363. if (!(this instanceof JQLite)) {
  1364. if (isString(element) && element.charAt(0) != '<') {
  1365. throw Error('selectors not implemented');
  1366. }
  1367. return new JQLite(element);
  1368. }
  1369. if (isString(element)) {
  1370. var div = document.createElement('div');
  1371. // Read about the NoScope elements here:
  1372. // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
  1373. div.innerHTML = '<div>&#160;</div>' + element; // IE insanity to make NoScope elements work!
  1374. div.removeChild(div.firstChild); // remove the superfluous div
  1375. JQLiteAddNodes(this, div.childNodes);
  1376. this.remove(); // detach the elements from the temporary DOM div.
  1377. } else {
  1378. JQLiteAddNodes(this, element);
  1379. }
  1380. }
  1381. function JQLiteClone(element) {
  1382. return element.cloneNode(true);
  1383. }
  1384. function JQLiteDealoc(element){
  1385. JQLiteRemoveData(element);
  1386. for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
  1387. JQLiteDealoc(children[i]);
  1388. }
  1389. }
  1390. function JQLiteUnbind(element, type, fn) {
  1391. var events = JQLiteExpandoStore(element, 'events'),
  1392. handle = JQLiteExpandoStore(element, 'handle');
  1393. if (!handle) return; //no listeners registered
  1394. if (isUndefined(type)) {
  1395. forEach(events, function(eventHandler, type) {
  1396. removeEventListenerFn(element, type, eventHandler);
  1397. delete events[type];
  1398. });
  1399. } else {
  1400. if (isUndefined(fn)) {
  1401. removeEventListenerFn(element, type, events[type]);
  1402. delete events[type];
  1403. } else {
  1404. arrayRemove(events[type], fn);
  1405. }
  1406. }
  1407. }
  1408. function JQLiteRemoveData(element) {
  1409. var expandoId = element[jqName],
  1410. expandoStore = jqCache[expandoId];
  1411. if (expandoStore) {
  1412. if (expandoStore.handle) {
  1413. expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
  1414. JQLiteUnbind(element);
  1415. }
  1416. delete jqCache[expandoId];
  1417. element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
  1418. }
  1419. }
  1420. function JQLiteExpandoStore(element, key, value) {
  1421. var expandoId = element[jqName],
  1422. expandoStore = jqCache[expandoId || -1];
  1423. if (isDefined(value)) {
  1424. if (!expandoStore) {
  1425. element[jqName] = expandoId = jqNextId();
  1426. expandoStore = jqCache[expandoId] = {};
  1427. }
  1428. expandoStore[key] = value;
  1429. } else {
  1430. return expandoStore && expandoStore[key];
  1431. }
  1432. }
  1433. function JQLiteData(element, key, value) {
  1434. var data = JQLiteExpandoStore(element, 'data'),
  1435. isSetter = isDefined(value),
  1436. keyDefined = !isSetter && isDefined(key),
  1437. isSimpleGetter = keyDefined && !isObject(key);
  1438. if (!data && !isSimpleGetter) {
  1439. JQLiteExpandoStore(element, 'data', data = {});
  1440. }
  1441. if (isSetter) {
  1442. data[key] = value;
  1443. } else {
  1444. if (keyDefined) {
  1445. if (isSimpleGetter) {
  1446. // don't create data in this case.
  1447. return data && data[key];
  1448. } else {
  1449. extend(data, key);
  1450. }
  1451. } else {
  1452. return data;
  1453. }
  1454. }
  1455. }
  1456. function JQLiteHasClass(element, selector) {
  1457. return ((" " + element.className + " ").replace(/[\n\t]/g, " ").
  1458. indexOf( " " + selector + " " ) > -1);
  1459. }
  1460. function JQLiteRemoveClass(element, cssClasses) {
  1461. if (cssClasses) {
  1462. forEach(cssClasses.split(' '), function(cssClass) {
  1463. element.className = trim(
  1464. (" " + element.className + " ")
  1465. .replace(/[\n\t]/g, " ")
  1466. .replace(" " + trim(cssClass) + " ", " ")
  1467. );
  1468. });
  1469. }
  1470. }
  1471. function JQLiteAddClass(element, cssClasses) {
  1472. if (cssClasses) {
  1473. forEach(cssClasses.split(' '), function(cssClass) {
  1474. if (!JQLiteHasClass(element, cssClass)) {
  1475. element.className = trim(element.className + ' ' + trim(cssClass));
  1476. }
  1477. });
  1478. }
  1479. }
  1480. function JQLiteAddNodes(root, elements) {
  1481. if (elements) {
  1482. elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
  1483. ? elements
  1484. : [ elements ];
  1485. for(var i=0; i < elements.length; i++) {
  1486. root.push(elements[i]);
  1487. }
  1488. }
  1489. }
  1490. function JQLiteController(element, name) {
  1491. return JQLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
  1492. }
  1493. function JQLiteInheritedData(element, name, value) {
  1494. element = jqLite(element);
  1495. // if element is the document object work with the html element instead
  1496. // this makes $(document).scope() possible
  1497. if(element[0].nodeType == 9) {
  1498. element = element.find('html');
  1499. }
  1500. while (element.length) {
  1501. if (value = element.data(name)) return value;
  1502. element = element.parent();
  1503. }
  1504. }
  1505. //////////////////////////////////////////
  1506. // Functions which are declared directly.
  1507. //////////////////////////////////////////
  1508. var JQLitePrototype = JQLite.prototype = {
  1509. ready: function(fn) {
  1510. var fired = false;
  1511. function trigger() {
  1512. if (fired) return;
  1513. fired = true;
  1514. fn();
  1515. }
  1516. this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
  1517. // we can not use jqLite since we are not done loading and jQuery could be loaded later.
  1518. JQLite(window).bind('load', trigger); // fallback to window.onload for others
  1519. },
  1520. toString: function() {
  1521. var value = [];
  1522. forEach(this, function(e){ value.push('' + e);});
  1523. return '[' + value.join(', ') + ']';
  1524. },
  1525. eq: function(index) {
  1526. return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
  1527. },
  1528. length: 0,
  1529. push: push,
  1530. sort: [].sort,
  1531. splice: [].splice
  1532. };
  1533. //////////////////////////////////////////
  1534. // Functions iterating getter/setters.
  1535. // these functions return self on setter and
  1536. // value on get.
  1537. //////////////////////////////////////////
  1538. var BOOLEAN_ATTR = {};
  1539. forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) {
  1540. BOOLEAN_ATTR[lowercase(value)] = value;
  1541. });
  1542. var BOOLEAN_ELEMENTS = {};
  1543. forEach('input,select,option,textarea,button,form'.split(','), function(value) {
  1544. BOOLEAN_ELEMENTS[uppercase(value)] = true;
  1545. });
  1546. function getBooleanAttrName(element, name) {
  1547. // check dom last since we will most likely fail on name
  1548. var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
  1549. // booleanAttr is here twice to minimize DOM access
  1550. return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr;
  1551. }
  1552. forEach({
  1553. data: JQLiteData,
  1554. inheritedData: JQLiteInheritedData,
  1555. scope: function(element) {
  1556. return JQLiteInheritedData(element, '$scope');
  1557. },
  1558. controller: JQLiteController ,
  1559. injector: function(element) {
  1560. return JQLiteInheritedData(element, '$injector');
  1561. },
  1562. removeAttr: function(element,name) {
  1563. element.removeAttribute(name);
  1564. },
  1565. hasClass: JQLiteHasClass,
  1566. css: function(element, name, value) {
  1567. name = camelCase(name);
  1568. if (isDefined(value)) {
  1569. element.style[name] = value;
  1570. } else {
  1571. var val;
  1572. if (msie <= 8) {
  1573. // this is some IE specific weirdness that jQuery 1.6.4 does not sure why
  1574. val = element.currentStyle && element.currentStyle[name];
  1575. if (val === '') val = 'auto';
  1576. }
  1577. val = val || element.style[name];
  1578. if (msie <= 8) {
  1579. // jquery weirdness :-/
  1580. val = (val === '') ? undefined : val;
  1581. }
  1582. return val;
  1583. }
  1584. },
  1585. attr: function(element, name, value){
  1586. var lowercasedName = lowercase(name);
  1587. if (BOOLEAN_ATTR[lowercasedName]) {
  1588. if (isDefined(value)) {
  1589. if (!!value) {
  1590. element[name] = true;
  1591. element.setAttribute(name, lowercasedName);
  1592. } else {
  1593. element[name] = false;
  1594. element.removeAttribute(lowercasedName);
  1595. }
  1596. } else {
  1597. return (element[name] ||
  1598. (element.attributes.getNamedItem(name)|| noop).specified)
  1599. ? lowercasedName
  1600. : undefined;
  1601. }
  1602. } else if (isDefined(value)) {
  1603. element.setAttribute(name, value);
  1604. } else if (element.getAttribute) {
  1605. // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
  1606. // some elements (e.g. Document) don't have get attribute, so return undefined
  1607. var ret = element.getAttribute(name, 2);
  1608. // normalize non-existing attributes to undefined (as jQuery)
  1609. return ret === null ? undefined : ret;
  1610. }
  1611. },
  1612. prop: function(element, name, value) {
  1613. if (isDefined(value)) {
  1614. element[name] = value;
  1615. } else {
  1616. return element[name];
  1617. }
  1618. },
  1619. text: extend((msie < 9)
  1620. ? function(element, value) {
  1621. if (element.nodeType == 1 /** Element */) {
  1622. if (isUndefined(value))
  1623. return element.innerText;
  1624. element.innerText = value;
  1625. } else {
  1626. if (isUndefined(value))
  1627. return element.nodeValue;
  1628. element.nodeValue = value;
  1629. }
  1630. }
  1631. : function(element, value) {
  1632. if (isUndefined(value)) {
  1633. return element.textContent;
  1634. }
  1635. element.textContent = value;
  1636. }, {$dv:''}),
  1637. val: function(element, value) {
  1638. if (isUndefined(value)) {
  1639. return element.value;
  1640. }
  1641. element.value = value;
  1642. },
  1643. html: function(element, value) {
  1644. if (isUndefined(value)) {
  1645. return element.innerHTML;
  1646. }
  1647. for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
  1648. JQLiteDealoc(childNodes[i]);
  1649. }
  1650. element.innerHTML = value;
  1651. }
  1652. }, function(fn, name){
  1653. /**
  1654. * Properties: writes return selection, reads return first value
  1655. */
  1656. JQLite.prototype[name] = function(arg1, arg2) {
  1657. var i, key;
  1658. // JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
  1659. // in a way that survives minification.
  1660. if (((fn.length == 2 && (fn !== JQLiteHasClass && fn !== JQLiteController)) ? arg1 : arg2) === undefined) {
  1661. if (isObject(arg1)) {
  1662. // we are a write, but the object properties are the key/values
  1663. for(i=0; i < this.length; i++) {
  1664. if (fn === JQLiteData) {
  1665. // data() takes the whole object in jQuery
  1666. fn(this[i], arg1);
  1667. } else {
  1668. for (key in arg1) {
  1669. fn(this[i], key, arg1[key]);
  1670. }
  1671. }
  1672. }
  1673. // return self for chaining
  1674. return this;
  1675. } else {
  1676. // we are a read, so read the first child.
  1677. if (this.length)
  1678. return fn(this[0], arg1, arg2);
  1679. }
  1680. } else {
  1681. // we are a write, so apply to all children
  1682. for(i=0; i < this.length; i++) {
  1683. fn(this[i], arg1, arg2);
  1684. }
  1685. // return self for chaining
  1686. return this;
  1687. }
  1688. return fn.$dv;
  1689. };
  1690. });
  1691. function createEventHandler(element, events) {
  1692. var eventHandler = function (event, type) {
  1693. if (!event.preventDefault) {
  1694. event.preventDefault = function() {
  1695. event.returnValue = false; //ie
  1696. };
  1697. }
  1698. if (!event.stopPropagation) {
  1699. event.stopPropagation = function() {
  1700. event.cancelBubble = true; //ie
  1701. };
  1702. }
  1703. if (!event.target) {
  1704. event.target = event.srcElement || document;
  1705. }
  1706. if (isUndefined(event.defaultPrevented)) {
  1707. var prevent = event.preventDefault;
  1708. event.preventDefault = function() {
  1709. event.defaultPrevented = true;
  1710. prevent.call(event);
  1711. };
  1712. event.defaultPrevented = false;
  1713. }
  1714. event.isDefaultPrevented = function() {
  1715. return event.defaultPrevented;
  1716. };
  1717. forEach(events[type || event.type], function(fn) {
  1718. fn.call(element, event);
  1719. });
  1720. // Remove monkey-patched methods (IE),
  1721. // as they would cause memory leaks in IE8.
  1722. if (msie <= 8) {
  1723. // IE7/8 does not allow to delete property on native object
  1724. event.preventDefault = null;
  1725. event.stopPropagation = null;
  1726. event.isDefaultPrevented = null;
  1727. } else {
  1728. // It shouldn't affect normal browsers (native methods are defined on prototype).
  1729. delete event.preventDefault;
  1730. delete event.stopPropagation;
  1731. delete event.isDefaultPrevented;
  1732. }
  1733. };
  1734. eventHandler.elem = element;
  1735. return eventHandler;
  1736. }
  1737. //////////////////////////////////////////
  1738. // Functions iterating traversal.
  1739. // These functions chain results into a single
  1740. // selector.
  1741. //////////////////////////////////////////
  1742. forEach({
  1743. removeData: JQLiteRemoveData,
  1744. dealoc: JQLiteDealoc,
  1745. bind: function bindFn(element, type, fn){
  1746. var events = JQLiteExpandoStore(element, 'events'),
  1747. handle = JQLiteExpandoStore(element, 'handle');
  1748. if (!events) JQLiteExpandoStore(element, 'events', events = {});
  1749. if (!handle) JQLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
  1750. forEach(type.split(' '), function(type){
  1751. var eventFns = events[type];
  1752. if (!eventFns) {
  1753. if (type == 'mouseenter' || type == 'mouseleave') {
  1754. var counter = 0;
  1755. events.mouseenter = [];
  1756. events.mouseleave = [];
  1757. bindFn(element, 'mouseover', function(event) {
  1758. counter++;
  1759. if (counter == 1) {
  1760. handle(event, 'mouseenter');
  1761. }
  1762. });
  1763. bindFn(element, 'mouseout', function(event) {
  1764. counter --;
  1765. if (counter == 0) {
  1766. handle(event, 'mouseleave');
  1767. }
  1768. });
  1769. } else {
  1770. addEventListenerFn(element, type, handle);
  1771. events[type] = [];
  1772. }
  1773. eventFns = events[type]
  1774. }
  1775. eventFns.push(fn);
  1776. });
  1777. },
  1778. unbind: JQLiteUnbind,
  1779. replaceWith: function(element, replaceNode) {
  1780. var index, parent = element.parentNode;
  1781. JQLiteDealoc(element);
  1782. forEach(new JQLite(replaceNode), function(node){
  1783. if (index) {
  1784. parent.insertBefore(node, index.nextSibling);
  1785. } else {
  1786. parent.replaceChild(node, element);
  1787. }
  1788. index = node;
  1789. });
  1790. },
  1791. children: function(element) {
  1792. var children = [];
  1793. forEach(element.childNodes, function(element){
  1794. if (element.nodeName != '#text')
  1795. children.push(element);
  1796. });
  1797. return children;
  1798. },
  1799. contents: function(element) {
  1800. return element.childNodes;
  1801. },
  1802. append: function(element, node) {
  1803. forEach(new JQLite(node), function(child){
  1804. if (element.nodeType === 1 || element.nodeType === 11) {
  1805. element.appendChild(child);
  1806. }
  1807. });
  1808. },
  1809. prepend: function(element, node) {
  1810. if (element.nodeType === 1) {
  1811. var index = element.firstChild;
  1812. forEach(new JQLite(node), function(child){
  1813. if (index) {
  1814. element.insertBefore(child, index);
  1815. } else {
  1816. element.appendChild(child);
  1817. index = child;
  1818. }
  1819. });
  1820. }
  1821. },
  1822. wrap: function(element, wrapNode) {
  1823. wrapNode = jqLite(wrapNode)[0];
  1824. var parent = element.parentNode;
  1825. if (parent) {
  1826. parent.replaceChild(wrapNode, element);
  1827. }
  1828. wrapNode.appendChild(element);
  1829. },
  1830. remove: function(element) {
  1831. JQLiteDealoc(element);
  1832. var parent = element.parentNode;
  1833. if (parent) parent.removeChild(element);
  1834. },
  1835. after: function(element, newElement) {
  1836. var index = element, parent = element.parentNode;
  1837. forEach(new JQLite(newElement), function(node){
  1838. parent.insertBefore(node, index.nextSibling);
  1839. index = node;
  1840. });
  1841. },
  1842. addClass: JQLiteAddClass,
  1843. removeClass: JQLiteRemoveClass,
  1844. toggleClass: function(element, selector, condition) {
  1845. if (isUndefined(condition)) {
  1846. condition = !JQLiteHasClass(element, selector);
  1847. }
  1848. (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector);
  1849. },
  1850. parent: function(element) {
  1851. var parent = element.parentNode;
  1852. return parent && parent.nodeType !== 11 ? parent : null;
  1853. },
  1854. next: function(element) {
  1855. return element.nextSibling;
  1856. },
  1857. find: function(element, selector) {
  1858. return element.getElementsByTagName(selector);
  1859. },
  1860. clone: JQLiteClone,
  1861. triggerHandler: function(element, eventName) {
  1862. var eventFns = (JQLiteExpandoStore(element, 'events') || {})[eventName];
  1863. forEach(eventFns, function(fn) {
  1864. fn.call(element, null);
  1865. });
  1866. }
  1867. }, function(fn, name){
  1868. /**
  1869. * chaining functions
  1870. */
  1871. JQLite.prototype[name] = function(arg1, arg2) {
  1872. var value;
  1873. for(var i=0; i < this.length; i++) {
  1874. if (value == undefined) {
  1875. value = fn(this[i], arg1, arg2);
  1876. if (value !== undefined) {
  1877. // any function which returns a value needs to be wrapped
  1878. value = jqLite(value);
  1879. }
  1880. } else {
  1881. JQLiteAddNodes(value, fn(this[i], arg1, arg2));
  1882. }
  1883. }
  1884. return value == undefined ? this : value;
  1885. };
  1886. });
  1887. /**
  1888. * Computes a hash of an 'obj'.
  1889. * Hash of a:
  1890. * string is string
  1891. * number is number as string
  1892. * object is either result of calling $$hashKey function on the object or uniquely generated id,
  1893. * that is also assigned to the $$hashKey property of the object.
  1894. *
  1895. * @param obj
  1896. * @returns {string} hash string such that the same input will have the same hash string.
  1897. * The resulting string key is in 'type:hashKey' format.
  1898. */
  1899. function hashKey(obj) {
  1900. var objType = typeof obj,
  1901. key;
  1902. if (objType == 'object' && obj !== null) {
  1903. if (typeof (key = obj.$$hashKey) == 'function') {
  1904. // must invoke on object to keep the right this
  1905. key = obj.$$hashKey();
  1906. } else if (key === undefined) {
  1907. key = obj.$$hashKey = nextUid();
  1908. }
  1909. } else {
  1910. key = obj;
  1911. }
  1912. return objType + ':' + key;
  1913. }
  1914. /**
  1915. * HashMap which can use objects as keys
  1916. */
  1917. function HashMap(array){
  1918. forEach(array, this.put, this);
  1919. }
  1920. HashMap.prototype = {
  1921. /**
  1922. * Store key value pair
  1923. * @param key key to store can be any type
  1924. * @param value value to store can be any type
  1925. */
  1926. put: function(key, value) {
  1927. this[hashKey(key)] = value;
  1928. },
  1929. /**
  1930. * @param key
  1931. * @returns the value for the key
  1932. */
  1933. get: function(key) {
  1934. return this[hashKey(key)];
  1935. },
  1936. /**
  1937. * Remove the key/value pair
  1938. * @param key
  1939. */
  1940. remove: function(key) {
  1941. var value = this[key = hashKey(key)];
  1942. delete this[key];
  1943. return value;
  1944. }
  1945. };
  1946. /**
  1947. * A map where multiple values can be added to the same key such that they form a queue.
  1948. * @returns {HashQueueMap}
  1949. */
  1950. function HashQueueMap() {}
  1951. HashQueueMap.prototype = {
  1952. /**
  1953. * Same as array push, but using an array as the value for the hash
  1954. */
  1955. push: function(key, value) {
  1956. var array = this[key = hashKey(key)];
  1957. if (!array) {
  1958. this[key] = [value];
  1959. } else {
  1960. array.push(value);
  1961. }
  1962. },
  1963. /**
  1964. * Same as array shift, but using an array as the value for the hash
  1965. */
  1966. shift: function(key) {
  1967. var array = this[key = hashKey(key)];
  1968. if (array) {
  1969. if (array.length == 1) {
  1970. delete this[key];
  1971. return array[0];
  1972. } else {
  1973. return array.shift();
  1974. }
  1975. }
  1976. },
  1977. /**
  1978. * return the first item without deleting it
  1979. */
  1980. peek: function(key) {
  1981. var array = this[hashKey(key)];
  1982. if (array) {
  1983. return array[0];
  1984. }
  1985. }
  1986. };
  1987. /**
  1988. * @ngdoc function
  1989. * @name angular.injector
  1990. * @function
  1991. *
  1992. * @description
  1993. * Creates an injector function that can be used for retrieving services as well as for
  1994. * dependency injection (see {@link guide/di dependency injection}).
  1995. *
  1996. * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
  1997. * {@link angular.module}. The `ng` module must be explicitly added.
  1998. * @returns {function()} Injector function. See {@link AUTO.$injector $injector}.
  1999. *
  2000. * @example
  2001. * Typical usage
  2002. * <pre>
  2003. * // create an injector
  2004. * var $injector = angular.injector(['ng']);
  2005. *
  2006. * // use the injector to kick off your application
  2007. * // use the type inference to auto inject arguments, or use implicit injection
  2008. * $injector.invoke(function($rootScope, $compile, $document){
  2009. * $compile($document)($rootScope);
  2010. * $rootScope.$digest();
  2011. * });
  2012. * </pre>
  2013. */
  2014. /**
  2015. * @ngdoc overview
  2016. * @name AUTO
  2017. * @description
  2018. *
  2019. * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
  2020. */
  2021. var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  2022. var FN_ARG_SPLIT = /,/;
  2023. var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
  2024. var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  2025. function annotate(fn) {
  2026. var $inject,
  2027. fnText,
  2028. argDecl,
  2029. last;
  2030. if (typeof fn == 'function') {
  2031. if (!($inject = fn.$inject)) {
  2032. $inject = [];
  2033. fnText = fn.toString().replace(STRIP_COMMENTS, '');
  2034. argDecl = fnText.match(FN_ARGS);
  2035. forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
  2036. arg.replace(FN_ARG, function(all, underscore, name){
  2037. $inject.push(name);
  2038. });
  2039. });
  2040. fn.$inject = $inject;
  2041. }
  2042. } else if (isArray(fn)) {
  2043. last = fn.length - 1;
  2044. assertArgFn(fn[last], 'fn')
  2045. $inject = fn.slice(0, last);
  2046. } else {
  2047. assertArgFn(fn, 'fn', true);
  2048. }
  2049. return $inject;
  2050. }
  2051. ///////////////////////////////////////
  2052. /**
  2053. * @ngdoc object
  2054. * @name AUTO.$injector
  2055. * @function
  2056. *
  2057. * @description
  2058. *
  2059. * `$injector` is used to retrieve object instances as defined by
  2060. * {@link AUTO.$provide provider}, instantiate types, invoke methods,
  2061. * and load modules.
  2062. *
  2063. * The following always holds true:
  2064. *
  2065. * <pre>
  2066. * var $injector = angular.injector();
  2067. * expect($injector.get('$injector')).toBe($injector);
  2068. * expect($injector.invoke(function($injector){
  2069. * return $injector;
  2070. * }).toBe($injector);
  2071. * </pre>
  2072. *
  2073. * # Injection Function Annotation
  2074. *
  2075. * JavaScript does not have annotations, and annotations are needed for dependency injection. The
  2076. * following ways are all valid way of annotating function with injection arguments and are equivalent.
  2077. *
  2078. * <pre>
  2079. * // inferred (only works if code not minified/obfuscated)
  2080. * $inject.invoke(function(serviceA){});
  2081. *
  2082. * // annotated
  2083. * function explicit(serviceA) {};
  2084. * explicit.$inject = ['serviceA'];
  2085. * $inject.invoke(explicit);
  2086. *
  2087. * // inline
  2088. * $inject.invoke(['serviceA', function(serviceA){}]);
  2089. * </pre>
  2090. *
  2091. * ## Inference
  2092. *
  2093. * In JavaScript calling `toString()` on a function returns the function definition. The definition can then be
  2094. * parsed and the function arguments can be extracted. *NOTE:* This does not work with minification, and obfuscation
  2095. * tools since these tools change the argument names.
  2096. *
  2097. * ## `$inject` Annotation
  2098. * By adding a `$inject` property onto a function the injection parameters can be specified.
  2099. *
  2100. * ## Inline
  2101. * As an array of injection names, where the last item in the array is the function to call.
  2102. */
  2103. /**
  2104. * @ngdoc method
  2105. * @name AUTO.$injector#get
  2106. * @methodOf AUTO.$injector
  2107. *
  2108. * @description
  2109. * Return an instance of the service.
  2110. *
  2111. * @param {string} name The name of the instance to retrieve.
  2112. * @return {*} The instance.
  2113. */
  2114. /**
  2115. * @ngdoc method
  2116. * @name AUTO.$injector#invoke
  2117. * @methodOf AUTO.$injector
  2118. *
  2119. * @description
  2120. * Invoke the method and supply the method arguments from the `$injector`.
  2121. *
  2122. * @param {!function} fn The function to invoke. The function arguments come form the function annotation.
  2123. * @param {Object=} self The `this` for the invoked method.
  2124. * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
  2125. * the `$injector` is consulted.
  2126. * @returns {*} the value returned by the invoked `fn` function.
  2127. */
  2128. /**
  2129. * @ngdoc method
  2130. * @name AUTO.$injector#instantiate
  2131. * @methodOf AUTO.$injector
  2132. * @description
  2133. * Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies
  2134. * all of the arguments to the constructor function as specified by the constructor annotation.
  2135. *
  2136. * @param {function} Type Annotated constructor function.
  2137. * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
  2138. * the `$injector` is consulted.
  2139. * @returns {Object} new instance of `Type`.
  2140. */
  2141. /**
  2142. * @ngdoc method
  2143. * @name AUTO.$injector#annotate
  2144. * @methodOf AUTO.$injector
  2145. *
  2146. * @description
  2147. * Returns an array of service names which the function is requesting for injection. This API is used by the injector
  2148. * to determine which services need to be injected into the function when the function is invoked. There are three
  2149. * ways in which the function can be annotated with the needed dependencies.
  2150. *
  2151. * # Argument names
  2152. *
  2153. * The simplest form is to extract the dependencies from the arguments of the function. This is done by converting
  2154. * the function into a string using `toString()` method and extracting the argument names.
  2155. * <pre>
  2156. * // Given
  2157. * function MyController($scope, $route) {
  2158. * // ...
  2159. * }
  2160. *
  2161. * // Then
  2162. * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
  2163. * </pre>
  2164. *
  2165. * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
  2166. * are supported.
  2167. *
  2168. * # The `$injector` property
  2169. *
  2170. * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
  2171. * services to be injected into the function.
  2172. * <pre>
  2173. * // Given
  2174. * var MyController = function(obfuscatedScope, obfuscatedRoute) {
  2175. * // ...
  2176. * }
  2177. * // Define function dependencies
  2178. * MyController.$inject = ['$scope', '$route'];
  2179. *
  2180. * // Then
  2181. * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
  2182. * </pre>
  2183. *
  2184. * # The array notation
  2185. *
  2186. * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very
  2187. * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives
  2188. * minification is a better choice:
  2189. *
  2190. * <pre>
  2191. * // We wish to write this (not minification / obfuscation safe)
  2192. * injector.invoke(function($compile, $rootScope) {
  2193. * // ...
  2194. * });
  2195. *
  2196. * // We are forced to write break inlining
  2197. * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
  2198. * // ...
  2199. * };
  2200. * tmpFn.$inject = ['$compile', '$rootScope'];
  2201. * injector.invoke(tempFn);
  2202. *
  2203. * // To better support inline function the inline annotation is supported
  2204. * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
  2205. * // ...
  2206. * }]);
  2207. *
  2208. * // Therefore
  2209. * expect(injector.annotate(
  2210. * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
  2211. * ).toEqual(['$compile', '$rootScope']);
  2212. * </pre>
  2213. *
  2214. * @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described
  2215. * above.
  2216. *
  2217. * @returns {Array.<string>} The names of the services which the function requires.
  2218. */
  2219. /**
  2220. * @ngdoc object
  2221. * @name AUTO.$provide
  2222. *
  2223. * @description
  2224. *
  2225. * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance.
  2226. * The providers share the same name as the instance they create with the `Provider` suffixed to them.
  2227. *
  2228. * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of
  2229. * a service. The Provider can have additional methods which would allow for configuration of the provider.
  2230. *
  2231. * <pre>
  2232. * function GreetProvider() {
  2233. * var salutation = 'Hello';
  2234. *
  2235. * this.salutation = function(text) {
  2236. * salutation = text;
  2237. * };
  2238. *
  2239. * this.$get = function() {
  2240. * return function (name) {
  2241. * return salutation + ' ' + name + '!';
  2242. * };
  2243. * };
  2244. * }
  2245. *
  2246. * describe('Greeter', function(){
  2247. *
  2248. * beforeEach(module(function($provide) {
  2249. * $provide.provider('greet', GreetProvider);
  2250. * });
  2251. *
  2252. * it('should greet', inject(function(greet) {
  2253. * expect(greet('angular')).toEqual('Hello angular!');
  2254. * }));
  2255. *
  2256. * it('should allow configuration of salutation', function() {
  2257. * module(function(greetProvider) {
  2258. * greetProvider.salutation('Ahoj');
  2259. * });
  2260. * inject(function(greet) {
  2261. * expect(greet('angular')).toEqual('Ahoj angular!');
  2262. * });
  2263. * )};
  2264. *
  2265. * });
  2266. * </pre>
  2267. */
  2268. /**
  2269. * @ngdoc method
  2270. * @name AUTO.$provide#provider
  2271. * @methodOf AUTO.$provide
  2272. * @description
  2273. *
  2274. * Register a provider for a service. The providers can be retrieved and can have additional configuration methods.
  2275. *
  2276. * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key.
  2277. * @param {(Object|function())} provider If the provider is:
  2278. *
  2279. * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
  2280. * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created.
  2281. * - `Constructor`: a new instance of the provider will be created using
  2282. * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.
  2283. *
  2284. * @returns {Object} registered provider instance
  2285. */
  2286. /**
  2287. * @ngdoc method
  2288. * @name AUTO.$provide#factory
  2289. * @methodOf AUTO.$provide
  2290. * @description
  2291. *
  2292. * A short hand for configuring services if only `$get` method is required.
  2293. *
  2294. * @param {string} name The name of the instance.
  2295. * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for
  2296. * `$provide.provider(name, {$get: $getFn})`.
  2297. * @returns {Object} registered provider instance
  2298. */
  2299. /**
  2300. * @ngdoc method
  2301. * @name AUTO.$provide#service
  2302. * @methodOf AUTO.$provide
  2303. * @description
  2304. *
  2305. * A short hand for registering service of given class.
  2306. *
  2307. * @param {string} name The name of the instance.
  2308. * @param {Function} constructor A class (constructor function) that will be instantiated.
  2309. * @returns {Object} registered provider instance
  2310. */
  2311. /**
  2312. * @ngdoc method
  2313. * @name AUTO.$provide#value
  2314. * @methodOf AUTO.$provide
  2315. * @description
  2316. *
  2317. * A short hand for configuring services if the `$get` method is a constant.
  2318. *
  2319. * @param {string} name The name of the instance.
  2320. * @param {*} value The value.
  2321. * @returns {Object} registered provider instance
  2322. */
  2323. /**
  2324. * @ngdoc method
  2325. * @name AUTO.$provide#constant
  2326. * @methodOf AUTO.$provide
  2327. * @description
  2328. *
  2329. * A constant value, but unlike {@link AUTO.$provide#value value} it can be injected
  2330. * into configuration function (other modules) and it is not interceptable by
  2331. * {@link AUTO.$provide#decorator decorator}.
  2332. *
  2333. * @param {string} name The name of the constant.
  2334. * @param {*} value The constant value.
  2335. * @returns {Object} registered instance
  2336. */
  2337. /**
  2338. * @ngdoc method
  2339. * @name AUTO.$provide#decorator
  2340. * @methodOf AUTO.$provide
  2341. * @description
  2342. *
  2343. * Decoration of service, allows the decorator to intercept the service instance creation. The
  2344. * returned instance may be the original instance, or a new instance which delegates to the
  2345. * original instance.
  2346. *
  2347. * @param {string} name The name of the service to decorate.
  2348. * @param {function()} decorator This function will be invoked when the service needs to be
  2349. * instanciated. The function is called using the {@link AUTO.$injector#invoke
  2350. * injector.invoke} method and is therefore fully injectable. Local injection arguments:
  2351. *
  2352. * * `$delegate` - The original service instance, which can be monkey patched, configured,
  2353. * decorated or delegated to.
  2354. */
  2355. function createInjector(modulesToLoad) {
  2356. var INSTANTIATING = {},
  2357. providerSuffix = 'Provider',
  2358. path = [],
  2359. loadedModules = new HashMap(),
  2360. providerCache = {
  2361. $provide: {
  2362. provider: supportObject(provider),
  2363. factory: supportObject(factory),
  2364. service: supportObject(service),
  2365. value: supportObject(value),
  2366. constant: supportObject(constant),
  2367. decorator: decorator
  2368. }
  2369. },
  2370. providerInjector = (providerCache.$injector =
  2371. createInternalInjector(providerCache, function() {
  2372. throw Error("Unknown provider: " + path.join(' <- '));
  2373. })),
  2374. instanceCache = {},
  2375. instanceInjector = (instanceCache.$injector =
  2376. createInternalInjector(instanceCache, function(servicename) {
  2377. var provider = providerInjector.get(servicename + providerSuffix);
  2378. return instanceInjector.invoke(provider.$get, provider);
  2379. }));
  2380. forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
  2381. return instanceInjector;
  2382. ////////////////////////////////////
  2383. // $provider
  2384. ////////////////////////////////////
  2385. function supportObject(delegate) {
  2386. return function(key, value) {
  2387. if (isObject(key)) {
  2388. forEach(key, reverseParams(delegate));
  2389. } else {
  2390. return delegate(key, value);
  2391. }
  2392. }
  2393. }
  2394. function provider(name, provider_) {
  2395. if (isFunction(provider_)) {
  2396. provider_ = providerInjector.instantiate(provider_);
  2397. }
  2398. if (!provider_.$get) {
  2399. throw Error('Provider ' + name + ' must define $get factory method.');
  2400. }
  2401. return providerCache[name + providerSuffix] = provider_;
  2402. }
  2403. function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
  2404. function service(name, constructor) {
  2405. return factory(name, ['$injector', function($injector) {
  2406. return $injector.instantiate(constructor);
  2407. }]);
  2408. }
  2409. function value(name, value) { return factory(name, valueFn(value)); }
  2410. function constant(name, value) {
  2411. providerCache[name] = value;
  2412. instanceCache[name] = value;
  2413. }
  2414. function decorator(serviceName, decorFn) {
  2415. var origProvider = providerInjector.get(serviceName + providerSuffix),
  2416. orig$get = origProvider.$get;
  2417. origProvider.$get = function() {
  2418. var origInstance = instanceInjector.invoke(orig$get, origProvider);
  2419. return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
  2420. };
  2421. }
  2422. ////////////////////////////////////
  2423. // Module Loading
  2424. ////////////////////////////////////
  2425. function loadModules(modulesToLoad){
  2426. var runBlocks = [];
  2427. forEach(modulesToLoad, function(module) {
  2428. if (loadedModules.get(module)) return;
  2429. loadedModules.put(module, true);
  2430. if (isString(module)) {
  2431. var moduleFn = angularModule(module);
  2432. runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
  2433. try {
  2434. for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
  2435. var invokeArgs = invokeQueue[i],
  2436. provider = providerInjector.get(invokeArgs[0]);
  2437. provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
  2438. }
  2439. } catch (e) {
  2440. if (e.message) e.message += ' from ' + module;
  2441. throw e;
  2442. }
  2443. } else if (isFunction(module)) {
  2444. try {
  2445. runBlocks.push(providerInjector.invoke(module));
  2446. } catch (e) {
  2447. if (e.message) e.message += ' from ' + module;
  2448. throw e;
  2449. }
  2450. } else if (isArray(module)) {
  2451. try {
  2452. runBlocks.push(providerInjector.invoke(module));
  2453. } catch (e) {
  2454. if (e.message) e.message += ' from ' + String(module[module.length - 1]);
  2455. throw e;
  2456. }
  2457. } else {
  2458. assertArgFn(module, 'module');
  2459. }
  2460. });
  2461. return runBlocks;
  2462. }
  2463. ////////////////////////////////////
  2464. // internal Injector
  2465. ////////////////////////////////////
  2466. function createInternalInjector(cache, factory) {
  2467. function getService(serviceName) {
  2468. if (typeof serviceName !== 'string') {
  2469. throw Error('Service name expected');
  2470. }
  2471. if (cache.hasOwnProperty(serviceName)) {
  2472. if (cache[serviceName] === INSTANTIATING) {
  2473. throw Error('Circular dependency: ' + path.join(' <- '));
  2474. }
  2475. return cache[serviceName];
  2476. } else {
  2477. try {
  2478. path.unshift(serviceName);
  2479. cache[serviceName] = INSTANTIATING;
  2480. return cache[serviceName] = factory(serviceName);
  2481. } finally {
  2482. path.shift();
  2483. }
  2484. }
  2485. }
  2486. function invoke(fn, self, locals){
  2487. var args = [],
  2488. $inject = annotate(fn),
  2489. length, i,
  2490. key;
  2491. for(i = 0, length = $inject.length; i < length; i++) {
  2492. key = $inject[i];
  2493. args.push(
  2494. locals && locals.hasOwnProperty(key)
  2495. ? locals[key]
  2496. : getService(key, path)
  2497. );
  2498. }
  2499. if (!fn.$inject) {
  2500. // this means that we must be an array.
  2501. fn = fn[length];
  2502. }
  2503. // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
  2504. switch (self ? -1 : args.length) {
  2505. case 0: return fn();
  2506. case 1: return fn(args[0]);
  2507. case 2: return fn(args[0], args[1]);
  2508. case 3: return fn(args[0], args[1], args[2]);
  2509. case 4: return fn(args[0], args[1], args[2], args[3]);
  2510. case 5: return fn(args[0], args[1], args[2], args[3], args[4]);
  2511. case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
  2512. case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
  2513. case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
  2514. case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
  2515. case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
  2516. default: return fn.apply(self, args);
  2517. }
  2518. }
  2519. function instantiate(Type, locals) {
  2520. var Constructor = function() {},
  2521. instance, returnedValue;
  2522. Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
  2523. instance = new Constructor();
  2524. returnedValue = invoke(Type, instance, locals);
  2525. return isObject(returnedValue) ? returnedValue : instance;
  2526. }
  2527. return {
  2528. invoke: invoke,
  2529. instantiate: instantiate,
  2530. get: getService,
  2531. annotate: annotate
  2532. };
  2533. }
  2534. }
  2535. /**
  2536. * @ngdoc function
  2537. * @name ng.$anchorScroll
  2538. * @requires $window
  2539. * @requires $location
  2540. * @requires $rootScope
  2541. *
  2542. * @description
  2543. * When called, it checks current value of `$location.hash()` and scroll to related element,
  2544. * according to rules specified in
  2545. * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}.
  2546. *
  2547. * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor.
  2548. * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
  2549. */
  2550. function $AnchorScrollProvider() {
  2551. var autoScrollingEnabled = true;
  2552. this.disableAutoScrolling = function() {
  2553. autoScrollingEnabled = false;
  2554. };
  2555. this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
  2556. var document = $window.document;
  2557. // helper function to get first anchor from a NodeList
  2558. // can't use filter.filter, as it accepts only instances of Array
  2559. // and IE can't convert NodeList to an array using [].slice
  2560. // TODO(vojta): use filter if we change it to accept lists as well
  2561. function getFirstAnchor(list) {
  2562. var result = null;
  2563. forEach(list, function(element) {
  2564. if (!result && lowercase(element.nodeName) === 'a') result = element;
  2565. });
  2566. return result;
  2567. }
  2568. function scroll() {
  2569. var hash = $location.hash(), elm;
  2570. // empty hash, scroll to the top of the page
  2571. if (!hash) $window.scrollTo(0, 0);
  2572. // element with given id
  2573. else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
  2574. // first anchor with given name :-D
  2575. else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
  2576. // no element and hash == 'top', scroll to the top of the page
  2577. else if (hash === 'top') $window.scrollTo(0, 0);
  2578. }
  2579. // does not scroll when user clicks on anchor link that is currently on
  2580. // (no url change, no $locaiton.hash() change), browser native does scroll
  2581. if (autoScrollingEnabled) {
  2582. $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
  2583. function autoScrollWatchAction() {
  2584. $rootScope.$evalAsync(scroll);
  2585. });
  2586. }
  2587. return scroll;
  2588. }];
  2589. }
  2590. /**
  2591. * ! This is a private undocumented service !
  2592. *
  2593. * @name ng.$browser
  2594. * @requires $log
  2595. * @description
  2596. * This object has two goals:
  2597. *
  2598. * - hide all the global state in the browser caused by the window object
  2599. * - abstract away all the browser specific features and inconsistencies
  2600. *
  2601. * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
  2602. * service, which can be used for convenient testing of the application without the interaction with
  2603. * the real browser apis.
  2604. */
  2605. /**
  2606. * @param {object} window The global window object.
  2607. * @param {object} document jQuery wrapped document.
  2608. * @param {function()} XHR XMLHttpRequest constructor.
  2609. * @param {object} $log console.log or an object with the same interface.
  2610. * @param {object} $sniffer $sniffer service
  2611. */
  2612. function Browser(window, document, $log, $sniffer) {
  2613. var self = this,
  2614. rawDocument = document[0],
  2615. location = window.location,
  2616. history = window.history,
  2617. setTimeout = window.setTimeout,
  2618. clearTimeout = window.clearTimeout,
  2619. pendingDeferIds = {};
  2620. self.isMock = false;
  2621. var outstandingRequestCount = 0;
  2622. var outstandingRequestCallbacks = [];
  2623. // TODO(vojta): remove this temporary api
  2624. self.$$completeOutstandingRequest = completeOutstandingRequest;
  2625. self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
  2626. /**
  2627. * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
  2628. * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
  2629. */
  2630. function completeOutstandingRequest(fn) {
  2631. try {
  2632. fn.apply(null, sliceArgs(arguments, 1));
  2633. } finally {
  2634. outstandingRequestCount--;
  2635. if (outstandingRequestCount === 0) {
  2636. while(outstandingRequestCallbacks.length) {
  2637. try {
  2638. outstandingRequestCallbacks.pop()();
  2639. } catch (e) {
  2640. $log.error(e);
  2641. }
  2642. }
  2643. }
  2644. }
  2645. }
  2646. /**
  2647. * @private
  2648. * Note: this method is used only by scenario runner
  2649. * TODO(vojta): prefix this method with $$ ?
  2650. * @param {function()} callback Function that will be called when no outstanding request
  2651. */
  2652. self.notifyWhenNoOutstandingRequests = function(callback) {
  2653. // force browser to execute all pollFns - this is needed so that cookies and other pollers fire
  2654. // at some deterministic time in respect to the test runner's actions. Leaving things up to the
  2655. // regular poller would result in flaky tests.
  2656. forEach(pollFns, function(pollFn){ pollFn(); });
  2657. if (outstandingRequestCount === 0) {
  2658. callback();
  2659. } else {
  2660. outstandingRequestCallbacks.push(callback);
  2661. }
  2662. };
  2663. //////////////////////////////////////////////////////////////
  2664. // Poll Watcher API
  2665. //////////////////////////////////////////////////////////////
  2666. var pollFns = [],
  2667. pollTimeout;
  2668. /**
  2669. * @name ng.$browser#addPollFn
  2670. * @methodOf ng.$browser
  2671. *
  2672. * @param {function()} fn Poll function to add
  2673. *
  2674. * @description
  2675. * Adds a function to the list of functions that poller periodically executes,
  2676. * and starts polling if not started yet.
  2677. *
  2678. * @returns {function()} the added function
  2679. */
  2680. self.addPollFn = function(fn) {
  2681. if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
  2682. pollFns.push(fn);
  2683. return fn;
  2684. };
  2685. /**
  2686. * @param {number} interval How often should browser call poll functions (ms)
  2687. * @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
  2688. *
  2689. * @description
  2690. * Configures the poller to run in the specified intervals, using the specified
  2691. * setTimeout fn and kicks it off.
  2692. */
  2693. function startPoller(interval, setTimeout) {
  2694. (function check() {
  2695. forEach(pollFns, function(pollFn){ pollFn(); });
  2696. pollTimeout = setTimeout(check, interval);
  2697. })();
  2698. }
  2699. //////////////////////////////////////////////////////////////
  2700. // URL API
  2701. //////////////////////////////////////////////////////////////
  2702. var lastBrowserUrl = location.href,
  2703. baseElement = document.find('base');
  2704. /**
  2705. * @name ng.$browser#url
  2706. * @methodOf ng.$browser
  2707. *
  2708. * @description
  2709. * GETTER:
  2710. * Without any argument, this method just returns current value of location.href.
  2711. *
  2712. * SETTER:
  2713. * With at least one argument, this method sets url to new value.
  2714. * If html5 history api supported, pushState/replaceState is used, otherwise
  2715. * location.href/location.replace is used.
  2716. * Returns its own instance to allow chaining
  2717. *
  2718. * NOTE: this api is intended for use only by the $location service. Please use the
  2719. * {@link ng.$location $location service} to change url.
  2720. *
  2721. * @param {string} url New url (when used as setter)
  2722. * @param {boolean=} replace Should new url replace current history record ?
  2723. */
  2724. self.url = function(url, replace) {
  2725. // setter
  2726. if (url) {
  2727. if (lastBrowserUrl == url) return;
  2728. lastBrowserUrl = url;
  2729. if ($sniffer.history) {
  2730. if (replace) history.replaceState(null, '', url);
  2731. else {
  2732. history.pushState(null, '', url);
  2733. // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
  2734. baseElement.attr('href', baseElement.attr('href'));
  2735. }
  2736. } else {
  2737. if (replace) location.replace(url);
  2738. else location.href = url;
  2739. }
  2740. return self;
  2741. // getter
  2742. } else {
  2743. // the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
  2744. return location.href.replace(/%27/g,"'");
  2745. }
  2746. };
  2747. var urlChangeListeners = [],
  2748. urlChangeInit = false;
  2749. function fireUrlChange() {
  2750. if (lastBrowserUrl == self.url()) return;
  2751. lastBrowserUrl = self.url();
  2752. forEach(urlChangeListeners, function(listener) {
  2753. listener(self.url());
  2754. });
  2755. }
  2756. /**
  2757. * @name ng.$browser#onUrlChange
  2758. * @methodOf ng.$browser
  2759. * @TODO(vojta): refactor to use node's syntax for events
  2760. *
  2761. * @description
  2762. * Register callback function that will be called, when url changes.
  2763. *
  2764. * It's only called when the url is changed by outside of angular:
  2765. * - user types different url into address bar
  2766. * - user clicks on history (forward/back) button
  2767. * - user clicks on a link
  2768. *
  2769. * It's not called when url is changed by $browser.url() method
  2770. *
  2771. * The listener gets called with new url as parameter.
  2772. *
  2773. * NOTE: this api is intended for use only by the $location service. Please use the
  2774. * {@link ng.$location $location service} to monitor url changes in angular apps.
  2775. *
  2776. * @param {function(string)} listener Listener function to be called when url changes.
  2777. * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
  2778. */
  2779. self.onUrlChange = function(callback) {
  2780. if (!urlChangeInit) {
  2781. // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
  2782. // don't fire popstate when user change the address bar and don't fire hashchange when url
  2783. // changed by push/replaceState
  2784. // html5 history api - popstate event
  2785. if ($sniffer.history) jqLite(window).bind('popstate', fireUrlChange);
  2786. // hashchange event
  2787. if ($sniffer.hashchange) jqLite(window).bind('hashchange', fireUrlChange);
  2788. // polling
  2789. else self.addPollFn(fireUrlChange);
  2790. urlChangeInit = true;
  2791. }
  2792. urlChangeListeners.push(callback);
  2793. return callback;
  2794. };
  2795. //////////////////////////////////////////////////////////////
  2796. // Misc API
  2797. //////////////////////////////////////////////////////////////
  2798. /**
  2799. * Returns current <base href>
  2800. * (always relative - without domain)
  2801. *
  2802. * @returns {string=}
  2803. */
  2804. self.baseHref = function() {
  2805. var href = baseElement.attr('href');
  2806. return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
  2807. };
  2808. //////////////////////////////////////////////////////////////
  2809. // Cookies API
  2810. //////////////////////////////////////////////////////////////
  2811. var lastCookies = {};
  2812. var lastCookieString = '';
  2813. var cookiePath = self.baseHref();
  2814. /**
  2815. * @name ng.$browser#cookies
  2816. * @methodOf ng.$browser
  2817. *
  2818. * @param {string=} name Cookie name
  2819. * @param {string=} value Cokkie value
  2820. *
  2821. * @description
  2822. * The cookies method provides a 'private' low level access to browser cookies.
  2823. * It is not meant to be used directly, use the $cookie service instead.
  2824. *
  2825. * The return values vary depending on the arguments that the method was called with as follows:
  2826. * <ul>
  2827. * <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li>
  2828. * <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li>
  2829. * <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li>
  2830. * </ul>
  2831. *
  2832. * @returns {Object} Hash of all cookies (if called without any parameter)
  2833. */
  2834. self.cookies = function(name, value) {
  2835. var cookieLength, cookieArray, cookie, i, index;
  2836. if (name) {
  2837. if (value === undefined) {
  2838. rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
  2839. } else {
  2840. if (isString(value)) {
  2841. cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1;
  2842. if (cookieLength > 4096) {
  2843. $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+
  2844. cookieLength + " > 4096 bytes)!");
  2845. }
  2846. if (lastCookies.length > 20) {
  2847. $log.warn("Cookie '"+ name +"' possibly not set or overflowed because too many cookies " +
  2848. "were already set (" + lastCookies.length + " > 20 )");
  2849. }
  2850. }
  2851. }
  2852. } else {
  2853. if (rawDocument.cookie !== lastCookieString) {
  2854. lastCookieString = rawDocument.cookie;
  2855. cookieArray = lastCookieString.split("; ");
  2856. lastCookies = {};
  2857. for (i = 0; i < cookieArray.length; i++) {
  2858. cookie = cookieArray[i];
  2859. index = cookie.indexOf('=');
  2860. if (index > 0) { //ignore nameless cookies
  2861. lastCookies[unescape(cookie.substring(0, index))] = unescape(cookie.substring(index + 1));
  2862. }
  2863. }
  2864. }
  2865. return lastCookies;
  2866. }
  2867. };
  2868. /**
  2869. * @name ng.$browser#defer
  2870. * @methodOf ng.$browser
  2871. * @param {function()} fn A function, who's execution should be defered.
  2872. * @param {number=} [delay=0] of milliseconds to defer the function execution.
  2873. * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
  2874. *
  2875. * @description
  2876. * Executes a fn asynchroniously via `setTimeout(fn, delay)`.
  2877. *
  2878. * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
  2879. * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
  2880. * via `$browser.defer.flush()`.
  2881. *
  2882. */
  2883. self.defer = function(fn, delay) {
  2884. var timeoutId;
  2885. outstandingRequestCount++;
  2886. timeoutId = setTimeout(function() {
  2887. delete pendingDeferIds[timeoutId];
  2888. completeOutstandingRequest(fn);
  2889. }, delay || 0);
  2890. pendingDeferIds[timeoutId] = true;
  2891. return timeoutId;
  2892. };
  2893. /**
  2894. * @name ng.$browser#defer.cancel
  2895. * @methodOf ng.$browser.defer
  2896. *
  2897. * @description
  2898. * Cancels a defered task identified with `deferId`.
  2899. *
  2900. * @param {*} deferId Token returned by the `$browser.defer` function.
  2901. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
  2902. */
  2903. self.defer.cancel = function(deferId) {
  2904. if (pendingDeferIds[deferId]) {
  2905. delete pendingDeferIds[deferId];
  2906. clearTimeout(deferId);
  2907. completeOutstandingRequest(noop);
  2908. return true;
  2909. }
  2910. return false;
  2911. };
  2912. }
  2913. function $BrowserProvider(){
  2914. this.$get = ['$window', '$log', '$sniffer', '$document',
  2915. function( $window, $log, $sniffer, $document){
  2916. return new Browser($window, $document, $log, $sniffer);
  2917. }];
  2918. }
  2919. /**
  2920. * @ngdoc object
  2921. * @name ng.$cacheFactory
  2922. *
  2923. * @description
  2924. * Factory that constructs cache objects.
  2925. *
  2926. *
  2927. * @param {string} cacheId Name or id of the newly created cache.
  2928. * @param {object=} options Options object that specifies the cache behavior. Properties:
  2929. *
  2930. * - `{number=}` `capacity` — turns the cache into LRU cache.
  2931. *
  2932. * @returns {object} Newly created cache object with the following set of methods:
  2933. *
  2934. * - `{object}` `info()` — Returns id, size, and options of cache.
  2935. * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns it.
  2936. * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
  2937. * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
  2938. * - `{void}` `removeAll()` — Removes all cached values.
  2939. * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
  2940. *
  2941. */
  2942. function $CacheFactoryProvider() {
  2943. this.$get = function() {
  2944. var caches = {};
  2945. function cacheFactory(cacheId, options) {
  2946. if (cacheId in caches) {
  2947. throw Error('cacheId ' + cacheId + ' taken');
  2948. }
  2949. var size = 0,
  2950. stats = extend({}, options, {id: cacheId}),
  2951. data = {},
  2952. capacity = (options && options.capacity) || Number.MAX_VALUE,
  2953. lruHash = {},
  2954. freshEnd = null,
  2955. staleEnd = null;
  2956. return caches[cacheId] = {
  2957. put: function(key, value) {
  2958. var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
  2959. refresh(lruEntry);
  2960. if (isUndefined(value)) return;
  2961. if (!(key in data)) size++;
  2962. data[key] = value;
  2963. if (size > capacity) {
  2964. this.remove(staleEnd.key);
  2965. }
  2966. return value;
  2967. },
  2968. get: function(key) {
  2969. var lruEntry = lruHash[key];
  2970. if (!lruEntry) return;
  2971. refresh(lruEntry);
  2972. return data[key];
  2973. },
  2974. remove: function(key) {
  2975. var lruEntry = lruHash[key];
  2976. if (!lruEntry) return;
  2977. if (lruEntry == freshEnd) freshEnd = lruEntry.p;
  2978. if (lruEntry == staleEnd) staleEnd = lruEntry.n;
  2979. link(lruEntry.n,lruEntry.p);
  2980. delete lruHash[key];
  2981. delete data[key];
  2982. size--;
  2983. },
  2984. removeAll: function() {
  2985. data = {};
  2986. size = 0;
  2987. lruHash = {};
  2988. freshEnd = staleEnd = null;
  2989. },
  2990. destroy: function() {
  2991. data = null;
  2992. stats = null;
  2993. lruHash = null;
  2994. delete caches[cacheId];
  2995. },
  2996. info: function() {
  2997. return extend({}, stats, {size: size});
  2998. }
  2999. };
  3000. /**
  3001. * makes the `entry` the freshEnd of the LRU linked list
  3002. */
  3003. function refresh(entry) {
  3004. if (entry != freshEnd) {
  3005. if (!staleEnd) {
  3006. staleEnd = entry;
  3007. } else if (staleEnd == entry) {
  3008. staleEnd = entry.n;
  3009. }
  3010. link(entry.n, entry.p);
  3011. link(entry, freshEnd);
  3012. freshEnd = entry;
  3013. freshEnd.n = null;
  3014. }
  3015. }
  3016. /**
  3017. * bidirectionally links two entries of the LRU linked list
  3018. */
  3019. function link(nextEntry, prevEntry) {
  3020. if (nextEntry != prevEntry) {
  3021. if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
  3022. if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
  3023. }
  3024. }
  3025. }
  3026. cacheFactory.info = function() {
  3027. var info = {};
  3028. forEach(caches, function(cache, cacheId) {
  3029. info[cacheId] = cache.info();
  3030. });
  3031. return info;
  3032. };
  3033. cacheFactory.get = function(cacheId) {
  3034. return caches[cacheId];
  3035. };
  3036. return cacheFactory;
  3037. };
  3038. }
  3039. /**
  3040. * @ngdoc object
  3041. * @name ng.$templateCache
  3042. *
  3043. * @description
  3044. * Cache used for storing html templates.
  3045. *
  3046. * See {@link ng.$cacheFactory $cacheFactory}.
  3047. *
  3048. */
  3049. function $TemplateCacheProvider() {
  3050. this.$get = ['$cacheFactory', function($cacheFactory) {
  3051. return $cacheFactory('templates');
  3052. }];
  3053. }
  3054. /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
  3055. *
  3056. * DOM-related variables:
  3057. *
  3058. * - "node" - DOM Node
  3059. * - "element" - DOM Element or Node
  3060. * - "$node" or "$element" - jqLite-wrapped node or element
  3061. *
  3062. *
  3063. * Compiler related stuff:
  3064. *
  3065. * - "linkFn" - linking fn of a single directive
  3066. * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
  3067. * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
  3068. * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
  3069. */
  3070. var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
  3071. /**
  3072. * @ngdoc function
  3073. * @name ng.$compile
  3074. * @function
  3075. *
  3076. * @description
  3077. * Compiles a piece of HTML string or DOM into a template and produces a template function, which
  3078. * can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
  3079. *
  3080. * The compilation is a process of walking the DOM tree and trying to match DOM elements to
  3081. * {@link ng.$compileProvider#directive directives}. For each match it
  3082. * executes corresponding template function and collects the
  3083. * instance functions into a single template function which is then returned.
  3084. *
  3085. * The template function can then be used once to produce the view or as it is the case with
  3086. * {@link ng.directive:ngRepeat repeater} many-times, in which
  3087. * case each call results in a view that is a DOM clone of the original template.
  3088. *
  3089. <doc:example module="compile">
  3090. <doc:source>
  3091. <script>
  3092. // declare a new module, and inject the $compileProvider
  3093. angular.module('compile', [], function($compileProvider) {
  3094. // configure new 'compile' directive by passing a directive
  3095. // factory function. The factory function injects the '$compile'
  3096. $compileProvider.directive('compile', function($compile) {
  3097. // directive factory creates a link function
  3098. return function(scope, element, attrs) {
  3099. scope.$watch(
  3100. function(scope) {
  3101. // watch the 'compile' expression for changes
  3102. return scope.$eval(attrs.compile);
  3103. },
  3104. function(value) {
  3105. // when the 'compile' expression changes
  3106. // assign it into the current DOM
  3107. element.html(value);
  3108. // compile the new DOM and link it to the current
  3109. // scope.
  3110. // NOTE: we only compile .childNodes so that
  3111. // we don't get into infinite loop compiling ourselves
  3112. $compile(element.contents())(scope);
  3113. }
  3114. );
  3115. };
  3116. })
  3117. });
  3118. function Ctrl($scope) {
  3119. $scope.name = 'Angular';
  3120. $scope.html = 'Hello {{name}}';
  3121. }
  3122. </script>
  3123. <div ng-controller="Ctrl">
  3124. <input ng-model="name"> <br>
  3125. <textarea ng-model="html"></textarea> <br>
  3126. <div compile="html"></div>
  3127. </div>
  3128. </doc:source>
  3129. <doc:scenario>
  3130. it('should auto compile', function() {
  3131. expect(element('div[compile]').text()).toBe('Hello Angular');
  3132. input('html').enter('{{name}}!');
  3133. expect(element('div[compile]').text()).toBe('Angular!');
  3134. });
  3135. </doc:scenario>
  3136. </doc:example>
  3137. *
  3138. *
  3139. * @param {string|DOMElement} element Element or HTML string to compile into a template function.
  3140. * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
  3141. * @param {number} maxPriority only apply directives lower then given priority (Only effects the
  3142. * root element(s), not their children)
  3143. * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
  3144. * (a DOM element/tree) to a scope. Where:
  3145. *
  3146. * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
  3147. * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
  3148. * `template` and call the `cloneAttachFn` function allowing the caller to attach the
  3149. * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
  3150. * called as: <br> `cloneAttachFn(clonedElement, scope)` where:
  3151. *
  3152. * * `clonedElement` - is a clone of the original `element` passed into the compiler.
  3153. * * `scope` - is the current scope with which the linking function is working with.
  3154. *
  3155. * Calling the linking function returns the element of the template. It is either the original element
  3156. * passed in, or the clone of the element if the `cloneAttachFn` is provided.
  3157. *
  3158. * After linking the view is not updated until after a call to $digest which typically is done by
  3159. * Angular automatically.
  3160. *
  3161. * If you need access to the bound view, there are two ways to do it:
  3162. *
  3163. * - If you are not asking the linking function to clone the template, create the DOM element(s)
  3164. * before you send them to the compiler and keep this reference around.
  3165. * <pre>
  3166. * var element = $compile('<p>{{total}}</p>')(scope);
  3167. * </pre>
  3168. *
  3169. * - if on the other hand, you need the element to be cloned, the view reference from the original
  3170. * example would not point to the clone, but rather to the original template that was cloned. In
  3171. * this case, you can access the clone via the cloneAttachFn:
  3172. * <pre>
  3173. * var templateHTML = angular.element('<p>{{total}}</p>'),
  3174. * scope = ....;
  3175. *
  3176. * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
  3177. * //attach the clone to DOM document at the right place
  3178. * });
  3179. *
  3180. * //now we have reference to the cloned DOM via `clone`
  3181. * </pre>
  3182. *
  3183. *
  3184. * For information on how the compiler works, see the
  3185. * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
  3186. */
  3187. /**
  3188. * @ngdoc service
  3189. * @name ng.$compileProvider
  3190. * @function
  3191. *
  3192. * @description
  3193. */
  3194. $CompileProvider.$inject = ['$provide'];
  3195. function $CompileProvider($provide) {
  3196. var hasDirectives = {},
  3197. Suffix = 'Directive',
  3198. COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
  3199. CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
  3200. MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
  3201. /**
  3202. * @ngdoc function
  3203. * @name ng.$compileProvider#directive
  3204. * @methodOf ng.$compileProvider
  3205. * @function
  3206. *
  3207. * @description
  3208. * Register a new directives with the compiler.
  3209. *
  3210. * @param {string} name Name of the directive in camel-case. (ie <code>ngBind</code> which will match as
  3211. * <code>ng-bind</code>).
  3212. * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more
  3213. * info.
  3214. * @returns {ng.$compileProvider} Self for chaining.
  3215. */
  3216. this.directive = function registerDirective(name, directiveFactory) {
  3217. if (isString(name)) {
  3218. assertArg(directiveFactory, 'directive');
  3219. if (!hasDirectives.hasOwnProperty(name)) {
  3220. hasDirectives[name] = [];
  3221. $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
  3222. function($injector, $exceptionHandler) {
  3223. var directives = [];
  3224. forEach(hasDirectives[name], function(directiveFactory) {
  3225. try {
  3226. var directive = $injector.invoke(directiveFactory);
  3227. if (isFunction(directive)) {
  3228. directive = { compile: valueFn(directive) };
  3229. } else if (!directive.compile && directive.link) {
  3230. directive.compile = valueFn(directive.link);
  3231. }
  3232. directive.priority = directive.priority || 0;
  3233. directive.name = directive.name || name;
  3234. directive.require = directive.require || (directive.controller && directive.name);
  3235. directive.restrict = directive.restrict || 'A';
  3236. directives.push(directive);
  3237. } catch (e) {
  3238. $exceptionHandler(e);
  3239. }
  3240. });
  3241. return directives;
  3242. }]);
  3243. }
  3244. hasDirectives[name].push(directiveFactory);
  3245. } else {
  3246. forEach(name, reverseParams(registerDirective));
  3247. }
  3248. return this;
  3249. };
  3250. this.$get = [
  3251. '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
  3252. '$controller', '$rootScope',
  3253. function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
  3254. $controller, $rootScope) {
  3255. var Attributes = function(element, attr) {
  3256. this.$$element = element;
  3257. this.$attr = attr || {};
  3258. };
  3259. Attributes.prototype = {
  3260. $normalize: directiveNormalize,
  3261. /**
  3262. * Set a normalized attribute on the element in a way such that all directives
  3263. * can share the attribute. This function properly handles boolean attributes.
  3264. * @param {string} key Normalized key. (ie ngAttribute)
  3265. * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
  3266. * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
  3267. * Defaults to true.
  3268. * @param {string=} attrName Optional none normalized name. Defaults to key.
  3269. */
  3270. $set: function(key, value, writeAttr, attrName) {
  3271. var booleanKey = getBooleanAttrName(this.$$element[0], key),
  3272. $$observers = this.$$observers;
  3273. if (booleanKey) {
  3274. this.$$element.prop(key, value);
  3275. attrName = booleanKey;
  3276. }
  3277. this[key] = value;
  3278. // translate normalized key to actual key
  3279. if (attrName) {
  3280. this.$attr[key] = attrName;
  3281. } else {
  3282. attrName = this.$attr[key];
  3283. if (!attrName) {
  3284. this.$attr[key] = attrName = snake_case(key, '-');
  3285. }
  3286. }
  3287. if (writeAttr !== false) {
  3288. if (value === null || value === undefined) {
  3289. this.$$element.removeAttr(attrName);
  3290. } else {
  3291. this.$$element.attr(attrName, value);
  3292. }
  3293. }
  3294. // fire observers
  3295. $$observers && forEach($$observers[key], function(fn) {
  3296. try {
  3297. fn(value);
  3298. } catch (e) {
  3299. $exceptionHandler(e);
  3300. }
  3301. });
  3302. },
  3303. /**
  3304. * Observe an interpolated attribute.
  3305. * The observer will never be called, if given attribute is not interpolated.
  3306. *
  3307. * @param {string} key Normalized key. (ie ngAttribute) .
  3308. * @param {function(*)} fn Function that will be called whenever the attribute value changes.
  3309. * @returns {function(*)} the `fn` Function passed in.
  3310. */
  3311. $observe: function(key, fn) {
  3312. var attrs = this,
  3313. $$observers = (attrs.$$observers || (attrs.$$observers = {})),
  3314. listeners = ($$observers[key] || ($$observers[key] = []));
  3315. listeners.push(fn);
  3316. $rootScope.$evalAsync(function() {
  3317. if (!listeners.$$inter) {
  3318. // no one registered attribute interpolation function, so lets call it manually
  3319. fn(attrs[key]);
  3320. }
  3321. });
  3322. return fn;
  3323. }
  3324. };
  3325. var startSymbol = $interpolate.startSymbol(),
  3326. endSymbol = $interpolate.endSymbol(),
  3327. denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
  3328. ? identity
  3329. : function denormalizeTemplate(template) {
  3330. return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
  3331. };
  3332. return compile;
  3333. //================================
  3334. function compile($compileNodes, transcludeFn, maxPriority) {
  3335. if (!($compileNodes instanceof jqLite)) {
  3336. // jquery always rewraps, where as we need to preserve the original selector so that we can modify it.
  3337. $compileNodes = jqLite($compileNodes);
  3338. }
  3339. // We can not compile top level text elements since text nodes can be merged and we will
  3340. // not be able to attach scope data to them, so we will wrap them in <span>
  3341. forEach($compileNodes, function(node, index){
  3342. if (node.nodeType == 3 /* text node */) {
  3343. $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
  3344. }
  3345. });
  3346. var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority);
  3347. return function publicLinkFn(scope, cloneConnectFn){
  3348. assertArg(scope, 'scope');
  3349. // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
  3350. // and sometimes changes the structure of the DOM.
  3351. var $linkNode = cloneConnectFn
  3352. ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
  3353. : $compileNodes;
  3354. $linkNode.data('$scope', scope);
  3355. safeAddClass($linkNode, 'ng-scope');
  3356. if (cloneConnectFn) cloneConnectFn($linkNode, scope);
  3357. if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
  3358. return $linkNode;
  3359. };
  3360. }
  3361. function wrongMode(localName, mode) {
  3362. throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
  3363. }
  3364. function safeAddClass($element, className) {
  3365. try {
  3366. $element.addClass(className);
  3367. } catch(e) {
  3368. // ignore, since it means that we are trying to set class on
  3369. // SVG element, where class name is read-only.
  3370. }
  3371. }
  3372. /**
  3373. * Compile function matches each node in nodeList against the directives. Once all directives
  3374. * for a particular node are collected their compile functions are executed. The compile
  3375. * functions return values - the linking functions - are combined into a composite linking
  3376. * function, which is the a linking function for the node.
  3377. *
  3378. * @param {NodeList} nodeList an array of nodes to compile
  3379. * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
  3380. * scope argument is auto-generated to the new child of the transcluded parent scope.
  3381. * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
  3382. * rootElement must be set the jqLite collection of the compile root. This is
  3383. * needed so that the jqLite collection items can be replaced with widgets.
  3384. * @param {number=} max directive priority
  3385. * @returns {?function} A composite linking function of all of the matched directives or null.
  3386. */
  3387. function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority) {
  3388. var linkFns = [],
  3389. nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
  3390. for(var i = 0; i < nodeList.length; i++) {
  3391. attrs = new Attributes();
  3392. // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
  3393. directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
  3394. nodeLinkFn = (directives.length)
  3395. ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
  3396. : null;
  3397. childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes.length)
  3398. ? null
  3399. : compileNodes(nodeList[i].childNodes,
  3400. nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
  3401. linkFns.push(nodeLinkFn);
  3402. linkFns.push(childLinkFn);
  3403. linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
  3404. }
  3405. // return a linking function if we have found anything, null otherwise
  3406. return linkFnFound ? compositeLinkFn : null;
  3407. function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
  3408. var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn;
  3409. for(var i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
  3410. node = nodeList[n];
  3411. nodeLinkFn = linkFns[i++];
  3412. childLinkFn = linkFns[i++];
  3413. if (nodeLinkFn) {
  3414. if (nodeLinkFn.scope) {
  3415. childScope = scope.$new(isObject(nodeLinkFn.scope));
  3416. jqLite(node).data('$scope', childScope);
  3417. } else {
  3418. childScope = scope;
  3419. }
  3420. childTranscludeFn = nodeLinkFn.transclude;
  3421. if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
  3422. nodeLinkFn(childLinkFn, childScope, node, $rootElement,
  3423. (function(transcludeFn) {
  3424. return function(cloneFn) {
  3425. var transcludeScope = scope.$new();
  3426. return transcludeFn(transcludeScope, cloneFn).
  3427. bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
  3428. };
  3429. })(childTranscludeFn || transcludeFn)
  3430. );
  3431. } else {
  3432. nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
  3433. }
  3434. } else if (childLinkFn) {
  3435. childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
  3436. }
  3437. }
  3438. }
  3439. }
  3440. /**
  3441. * Looks for directives on the given node and adds them to the directive collection which is
  3442. * sorted.
  3443. *
  3444. * @param node Node to search.
  3445. * @param directives An array to which the directives are added to. This array is sorted before
  3446. * the function returns.
  3447. * @param attrs The shared attrs object which is used to populate the normalized attributes.
  3448. * @param {number=} maxPriority Max directive priority.
  3449. */
  3450. function collectDirectives(node, directives, attrs, maxPriority) {
  3451. var nodeType = node.nodeType,
  3452. attrsMap = attrs.$attr,
  3453. match,
  3454. className;
  3455. switch(nodeType) {
  3456. case 1: /* Element */
  3457. // use the node name: <directive>
  3458. addDirective(directives,
  3459. directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
  3460. // iterate over the attributes
  3461. for (var attr, name, nName, value, nAttrs = node.attributes,
  3462. j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
  3463. attr = nAttrs[j];
  3464. if (attr.specified) {
  3465. name = attr.name;
  3466. nName = directiveNormalize(name.toLowerCase());
  3467. attrsMap[nName] = name;
  3468. attrs[nName] = value = trim((msie && name == 'href')
  3469. ? decodeURIComponent(node.getAttribute(name, 2))
  3470. : attr.value);
  3471. if (getBooleanAttrName(node, nName)) {
  3472. attrs[nName] = true; // presence means true
  3473. }
  3474. addAttrInterpolateDirective(node, directives, value, nName);
  3475. addDirective(directives, nName, 'A', maxPriority);
  3476. }
  3477. }
  3478. // use class as directive
  3479. className = node.className;
  3480. if (isString(className) && className !== '') {
  3481. while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
  3482. nName = directiveNormalize(match[2]);
  3483. if (addDirective(directives, nName, 'C', maxPriority)) {
  3484. attrs[nName] = trim(match[3]);
  3485. }
  3486. className = className.substr(match.index + match[0].length);
  3487. }
  3488. }
  3489. break;
  3490. case 3: /* Text Node */
  3491. addTextInterpolateDirective(directives, node.nodeValue);
  3492. break;
  3493. case 8: /* Comment */
  3494. try {
  3495. match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
  3496. if (match) {
  3497. nName = directiveNormalize(match[1]);
  3498. if (addDirective(directives, nName, 'M', maxPriority)) {
  3499. attrs[nName] = trim(match[2]);
  3500. }
  3501. }
  3502. } catch (e) {
  3503. // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
  3504. // Just ignore it and continue. (Can't seem to reproduce in test case.)
  3505. }
  3506. break;
  3507. }
  3508. directives.sort(byPriority);
  3509. return directives;
  3510. }
  3511. /**
  3512. * Once the directives have been collected their compile functions is executed. This method
  3513. * is responsible for inlining directive templates as well as terminating the application
  3514. * of the directives if the terminal directive has been reached..
  3515. *
  3516. * @param {Array} directives Array of collected directives to execute their compile function.
  3517. * this needs to be pre-sorted by priority order.
  3518. * @param {Node} compileNode The raw DOM node to apply the compile functions to
  3519. * @param {Object} templateAttrs The shared attribute function
  3520. * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
  3521. * scope argument is auto-generated to the new child of the transcluded parent scope.
  3522. * @param {DOMElement} $rootElement If we are working on the root of the compile tree then this
  3523. * argument has the root jqLite array so that we can replace widgets on it.
  3524. * @returns linkFn
  3525. */
  3526. function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, $rootElement) {
  3527. var terminalPriority = -Number.MAX_VALUE,
  3528. preLinkFns = [],
  3529. postLinkFns = [],
  3530. newScopeDirective = null,
  3531. newIsolateScopeDirective = null,
  3532. templateDirective = null,
  3533. $compileNode = templateAttrs.$$element = jqLite(compileNode),
  3534. directive,
  3535. directiveName,
  3536. $template,
  3537. transcludeDirective,
  3538. childTranscludeFn = transcludeFn,
  3539. controllerDirectives,
  3540. linkFn,
  3541. directiveValue;
  3542. // executes all directives on the current element
  3543. for(var i = 0, ii = directives.length; i < ii; i++) {
  3544. directive = directives[i];
  3545. $template = undefined;
  3546. if (terminalPriority > directive.priority) {
  3547. break; // prevent further processing of directives
  3548. }
  3549. if (directiveValue = directive.scope) {
  3550. assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode);
  3551. if (isObject(directiveValue)) {
  3552. safeAddClass($compileNode, 'ng-isolate-scope');
  3553. newIsolateScopeDirective = directive;
  3554. }
  3555. safeAddClass($compileNode, 'ng-scope');
  3556. newScopeDirective = newScopeDirective || directive;
  3557. }
  3558. directiveName = directive.name;
  3559. if (directiveValue = directive.controller) {
  3560. controllerDirectives = controllerDirectives || {};
  3561. assertNoDuplicate("'" + directiveName + "' controller",
  3562. controllerDirectives[directiveName], directive, $compileNode);
  3563. controllerDirectives[directiveName] = directive;
  3564. }
  3565. if (directiveValue = directive.transclude) {
  3566. assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
  3567. transcludeDirective = directive;
  3568. terminalPriority = directive.priority;
  3569. if (directiveValue == 'element') {
  3570. $template = jqLite(compileNode);
  3571. $compileNode = templateAttrs.$$element =
  3572. jqLite('<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->');
  3573. compileNode = $compileNode[0];
  3574. replaceWith($rootElement, jqLite($template[0]), compileNode);
  3575. childTranscludeFn = compile($template, transcludeFn, terminalPriority);
  3576. } else {
  3577. $template = jqLite(JQLiteClone(compileNode)).contents();
  3578. $compileNode.html(''); // clear contents
  3579. childTranscludeFn = compile($template, transcludeFn);
  3580. }
  3581. }
  3582. if ((directiveValue = directive.template)) {
  3583. assertNoDuplicate('template', templateDirective, directive, $compileNode);
  3584. templateDirective = directive;
  3585. directiveValue = denormalizeTemplate(directiveValue);
  3586. if (directive.replace) {
  3587. $template = jqLite('<div>' +
  3588. trim(directiveValue) +
  3589. '</div>').contents();
  3590. compileNode = $template[0];
  3591. if ($template.length != 1 || compileNode.nodeType !== 1) {
  3592. throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue);
  3593. }
  3594. replaceWith($rootElement, $compileNode, compileNode);
  3595. var newTemplateAttrs = {$attr: {}};
  3596. // combine directives from the original node and from the template:
  3597. // - take the array of directives for this element
  3598. // - split it into two parts, those that were already applied and those that weren't
  3599. // - collect directives from the template, add them to the second group and sort them
  3600. // - append the second group with new directives to the first group
  3601. directives = directives.concat(
  3602. collectDirectives(
  3603. compileNode,
  3604. directives.splice(i + 1, directives.length - (i + 1)),
  3605. newTemplateAttrs
  3606. )
  3607. );
  3608. mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
  3609. ii = directives.length;
  3610. } else {
  3611. $compileNode.html(directiveValue);
  3612. }
  3613. }
  3614. if (directive.templateUrl) {
  3615. assertNoDuplicate('template', templateDirective, directive, $compileNode);
  3616. templateDirective = directive;
  3617. nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i),
  3618. nodeLinkFn, $compileNode, templateAttrs, $rootElement, directive.replace,
  3619. childTranscludeFn);
  3620. ii = directives.length;
  3621. } else if (directive.compile) {
  3622. try {
  3623. linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
  3624. if (isFunction(linkFn)) {
  3625. addLinkFns(null, linkFn);
  3626. } else if (linkFn) {
  3627. addLinkFns(linkFn.pre, linkFn.post);
  3628. }
  3629. } catch (e) {
  3630. $exceptionHandler(e, startingTag($compileNode));
  3631. }
  3632. }
  3633. if (directive.terminal) {
  3634. nodeLinkFn.terminal = true;
  3635. terminalPriority = Math.max(terminalPriority, directive.priority);
  3636. }
  3637. }
  3638. nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
  3639. nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
  3640. // might be normal or delayed nodeLinkFn depending on if templateUrl is present
  3641. return nodeLinkFn;
  3642. ////////////////////
  3643. function addLinkFns(pre, post) {
  3644. if (pre) {
  3645. pre.require = directive.require;
  3646. preLinkFns.push(pre);
  3647. }
  3648. if (post) {
  3649. post.require = directive.require;
  3650. postLinkFns.push(post);
  3651. }
  3652. }
  3653. function getControllers(require, $element) {
  3654. var value, retrievalMethod = 'data', optional = false;
  3655. if (isString(require)) {
  3656. while((value = require.charAt(0)) == '^' || value == '?') {
  3657. require = require.substr(1);
  3658. if (value == '^') {
  3659. retrievalMethod = 'inheritedData';
  3660. }
  3661. optional = optional || value == '?';
  3662. }
  3663. value = $element[retrievalMethod]('$' + require + 'Controller');
  3664. if (!value && !optional) {
  3665. throw Error("No controller: " + require);
  3666. }
  3667. return value;
  3668. } else if (isArray(require)) {
  3669. value = [];
  3670. forEach(require, function(require) {
  3671. value.push(getControllers(require, $element));
  3672. });
  3673. }
  3674. return value;
  3675. }
  3676. function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
  3677. var attrs, $element, i, ii, linkFn, controller;
  3678. if (compileNode === linkNode) {
  3679. attrs = templateAttrs;
  3680. } else {
  3681. attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
  3682. }
  3683. $element = attrs.$$element;
  3684. if (newIsolateScopeDirective) {
  3685. var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
  3686. var parentScope = scope.$parent || scope;
  3687. forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
  3688. var match = definiton.match(LOCAL_REGEXP) || [],
  3689. attrName = match[2]|| scopeName,
  3690. mode = match[1], // @, =, or &
  3691. lastValue,
  3692. parentGet, parentSet;
  3693. switch (mode) {
  3694. case '@': {
  3695. attrs.$observe(attrName, function(value) {
  3696. scope[scopeName] = value;
  3697. });
  3698. attrs.$$observers[attrName].$$scope = parentScope;
  3699. break;
  3700. }
  3701. case '=': {
  3702. parentGet = $parse(attrs[attrName]);
  3703. parentSet = parentGet.assign || function() {
  3704. // reset the change, or we will throw this exception on every $digest
  3705. lastValue = scope[scopeName] = parentGet(parentScope);
  3706. throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
  3707. ' (directive: ' + newIsolateScopeDirective.name + ')');
  3708. };
  3709. lastValue = scope[scopeName] = parentGet(parentScope);
  3710. scope.$watch(function parentValueWatch() {
  3711. var parentValue = parentGet(parentScope);
  3712. if (parentValue !== scope[scopeName]) {
  3713. // we are out of sync and need to copy
  3714. if (parentValue !== lastValue) {
  3715. // parent changed and it has precedence
  3716. lastValue = scope[scopeName] = parentValue;
  3717. } else {
  3718. // if the parent can be assigned then do so
  3719. parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
  3720. }
  3721. }
  3722. return parentValue;
  3723. });
  3724. break;
  3725. }
  3726. case '&': {
  3727. parentGet = $parse(attrs[attrName]);
  3728. scope[scopeName] = function(locals) {
  3729. return parentGet(parentScope, locals);
  3730. }
  3731. break;
  3732. }
  3733. default: {
  3734. throw Error('Invalid isolate scope definition for directive ' +
  3735. newIsolateScopeDirective.name + ': ' + definiton);
  3736. }
  3737. }
  3738. });
  3739. }
  3740. if (controllerDirectives) {
  3741. forEach(controllerDirectives, function(directive) {
  3742. var locals = {
  3743. $scope: scope,
  3744. $element: $element,
  3745. $attrs: attrs,
  3746. $transclude: boundTranscludeFn
  3747. };
  3748. controller = directive.controller;
  3749. if (controller == '@') {
  3750. controller = attrs[directive.name];
  3751. }
  3752. $element.data(
  3753. '$' + directive.name + 'Controller',
  3754. $controller(controller, locals));
  3755. });
  3756. }
  3757. // PRELINKING
  3758. for(i = 0, ii = preLinkFns.length; i < ii; i++) {
  3759. try {
  3760. linkFn = preLinkFns[i];
  3761. linkFn(scope, $element, attrs,
  3762. linkFn.require && getControllers(linkFn.require, $element));
  3763. } catch (e) {
  3764. $exceptionHandler(e, startingTag($element));
  3765. }
  3766. }
  3767. // RECURSION
  3768. childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
  3769. // POSTLINKING
  3770. for(i = 0, ii = postLinkFns.length; i < ii; i++) {
  3771. try {
  3772. linkFn = postLinkFns[i];
  3773. linkFn(scope, $element, attrs,
  3774. linkFn.require && getControllers(linkFn.require, $element));
  3775. } catch (e) {
  3776. $exceptionHandler(e, startingTag($element));
  3777. }
  3778. }
  3779. }
  3780. }
  3781. /**
  3782. * looks up the directive and decorates it with exception handling and proper parameters. We
  3783. * call this the boundDirective.
  3784. *
  3785. * @param {string} name name of the directive to look up.
  3786. * @param {string} location The directive must be found in specific format.
  3787. * String containing any of theses characters:
  3788. *
  3789. * * `E`: element name
  3790. * * `A': attribute
  3791. * * `C`: class
  3792. * * `M`: comment
  3793. * @returns true if directive was added.
  3794. */
  3795. function addDirective(tDirectives, name, location, maxPriority) {
  3796. var match = false;
  3797. if (hasDirectives.hasOwnProperty(name)) {
  3798. for(var directive, directives = $injector.get(name + Suffix),
  3799. i = 0, ii = directives.length; i<ii; i++) {
  3800. try {
  3801. directive = directives[i];
  3802. if ( (maxPriority === undefined || maxPriority > directive.priority) &&
  3803. directive.restrict.indexOf(location) != -1) {
  3804. tDirectives.push(directive);
  3805. match = true;
  3806. }
  3807. } catch(e) { $exceptionHandler(e); }
  3808. }
  3809. }
  3810. return match;
  3811. }
  3812. /**
  3813. * When the element is replaced with HTML template then the new attributes
  3814. * on the template need to be merged with the existing attributes in the DOM.
  3815. * The desired effect is to have both of the attributes present.
  3816. *
  3817. * @param {object} dst destination attributes (original DOM)
  3818. * @param {object} src source attributes (from the directive template)
  3819. */
  3820. function mergeTemplateAttributes(dst, src) {
  3821. var srcAttr = src.$attr,
  3822. dstAttr = dst.$attr,
  3823. $element = dst.$$element;
  3824. // reapply the old attributes to the new element
  3825. forEach(dst, function(value, key) {
  3826. if (key.charAt(0) != '$') {
  3827. if (src[key]) {
  3828. value += (key === 'style' ? ';' : ' ') + src[key];
  3829. }
  3830. dst.$set(key, value, true, srcAttr[key]);
  3831. }
  3832. });
  3833. // copy the new attributes on the old attrs object
  3834. forEach(src, function(value, key) {
  3835. if (key == 'class') {
  3836. safeAddClass($element, value);
  3837. dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
  3838. } else if (key == 'style') {
  3839. $element.attr('style', $element.attr('style') + ';' + value);
  3840. } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
  3841. dst[key] = value;
  3842. dstAttr[key] = srcAttr[key];
  3843. }
  3844. });
  3845. }
  3846. function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
  3847. $rootElement, replace, childTranscludeFn) {
  3848. var linkQueue = [],
  3849. afterTemplateNodeLinkFn,
  3850. afterTemplateChildLinkFn,
  3851. beforeTemplateCompileNode = $compileNode[0],
  3852. origAsyncDirective = directives.shift(),
  3853. // The fact that we have to copy and patch the directive seems wrong!
  3854. derivedSyncDirective = extend({}, origAsyncDirective, {
  3855. controller: null, templateUrl: null, transclude: null, scope: null
  3856. });
  3857. $compileNode.html('');
  3858. $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}).
  3859. success(function(content) {
  3860. var compileNode, tempTemplateAttrs, $template;
  3861. content = denormalizeTemplate(content);
  3862. if (replace) {
  3863. $template = jqLite('<div>' + trim(content) + '</div>').contents();
  3864. compileNode = $template[0];
  3865. if ($template.length != 1 || compileNode.nodeType !== 1) {
  3866. throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content);
  3867. }
  3868. tempTemplateAttrs = {$attr: {}};
  3869. replaceWith($rootElement, $compileNode, compileNode);
  3870. collectDirectives(compileNode, directives, tempTemplateAttrs);
  3871. mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
  3872. } else {
  3873. compileNode = beforeTemplateCompileNode;
  3874. $compileNode.html(content);
  3875. }
  3876. directives.unshift(derivedSyncDirective);
  3877. afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
  3878. afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
  3879. while(linkQueue.length) {
  3880. var controller = linkQueue.pop(),
  3881. linkRootElement = linkQueue.pop(),
  3882. beforeTemplateLinkNode = linkQueue.pop(),
  3883. scope = linkQueue.pop(),
  3884. linkNode = compileNode;
  3885. if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
  3886. // it was cloned therefore we have to clone as well.
  3887. linkNode = JQLiteClone(compileNode);
  3888. replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
  3889. }
  3890. afterTemplateNodeLinkFn(function() {
  3891. beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
  3892. }, scope, linkNode, $rootElement, controller);
  3893. }
  3894. linkQueue = null;
  3895. }).
  3896. error(function(response, code, headers, config) {
  3897. throw Error('Failed to load template: ' + config.url);
  3898. });
  3899. return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
  3900. if (linkQueue) {
  3901. linkQueue.push(scope);
  3902. linkQueue.push(node);
  3903. linkQueue.push(rootElement);
  3904. linkQueue.push(controller);
  3905. } else {
  3906. afterTemplateNodeLinkFn(function() {
  3907. beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
  3908. }, scope, node, rootElement, controller);
  3909. }
  3910. };
  3911. }
  3912. /**
  3913. * Sorting function for bound directives.
  3914. */
  3915. function byPriority(a, b) {
  3916. return b.priority - a.priority;
  3917. }
  3918. function assertNoDuplicate(what, previousDirective, directive, element) {
  3919. if (previousDirective) {
  3920. throw Error('Multiple directives [' + previousDirective.name + ', ' +
  3921. directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
  3922. }
  3923. }
  3924. function addTextInterpolateDirective(directives, text) {
  3925. var interpolateFn = $interpolate(text, true);
  3926. if (interpolateFn) {
  3927. directives.push({
  3928. priority: 0,
  3929. compile: valueFn(function textInterpolateLinkFn(scope, node) {
  3930. var parent = node.parent(),
  3931. bindings = parent.data('$binding') || [];
  3932. bindings.push(interpolateFn);
  3933. safeAddClass(parent.data('$binding', bindings), 'ng-binding');
  3934. scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
  3935. node[0].nodeValue = value;
  3936. });
  3937. })
  3938. });
  3939. }
  3940. }
  3941. function addAttrInterpolateDirective(node, directives, value, name) {
  3942. var interpolateFn = $interpolate(value, true);
  3943. // no interpolation found -> ignore
  3944. if (!interpolateFn) return;
  3945. directives.push({
  3946. priority: 100,
  3947. compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
  3948. var $$observers = (attr.$$observers || (attr.$$observers = {}));
  3949. if (name === 'class') {
  3950. // we need to interpolate classes again, in the case the element was replaced
  3951. // and therefore the two class attrs got merged - we want to interpolate the result
  3952. interpolateFn = $interpolate(attr[name], true);
  3953. }
  3954. attr[name] = undefined;
  3955. ($$observers[name] || ($$observers[name] = [])).$$inter = true;
  3956. (attr.$$observers && attr.$$observers[name].$$scope || scope).
  3957. $watch(interpolateFn, function interpolateFnWatchAction(value) {
  3958. attr.$set(name, value);
  3959. });
  3960. })
  3961. });
  3962. }
  3963. /**
  3964. * This is a special jqLite.replaceWith, which can replace items which
  3965. * have no parents, provided that the containing jqLite collection is provided.
  3966. *
  3967. * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
  3968. * in the root of the tree.
  3969. * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell,
  3970. * but replace its DOM node reference.
  3971. * @param {Node} newNode The new DOM node.
  3972. */
  3973. function replaceWith($rootElement, $element, newNode) {
  3974. var oldNode = $element[0],
  3975. parent = oldNode.parentNode,
  3976. i, ii;
  3977. if ($rootElement) {
  3978. for(i = 0, ii = $rootElement.length; i < ii; i++) {
  3979. if ($rootElement[i] == oldNode) {
  3980. $rootElement[i] = newNode;
  3981. break;
  3982. }
  3983. }
  3984. }
  3985. if (parent) {
  3986. parent.replaceChild(newNode, oldNode);
  3987. }
  3988. newNode[jqLite.expando] = oldNode[jqLite.expando];
  3989. $element[0] = newNode;
  3990. }
  3991. }];
  3992. }
  3993. var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
  3994. /**
  3995. * Converts all accepted directives format into proper directive name.
  3996. * All of these will become 'myDirective':
  3997. * my:DiRective
  3998. * my-directive
  3999. * x-my-directive
  4000. * data-my:directive
  4001. *
  4002. * Also there is special case for Moz prefix starting with upper case letter.
  4003. * @param name Name to normalize
  4004. */
  4005. function directiveNormalize(name) {
  4006. return camelCase(name.replace(PREFIX_REGEXP, ''));
  4007. }
  4008. /**
  4009. * @ngdoc object
  4010. * @name ng.$compile.directive.Attributes
  4011. * @description
  4012. *
  4013. * A shared object between directive compile / linking functions which contains normalized DOM element
  4014. * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
  4015. * since all of these are treated as equivalent in Angular:
  4016. *
  4017. * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
  4018. */
  4019. /**
  4020. * @ngdoc property
  4021. * @name ng.$compile.directive.Attributes#$attr
  4022. * @propertyOf ng.$compile.directive.Attributes
  4023. * @returns {object} A map of DOM element attribute names to the normalized name. This is
  4024. * needed to do reverse lookup from normalized name back to actual name.
  4025. */
  4026. /**
  4027. * @ngdoc function
  4028. * @name ng.$compile.directive.Attributes#$set
  4029. * @methodOf ng.$compile.directive.Attributes
  4030. * @function
  4031. *
  4032. * @description
  4033. * Set DOM element attribute value.
  4034. *
  4035. *
  4036. * @param {string} name Normalized element attribute name of the property to modify. The name is
  4037. * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
  4038. * property to the original name.
  4039. * @param {string} value Value to set the attribute to.
  4040. */
  4041. /**
  4042. * Closure compiler type information
  4043. */
  4044. function nodesetLinkingFn(
  4045. /* angular.Scope */ scope,
  4046. /* NodeList */ nodeList,
  4047. /* Element */ rootElement,
  4048. /* function(Function) */ boundTranscludeFn
  4049. ){}
  4050. function directiveLinkingFn(
  4051. /* nodesetLinkingFn */ nodesetLinkingFn,
  4052. /* angular.Scope */ scope,
  4053. /* Node */ node,
  4054. /* Element */ rootElement,
  4055. /* function(Function) */ boundTranscludeFn
  4056. ){}
  4057. /**
  4058. * @ngdoc object
  4059. * @name ng.$controllerProvider
  4060. * @description
  4061. * The {@link ng.$controller $controller service} is used by Angular to create new
  4062. * controllers.
  4063. *
  4064. * This provider allows controller registration via the
  4065. * {@link ng.$controllerProvider#register register} method.
  4066. */
  4067. function $ControllerProvider() {
  4068. var controllers = {};
  4069. /**
  4070. * @ngdoc function
  4071. * @name ng.$controllerProvider#register
  4072. * @methodOf ng.$controllerProvider
  4073. * @param {string} name Controller name
  4074. * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
  4075. * annotations in the array notation).
  4076. */
  4077. this.register = function(name, constructor) {
  4078. if (isObject(name)) {
  4079. extend(controllers, name)
  4080. } else {
  4081. controllers[name] = constructor;
  4082. }
  4083. };
  4084. this.$get = ['$injector', '$window', function($injector, $window) {
  4085. /**
  4086. * @ngdoc function
  4087. * @name ng.$controller
  4088. * @requires $injector
  4089. *
  4090. * @param {Function|string} constructor If called with a function then it's considered to be the
  4091. * controller constructor function. Otherwise it's considered to be a string which is used
  4092. * to retrieve the controller constructor using the following steps:
  4093. *
  4094. * * check if a controller with given name is registered via `$controllerProvider`
  4095. * * check if evaluating the string on the current scope returns a constructor
  4096. * * check `window[constructor]` on the global `window` object
  4097. *
  4098. * @param {Object} locals Injection locals for Controller.
  4099. * @return {Object} Instance of given controller.
  4100. *
  4101. * @description
  4102. * `$controller` service is responsible for instantiating controllers.
  4103. *
  4104. * It's just simple call to {@link AUTO.$injector $injector}, but extracted into
  4105. * a service, so that one can override this service with {@link https://gist.github.com/1649788
  4106. * BC version}.
  4107. */
  4108. return function(constructor, locals) {
  4109. if(isString(constructor)) {
  4110. var name = constructor;
  4111. constructor = controllers.hasOwnProperty(name)
  4112. ? controllers[name]
  4113. : getter(locals.$scope, name, true) || getter($window, name, true);
  4114. assertArgFn(constructor, name, true);
  4115. }
  4116. return $injector.instantiate(constructor, locals);
  4117. };
  4118. }];
  4119. }
  4120. /**
  4121. * @ngdoc object
  4122. * @name ng.$document
  4123. * @requires $window
  4124. *
  4125. * @description
  4126. * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
  4127. * element.
  4128. */
  4129. function $DocumentProvider(){
  4130. this.$get = ['$window', function(window){
  4131. return jqLite(window.document);
  4132. }];
  4133. }
  4134. /**
  4135. * @ngdoc function
  4136. * @name ng.$exceptionHandler
  4137. * @requires $log
  4138. *
  4139. * @description
  4140. * Any uncaught exception in angular expressions is delegated to this service.
  4141. * The default implementation simply delegates to `$log.error` which logs it into
  4142. * the browser console.
  4143. *
  4144. * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
  4145. * {@link ngMock.$exceptionHandler mock $exceptionHandler}
  4146. *
  4147. * @param {Error} exception Exception associated with the error.
  4148. * @param {string=} cause optional information about the context in which
  4149. * the error was thrown.
  4150. */
  4151. function $ExceptionHandlerProvider() {
  4152. this.$get = ['$log', function($log){
  4153. return function(exception, cause) {
  4154. $log.error.apply($log, arguments);
  4155. };
  4156. }];
  4157. }
  4158. /**
  4159. * @ngdoc object
  4160. * @name ng.$interpolateProvider
  4161. * @function
  4162. *
  4163. * @description
  4164. *
  4165. * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
  4166. */
  4167. function $InterpolateProvider() {
  4168. var startSymbol = '{{';
  4169. var endSymbol = '}}';
  4170. /**
  4171. * @ngdoc method
  4172. * @name ng.$interpolateProvider#startSymbol
  4173. * @methodOf ng.$interpolateProvider
  4174. * @description
  4175. * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
  4176. *
  4177. * @param {string=} value new value to set the starting symbol to.
  4178. * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
  4179. */
  4180. this.startSymbol = function(value){
  4181. if (value) {
  4182. startSymbol = value;
  4183. return this;
  4184. } else {
  4185. return startSymbol;
  4186. }
  4187. };
  4188. /**
  4189. * @ngdoc method
  4190. * @name ng.$interpolateProvider#endSymbol
  4191. * @methodOf ng.$interpolateProvider
  4192. * @description
  4193. * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
  4194. *
  4195. * @param {string=} value new value to set the ending symbol to.
  4196. * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
  4197. */
  4198. this.endSymbol = function(value){
  4199. if (value) {
  4200. endSymbol = value;
  4201. return this;
  4202. } else {
  4203. return endSymbol;
  4204. }
  4205. };
  4206. this.$get = ['$parse', '$exceptionHandler', function($parse, $exceptionHandler) {
  4207. var startSymbolLength = startSymbol.length,
  4208. endSymbolLength = endSymbol.length;
  4209. /**
  4210. * @ngdoc function
  4211. * @name ng.$interpolate
  4212. * @function
  4213. *
  4214. * @requires $parse
  4215. *
  4216. * @description
  4217. *
  4218. * Compiles a string with markup into an interpolation function. This service is used by the
  4219. * HTML {@link ng.$compile $compile} service for data binding. See
  4220. * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
  4221. * interpolation markup.
  4222. *
  4223. *
  4224. <pre>
  4225. var $interpolate = ...; // injected
  4226. var exp = $interpolate('Hello {{name}}!');
  4227. expect(exp({name:'Angular'}).toEqual('Hello Angular!');
  4228. </pre>
  4229. *
  4230. *
  4231. * @param {string} text The text with markup to interpolate.
  4232. * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
  4233. * embedded expression in order to return an interpolation function. Strings with no
  4234. * embedded expression will return null for the interpolation function.
  4235. * @returns {function(context)} an interpolation function which is used to compute the interpolated
  4236. * string. The function has these parameters:
  4237. *
  4238. * * `context`: an object against which any expressions embedded in the strings are evaluated
  4239. * against.
  4240. *
  4241. */
  4242. function $interpolate(text, mustHaveExpression) {
  4243. var startIndex,
  4244. endIndex,
  4245. index = 0,
  4246. parts = [],
  4247. length = text.length,
  4248. hasInterpolation = false,
  4249. fn,
  4250. exp,
  4251. concat = [];
  4252. while(index < length) {
  4253. if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
  4254. ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
  4255. (index != startIndex) && parts.push(text.substring(index, startIndex));
  4256. parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
  4257. fn.exp = exp;
  4258. index = endIndex + endSymbolLength;
  4259. hasInterpolation = true;
  4260. } else {
  4261. // we did not find anything, so we have to add the remainder to the parts array
  4262. (index != length) && parts.push(text.substring(index));
  4263. index = length;
  4264. }
  4265. }
  4266. if (!(length = parts.length)) {
  4267. // we added, nothing, must have been an empty string.
  4268. parts.push('');
  4269. length = 1;
  4270. }
  4271. if (!mustHaveExpression || hasInterpolation) {
  4272. concat.length = length;
  4273. fn = function(context) {
  4274. try {
  4275. for(var i = 0, ii = length, part; i<ii; i++) {
  4276. if (typeof (part = parts[i]) == 'function') {
  4277. part = part(context);
  4278. if (part == null || part == undefined) {
  4279. part = '';
  4280. } else if (typeof part != 'string') {
  4281. part = toJson(part);
  4282. }
  4283. }
  4284. concat[i] = part;
  4285. }
  4286. return concat.join('');
  4287. }
  4288. catch(err) {
  4289. var newErr = new Error('Error while interpolating: ' + text + '\n' + err.toString());
  4290. $exceptionHandler(newErr);
  4291. }
  4292. };
  4293. fn.exp = text;
  4294. fn.parts = parts;
  4295. return fn;
  4296. }
  4297. }
  4298. /**
  4299. * @ngdoc method
  4300. * @name ng.$interpolate#startSymbol
  4301. * @methodOf ng.$interpolate
  4302. * @description
  4303. * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
  4304. *
  4305. * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change
  4306. * the symbol.
  4307. *
  4308. * @returns {string} start symbol.
  4309. */
  4310. $interpolate.startSymbol = function() {
  4311. return startSymbol;
  4312. }
  4313. /**
  4314. * @ngdoc method
  4315. * @name ng.$interpolate#endSymbol
  4316. * @methodOf ng.$interpolate
  4317. * @description
  4318. * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
  4319. *
  4320. * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
  4321. * the symbol.
  4322. *
  4323. * @returns {string} start symbol.
  4324. */
  4325. $interpolate.endSymbol = function() {
  4326. return endSymbol;
  4327. }
  4328. return $interpolate;
  4329. }];
  4330. }
  4331. var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
  4332. PATH_MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
  4333. HASH_MATCH = PATH_MATCH,
  4334. DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
  4335. /**
  4336. * Encode path using encodeUriSegment, ignoring forward slashes
  4337. *
  4338. * @param {string} path Path to encode
  4339. * @returns {string}
  4340. */
  4341. function encodePath(path) {
  4342. var segments = path.split('/'),
  4343. i = segments.length;
  4344. while (i--) {
  4345. segments[i] = encodeUriSegment(segments[i]);
  4346. }
  4347. return segments.join('/');
  4348. }
  4349. function stripHash(url) {
  4350. return url.split('#')[0];
  4351. }
  4352. function matchUrl(url, obj) {
  4353. var match = URL_MATCH.exec(url);
  4354. match = {
  4355. protocol: match[1],
  4356. host: match[3],
  4357. port: int(match[5]) || DEFAULT_PORTS[match[1]] || null,
  4358. path: match[6] || '/',
  4359. search: match[8],
  4360. hash: match[10]
  4361. };
  4362. if (obj) {
  4363. obj.$$protocol = match.protocol;
  4364. obj.$$host = match.host;
  4365. obj.$$port = match.port;
  4366. }
  4367. return match;
  4368. }
  4369. function composeProtocolHostPort(protocol, host, port) {
  4370. return protocol + '://' + host + (port == DEFAULT_PORTS[protocol] ? '' : ':' + port);
  4371. }
  4372. function pathPrefixFromBase(basePath) {
  4373. return basePath.substr(0, basePath.lastIndexOf('/'));
  4374. }
  4375. function convertToHtml5Url(url, basePath, hashPrefix) {
  4376. var match = matchUrl(url);
  4377. // already html5 url
  4378. if (decodeURIComponent(match.path) != basePath || isUndefined(match.hash) ||
  4379. match.hash.indexOf(hashPrefix) !== 0) {
  4380. return url;
  4381. // convert hashbang url -> html5 url
  4382. } else {
  4383. return composeProtocolHostPort(match.protocol, match.host, match.port) +
  4384. pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length);
  4385. }
  4386. }
  4387. function convertToHashbangUrl(url, basePath, hashPrefix) {
  4388. var match = matchUrl(url);
  4389. // already hashbang url
  4390. if (decodeURIComponent(match.path) == basePath) {
  4391. return url;
  4392. // convert html5 url -> hashbang url
  4393. } else {
  4394. var search = match.search && '?' + match.search || '',
  4395. hash = match.hash && '#' + match.hash || '',
  4396. pathPrefix = pathPrefixFromBase(basePath),
  4397. path = match.path.substr(pathPrefix.length);
  4398. if (match.path.indexOf(pathPrefix) !== 0) {
  4399. throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
  4400. }
  4401. return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath +
  4402. '#' + hashPrefix + path + search + hash;
  4403. }
  4404. }
  4405. /**
  4406. * LocationUrl represents an url
  4407. * This object is exposed as $location service when HTML5 mode is enabled and supported
  4408. *
  4409. * @constructor
  4410. * @param {string} url HTML5 url
  4411. * @param {string} pathPrefix
  4412. */
  4413. function LocationUrl(url, pathPrefix, appBaseUrl) {
  4414. pathPrefix = pathPrefix || '';
  4415. /**
  4416. * Parse given html5 (regular) url string into properties
  4417. * @param {string} newAbsoluteUrl HTML5 url
  4418. * @private
  4419. */
  4420. this.$$parse = function(newAbsoluteUrl) {
  4421. var match = matchUrl(newAbsoluteUrl, this);
  4422. if (match.path.indexOf(pathPrefix) !== 0) {
  4423. throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
  4424. }
  4425. this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
  4426. this.$$search = parseKeyValue(match.search);
  4427. this.$$hash = match.hash && decodeURIComponent(match.hash) || '';
  4428. this.$$compose();
  4429. };
  4430. /**
  4431. * Compose url and update `absUrl` property
  4432. * @private
  4433. */
  4434. this.$$compose = function() {
  4435. var search = toKeyValue(this.$$search),
  4436. hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
  4437. this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
  4438. this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
  4439. pathPrefix + this.$$url;
  4440. };
  4441. this.$$rewriteAppUrl = function(absoluteLinkUrl) {
  4442. if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
  4443. return absoluteLinkUrl;
  4444. }
  4445. }
  4446. this.$$parse(url);
  4447. }
  4448. /**
  4449. * LocationHashbangUrl represents url
  4450. * This object is exposed as $location service when html5 history api is disabled or not supported
  4451. *
  4452. * @constructor
  4453. * @param {string} url Legacy url
  4454. * @param {string} hashPrefix Prefix for hash part (containing path and search)
  4455. */
  4456. function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
  4457. var basePath;
  4458. /**
  4459. * Parse given hashbang url into properties
  4460. * @param {string} url Hashbang url
  4461. * @private
  4462. */
  4463. this.$$parse = function(url) {
  4464. var match = matchUrl(url, this);
  4465. if (match.hash && match.hash.indexOf(hashPrefix) !== 0) {
  4466. throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !');
  4467. }
  4468. basePath = match.path + (match.search ? '?' + match.search : '');
  4469. match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length));
  4470. if (match[1]) {
  4471. this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]);
  4472. } else {
  4473. this.$$path = '';
  4474. }
  4475. this.$$search = parseKeyValue(match[3]);
  4476. this.$$hash = match[5] && decodeURIComponent(match[5]) || '';
  4477. this.$$compose();
  4478. };
  4479. /**
  4480. * Compose hashbang url and update `absUrl` property
  4481. * @private
  4482. */
  4483. this.$$compose = function() {
  4484. var search = toKeyValue(this.$$search),
  4485. hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
  4486. this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
  4487. this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
  4488. basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
  4489. };
  4490. this.$$rewriteAppUrl = function(absoluteLinkUrl) {
  4491. if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
  4492. return absoluteLinkUrl;
  4493. }
  4494. }
  4495. this.$$parse(url);
  4496. }
  4497. LocationUrl.prototype = {
  4498. /**
  4499. * Has any change been replacing ?
  4500. * @private
  4501. */
  4502. $$replace: false,
  4503. /**
  4504. * @ngdoc method
  4505. * @name ng.$location#absUrl
  4506. * @methodOf ng.$location
  4507. *
  4508. * @description
  4509. * This method is getter only.
  4510. *
  4511. * Return full url representation with all segments encoded according to rules specified in
  4512. * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}.
  4513. *
  4514. * @return {string} full url
  4515. */
  4516. absUrl: locationGetter('$$absUrl'),
  4517. /**
  4518. * @ngdoc method
  4519. * @name ng.$location#url
  4520. * @methodOf ng.$location
  4521. *
  4522. * @description
  4523. * This method is getter / setter.
  4524. *
  4525. * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
  4526. *
  4527. * Change path, search and hash, when called with parameter and return `$location`.
  4528. *
  4529. * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
  4530. * @return {string} url
  4531. */
  4532. url: function(url, replace) {
  4533. if (isUndefined(url))
  4534. return this.$$url;
  4535. var match = PATH_MATCH.exec(url);
  4536. if (match[1]) this.path(decodeURIComponent(match[1]));
  4537. if (match[2] || match[1]) this.search(match[3] || '');
  4538. this.hash(match[5] || '', replace);
  4539. return this;
  4540. },
  4541. /**
  4542. * @ngdoc method
  4543. * @name ng.$location#protocol
  4544. * @methodOf ng.$location
  4545. *
  4546. * @description
  4547. * This method is getter only.
  4548. *
  4549. * Return protocol of current url.
  4550. *
  4551. * @return {string} protocol of current url
  4552. */
  4553. protocol: locationGetter('$$protocol'),
  4554. /**
  4555. * @ngdoc method
  4556. * @name ng.$location#host
  4557. * @methodOf ng.$location
  4558. *
  4559. * @description
  4560. * This method is getter only.
  4561. *
  4562. * Return host of current url.
  4563. *
  4564. * @return {string} host of current url.
  4565. */
  4566. host: locationGetter('$$host'),
  4567. /**
  4568. * @ngdoc method
  4569. * @name ng.$location#port
  4570. * @methodOf ng.$location
  4571. *
  4572. * @description
  4573. * This method is getter only.
  4574. *
  4575. * Return port of current url.
  4576. *
  4577. * @return {Number} port
  4578. */
  4579. port: locationGetter('$$port'),
  4580. /**
  4581. * @ngdoc method
  4582. * @name ng.$location#path
  4583. * @methodOf ng.$location
  4584. *
  4585. * @description
  4586. * This method is getter / setter.
  4587. *
  4588. * Return path of current url when called without any parameter.
  4589. *
  4590. * Change path when called with parameter and return `$location`.
  4591. *
  4592. * Note: Path should always begin with forward slash (/), this method will add the forward slash
  4593. * if it is missing.
  4594. *
  4595. * @param {string=} path New path
  4596. * @return {string} path
  4597. */
  4598. path: locationGetterSetter('$$path', function(path) {
  4599. return path.charAt(0) == '/' ? path : '/' + path;
  4600. }),
  4601. /**
  4602. * @ngdoc method
  4603. * @name ng.$location#search
  4604. * @methodOf ng.$location
  4605. *
  4606. * @description
  4607. * This method is getter / setter.
  4608. *
  4609. * Return search part (as object) of current url when called without any parameter.
  4610. *
  4611. * Change search part when called with parameter and return `$location`.
  4612. *
  4613. * @param {string|object<string,string>=} search New search params - string or hash object
  4614. * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a
  4615. * single search parameter. If the value is `null`, the parameter will be deleted.
  4616. *
  4617. * @return {string} search
  4618. */
  4619. search: function(search, paramValue) {
  4620. if (isUndefined(search))
  4621. return this.$$search;
  4622. if (isDefined(paramValue)) {
  4623. if (paramValue === null) {
  4624. delete this.$$search[search];
  4625. } else {
  4626. this.$$search[search] = paramValue;
  4627. }
  4628. } else {
  4629. this.$$search = isString(search) ? parseKeyValue(search) : search;
  4630. }
  4631. this.$$compose();
  4632. return this;
  4633. },
  4634. /**
  4635. * @ngdoc method
  4636. * @name ng.$location#hash
  4637. * @methodOf ng.$location
  4638. *
  4639. * @description
  4640. * This method is getter / setter.
  4641. *
  4642. * Return hash fragment when called without any parameter.
  4643. *
  4644. * Change hash fragment when called with parameter and return `$location`.
  4645. *
  4646. * @param {string=} hash New hash fragment
  4647. * @return {string} hash
  4648. */
  4649. hash: locationGetterSetter('$$hash', identity),
  4650. /**
  4651. * @ngdoc method
  4652. * @name ng.$location#replace
  4653. * @methodOf ng.$location
  4654. *
  4655. * @description
  4656. * If called, all changes to $location during current `$digest` will be replacing current history
  4657. * record, instead of adding new one.
  4658. */
  4659. replace: function() {
  4660. this.$$replace = true;
  4661. return this;
  4662. }
  4663. };
  4664. LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
  4665. function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
  4666. LocationHashbangUrl.apply(this, arguments);
  4667. this.$$rewriteAppUrl = function(absoluteLinkUrl) {
  4668. if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
  4669. return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
  4670. }
  4671. }
  4672. }
  4673. LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
  4674. function locationGetter(property) {
  4675. return function() {
  4676. return this[property];
  4677. };
  4678. }
  4679. function locationGetterSetter(property, preprocess) {
  4680. return function(value) {
  4681. if (isUndefined(value))
  4682. return this[property];
  4683. this[property] = preprocess(value);
  4684. this.$$compose();
  4685. return this;
  4686. };
  4687. }
  4688. /**
  4689. * @ngdoc object
  4690. * @name ng.$location
  4691. *
  4692. * @requires $browser
  4693. * @requires $sniffer
  4694. * @requires $rootElement
  4695. *
  4696. * @description
  4697. * The $location service parses the URL in the browser address bar (based on the
  4698. * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
  4699. * available to your application. Changes to the URL in the address bar are reflected into
  4700. * $location service and changes to $location are reflected into the browser address bar.
  4701. *
  4702. * **The $location service:**
  4703. *
  4704. * - Exposes the current URL in the browser address bar, so you can
  4705. * - Watch and observe the URL.
  4706. * - Change the URL.
  4707. * - Synchronizes the URL with the browser when the user
  4708. * - Changes the address bar.
  4709. * - Clicks the back or forward button (or clicks a History link).
  4710. * - Clicks on a link.
  4711. * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
  4712. *
  4713. * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
  4714. * Services: Using $location}
  4715. */
  4716. /**
  4717. * @ngdoc object
  4718. * @name ng.$locationProvider
  4719. * @description
  4720. * Use the `$locationProvider` to configure how the application deep linking paths are stored.
  4721. */
  4722. function $LocationProvider(){
  4723. var hashPrefix = '',
  4724. html5Mode = false;
  4725. /**
  4726. * @ngdoc property
  4727. * @name ng.$locationProvider#hashPrefix
  4728. * @methodOf ng.$locationProvider
  4729. * @description
  4730. * @param {string=} prefix Prefix for hash part (containing path and search)
  4731. * @returns {*} current value if used as getter or itself (chaining) if used as setter
  4732. */
  4733. this.hashPrefix = function(prefix) {
  4734. if (isDefined(prefix)) {
  4735. hashPrefix = prefix;
  4736. return this;
  4737. } else {
  4738. return hashPrefix;
  4739. }
  4740. };
  4741. /**
  4742. * @ngdoc property
  4743. * @name ng.$locationProvider#html5Mode
  4744. * @methodOf ng.$locationProvider
  4745. * @description
  4746. * @param {string=} mode Use HTML5 strategy if available.
  4747. * @returns {*} current value if used as getter or itself (chaining) if used as setter
  4748. */
  4749. this.html5Mode = function(mode) {
  4750. if (isDefined(mode)) {
  4751. html5Mode = mode;
  4752. return this;
  4753. } else {
  4754. return html5Mode;
  4755. }
  4756. };
  4757. this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
  4758. function( $rootScope, $browser, $sniffer, $rootElement) {
  4759. var $location,
  4760. basePath,
  4761. pathPrefix,
  4762. initUrl = $browser.url(),
  4763. initUrlParts = matchUrl(initUrl),
  4764. appBaseUrl;
  4765. if (html5Mode) {
  4766. basePath = $browser.baseHref() || '/';
  4767. pathPrefix = pathPrefixFromBase(basePath);
  4768. appBaseUrl =
  4769. composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
  4770. pathPrefix + '/';
  4771. if ($sniffer.history) {
  4772. $location = new LocationUrl(
  4773. convertToHtml5Url(initUrl, basePath, hashPrefix),
  4774. pathPrefix, appBaseUrl);
  4775. } else {
  4776. $location = new LocationHashbangInHtml5Url(
  4777. convertToHashbangUrl(initUrl, basePath, hashPrefix),
  4778. hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
  4779. }
  4780. } else {
  4781. appBaseUrl =
  4782. composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
  4783. (initUrlParts.path || '') +
  4784. (initUrlParts.search ? ('?' + initUrlParts.search) : '') +
  4785. '#' + hashPrefix + '/';
  4786. $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
  4787. }
  4788. $rootElement.bind('click', function(event) {
  4789. // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
  4790. // currently we open nice url link and redirect then
  4791. if (event.ctrlKey || event.metaKey || event.which == 2) return;
  4792. var elm = jqLite(event.target);
  4793. // traverse the DOM up to find first A tag
  4794. while (lowercase(elm[0].nodeName) !== 'a') {
  4795. // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
  4796. if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
  4797. }
  4798. var absHref = elm.prop('href'),
  4799. rewrittenUrl = $location.$$rewriteAppUrl(absHref);
  4800. if (absHref && !elm.attr('target') && rewrittenUrl) {
  4801. // update location manually
  4802. $location.$$parse(rewrittenUrl);
  4803. $rootScope.$apply();
  4804. event.preventDefault();
  4805. // hack to work around FF6 bug 684208 when scenario runner clicks on links
  4806. window.angular['ff-684208-preventDefault'] = true;
  4807. }
  4808. });
  4809. // rewrite hashbang url <> html5 url
  4810. if ($location.absUrl() != initUrl) {
  4811. $browser.url($location.absUrl(), true);
  4812. }
  4813. // update $location when $browser url changes
  4814. $browser.onUrlChange(function(newUrl) {
  4815. if ($location.absUrl() != newUrl) {
  4816. $rootScope.$evalAsync(function() {
  4817. var oldUrl = $location.absUrl();
  4818. $location.$$parse(newUrl);
  4819. afterLocationChange(oldUrl);
  4820. });
  4821. if (!$rootScope.$$phase) $rootScope.$digest();
  4822. }
  4823. });
  4824. // update browser
  4825. var changeCounter = 0;
  4826. $rootScope.$watch(function $locationWatch() {
  4827. var oldUrl = $browser.url();
  4828. var currentReplace = $location.$$replace;
  4829. if (!changeCounter || oldUrl != $location.absUrl()) {
  4830. changeCounter++;
  4831. $rootScope.$evalAsync(function() {
  4832. if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
  4833. defaultPrevented) {
  4834. $location.$$parse(oldUrl);
  4835. } else {
  4836. $browser.url($location.absUrl(), currentReplace);
  4837. afterLocationChange(oldUrl);
  4838. }
  4839. });
  4840. }
  4841. $location.$$replace = false;
  4842. return changeCounter;
  4843. });
  4844. return $location;
  4845. function afterLocationChange(oldUrl) {
  4846. $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
  4847. }
  4848. }];
  4849. }
  4850. /**
  4851. * @ngdoc object
  4852. * @name ng.$log
  4853. * @requires $window
  4854. *
  4855. * @description
  4856. * Simple service for logging. Default implementation writes the message
  4857. * into the browser's console (if present).
  4858. *
  4859. * The main purpose of this service is to simplify debugging and troubleshooting.
  4860. *
  4861. * @example
  4862. <example>
  4863. <file name="script.js">
  4864. function LogCtrl($scope, $log) {
  4865. $scope.$log = $log;
  4866. $scope.message = 'Hello World!';
  4867. }
  4868. </file>
  4869. <file name="index.html">
  4870. <div ng-controller="LogCtrl">
  4871. <p>Reload this page with open console, enter text and hit the log button...</p>
  4872. Message:
  4873. <input type="text" ng-model="message"/>
  4874. <button ng-click="$log.log(message)">log</button>
  4875. <button ng-click="$log.warn(message)">warn</button>
  4876. <button ng-click="$log.info(message)">info</button>
  4877. <button ng-click="$log.error(message)">error</button>
  4878. </div>
  4879. </file>
  4880. </example>
  4881. */
  4882. function $LogProvider(){
  4883. this.$get = ['$window', function($window){
  4884. return {
  4885. /**
  4886. * @ngdoc method
  4887. * @name ng.$log#log
  4888. * @methodOf ng.$log
  4889. *
  4890. * @description
  4891. * Write a log message
  4892. */
  4893. log: consoleLog('log'),
  4894. /**
  4895. * @ngdoc method
  4896. * @name ng.$log#warn
  4897. * @methodOf ng.$log
  4898. *
  4899. * @description
  4900. * Write a warning message
  4901. */
  4902. warn: consoleLog('warn'),
  4903. /**
  4904. * @ngdoc method
  4905. * @name ng.$log#info
  4906. * @methodOf ng.$log
  4907. *
  4908. * @description
  4909. * Write an information message
  4910. */
  4911. info: consoleLog('info'),
  4912. /**
  4913. * @ngdoc method
  4914. * @name ng.$log#error
  4915. * @methodOf ng.$log
  4916. *
  4917. * @description
  4918. * Write an error message
  4919. */
  4920. error: consoleLog('error')
  4921. };
  4922. function formatError(arg) {
  4923. if (arg instanceof Error) {
  4924. if (arg.stack) {
  4925. arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
  4926. ? 'Error: ' + arg.message + '\n' + arg.stack
  4927. : arg.stack;
  4928. } else if (arg.sourceURL) {
  4929. arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
  4930. }
  4931. }
  4932. return arg;
  4933. }
  4934. function consoleLog(type) {
  4935. var console = $window.console || {},
  4936. logFn = console[type] || console.log || noop;
  4937. if (logFn.apply) {
  4938. return function() {
  4939. var args = [];
  4940. forEach(arguments, function(arg) {
  4941. args.push(formatError(arg));
  4942. });
  4943. return logFn.apply(console, args);
  4944. };
  4945. }
  4946. // we are IE which either doesn't have window.console => this is noop and we do nothing,
  4947. // or we are IE where console.log doesn't have apply so we log at least first 2 args
  4948. return function(arg1, arg2) {
  4949. logFn(arg1, arg2);
  4950. }
  4951. }
  4952. }];
  4953. }
  4954. var OPERATORS = {
  4955. 'null':function(){return null;},
  4956. 'true':function(){return true;},
  4957. 'false':function(){return false;},
  4958. undefined:noop,
  4959. '+':function(self, locals, a,b){
  4960. a=a(self, locals); b=b(self, locals);
  4961. if (isDefined(a)) {
  4962. if (isDefined(b)) {
  4963. return a + b;
  4964. }
  4965. return a;
  4966. }
  4967. return isDefined(b)?b:undefined;},
  4968. '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
  4969. '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
  4970. '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
  4971. '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
  4972. '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
  4973. '=':noop,
  4974. '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
  4975. '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
  4976. '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
  4977. '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
  4978. '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
  4979. '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
  4980. '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
  4981. '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
  4982. '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
  4983. // '|':function(self, locals, a,b){return a|b;},
  4984. '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
  4985. '!':function(self, locals, a){return !a(self, locals);}
  4986. };
  4987. var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
  4988. function lex(text, csp){
  4989. var tokens = [],
  4990. token,
  4991. index = 0,
  4992. json = [],
  4993. ch,
  4994. lastCh = ':'; // can start regexp
  4995. while (index < text.length) {
  4996. ch = text.charAt(index);
  4997. if (is('"\'')) {
  4998. readString(ch);
  4999. } else if (isNumber(ch) || is('.') && isNumber(peek())) {
  5000. readNumber();
  5001. } else if (isIdent(ch)) {
  5002. readIdent();
  5003. // identifiers can only be if the preceding char was a { or ,
  5004. if (was('{,') && json[0]=='{' &&
  5005. (token=tokens[tokens.length-1])) {
  5006. token.json = token.text.indexOf('.') == -1;
  5007. }
  5008. } else if (is('(){}[].,;:')) {
  5009. tokens.push({
  5010. index:index,
  5011. text:ch,
  5012. json:(was(':[,') && is('{[')) || is('}]:,')
  5013. });
  5014. if (is('{[')) json.unshift(ch);
  5015. if (is('}]')) json.shift();
  5016. index++;
  5017. } else if (isWhitespace(ch)) {
  5018. index++;
  5019. continue;
  5020. } else {
  5021. var ch2 = ch + peek(),
  5022. fn = OPERATORS[ch],
  5023. fn2 = OPERATORS[ch2];
  5024. if (fn2) {
  5025. tokens.push({index:index, text:ch2, fn:fn2});
  5026. index += 2;
  5027. } else if (fn) {
  5028. tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
  5029. index += 1;
  5030. } else {
  5031. throwError("Unexpected next character ", index, index+1);
  5032. }
  5033. }
  5034. lastCh = ch;
  5035. }
  5036. return tokens;
  5037. function is(chars) {
  5038. return chars.indexOf(ch) != -1;
  5039. }
  5040. function was(chars) {
  5041. return chars.indexOf(lastCh) != -1;
  5042. }
  5043. function peek() {
  5044. return index + 1 < text.length ? text.charAt(index + 1) : false;
  5045. }
  5046. function isNumber(ch) {
  5047. return '0' <= ch && ch <= '9';
  5048. }
  5049. function isWhitespace(ch) {
  5050. return ch == ' ' || ch == '\r' || ch == '\t' ||
  5051. ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
  5052. }
  5053. function isIdent(ch) {
  5054. return 'a' <= ch && ch <= 'z' ||
  5055. 'A' <= ch && ch <= 'Z' ||
  5056. '_' == ch || ch == '$';
  5057. }
  5058. function isExpOperator(ch) {
  5059. return ch == '-' || ch == '+' || isNumber(ch);
  5060. }
  5061. function throwError(error, start, end) {
  5062. end = end || index;
  5063. throw Error("Lexer Error: " + error + " at column" +
  5064. (isDefined(start)
  5065. ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]"
  5066. : " " + end) +
  5067. " in expression [" + text + "].");
  5068. }
  5069. function readNumber() {
  5070. var number = "";
  5071. var start = index;
  5072. while (index < text.length) {
  5073. var ch = lowercase(text.charAt(index));
  5074. if (ch == '.' || isNumber(ch)) {
  5075. number += ch;
  5076. } else {
  5077. var peekCh = peek();
  5078. if (ch == 'e' && isExpOperator(peekCh)) {
  5079. number += ch;
  5080. } else if (isExpOperator(ch) &&
  5081. peekCh && isNumber(peekCh) &&
  5082. number.charAt(number.length - 1) == 'e') {
  5083. number += ch;
  5084. } else if (isExpOperator(ch) &&
  5085. (!peekCh || !isNumber(peekCh)) &&
  5086. number.charAt(number.length - 1) == 'e') {
  5087. throwError('Invalid exponent');
  5088. } else {
  5089. break;
  5090. }
  5091. }
  5092. index++;
  5093. }
  5094. number = 1 * number;
  5095. tokens.push({index:start, text:number, json:true,
  5096. fn:function() {return number;}});
  5097. }
  5098. function readIdent() {
  5099. var ident = "",
  5100. start = index,
  5101. lastDot, peekIndex, methodName;
  5102. while (index < text.length) {
  5103. var ch = text.charAt(index);
  5104. if (ch == '.' || isIdent(ch) || isNumber(ch)) {
  5105. if (ch == '.') lastDot = index;
  5106. ident += ch;
  5107. } else {
  5108. break;
  5109. }
  5110. index++;
  5111. }
  5112. //check if this is not a method invocation and if it is back out to last dot
  5113. if (lastDot) {
  5114. peekIndex = index;
  5115. while(peekIndex < text.length) {
  5116. var ch = text.charAt(peekIndex);
  5117. if (ch == '(') {
  5118. methodName = ident.substr(lastDot - start + 1);
  5119. ident = ident.substr(0, lastDot - start);
  5120. index = peekIndex;
  5121. break;
  5122. }
  5123. if(isWhitespace(ch)) {
  5124. peekIndex++;
  5125. } else {
  5126. break;
  5127. }
  5128. }
  5129. }
  5130. var token = {
  5131. index:start,
  5132. text:ident
  5133. };
  5134. if (OPERATORS.hasOwnProperty(ident)) {
  5135. token.fn = token.json = OPERATORS[ident];
  5136. } else {
  5137. var getter = getterFn(ident, csp);
  5138. token.fn = extend(function(self, locals) {
  5139. return (getter(self, locals));
  5140. }, {
  5141. assign: function(self, value) {
  5142. return setter(self, ident, value);
  5143. }
  5144. });
  5145. }
  5146. tokens.push(token);
  5147. if (methodName) {
  5148. tokens.push({
  5149. index:lastDot,
  5150. text: '.',
  5151. json: false
  5152. });
  5153. tokens.push({
  5154. index: lastDot + 1,
  5155. text: methodName,
  5156. json: false
  5157. });
  5158. }
  5159. }
  5160. function readString(quote) {
  5161. var start = index;
  5162. index++;
  5163. var string = "";
  5164. var rawString = quote;
  5165. var escape = false;
  5166. while (index < text.length) {
  5167. var ch = text.charAt(index);
  5168. rawString += ch;
  5169. if (escape) {
  5170. if (ch == 'u') {
  5171. var hex = text.substring(index + 1, index + 5);
  5172. if (!hex.match(/[\da-f]{4}/i))
  5173. throwError( "Invalid unicode escape [\\u" + hex + "]");
  5174. index += 4;
  5175. string += String.fromCharCode(parseInt(hex, 16));
  5176. } else {
  5177. var rep = ESCAPE[ch];
  5178. if (rep) {
  5179. string += rep;
  5180. } else {
  5181. string += ch;
  5182. }
  5183. }
  5184. escape = false;
  5185. } else if (ch == '\\') {
  5186. escape = true;
  5187. } else if (ch == quote) {
  5188. index++;
  5189. tokens.push({
  5190. index:start,
  5191. text:rawString,
  5192. string:string,
  5193. json:true,
  5194. fn:function() { return string; }
  5195. });
  5196. return;
  5197. } else {
  5198. string += ch;
  5199. }
  5200. index++;
  5201. }
  5202. throwError("Unterminated quote", start);
  5203. }
  5204. }
  5205. /////////////////////////////////////////
  5206. function parser(text, json, $filter, csp){
  5207. var ZERO = valueFn(0),
  5208. value,
  5209. tokens = lex(text, csp),
  5210. assignment = _assignment,
  5211. functionCall = _functionCall,
  5212. fieldAccess = _fieldAccess,
  5213. objectIndex = _objectIndex,
  5214. filterChain = _filterChain;
  5215. if(json){
  5216. // The extra level of aliasing is here, just in case the lexer misses something, so that
  5217. // we prevent any accidental execution in JSON.
  5218. assignment = logicalOR;
  5219. functionCall =
  5220. fieldAccess =
  5221. objectIndex =
  5222. filterChain =
  5223. function() { throwError("is not valid json", {text:text, index:0}); };
  5224. value = primary();
  5225. } else {
  5226. value = statements();
  5227. }
  5228. if (tokens.length !== 0) {
  5229. throwError("is an unexpected token", tokens[0]);
  5230. }
  5231. return value;
  5232. ///////////////////////////////////
  5233. function throwError(msg, token) {
  5234. throw Error("Syntax Error: Token '" + token.text +
  5235. "' " + msg + " at column " +
  5236. (token.index + 1) + " of the expression [" +
  5237. text + "] starting at [" + text.substring(token.index) + "].");
  5238. }
  5239. function peekToken() {
  5240. if (tokens.length === 0)
  5241. throw Error("Unexpected end of expression: " + text);
  5242. return tokens[0];
  5243. }
  5244. function peek(e1, e2, e3, e4) {
  5245. if (tokens.length > 0) {
  5246. var token = tokens[0];
  5247. var t = token.text;
  5248. if (t==e1 || t==e2 || t==e3 || t==e4 ||
  5249. (!e1 && !e2 && !e3 && !e4)) {
  5250. return token;
  5251. }
  5252. }
  5253. return false;
  5254. }
  5255. function expect(e1, e2, e3, e4){
  5256. var token = peek(e1, e2, e3, e4);
  5257. if (token) {
  5258. if (json && !token.json) {
  5259. throwError("is not valid json", token);
  5260. }
  5261. tokens.shift();
  5262. return token;
  5263. }
  5264. return false;
  5265. }
  5266. function consume(e1){
  5267. if (!expect(e1)) {
  5268. throwError("is unexpected, expecting [" + e1 + "]", peek());
  5269. }
  5270. }
  5271. function unaryFn(fn, right) {
  5272. return function(self, locals) {
  5273. return fn(self, locals, right);
  5274. };
  5275. }
  5276. function binaryFn(left, fn, right) {
  5277. return function(self, locals) {
  5278. return fn(self, locals, left, right);
  5279. };
  5280. }
  5281. function statements() {
  5282. var statements = [];
  5283. while(true) {
  5284. if (tokens.length > 0 && !peek('}', ')', ';', ']'))
  5285. statements.push(filterChain());
  5286. if (!expect(';')) {
  5287. // optimize for the common case where there is only one statement.
  5288. // TODO(size): maybe we should not support multiple statements?
  5289. return statements.length == 1
  5290. ? statements[0]
  5291. : function(self, locals){
  5292. var value;
  5293. for ( var i = 0; i < statements.length; i++) {
  5294. var statement = statements[i];
  5295. if (statement)
  5296. value = statement(self, locals);
  5297. }
  5298. return value;
  5299. };
  5300. }
  5301. }
  5302. }
  5303. function _filterChain() {
  5304. var left = expression();
  5305. var token;
  5306. while(true) {
  5307. if ((token = expect('|'))) {
  5308. left = binaryFn(left, token.fn, filter());
  5309. } else {
  5310. return left;
  5311. }
  5312. }
  5313. }
  5314. function filter() {
  5315. var token = expect();
  5316. var fn = $filter(token.text);
  5317. var argsFn = [];
  5318. while(true) {
  5319. if ((token = expect(':'))) {
  5320. argsFn.push(expression());
  5321. } else {
  5322. var fnInvoke = function(self, locals, input){
  5323. var args = [input];
  5324. for ( var i = 0; i < argsFn.length; i++) {
  5325. args.push(argsFn[i](self, locals));
  5326. }
  5327. return fn.apply(self, args);
  5328. };
  5329. return function() {
  5330. return fnInvoke;
  5331. };
  5332. }
  5333. }
  5334. }
  5335. function expression() {
  5336. return assignment();
  5337. }
  5338. function _assignment() {
  5339. var left = logicalOR();
  5340. var right;
  5341. var token;
  5342. if ((token = expect('='))) {
  5343. if (!left.assign) {
  5344. throwError("implies assignment but [" +
  5345. text.substring(0, token.index) + "] can not be assigned to", token);
  5346. }
  5347. right = logicalOR();
  5348. return function(self, locals){
  5349. return left.assign(self, right(self, locals), locals);
  5350. };
  5351. } else {
  5352. return left;
  5353. }
  5354. }
  5355. function logicalOR() {
  5356. var left = logicalAND();
  5357. var token;
  5358. while(true) {
  5359. if ((token = expect('||'))) {
  5360. left = binaryFn(left, token.fn, logicalAND());
  5361. } else {
  5362. return left;
  5363. }
  5364. }
  5365. }
  5366. function logicalAND() {
  5367. var left = equality();
  5368. var token;
  5369. if ((token = expect('&&'))) {
  5370. left = binaryFn(left, token.fn, logicalAND());
  5371. }
  5372. return left;
  5373. }
  5374. function equality() {
  5375. var left = relational();
  5376. var token;
  5377. if ((token = expect('==','!='))) {
  5378. left = binaryFn(left, token.fn, equality());
  5379. }
  5380. return left;
  5381. }
  5382. function relational() {
  5383. var left = additive();
  5384. var token;
  5385. if ((token = expect('<', '>', '<=', '>='))) {
  5386. left = binaryFn(left, token.fn, relational());
  5387. }
  5388. return left;
  5389. }
  5390. function additive() {
  5391. var left = multiplicative();
  5392. var token;
  5393. while ((token = expect('+','-'))) {
  5394. left = binaryFn(left, token.fn, multiplicative());
  5395. }
  5396. return left;
  5397. }
  5398. function multiplicative() {
  5399. var left = unary();
  5400. var token;
  5401. while ((token = expect('*','/','%'))) {
  5402. left = binaryFn(left, token.fn, unary());
  5403. }
  5404. return left;
  5405. }
  5406. function unary() {
  5407. var token;
  5408. if (expect('+')) {
  5409. return primary();
  5410. } else if ((token = expect('-'))) {
  5411. return binaryFn(ZERO, token.fn, unary());
  5412. } else if ((token = expect('!'))) {
  5413. return unaryFn(token.fn, unary());
  5414. } else {
  5415. return primary();
  5416. }
  5417. }
  5418. function primary() {
  5419. var primary;
  5420. if (expect('(')) {
  5421. primary = filterChain();
  5422. consume(')');
  5423. } else if (expect('[')) {
  5424. primary = arrayDeclaration();
  5425. } else if (expect('{')) {
  5426. primary = object();
  5427. } else {
  5428. var token = expect();
  5429. primary = token.fn;
  5430. if (!primary) {
  5431. throwError("not a primary expression", token);
  5432. }
  5433. }
  5434. var next, context;
  5435. while ((next = expect('(', '[', '.'))) {
  5436. if (next.text === '(') {
  5437. primary = functionCall(primary, context);
  5438. context = null;
  5439. } else if (next.text === '[') {
  5440. context = primary;
  5441. primary = objectIndex(primary);
  5442. } else if (next.text === '.') {
  5443. context = primary;
  5444. primary = fieldAccess(primary);
  5445. } else {
  5446. throwError("IMPOSSIBLE");
  5447. }
  5448. }
  5449. return primary;
  5450. }
  5451. function _fieldAccess(object) {
  5452. var field = expect().text;
  5453. var getter = getterFn(field, csp);
  5454. return extend(
  5455. function(self, locals) {
  5456. return getter(object(self, locals), locals);
  5457. },
  5458. {
  5459. assign:function(self, value, locals) {
  5460. return setter(object(self, locals), field, value);
  5461. }
  5462. }
  5463. );
  5464. }
  5465. function _objectIndex(obj) {
  5466. var indexFn = expression();
  5467. consume(']');
  5468. return extend(
  5469. function(self, locals){
  5470. var o = obj(self, locals),
  5471. i = indexFn(self, locals),
  5472. v, p;
  5473. if (!o) return undefined;
  5474. v = o[i];
  5475. if (v && v.then) {
  5476. p = v;
  5477. if (!('$$v' in v)) {
  5478. p.$$v = undefined;
  5479. p.then(function(val) { p.$$v = val; });
  5480. }
  5481. v = v.$$v;
  5482. }
  5483. return v;
  5484. }, {
  5485. assign:function(self, value, locals){
  5486. return obj(self, locals)[indexFn(self, locals)] = value;
  5487. }
  5488. });
  5489. }
  5490. function _functionCall(fn, contextGetter) {
  5491. var argsFn = [];
  5492. if (peekToken().text != ')') {
  5493. do {
  5494. argsFn.push(expression());
  5495. } while (expect(','));
  5496. }
  5497. consume(')');
  5498. return function(self, locals){
  5499. var args = [],
  5500. context = contextGetter ? contextGetter(self, locals) : self;
  5501. for ( var i = 0; i < argsFn.length; i++) {
  5502. args.push(argsFn[i](self, locals));
  5503. }
  5504. var fnPtr = fn(self, locals) || noop;
  5505. // IE stupidity!
  5506. return fnPtr.apply
  5507. ? fnPtr.apply(context, args)
  5508. : fnPtr(args[0], args[1], args[2], args[3], args[4]);
  5509. };
  5510. }
  5511. // This is used with json array declaration
  5512. function arrayDeclaration () {
  5513. var elementFns = [];
  5514. if (peekToken().text != ']') {
  5515. do {
  5516. elementFns.push(expression());
  5517. } while (expect(','));
  5518. }
  5519. consume(']');
  5520. return function(self, locals){
  5521. var array = [];
  5522. for ( var i = 0; i < elementFns.length; i++) {
  5523. array.push(elementFns[i](self, locals));
  5524. }
  5525. return array;
  5526. };
  5527. }
  5528. function object () {
  5529. var keyValues = [];
  5530. if (peekToken().text != '}') {
  5531. do {
  5532. var token = expect(),
  5533. key = token.string || token.text;
  5534. consume(":");
  5535. var value = expression();
  5536. keyValues.push({key:key, value:value});
  5537. } while (expect(','));
  5538. }
  5539. consume('}');
  5540. return function(self, locals){
  5541. var object = {};
  5542. for ( var i = 0; i < keyValues.length; i++) {
  5543. var keyValue = keyValues[i];
  5544. var value = keyValue.value(self, locals);
  5545. object[keyValue.key] = value;
  5546. }
  5547. return object;
  5548. };
  5549. }
  5550. }
  5551. //////////////////////////////////////////////////
  5552. // Parser helper functions
  5553. //////////////////////////////////////////////////
  5554. function setter(obj, path, setValue) {
  5555. var element = path.split('.');
  5556. for (var i = 0; element.length > 1; i++) {
  5557. var key = element.shift();
  5558. var propertyObj = obj[key];
  5559. if (!propertyObj) {
  5560. propertyObj = {};
  5561. obj[key] = propertyObj;
  5562. }
  5563. obj = propertyObj;
  5564. }
  5565. obj[element.shift()] = setValue;
  5566. return setValue;
  5567. }
  5568. /**
  5569. * Return the value accesible from the object by path. Any undefined traversals are ignored
  5570. * @param {Object} obj starting object
  5571. * @param {string} path path to traverse
  5572. * @param {boolean=true} bindFnToScope
  5573. * @returns value as accesbile by path
  5574. */
  5575. //TODO(misko): this function needs to be removed
  5576. function getter(obj, path, bindFnToScope) {
  5577. if (!path) return obj;
  5578. var keys = path.split('.');
  5579. var key;
  5580. var lastInstance = obj;
  5581. var len = keys.length;
  5582. for (var i = 0; i < len; i++) {
  5583. key = keys[i];
  5584. if (obj) {
  5585. obj = (lastInstance = obj)[key];
  5586. }
  5587. }
  5588. if (!bindFnToScope && isFunction(obj)) {
  5589. return bind(lastInstance, obj);
  5590. }
  5591. return obj;
  5592. }
  5593. var getterFnCache = {};
  5594. /**
  5595. * Implementation of the "Black Hole" variant from:
  5596. * - http://jsperf.com/angularjs-parse-getter/4
  5597. * - http://jsperf.com/path-evaluation-simplified/7
  5598. */
  5599. function cspSafeGetterFn(key0, key1, key2, key3, key4) {
  5600. return function(scope, locals) {
  5601. var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
  5602. promise;
  5603. if (pathVal === null || pathVal === undefined) return pathVal;
  5604. pathVal = pathVal[key0];
  5605. if (pathVal && pathVal.then) {
  5606. if (!("$$v" in pathVal)) {
  5607. promise = pathVal;
  5608. promise.$$v = undefined;
  5609. promise.then(function(val) { promise.$$v = val; });
  5610. }
  5611. pathVal = pathVal.$$v;
  5612. }
  5613. if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
  5614. pathVal = pathVal[key1];
  5615. if (pathVal && pathVal.then) {
  5616. if (!("$$v" in pathVal)) {
  5617. promise = pathVal;
  5618. promise.$$v = undefined;
  5619. promise.then(function(val) { promise.$$v = val; });
  5620. }
  5621. pathVal = pathVal.$$v;
  5622. }
  5623. if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
  5624. pathVal = pathVal[key2];
  5625. if (pathVal && pathVal.then) {
  5626. if (!("$$v" in pathVal)) {
  5627. promise = pathVal;
  5628. promise.$$v = undefined;
  5629. promise.then(function(val) { promise.$$v = val; });
  5630. }
  5631. pathVal = pathVal.$$v;
  5632. }
  5633. if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
  5634. pathVal = pathVal[key3];
  5635. if (pathVal && pathVal.then) {
  5636. if (!("$$v" in pathVal)) {
  5637. promise = pathVal;
  5638. promise.$$v = undefined;
  5639. promise.then(function(val) { promise.$$v = val; });
  5640. }
  5641. pathVal = pathVal.$$v;
  5642. }
  5643. if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
  5644. pathVal = pathVal[key4];
  5645. if (pathVal && pathVal.then) {
  5646. if (!("$$v" in pathVal)) {
  5647. promise = pathVal;
  5648. promise.$$v = undefined;
  5649. promise.then(function(val) { promise.$$v = val; });
  5650. }
  5651. pathVal = pathVal.$$v;
  5652. }
  5653. return pathVal;
  5654. };
  5655. };
  5656. function getterFn(path, csp) {
  5657. if (getterFnCache.hasOwnProperty(path)) {
  5658. return getterFnCache[path];
  5659. }
  5660. var pathKeys = path.split('.'),
  5661. pathKeysLength = pathKeys.length,
  5662. fn;
  5663. if (csp) {
  5664. fn = (pathKeysLength < 6)
  5665. ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4])
  5666. : function(scope, locals) {
  5667. var i = 0, val
  5668. do {
  5669. val = cspSafeGetterFn(
  5670. pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
  5671. )(scope, locals);
  5672. locals = undefined; // clear after first iteration
  5673. scope = val;
  5674. } while (i < pathKeysLength);
  5675. return val;
  5676. }
  5677. } else {
  5678. var code = 'var l, fn, p;\n';
  5679. forEach(pathKeys, function(key, index) {
  5680. code += 'if(s === null || s === undefined) return s;\n' +
  5681. 'l=s;\n' +
  5682. 's='+ (index
  5683. // we simply dereference 's' on any .dot notation
  5684. ? 's'
  5685. // but if we are first then we check locals first, and if so read it first
  5686. : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
  5687. 'if (s && s.then) {\n' +
  5688. ' if (!("$$v" in s)) {\n' +
  5689. ' p=s;\n' +
  5690. ' p.$$v = undefined;\n' +
  5691. ' p.then(function(v) {p.$$v=v;});\n' +
  5692. '}\n' +
  5693. ' s=s.$$v\n' +
  5694. '}\n';
  5695. });
  5696. code += 'return s;';
  5697. fn = Function('s', 'k', code); // s=scope, k=locals
  5698. fn.toString = function() { return code; };
  5699. }
  5700. return getterFnCache[path] = fn;
  5701. }
  5702. ///////////////////////////////////
  5703. /**
  5704. * @ngdoc function
  5705. * @name ng.$parse
  5706. * @function
  5707. *
  5708. * @description
  5709. *
  5710. * Converts Angular {@link guide/expression expression} into a function.
  5711. *
  5712. * <pre>
  5713. * var getter = $parse('user.name');
  5714. * var setter = getter.assign;
  5715. * var context = {user:{name:'angular'}};
  5716. * var locals = {user:{name:'local'}};
  5717. *
  5718. * expect(getter(context)).toEqual('angular');
  5719. * setter(context, 'newValue');
  5720. * expect(context.user.name).toEqual('newValue');
  5721. * expect(getter(context, locals)).toEqual('local');
  5722. * </pre>
  5723. *
  5724. *
  5725. * @param {string} expression String expression to compile.
  5726. * @returns {function(context, locals)} a function which represents the compiled expression:
  5727. *
  5728. * * `context`: an object against which any expressions embedded in the strings are evaluated
  5729. * against (Topically a scope object).
  5730. * * `locals`: local variables context object, useful for overriding values in `context`.
  5731. *
  5732. * The return function also has an `assign` property, if the expression is assignable, which
  5733. * allows one to set values to expressions.
  5734. *
  5735. */
  5736. function $ParseProvider() {
  5737. var cache = {};
  5738. this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
  5739. return function(exp) {
  5740. switch(typeof exp) {
  5741. case 'string':
  5742. return cache.hasOwnProperty(exp)
  5743. ? cache[exp]
  5744. : cache[exp] = parser(exp, false, $filter, $sniffer.csp);
  5745. case 'function':
  5746. return exp;
  5747. default:
  5748. return noop;
  5749. }
  5750. };
  5751. }];
  5752. }
  5753. /**
  5754. * @ngdoc service
  5755. * @name ng.$q
  5756. * @requires $rootScope
  5757. *
  5758. * @description
  5759. * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
  5760. *
  5761. * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
  5762. * interface for interacting with an object that represents the result of an action that is
  5763. * performed asynchronously, and may or may not be finished at any given point in time.
  5764. *
  5765. * From the perspective of dealing with error handling, deferred and promise apis are to
  5766. * asynchronous programing what `try`, `catch` and `throw` keywords are to synchronous programing.
  5767. *
  5768. * <pre>
  5769. * // for the purpose of this example let's assume that variables `$q` and `scope` are
  5770. * // available in the current lexical scope (they could have been injected or passed in).
  5771. *
  5772. * function asyncGreet(name) {
  5773. * var deferred = $q.defer();
  5774. *
  5775. * setTimeout(function() {
  5776. * // since this fn executes async in a future turn of the event loop, we need to wrap
  5777. * // our code into an $apply call so that the model changes are properly observed.
  5778. * scope.$apply(function() {
  5779. * if (okToGreet(name)) {
  5780. * deferred.resolve('Hello, ' + name + '!');
  5781. * } else {
  5782. * deferred.reject('Greeting ' + name + ' is not allowed.');
  5783. * }
  5784. * });
  5785. * }, 1000);
  5786. *
  5787. * return deferred.promise;
  5788. * }
  5789. *
  5790. * var promise = asyncGreet('Robin Hood');
  5791. * promise.then(function(greeting) {
  5792. * alert('Success: ' + greeting);
  5793. * }, function(reason) {
  5794. * alert('Failed: ' + reason);
  5795. * });
  5796. * </pre>
  5797. *
  5798. * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
  5799. * comes in the way of
  5800. * [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
  5801. *
  5802. * Additionally the promise api allows for composition that is very hard to do with the
  5803. * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
  5804. * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
  5805. * section on serial or parallel joining of promises.
  5806. *
  5807. *
  5808. * # The Deferred API
  5809. *
  5810. * A new instance of deferred is constructed by calling `$q.defer()`.
  5811. *
  5812. * The purpose of the deferred object is to expose the associated Promise instance as well as apis
  5813. * that can be used for signaling the successful or unsuccessful completion of the task.
  5814. *
  5815. * **Methods**
  5816. *
  5817. * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
  5818. * constructed via `$q.reject`, the promise will be rejected instead.
  5819. * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
  5820. * resolving it with a rejection constructed via `$q.reject`.
  5821. *
  5822. * **Properties**
  5823. *
  5824. * - promise – `{Promise}` – promise object associated with this deferred.
  5825. *
  5826. *
  5827. * # The Promise API
  5828. *
  5829. * A new promise instance is created when a deferred instance is created and can be retrieved by
  5830. * calling `deferred.promise`.
  5831. *
  5832. * The purpose of the promise object is to allow for interested parties to get access to the result
  5833. * of the deferred task when it completes.
  5834. *
  5835. * **Methods**
  5836. *
  5837. * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved
  5838. * or rejected calls one of the success or error callbacks asynchronously as soon as the result
  5839. * is available. The callbacks are called with a single argument the result or rejection reason.
  5840. *
  5841. * This method *returns a new promise* which is resolved or rejected via the return value of the
  5842. * `successCallback` or `errorCallback`.
  5843. *
  5844. *
  5845. * # Chaining promises
  5846. *
  5847. * Because calling `then` api of a promise returns a new derived promise, it is easily possible
  5848. * to create a chain of promises:
  5849. *
  5850. * <pre>
  5851. * promiseB = promiseA.then(function(result) {
  5852. * return result + 1;
  5853. * });
  5854. *
  5855. * // promiseB will be resolved immediately after promiseA is resolved and it's value will be
  5856. * // the result of promiseA incremented by 1
  5857. * </pre>
  5858. *
  5859. * It is possible to create chains of any length and since a promise can be resolved with another
  5860. * promise (which will defer its resolution further), it is possible to pause/defer resolution of
  5861. * the promises at any point in the chain. This makes it possible to implement powerful apis like
  5862. * $http's response interceptors.
  5863. *
  5864. *
  5865. * # Differences between Kris Kowal's Q and $q
  5866. *
  5867. * There are three main differences:
  5868. *
  5869. * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
  5870. * mechanism in angular, which means faster propagation of resolution or rejection into your
  5871. * models and avoiding unnecessary browser repaints, which would result in flickering UI.
  5872. * - $q promises are recognized by the templating engine in angular, which means that in templates
  5873. * you can treat promises attached to a scope as if they were the resulting values.
  5874. * - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains
  5875. * all the important functionality needed for common async tasks.
  5876. */
  5877. function $QProvider() {
  5878. this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
  5879. return qFactory(function(callback) {
  5880. $rootScope.$evalAsync(callback);
  5881. }, $exceptionHandler);
  5882. }];
  5883. }
  5884. /**
  5885. * Constructs a promise manager.
  5886. *
  5887. * @param {function(function)} nextTick Function for executing functions in the next turn.
  5888. * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
  5889. * debugging purposes.
  5890. * @returns {object} Promise manager.
  5891. */
  5892. function qFactory(nextTick, exceptionHandler) {
  5893. /**
  5894. * @ngdoc
  5895. * @name ng.$q#defer
  5896. * @methodOf ng.$q
  5897. * @description
  5898. * Creates a `Deferred` object which represents a task which will finish in the future.
  5899. *
  5900. * @returns {Deferred} Returns a new instance of deferred.
  5901. */
  5902. var defer = function() {
  5903. var pending = [],
  5904. value, deferred;
  5905. deferred = {
  5906. resolve: function(val) {
  5907. if (pending) {
  5908. var callbacks = pending;
  5909. pending = undefined;
  5910. value = ref(val);
  5911. if (callbacks.length) {
  5912. nextTick(function() {
  5913. var callback;
  5914. for (var i = 0, ii = callbacks.length; i < ii; i++) {
  5915. callback = callbacks[i];
  5916. value.then(callback[0], callback[1]);
  5917. }
  5918. });
  5919. }
  5920. }
  5921. },
  5922. reject: function(reason) {
  5923. deferred.resolve(reject(reason));
  5924. },
  5925. promise: {
  5926. then: function(callback, errback) {
  5927. var result = defer();
  5928. var wrappedCallback = function(value) {
  5929. try {
  5930. result.resolve((callback || defaultCallback)(value));
  5931. } catch(e) {
  5932. exceptionHandler(e);
  5933. result.reject(e);
  5934. }
  5935. };
  5936. var wrappedErrback = function(reason) {
  5937. try {
  5938. result.resolve((errback || defaultErrback)(reason));
  5939. } catch(e) {
  5940. exceptionHandler(e);
  5941. result.reject(e);
  5942. }
  5943. };
  5944. if (pending) {
  5945. pending.push([wrappedCallback, wrappedErrback]);
  5946. } else {
  5947. value.then(wrappedCallback, wrappedErrback);
  5948. }
  5949. return result.promise;
  5950. }
  5951. }
  5952. };
  5953. return deferred;
  5954. };
  5955. var ref = function(value) {
  5956. if (value && value.then) return value;
  5957. return {
  5958. then: function(callback) {
  5959. var result = defer();
  5960. nextTick(function() {
  5961. result.resolve(callback(value));
  5962. });
  5963. return result.promise;
  5964. }
  5965. };
  5966. };
  5967. /**
  5968. * @ngdoc
  5969. * @name ng.$q#reject
  5970. * @methodOf ng.$q
  5971. * @description
  5972. * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
  5973. * used to forward rejection in a chain of promises. If you are dealing with the last promise in
  5974. * a promise chain, you don't need to worry about it.
  5975. *
  5976. * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
  5977. * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
  5978. * a promise error callback and you want to forward the error to the promise derived from the
  5979. * current promise, you have to "rethrow" the error by returning a rejection constructed via
  5980. * `reject`.
  5981. *
  5982. * <pre>
  5983. * promiseB = promiseA.then(function(result) {
  5984. * // success: do something and resolve promiseB
  5985. * // with the old or a new result
  5986. * return result;
  5987. * }, function(reason) {
  5988. * // error: handle the error if possible and
  5989. * // resolve promiseB with newPromiseOrValue,
  5990. * // otherwise forward the rejection to promiseB
  5991. * if (canHandle(reason)) {
  5992. * // handle the error and recover
  5993. * return newPromiseOrValue;
  5994. * }
  5995. * return $q.reject(reason);
  5996. * });
  5997. * </pre>
  5998. *
  5999. * @param {*} reason Constant, message, exception or an object representing the rejection reason.
  6000. * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
  6001. */
  6002. var reject = function(reason) {
  6003. return {
  6004. then: function(callback, errback) {
  6005. var result = defer();
  6006. nextTick(function() {
  6007. result.resolve((errback || defaultErrback)(reason));
  6008. });
  6009. return result.promise;
  6010. }
  6011. };
  6012. };
  6013. /**
  6014. * @ngdoc
  6015. * @name ng.$q#when
  6016. * @methodOf ng.$q
  6017. * @description
  6018. * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
  6019. * This is useful when you are dealing with on object that might or might not be a promise, or if
  6020. * the promise comes from a source that can't be trusted.
  6021. *
  6022. * @param {*} value Value or a promise
  6023. * @returns {Promise} Returns a single promise that will be resolved with an array of values,
  6024. * each value coresponding to the promise at the same index in the `promises` array. If any of
  6025. * the promises is resolved with a rejection, this resulting promise will be resolved with the
  6026. * same rejection.
  6027. */
  6028. var when = function(value, callback, errback) {
  6029. var result = defer(),
  6030. done;
  6031. var wrappedCallback = function(value) {
  6032. try {
  6033. return (callback || defaultCallback)(value);
  6034. } catch (e) {
  6035. exceptionHandler(e);
  6036. return reject(e);
  6037. }
  6038. };
  6039. var wrappedErrback = function(reason) {
  6040. try {
  6041. return (errback || defaultErrback)(reason);
  6042. } catch (e) {
  6043. exceptionHandler(e);
  6044. return reject(e);
  6045. }
  6046. };
  6047. nextTick(function() {
  6048. ref(value).then(function(value) {
  6049. if (done) return;
  6050. done = true;
  6051. result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
  6052. }, function(reason) {
  6053. if (done) return;
  6054. done = true;
  6055. result.resolve(wrappedErrback(reason));
  6056. });
  6057. });
  6058. return result.promise;
  6059. };
  6060. function defaultCallback(value) {
  6061. return value;
  6062. }
  6063. function defaultErrback(reason) {
  6064. return reject(reason);
  6065. }
  6066. /**
  6067. * @ngdoc
  6068. * @name ng.$q#all
  6069. * @methodOf ng.$q
  6070. * @description
  6071. * Combines multiple promises into a single promise that is resolved when all of the input
  6072. * promises are resolved.
  6073. *
  6074. * @param {Array.<Promise>} promises An array of promises.
  6075. * @returns {Promise} Returns a single promise that will be resolved with an array of values,
  6076. * each value coresponding to the promise at the same index in the `promises` array. If any of
  6077. * the promises is resolved with a rejection, this resulting promise will be resolved with the
  6078. * same rejection.
  6079. */
  6080. function all(promises) {
  6081. var deferred = defer(),
  6082. counter = promises.length,
  6083. results = [];
  6084. if (counter) {
  6085. forEach(promises, function(promise, index) {
  6086. ref(promise).then(function(value) {
  6087. if (index in results) return;
  6088. results[index] = value;
  6089. if (!(--counter)) deferred.resolve(results);
  6090. }, function(reason) {
  6091. if (index in results) return;
  6092. deferred.reject(reason);
  6093. });
  6094. });
  6095. } else {
  6096. deferred.resolve(results);
  6097. }
  6098. return deferred.promise;
  6099. }
  6100. return {
  6101. defer: defer,
  6102. reject: reject,
  6103. when: when,
  6104. all: all
  6105. };
  6106. }
  6107. /**
  6108. * @ngdoc object
  6109. * @name ng.$routeProvider
  6110. * @function
  6111. *
  6112. * @description
  6113. *
  6114. * Used for configuring routes. See {@link ng.$route $route} for an example.
  6115. */
  6116. function $RouteProvider(){
  6117. var routes = {};
  6118. /**
  6119. * @ngdoc method
  6120. * @name ng.$routeProvider#when
  6121. * @methodOf ng.$routeProvider
  6122. *
  6123. * @param {string} path Route path (matched against `$location.path`). If `$location.path`
  6124. * contains redundant trailing slash or is missing one, the route will still match and the
  6125. * `$location.path` will be updated to add or drop the trailing slash to exacly match the
  6126. * route definition.
  6127. * @param {Object} route Mapping information to be assigned to `$route.current` on route
  6128. * match.
  6129. *
  6130. * Object properties:
  6131. *
  6132. * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly
  6133. * created scope or the name of a {@link angular.Module#controller registered controller}
  6134. * if passed as a string.
  6135. * - `template` – `{string=}` – html template as a string that should be used by
  6136. * {@link ng.directive:ngView ngView} or
  6137. * {@link ng.directive:ngInclude ngInclude} directives.
  6138. * this property takes precedence over `templateUrl`.
  6139. * - `templateUrl` – `{string=}` – path to an html template that should be used by
  6140. * {@link ng.directive:ngView ngView}.
  6141. * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
  6142. * be injected into the controller. If any of these dependencies are promises, they will be
  6143. * resolved and converted to a value before the controller is instantiated and the
  6144. * `$routeChangeSuccess` event is fired. The map object is:
  6145. *
  6146. * - `key` – `{string}`: a name of a dependency to be injected into the controller.
  6147. * - `factory` - `{string|function}`: If `string` then it is an alias for a service.
  6148. * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
  6149. * and the return value is treated as the dependency. If the result is a promise, it is resolved
  6150. * before its value is injected into the controller.
  6151. *
  6152. * - `redirectTo` – {(string|function())=} – value to update
  6153. * {@link ng.$location $location} path with and trigger route redirection.
  6154. *
  6155. * If `redirectTo` is a function, it will be called with the following parameters:
  6156. *
  6157. * - `{Object.<string>}` - route parameters extracted from the current
  6158. * `$location.path()` by applying the current route templateUrl.
  6159. * - `{string}` - current `$location.path()`
  6160. * - `{Object}` - current `$location.search()`
  6161. *
  6162. * The custom `redirectTo` function is expected to return a string which will be used
  6163. * to update `$location.path()` and `$location.search()`.
  6164. *
  6165. * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
  6166. * changes.
  6167. *
  6168. * If the option is set to `false` and url in the browser changes, then
  6169. * `$routeUpdate` event is broadcasted on the root scope.
  6170. *
  6171. * @returns {Object} self
  6172. *
  6173. * @description
  6174. * Adds a new route definition to the `$route` service.
  6175. */
  6176. this.when = function(path, route) {
  6177. routes[path] = extend({reloadOnSearch: true}, route);
  6178. // create redirection for trailing slashes
  6179. if (path) {
  6180. var redirectPath = (path[path.length-1] == '/')
  6181. ? path.substr(0, path.length-1)
  6182. : path +'/';
  6183. routes[redirectPath] = {redirectTo: path};
  6184. }
  6185. return this;
  6186. };
  6187. /**
  6188. * @ngdoc method
  6189. * @name ng.$routeProvider#otherwise
  6190. * @methodOf ng.$routeProvider
  6191. *
  6192. * @description
  6193. * Sets route definition that will be used on route change when no other route definition
  6194. * is matched.
  6195. *
  6196. * @param {Object} params Mapping information to be assigned to `$route.current`.
  6197. * @returns {Object} self
  6198. */
  6199. this.otherwise = function(params) {
  6200. this.when(null, params);
  6201. return this;
  6202. };
  6203. this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache',
  6204. function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) {
  6205. /**
  6206. * @ngdoc object
  6207. * @name ng.$route
  6208. * @requires $location
  6209. * @requires $routeParams
  6210. *
  6211. * @property {Object} current Reference to the current route definition.
  6212. * The route definition contains:
  6213. *
  6214. * - `controller`: The controller constructor as define in route definition.
  6215. * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
  6216. * controller instantiation. The `locals` contain
  6217. * the resolved values of the `resolve` map. Additionally the `locals` also contain:
  6218. *
  6219. * - `$scope` - The current route scope.
  6220. * - `$template` - The current route template HTML.
  6221. *
  6222. * @property {Array.<Object>} routes Array of all configured routes.
  6223. *
  6224. * @description
  6225. * Is used for deep-linking URLs to controllers and views (HTML partials).
  6226. * It watches `$location.url()` and tries to map the path to an existing route definition.
  6227. *
  6228. * You can define routes through {@link ng.$routeProvider $routeProvider}'s API.
  6229. *
  6230. * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView}
  6231. * directive and the {@link ng.$routeParams $routeParams} service.
  6232. *
  6233. * @example
  6234. This example shows how changing the URL hash causes the `$route` to match a route against the
  6235. URL, and the `ngView` pulls in the partial.
  6236. Note that this example is using {@link ng.directive:script inlined templates}
  6237. to get it working on jsfiddle as well.
  6238. <example module="ngView">
  6239. <file name="index.html">
  6240. <div ng-controller="MainCntl">
  6241. Choose:
  6242. <a href="Book/Moby">Moby</a> |
  6243. <a href="Book/Moby/ch/1">Moby: Ch1</a> |
  6244. <a href="Book/Gatsby">Gatsby</a> |
  6245. <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
  6246. <a href="Book/Scarlet">Scarlet Letter</a><br/>
  6247. <div ng-view></div>
  6248. <hr />
  6249. <pre>$location.path() = {{$location.path()}}</pre>
  6250. <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
  6251. <pre>$route.current.params = {{$route.current.params}}</pre>
  6252. <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
  6253. <pre>$routeParams = {{$routeParams}}</pre>
  6254. </div>
  6255. </file>
  6256. <file name="book.html">
  6257. controller: {{name}}<br />
  6258. Book Id: {{params.bookId}}<br />
  6259. </file>
  6260. <file name="chapter.html">
  6261. controller: {{name}}<br />
  6262. Book Id: {{params.bookId}}<br />
  6263. Chapter Id: {{params.chapterId}}
  6264. </file>
  6265. <file name="script.js">
  6266. angular.module('ngView', [], function($routeProvider, $locationProvider) {
  6267. $routeProvider.when('/Book/:bookId', {
  6268. templateUrl: 'book.html',
  6269. controller: BookCntl,
  6270. resolve: {
  6271. // I will cause a 1 second delay
  6272. delay: function($q, $timeout) {
  6273. var delay = $q.defer();
  6274. $timeout(delay.resolve, 1000);
  6275. return delay.promise;
  6276. }
  6277. }
  6278. });
  6279. $routeProvider.when('/Book/:bookId/ch/:chapterId', {
  6280. templateUrl: 'chapter.html',
  6281. controller: ChapterCntl
  6282. });
  6283. // configure html5 to get links working on jsfiddle
  6284. $locationProvider.html5Mode(true);
  6285. });
  6286. function MainCntl($scope, $route, $routeParams, $location) {
  6287. $scope.$route = $route;
  6288. $scope.$location = $location;
  6289. $scope.$routeParams = $routeParams;
  6290. }
  6291. function BookCntl($scope, $routeParams) {
  6292. $scope.name = "BookCntl";
  6293. $scope.params = $routeParams;
  6294. }
  6295. function ChapterCntl($scope, $routeParams) {
  6296. $scope.name = "ChapterCntl";
  6297. $scope.params = $routeParams;
  6298. }
  6299. </file>
  6300. <file name="scenario.js">
  6301. it('should load and compile correct template', function() {
  6302. element('a:contains("Moby: Ch1")').click();
  6303. var content = element('.doc-example-live [ng-view]').text();
  6304. expect(content).toMatch(/controller\: ChapterCntl/);
  6305. expect(content).toMatch(/Book Id\: Moby/);
  6306. expect(content).toMatch(/Chapter Id\: 1/);
  6307. element('a:contains("Scarlet")').click();
  6308. sleep(2); // promises are not part of scenario waiting
  6309. content = element('.doc-example-live [ng-view]').text();
  6310. expect(content).toMatch(/controller\: BookCntl/);
  6311. expect(content).toMatch(/Book Id\: Scarlet/);
  6312. });
  6313. </file>
  6314. </example>
  6315. */
  6316. /**
  6317. * @ngdoc event
  6318. * @name ng.$route#$routeChangeStart
  6319. * @eventOf ng.$route
  6320. * @eventType broadcast on root scope
  6321. * @description
  6322. * Broadcasted before a route change. At this point the route services starts
  6323. * resolving all of the dependencies needed for the route change to occurs.
  6324. * Typically this involves fetching the view template as well as any dependencies
  6325. * defined in `resolve` route property. Once all of the dependencies are resolved
  6326. * `$routeChangeSuccess` is fired.
  6327. *
  6328. * @param {Route} next Future route information.
  6329. * @param {Route} current Current route information.
  6330. */
  6331. /**
  6332. * @ngdoc event
  6333. * @name ng.$route#$routeChangeSuccess
  6334. * @eventOf ng.$route
  6335. * @eventType broadcast on root scope
  6336. * @description
  6337. * Broadcasted after a route dependencies are resolved.
  6338. * {@link ng.directive:ngView ngView} listens for the directive
  6339. * to instantiate the controller and render the view.
  6340. *
  6341. * @param {Route} current Current route information.
  6342. * @param {Route} previous Previous route information.
  6343. */
  6344. /**
  6345. * @ngdoc event
  6346. * @name ng.$route#$routeChangeError
  6347. * @eventOf ng.$route
  6348. * @eventType broadcast on root scope
  6349. * @description
  6350. * Broadcasted if any of the resolve promises are rejected.
  6351. *
  6352. * @param {Route} current Current route information.
  6353. * @param {Route} previous Previous route information.
  6354. * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
  6355. */
  6356. /**
  6357. * @ngdoc event
  6358. * @name ng.$route#$routeUpdate
  6359. * @eventOf ng.$route
  6360. * @eventType broadcast on root scope
  6361. * @description
  6362. *
  6363. * The `reloadOnSearch` property has been set to false, and we are reusing the same
  6364. * instance of the Controller.
  6365. */
  6366. var matcher = switchRouteMatcher,
  6367. forceReload = false,
  6368. $route = {
  6369. routes: routes,
  6370. /**
  6371. * @ngdoc method
  6372. * @name ng.$route#reload
  6373. * @methodOf ng.$route
  6374. *
  6375. * @description
  6376. * Causes `$route` service to reload the current route even if
  6377. * {@link ng.$location $location} hasn't changed.
  6378. *
  6379. * As a result of that, {@link ng.directive:ngView ngView}
  6380. * creates new scope, reinstantiates the controller.
  6381. */
  6382. reload: function() {
  6383. forceReload = true;
  6384. $rootScope.$evalAsync(updateRoute);
  6385. }
  6386. };
  6387. $rootScope.$on('$locationChangeSuccess', updateRoute);
  6388. return $route;
  6389. /////////////////////////////////////////////////////
  6390. function switchRouteMatcher(on, when) {
  6391. // TODO(i): this code is convoluted and inefficient, we should construct the route matching
  6392. // regex only once and then reuse it
  6393. var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
  6394. params = [],
  6395. dst = {};
  6396. forEach(when.split(/\W/), function(param) {
  6397. if (param) {
  6398. var paramRegExp = new RegExp(":" + param + "([\\W])");
  6399. if (regex.match(paramRegExp)) {
  6400. regex = regex.replace(paramRegExp, "([^\\/]*)$1");
  6401. params.push(param);
  6402. }
  6403. }
  6404. });
  6405. var match = on.match(new RegExp(regex));
  6406. if (match) {
  6407. forEach(params, function(name, index) {
  6408. dst[name] = match[index + 1];
  6409. });
  6410. }
  6411. return match ? dst : null;
  6412. }
  6413. function updateRoute() {
  6414. var next = parseRoute(),
  6415. last = $route.current;
  6416. if (next && last && next.$route === last.$route
  6417. && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
  6418. last.params = next.params;
  6419. copy(last.params, $routeParams);
  6420. $rootScope.$broadcast('$routeUpdate', last);
  6421. } else if (next || last) {
  6422. forceReload = false;
  6423. $rootScope.$broadcast('$routeChangeStart', next, last);
  6424. $route.current = next;
  6425. if (next) {
  6426. if (next.redirectTo) {
  6427. if (isString(next.redirectTo)) {
  6428. $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
  6429. .replace();
  6430. } else {
  6431. $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
  6432. .replace();
  6433. }
  6434. }
  6435. }
  6436. $q.when(next).
  6437. then(function() {
  6438. if (next) {
  6439. var keys = [],
  6440. values = [],
  6441. template;
  6442. forEach(next.resolve || {}, function(value, key) {
  6443. keys.push(key);
  6444. values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
  6445. });
  6446. if (isDefined(template = next.template)) {
  6447. } else if (isDefined(template = next.templateUrl)) {
  6448. template = $http.get(template, {cache: $templateCache}).
  6449. then(function(response) { return response.data; });
  6450. }
  6451. if (isDefined(template)) {
  6452. keys.push('$template');
  6453. values.push(template);
  6454. }
  6455. return $q.all(values).then(function(values) {
  6456. var locals = {};
  6457. forEach(values, function(value, index) {
  6458. locals[keys[index]] = value;
  6459. });
  6460. return locals;
  6461. });
  6462. }
  6463. }).
  6464. // after route change
  6465. then(function(locals) {
  6466. if (next == $route.current) {
  6467. if (next) {
  6468. next.locals = locals;
  6469. copy(next.params, $routeParams);
  6470. }
  6471. $rootScope.$broadcast('$routeChangeSuccess', next, last);
  6472. }
  6473. }, function(error) {
  6474. if (next == $route.current) {
  6475. $rootScope.$broadcast('$routeChangeError', next, last, error);
  6476. }
  6477. });
  6478. }
  6479. }
  6480. /**
  6481. * @returns the current active route, by matching it against the URL
  6482. */
  6483. function parseRoute() {
  6484. // Match a route
  6485. var params, match;
  6486. forEach(routes, function(route, path) {
  6487. if (!match && (params = matcher($location.path(), path))) {
  6488. match = inherit(route, {
  6489. params: extend({}, $location.search(), params),
  6490. pathParams: params});
  6491. match.$route = route;
  6492. }
  6493. });
  6494. // No route matched; fallback to "otherwise" route
  6495. return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
  6496. }
  6497. /**
  6498. * @returns interpolation of the redirect path with the parametrs
  6499. */
  6500. function interpolate(string, params) {
  6501. var result = [];
  6502. forEach((string||'').split(':'), function(segment, i) {
  6503. if (i == 0) {
  6504. result.push(segment);
  6505. } else {
  6506. var segmentMatch = segment.match(/(\w+)(.*)/);
  6507. var key = segmentMatch[1];
  6508. result.push(params[key]);
  6509. result.push(segmentMatch[2] || '');
  6510. delete params[key];
  6511. }
  6512. });
  6513. return result.join('');
  6514. }
  6515. }];
  6516. }
  6517. /**
  6518. * @ngdoc object
  6519. * @name ng.$routeParams
  6520. * @requires $route
  6521. *
  6522. * @description
  6523. * Current set of route parameters. The route parameters are a combination of the
  6524. * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters
  6525. * are extracted when the {@link ng.$route $route} path is matched.
  6526. *
  6527. * In case of parameter name collision, `path` params take precedence over `search` params.
  6528. *
  6529. * The service guarantees that the identity of the `$routeParams` object will remain unchanged
  6530. * (but its properties will likely change) even when a route change occurs.
  6531. *
  6532. * @example
  6533. * <pre>
  6534. * // Given:
  6535. * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
  6536. * // Route: /Chapter/:chapterId/Section/:sectionId
  6537. * //
  6538. * // Then
  6539. * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
  6540. * </pre>
  6541. */
  6542. function $RouteParamsProvider() {
  6543. this.$get = valueFn({});
  6544. }
  6545. /**
  6546. * DESIGN NOTES
  6547. *
  6548. * The design decisions behind the scope ware heavily favored for speed and memory consumption.
  6549. *
  6550. * The typical use of scope is to watch the expressions, which most of the time return the same
  6551. * value as last time so we optimize the operation.
  6552. *
  6553. * Closures construction is expensive from speed as well as memory:
  6554. * - no closures, instead ups prototypical inheritance for API
  6555. * - Internal state needs to be stored on scope directly, which means that private state is
  6556. * exposed as $$____ properties
  6557. *
  6558. * Loop operations are optimized by using while(count--) { ... }
  6559. * - this means that in order to keep the same order of execution as addition we have to add
  6560. * items to the array at the begging (shift) instead of at the end (push)
  6561. *
  6562. * Child scopes are created and removed often
  6563. * - Using array would be slow since inserts in meddle are expensive so we use linked list
  6564. *
  6565. * There are few watches then a lot of observers. This is why you don't want the observer to be
  6566. * implemented in the same way as watch. Watch requires return of initialization function which
  6567. * are expensive to construct.
  6568. */
  6569. /**
  6570. * @ngdoc object
  6571. * @name ng.$rootScopeProvider
  6572. * @description
  6573. *
  6574. * Provider for the $rootScope service.
  6575. */
  6576. /**
  6577. * @ngdoc function
  6578. * @name ng.$rootScopeProvider#digestTtl
  6579. * @methodOf ng.$rootScopeProvider
  6580. * @description
  6581. *
  6582. * Sets the number of digest iteration the scope should attempt to execute before giving up and
  6583. * assuming that the model is unstable.
  6584. *
  6585. * The current default is 10 iterations.
  6586. *
  6587. * @param {number} limit The number of digest iterations.
  6588. */
  6589. /**
  6590. * @ngdoc object
  6591. * @name ng.$rootScope
  6592. * @description
  6593. *
  6594. * Every application has a single root {@link ng.$rootScope.Scope scope}.
  6595. * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide
  6596. * event processing life-cycle. See {@link guide/scope developer guide on scopes}.
  6597. */
  6598. function $RootScopeProvider(){
  6599. var TTL = 10;
  6600. this.digestTtl = function(value) {
  6601. if (arguments.length) {
  6602. TTL = value;
  6603. }
  6604. return TTL;
  6605. };
  6606. this.$get = ['$injector', '$exceptionHandler', '$parse',
  6607. function( $injector, $exceptionHandler, $parse) {
  6608. /**
  6609. * @ngdoc function
  6610. * @name ng.$rootScope.Scope
  6611. *
  6612. * @description
  6613. * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
  6614. * {@link AUTO.$injector $injector}. Child scopes are created using the
  6615. * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
  6616. * compiled HTML template is executed.)
  6617. *
  6618. * Here is a simple scope snippet to show how you can interact with the scope.
  6619. * <pre>
  6620. angular.injector(['ng']).invoke(function($rootScope) {
  6621. var scope = $rootScope.$new();
  6622. scope.salutation = 'Hello';
  6623. scope.name = 'World';
  6624. expect(scope.greeting).toEqual(undefined);
  6625. scope.$watch('name', function() {
  6626. this.greeting = this.salutation + ' ' + this.name + '!';
  6627. }); // initialize the watch
  6628. expect(scope.greeting).toEqual(undefined);
  6629. scope.name = 'Misko';
  6630. // still old value, since watches have not been called yet
  6631. expect(scope.greeting).toEqual(undefined);
  6632. scope.$digest(); // fire all the watches
  6633. expect(scope.greeting).toEqual('Hello Misko!');
  6634. });
  6635. * </pre>
  6636. *
  6637. * # Inheritance
  6638. * A scope can inherit from a parent scope, as in this example:
  6639. * <pre>
  6640. var parent = $rootScope;
  6641. var child = parent.$new();
  6642. parent.salutation = "Hello";
  6643. child.name = "World";
  6644. expect(child.salutation).toEqual('Hello');
  6645. child.salutation = "Welcome";
  6646. expect(child.salutation).toEqual('Welcome');
  6647. expect(parent.salutation).toEqual('Hello');
  6648. * </pre>
  6649. *
  6650. *
  6651. * @param {Object.<string, function()>=} providers Map of service factory which need to be provided
  6652. * for the current scope. Defaults to {@link ng}.
  6653. * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
  6654. * append/override services provided by `providers`. This is handy when unit-testing and having
  6655. * the need to override a default service.
  6656. * @returns {Object} Newly created scope.
  6657. *
  6658. */
  6659. function Scope() {
  6660. this.$id = nextUid();
  6661. this.$$phase = this.$parent = this.$$watchers =
  6662. this.$$nextSibling = this.$$prevSibling =
  6663. this.$$childHead = this.$$childTail = null;
  6664. this['this'] = this.$root = this;
  6665. this.$$asyncQueue = [];
  6666. this.$$listeners = {};
  6667. }
  6668. /**
  6669. * @ngdoc property
  6670. * @name ng.$rootScope.Scope#$id
  6671. * @propertyOf ng.$rootScope.Scope
  6672. * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
  6673. * debugging.
  6674. */
  6675. Scope.prototype = {
  6676. /**
  6677. * @ngdoc function
  6678. * @name ng.$rootScope.Scope#$new
  6679. * @methodOf ng.$rootScope.Scope
  6680. * @function
  6681. *
  6682. * @description
  6683. * Creates a new child {@link ng.$rootScope.Scope scope}.
  6684. *
  6685. * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and
  6686. * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope
  6687. * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
  6688. *
  6689. * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for
  6690. * the scope and its child scopes to be permanently detached from the parent and thus stop
  6691. * participating in model change detection and listener notification by invoking.
  6692. *
  6693. * @param {boolean} isolate if true then the scope does not prototypically inherit from the
  6694. * parent scope. The scope is isolated, as it can not see parent scope properties.
  6695. * When creating widgets it is useful for the widget to not accidentally read parent
  6696. * state.
  6697. *
  6698. * @returns {Object} The newly created child scope.
  6699. *
  6700. */
  6701. $new: function(isolate) {
  6702. var Child,
  6703. child;
  6704. if (isFunction(isolate)) {
  6705. // TODO: remove at some point
  6706. throw Error('API-CHANGE: Use $controller to instantiate controllers.');
  6707. }
  6708. if (isolate) {
  6709. child = new Scope();
  6710. child.$root = this.$root;
  6711. } else {
  6712. Child = function() {}; // should be anonymous; This is so that when the minifier munges
  6713. // the name it does not become random set of chars. These will then show up as class
  6714. // name in the debugger.
  6715. Child.prototype = this;
  6716. child = new Child();
  6717. child.$id = nextUid();
  6718. }
  6719. child['this'] = child;
  6720. child.$$listeners = {};
  6721. child.$parent = this;
  6722. child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
  6723. child.$$prevSibling = this.$$childTail;
  6724. if (this.$$childHead) {
  6725. this.$$childTail.$$nextSibling = child;
  6726. this.$$childTail = child;
  6727. } else {
  6728. this.$$childHead = this.$$childTail = child;
  6729. }
  6730. return child;
  6731. },
  6732. /**
  6733. * @ngdoc function
  6734. * @name ng.$rootScope.Scope#$watch
  6735. * @methodOf ng.$rootScope.Scope
  6736. * @function
  6737. *
  6738. * @description
  6739. * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
  6740. *
  6741. * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and
  6742. * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()}
  6743. * reruns when it detects changes the `watchExpression` can execute multiple times per
  6744. * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
  6745. * - The `listener` is called only when the value from the current `watchExpression` and the
  6746. * previous call to `watchExpression` are not equal (with the exception of the initial run,
  6747. * see below). The inequality is determined according to
  6748. * {@link angular.equals} function. To save the value of the object for later comparison, the
  6749. * {@link angular.copy} function is used. It also means that watching complex options will
  6750. * have adverse memory and performance implications.
  6751. * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
  6752. * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
  6753. * limit is 10 to prevent an infinite loop deadlock.
  6754. *
  6755. *
  6756. * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
  6757. * you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
  6758. * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is
  6759. * detected, be prepared for multiple calls to your listener.)
  6760. *
  6761. * After a watcher is registered with the scope, the `listener` fn is called asynchronously
  6762. * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
  6763. * watcher. In rare cases, this is undesirable because the listener is called when the result
  6764. * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
  6765. * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
  6766. * listener was called due to initialization.
  6767. *
  6768. *
  6769. * # Example
  6770. * <pre>
  6771. // let's assume that scope was dependency injected as the $rootScope
  6772. var scope = $rootScope;
  6773. scope.name = 'misko';
  6774. scope.counter = 0;
  6775. expect(scope.counter).toEqual(0);
  6776. scope.$watch('name', function(newValue, oldValue) { counter = counter + 1; });
  6777. expect(scope.counter).toEqual(0);
  6778. scope.$digest();
  6779. // no variable change
  6780. expect(scope.counter).toEqual(0);
  6781. scope.name = 'adam';
  6782. scope.$digest();
  6783. expect(scope.counter).toEqual(1);
  6784. * </pre>
  6785. *
  6786. *
  6787. *
  6788. * @param {(function()|string)} watchExpression Expression that is evaluated on each
  6789. * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a
  6790. * call to the `listener`.
  6791. *
  6792. * - `string`: Evaluated as {@link guide/expression expression}
  6793. * - `function(scope)`: called with current `scope` as a parameter.
  6794. * @param {(function()|string)=} listener Callback called whenever the return value of
  6795. * the `watchExpression` changes.
  6796. *
  6797. * - `string`: Evaluated as {@link guide/expression expression}
  6798. * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
  6799. *
  6800. * @param {boolean=} objectEquality Compare object for equality rather than for reference.
  6801. * @returns {function()} Returns a deregistration function for this listener.
  6802. */
  6803. $watch: function(watchExp, listener, objectEquality) {
  6804. var scope = this,
  6805. get = compileToFn(watchExp, 'watch'),
  6806. array = scope.$$watchers,
  6807. watcher = {
  6808. fn: listener,
  6809. last: initWatchVal,
  6810. get: get,
  6811. exp: watchExp,
  6812. eq: !!objectEquality
  6813. };
  6814. // in the case user pass string, we need to compile it, do we really need this ?
  6815. if (!isFunction(listener)) {
  6816. var listenFn = compileToFn(listener || noop, 'listener');
  6817. watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
  6818. }
  6819. if (!array) {
  6820. array = scope.$$watchers = [];
  6821. }
  6822. // we use unshift since we use a while loop in $digest for speed.
  6823. // the while loop reads in reverse order.
  6824. array.unshift(watcher);
  6825. return function() {
  6826. arrayRemove(array, watcher);
  6827. };
  6828. },
  6829. /**
  6830. * @ngdoc function
  6831. * @name ng.$rootScope.Scope#$digest
  6832. * @methodOf ng.$rootScope.Scope
  6833. * @function
  6834. *
  6835. * @description
  6836. * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children.
  6837. * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the
  6838. * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are
  6839. * firing. This means that it is possible to get into an infinite loop. This function will throw
  6840. * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10.
  6841. *
  6842. * Usually you don't call `$digest()` directly in
  6843. * {@link ng.directive:ngController controllers} or in
  6844. * {@link ng.$compileProvider#directive directives}.
  6845. * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a
  6846. * {@link ng.$compileProvider#directive directives}) will force a `$digest()`.
  6847. *
  6848. * If you want to be notified whenever `$digest()` is called,
  6849. * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()}
  6850. * with no `listener`.
  6851. *
  6852. * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
  6853. * life-cycle.
  6854. *
  6855. * # Example
  6856. * <pre>
  6857. var scope = ...;
  6858. scope.name = 'misko';
  6859. scope.counter = 0;
  6860. expect(scope.counter).toEqual(0);
  6861. scope.$watch('name', function(newValue, oldValue) {
  6862. counter = counter + 1;
  6863. });
  6864. expect(scope.counter).toEqual(0);
  6865. scope.$digest();
  6866. // no variable change
  6867. expect(scope.counter).toEqual(0);
  6868. scope.name = 'adam';
  6869. scope.$digest();
  6870. expect(scope.counter).toEqual(1);
  6871. * </pre>
  6872. *
  6873. */
  6874. $digest: function() {
  6875. var watch, value, last,
  6876. watchers,
  6877. asyncQueue = this.$$asyncQueue,
  6878. length,
  6879. dirty, ttl = TTL,
  6880. next, current, target = this,
  6881. watchLog = [],
  6882. logIdx, logMsg;
  6883. beginPhase('$digest');
  6884. do { // "while dirty" loop
  6885. dirty = false;
  6886. current = target;
  6887. while(asyncQueue.length) {
  6888. try {
  6889. current.$eval(asyncQueue.shift());
  6890. } catch (e) {
  6891. $exceptionHandler(e);
  6892. }
  6893. }
  6894. do { // "traverse the scopes" loop
  6895. if ((watchers = current.$$watchers)) {
  6896. // process our watches
  6897. length = watchers.length;
  6898. while (length--) {
  6899. try {
  6900. watch = watchers[length];
  6901. // Most common watches are on primitives, in which case we can short
  6902. // circuit it with === operator, only when === fails do we use .equals
  6903. if ((value = watch.get(current)) !== (last = watch.last) &&
  6904. !(watch.eq
  6905. ? equals(value, last)
  6906. : (typeof value == 'number' && typeof last == 'number'
  6907. && isNaN(value) && isNaN(last)))) {
  6908. dirty = true;
  6909. watch.last = watch.eq ? copy(value) : value;
  6910. watch.fn(value, ((last === initWatchVal) ? value : last), current);
  6911. if (ttl < 5) {
  6912. logIdx = 4 - ttl;
  6913. if (!watchLog[logIdx]) watchLog[logIdx] = [];
  6914. logMsg = (isFunction(watch.exp))
  6915. ? 'fn: ' + (watch.exp.name || watch.exp.toString())
  6916. : watch.exp;
  6917. logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
  6918. watchLog[logIdx].push(logMsg);
  6919. }
  6920. }
  6921. } catch (e) {
  6922. $exceptionHandler(e);
  6923. }
  6924. }
  6925. }
  6926. // Insanity Warning: scope depth-first traversal
  6927. // yes, this code is a bit crazy, but it works and we have tests to prove it!
  6928. // this piece should be kept in sync with the traversal in $broadcast
  6929. if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
  6930. while(current !== target && !(next = current.$$nextSibling)) {
  6931. current = current.$parent;
  6932. }
  6933. }
  6934. } while ((current = next));
  6935. if(dirty && !(ttl--)) {
  6936. clearPhase();
  6937. throw Error(TTL + ' $digest() iterations reached. Aborting!\n' +
  6938. 'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
  6939. }
  6940. } while (dirty || asyncQueue.length);
  6941. clearPhase();
  6942. },
  6943. /**
  6944. * @ngdoc event
  6945. * @name ng.$rootScope.Scope#$destroy
  6946. * @eventOf ng.$rootScope.Scope
  6947. * @eventType broadcast on scope being destroyed
  6948. *
  6949. * @description
  6950. * Broadcasted when a scope and its children are being destroyed.
  6951. */
  6952. /**
  6953. * @ngdoc function
  6954. * @name ng.$rootScope.Scope#$destroy
  6955. * @methodOf ng.$rootScope.Scope
  6956. * @function
  6957. *
  6958. * @description
  6959. * Removes the current scope (and all of its children) from the parent scope. Removal implies
  6960. * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
  6961. * propagate to the current scope and its children. Removal also implies that the current
  6962. * scope is eligible for garbage collection.
  6963. *
  6964. * The `$destroy()` is usually used by directives such as
  6965. * {@link ng.directive:ngRepeat ngRepeat} for managing the
  6966. * unrolling of the loop.
  6967. *
  6968. * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope.
  6969. * Application code can register a `$destroy` event handler that will give it chance to
  6970. * perform any necessary cleanup.
  6971. */
  6972. $destroy: function() {
  6973. if ($rootScope == this) return; // we can't remove the root node;
  6974. var parent = this.$parent;
  6975. this.$broadcast('$destroy');
  6976. if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
  6977. if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
  6978. if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
  6979. if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
  6980. // This is bogus code that works around Chrome's GC leak
  6981. // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
  6982. this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
  6983. this.$$childTail = null;
  6984. },
  6985. /**
  6986. * @ngdoc function
  6987. * @name ng.$rootScope.Scope#$eval
  6988. * @methodOf ng.$rootScope.Scope
  6989. * @function
  6990. *
  6991. * @description
  6992. * Executes the `expression` on the current scope returning the result. Any exceptions in the
  6993. * expression are propagated (uncaught). This is useful when evaluating Angular expressions.
  6994. *
  6995. * # Example
  6996. * <pre>
  6997. var scope = ng.$rootScope.Scope();
  6998. scope.a = 1;
  6999. scope.b = 2;
  7000. expect(scope.$eval('a+b')).toEqual(3);
  7001. expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
  7002. * </pre>
  7003. *
  7004. * @param {(string|function())=} expression An angular expression to be executed.
  7005. *
  7006. * - `string`: execute using the rules as defined in {@link guide/expression expression}.
  7007. * - `function(scope)`: execute the function with the current `scope` parameter.
  7008. *
  7009. * @returns {*} The result of evaluating the expression.
  7010. */
  7011. $eval: function(expr, locals) {
  7012. return $parse(expr)(this, locals);
  7013. },
  7014. /**
  7015. * @ngdoc function
  7016. * @name ng.$rootScope.Scope#$evalAsync
  7017. * @methodOf ng.$rootScope.Scope
  7018. * @function
  7019. *
  7020. * @description
  7021. * Executes the expression on the current scope at a later point in time.
  7022. *
  7023. * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
  7024. *
  7025. * - it will execute in the current script execution context (before any DOM rendering).
  7026. * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
  7027. * `expression` execution.
  7028. *
  7029. * Any exceptions from the execution of the expression are forwarded to the
  7030. * {@link ng.$exceptionHandler $exceptionHandler} service.
  7031. *
  7032. * @param {(string|function())=} expression An angular expression to be executed.
  7033. *
  7034. * - `string`: execute using the rules as defined in {@link guide/expression expression}.
  7035. * - `function(scope)`: execute the function with the current `scope` parameter.
  7036. *
  7037. */
  7038. $evalAsync: function(expr) {
  7039. this.$$asyncQueue.push(expr);
  7040. },
  7041. /**
  7042. * @ngdoc function
  7043. * @name ng.$rootScope.Scope#$apply
  7044. * @methodOf ng.$rootScope.Scope
  7045. * @function
  7046. *
  7047. * @description
  7048. * `$apply()` is used to execute an expression in angular from outside of the angular framework.
  7049. * (For example from browser DOM events, setTimeout, XHR or third party libraries).
  7050. * Because we are calling into the angular framework we need to perform proper scope life-cycle
  7051. * of {@link ng.$exceptionHandler exception handling},
  7052. * {@link ng.$rootScope.Scope#$digest executing watches}.
  7053. *
  7054. * ## Life cycle
  7055. *
  7056. * # Pseudo-Code of `$apply()`
  7057. * <pre>
  7058. function $apply(expr) {
  7059. try {
  7060. return $eval(expr);
  7061. } catch (e) {
  7062. $exceptionHandler(e);
  7063. } finally {
  7064. $root.$digest();
  7065. }
  7066. }
  7067. * </pre>
  7068. *
  7069. *
  7070. * Scope's `$apply()` method transitions through the following stages:
  7071. *
  7072. * 1. The {@link guide/expression expression} is executed using the
  7073. * {@link ng.$rootScope.Scope#$eval $eval()} method.
  7074. * 2. Any exceptions from the execution of the expression are forwarded to the
  7075. * {@link ng.$exceptionHandler $exceptionHandler} service.
  7076. * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression
  7077. * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
  7078. *
  7079. *
  7080. * @param {(string|function())=} exp An angular expression to be executed.
  7081. *
  7082. * - `string`: execute using the rules as defined in {@link guide/expression expression}.
  7083. * - `function(scope)`: execute the function with current `scope` parameter.
  7084. *
  7085. * @returns {*} The result of evaluating the expression.
  7086. */
  7087. $apply: function(expr) {
  7088. try {
  7089. beginPhase('$apply');
  7090. return this.$eval(expr);
  7091. } catch (e) {
  7092. $exceptionHandler(e);
  7093. } finally {
  7094. clearPhase();
  7095. try {
  7096. $rootScope.$digest();
  7097. } catch (e) {
  7098. $exceptionHandler(e);
  7099. throw e;
  7100. }
  7101. }
  7102. },
  7103. /**
  7104. * @ngdoc function
  7105. * @name ng.$rootScope.Scope#$on
  7106. * @methodOf ng.$rootScope.Scope
  7107. * @function
  7108. *
  7109. * @description
  7110. * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
  7111. * event life cycle.
  7112. *
  7113. * @param {string} name Event name to listen on.
  7114. * @param {function(event)} listener Function to call when the event is emitted.
  7115. * @returns {function()} Returns a deregistration function for this listener.
  7116. *
  7117. * The event listener function format is: `function(event, args...)`. The `event` object
  7118. * passed into the listener has the following attributes:
  7119. *
  7120. * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
  7121. * - `currentScope` - `{Scope}`: the current scope which is handling the event.
  7122. * - `name` - `{string}`: Name of the event.
  7123. * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event
  7124. * propagation (available only for events that were `$emit`-ed).
  7125. * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
  7126. * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
  7127. */
  7128. $on: function(name, listener) {
  7129. var namedListeners = this.$$listeners[name];
  7130. if (!namedListeners) {
  7131. this.$$listeners[name] = namedListeners = [];
  7132. }
  7133. namedListeners.push(listener);
  7134. return function() {
  7135. namedListeners[indexOf(namedListeners, listener)] = null;
  7136. };
  7137. },
  7138. /**
  7139. * @ngdoc function
  7140. * @name ng.$rootScope.Scope#$emit
  7141. * @methodOf ng.$rootScope.Scope
  7142. * @function
  7143. *
  7144. * @description
  7145. * Dispatches an event `name` upwards through the scope hierarchy notifying the
  7146. * registered {@link ng.$rootScope.Scope#$on} listeners.
  7147. *
  7148. * The event life cycle starts at the scope on which `$emit` was called. All
  7149. * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
  7150. * Afterwards, the event traverses upwards toward the root scope and calls all registered
  7151. * listeners along the way. The event will stop propagating if one of the listeners cancels it.
  7152. *
  7153. * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
  7154. * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
  7155. *
  7156. * @param {string} name Event name to emit.
  7157. * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
  7158. * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
  7159. */
  7160. $emit: function(name, args) {
  7161. var empty = [],
  7162. namedListeners,
  7163. scope = this,
  7164. stopPropagation = false,
  7165. event = {
  7166. name: name,
  7167. targetScope: scope,
  7168. stopPropagation: function() {stopPropagation = true;},
  7169. preventDefault: function() {
  7170. event.defaultPrevented = true;
  7171. },
  7172. defaultPrevented: false
  7173. },
  7174. listenerArgs = concat([event], arguments, 1),
  7175. i, length;
  7176. do {
  7177. namedListeners = scope.$$listeners[name] || empty;
  7178. event.currentScope = scope;
  7179. for (i=0, length=namedListeners.length; i<length; i++) {
  7180. // if listeners were deregistered, defragment the array
  7181. if (!namedListeners[i]) {
  7182. namedListeners.splice(i, 1);
  7183. i--;
  7184. length--;
  7185. continue;
  7186. }
  7187. try {
  7188. namedListeners[i].apply(null, listenerArgs);
  7189. if (stopPropagation) return event;
  7190. } catch (e) {
  7191. $exceptionHandler(e);
  7192. }
  7193. }
  7194. //traverse upwards
  7195. scope = scope.$parent;
  7196. } while (scope);
  7197. return event;
  7198. },
  7199. /**
  7200. * @ngdoc function
  7201. * @name ng.$rootScope.Scope#$broadcast
  7202. * @methodOf ng.$rootScope.Scope
  7203. * @function
  7204. *
  7205. * @description
  7206. * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
  7207. * registered {@link ng.$rootScope.Scope#$on} listeners.
  7208. *
  7209. * The event life cycle starts at the scope on which `$broadcast` was called. All
  7210. * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
  7211. * Afterwards, the event propagates to all direct and indirect scopes of the current scope and
  7212. * calls all registered listeners along the way. The event cannot be canceled.
  7213. *
  7214. * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
  7215. * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
  7216. *
  7217. * @param {string} name Event name to emit.
  7218. * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
  7219. * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
  7220. */
  7221. $broadcast: function(name, args) {
  7222. var target = this,
  7223. current = target,
  7224. next = target,
  7225. event = {
  7226. name: name,
  7227. targetScope: target,
  7228. preventDefault: function() {
  7229. event.defaultPrevented = true;
  7230. },
  7231. defaultPrevented: false
  7232. },
  7233. listenerArgs = concat([event], arguments, 1),
  7234. listeners, i, length;
  7235. //down while you can, then up and next sibling or up and next sibling until back at root
  7236. do {
  7237. current = next;
  7238. event.currentScope = current;
  7239. listeners = current.$$listeners[name] || [];
  7240. for (i=0, length = listeners.length; i<length; i++) {
  7241. // if listeners were deregistered, defragment the array
  7242. if (!listeners[i]) {
  7243. listeners.splice(i, 1);
  7244. i--;
  7245. length--;
  7246. continue;
  7247. }
  7248. try {
  7249. listeners[i].apply(null, listenerArgs);
  7250. } catch(e) {
  7251. $exceptionHandler(e);
  7252. }
  7253. }
  7254. // Insanity Warning: scope depth-first traversal
  7255. // yes, this code is a bit crazy, but it works and we have tests to prove it!
  7256. // this piece should be kept in sync with the traversal in $digest
  7257. if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
  7258. while(current !== target && !(next = current.$$nextSibling)) {
  7259. current = current.$parent;
  7260. }
  7261. }
  7262. } while ((current = next));
  7263. return event;
  7264. }
  7265. };
  7266. var $rootScope = new Scope();
  7267. return $rootScope;
  7268. function beginPhase(phase) {
  7269. if ($rootScope.$$phase) {
  7270. throw Error($rootScope.$$phase + ' already in progress');
  7271. }
  7272. $rootScope.$$phase = phase;
  7273. }
  7274. function clearPhase() {
  7275. $rootScope.$$phase = null;
  7276. }
  7277. function compileToFn(exp, name) {
  7278. var fn = $parse(exp);
  7279. assertArgFn(fn, name);
  7280. return fn;
  7281. }
  7282. /**
  7283. * function used as an initial value for watchers.
  7284. * because it's uniqueue we can easily tell it apart from other values
  7285. */
  7286. function initWatchVal() {}
  7287. }];
  7288. }
  7289. /**
  7290. * !!! This is an undocumented "private" service !!!
  7291. *
  7292. * @name ng.$sniffer
  7293. * @requires $window
  7294. * @requires $document
  7295. *
  7296. * @property {boolean} history Does the browser support html5 history api ?
  7297. * @property {boolean} hashchange Does the browser support hashchange event ?
  7298. *
  7299. * @description
  7300. * This is very simple implementation of testing browser's features.
  7301. */
  7302. function $SnifferProvider() {
  7303. this.$get = ['$window', '$document', function($window, $document) {
  7304. var eventSupport = {},
  7305. android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]),
  7306. document = $document[0];
  7307. return {
  7308. // Android has history.pushState, but it does not update location correctly
  7309. // so let's not use the history API at all.
  7310. // http://code.google.com/p/android/issues/detail?id=17471
  7311. // https://github.com/angular/angular.js/issues/904
  7312. history: !!($window.history && $window.history.pushState && !(android < 4)),
  7313. hashchange: 'onhashchange' in $window &&
  7314. // IE8 compatible mode lies
  7315. (!document.documentMode || document.documentMode > 7),
  7316. hasEvent: function(event) {
  7317. // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
  7318. // it. In particular the event is not fired when backspace or delete key are pressed or
  7319. // when cut operation is performed.
  7320. if (event == 'input' && msie == 9) return false;
  7321. if (isUndefined(eventSupport[event])) {
  7322. var divElm = document.createElement('div');
  7323. eventSupport[event] = 'on' + event in divElm;
  7324. }
  7325. return eventSupport[event];
  7326. },
  7327. csp: document.securityPolicy ? document.securityPolicy.isActive : false
  7328. };
  7329. }];
  7330. }
  7331. /**
  7332. * @ngdoc object
  7333. * @name ng.$window
  7334. *
  7335. * @description
  7336. * A reference to the browser's `window` object. While `window`
  7337. * is globally available in JavaScript, it causes testability problems, because
  7338. * it is a global variable. In angular we always refer to it through the
  7339. * `$window` service, so it may be overriden, removed or mocked for testing.
  7340. *
  7341. * All expressions are evaluated with respect to current scope so they don't
  7342. * suffer from window globality.
  7343. *
  7344. * @example
  7345. <doc:example>
  7346. <doc:source>
  7347. <input ng-init="$window = $service('$window'); greeting='Hello World!'" type="text" ng-model="greeting" />
  7348. <button ng-click="$window.alert(greeting)">ALERT</button>
  7349. </doc:source>
  7350. <doc:scenario>
  7351. </doc:scenario>
  7352. </doc:example>
  7353. */
  7354. function $WindowProvider(){
  7355. this.$get = valueFn(window);
  7356. }
  7357. /**
  7358. * Parse headers into key value object
  7359. *
  7360. * @param {string} headers Raw headers as a string
  7361. * @returns {Object} Parsed headers as key value object
  7362. */
  7363. function parseHeaders(headers) {
  7364. var parsed = {}, key, val, i;
  7365. if (!headers) return parsed;
  7366. forEach(headers.split('\n'), function(line) {
  7367. i = line.indexOf(':');
  7368. key = lowercase(trim(line.substr(0, i)));
  7369. val = trim(line.substr(i + 1));
  7370. if (key) {
  7371. if (parsed[key]) {
  7372. parsed[key] += ', ' + val;
  7373. } else {
  7374. parsed[key] = val;
  7375. }
  7376. }
  7377. });
  7378. return parsed;
  7379. }
  7380. var IS_SAME_DOMAIN_URL_MATCH = /^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/;
  7381. /**
  7382. * Parse a request and location URL and determine whether this is a same-domain request.
  7383. *
  7384. * @param {string} requestUrl The url of the request.
  7385. * @param {string} locationUrl The current browser location url.
  7386. * @returns {boolean} Whether the request is for the same domain.
  7387. */
  7388. function isSameDomain(requestUrl, locationUrl) {
  7389. var match = IS_SAME_DOMAIN_URL_MATCH.exec(requestUrl);
  7390. // if requestUrl is relative, the regex does not match.
  7391. if (match == null) return true;
  7392. var domain1 = {
  7393. protocol: match[2],
  7394. host: match[4],
  7395. port: int(match[6]) || DEFAULT_PORTS[match[2]] || null,
  7396. // IE8 sets unmatched groups to '' instead of undefined.
  7397. relativeProtocol: match[2] === undefined || match[2] === ''
  7398. };
  7399. match = URL_MATCH.exec(locationUrl);
  7400. var domain2 = {
  7401. protocol: match[1],
  7402. host: match[3],
  7403. port: int(match[5]) || DEFAULT_PORTS[match[1]] || null
  7404. };
  7405. return (domain1.protocol == domain2.protocol || domain1.relativeProtocol) &&
  7406. domain1.host == domain2.host &&
  7407. (domain1.port == domain2.port || (domain1.relativeProtocol &&
  7408. domain2.port == DEFAULT_PORTS[domain2.protocol]));
  7409. }
  7410. /**
  7411. * Returns a function that provides access to parsed headers.
  7412. *
  7413. * Headers are lazy parsed when first requested.
  7414. * @see parseHeaders
  7415. *
  7416. * @param {(string|Object)} headers Headers to provide access to.
  7417. * @returns {function(string=)} Returns a getter function which if called with:
  7418. *
  7419. * - if called with single an argument returns a single header value or null
  7420. * - if called with no arguments returns an object containing all headers.
  7421. */
  7422. function headersGetter(headers) {
  7423. var headersObj = isObject(headers) ? headers : undefined;
  7424. return function(name) {
  7425. if (!headersObj) headersObj = parseHeaders(headers);
  7426. if (name) {
  7427. return headersObj[lowercase(name)] || null;
  7428. }
  7429. return headersObj;
  7430. };
  7431. }
  7432. /**
  7433. * Chain all given functions
  7434. *
  7435. * This function is used for both request and response transforming
  7436. *
  7437. * @param {*} data Data to transform.
  7438. * @param {function(string=)} headers Http headers getter fn.
  7439. * @param {(function|Array.<function>)} fns Function or an array of functions.
  7440. * @returns {*} Transformed data.
  7441. */
  7442. function transformData(data, headers, fns) {
  7443. if (isFunction(fns))
  7444. return fns(data, headers);
  7445. forEach(fns, function(fn) {
  7446. data = fn(data, headers);
  7447. });
  7448. return data;
  7449. }
  7450. function isSuccess(status) {
  7451. return 200 <= status && status < 300;
  7452. }
  7453. function $HttpProvider() {
  7454. var JSON_START = /^\s*(\[|\{[^\{])/,
  7455. JSON_END = /[\}\]]\s*$/,
  7456. PROTECTION_PREFIX = /^\)\]\}',?\n/;
  7457. var defaults = this.defaults = {
  7458. // transform incoming response data
  7459. transformResponse: [function(data) {
  7460. if (isString(data)) {
  7461. // strip json vulnerability protection prefix
  7462. data = data.replace(PROTECTION_PREFIX, '');
  7463. if (JSON_START.test(data) && JSON_END.test(data))
  7464. data = fromJson(data, true);
  7465. }
  7466. return data;
  7467. }],
  7468. // transform outgoing request data
  7469. transformRequest: [function(d) {
  7470. return isObject(d) && !isFile(d) ? toJson(d) : d;
  7471. }],
  7472. // default headers
  7473. headers: {
  7474. common: {
  7475. 'Accept': 'application/json, text/plain, */*'
  7476. },
  7477. post: {'Content-Type': 'application/json;charset=utf-8'},
  7478. put: {'Content-Type': 'application/json;charset=utf-8'}
  7479. }
  7480. };
  7481. var providerResponseInterceptors = this.responseInterceptors = [];
  7482. this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
  7483. function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
  7484. var defaultCache = $cacheFactory('$http'),
  7485. responseInterceptors = [];
  7486. forEach(providerResponseInterceptors, function(interceptor) {
  7487. responseInterceptors.push(
  7488. isString(interceptor)
  7489. ? $injector.get(interceptor)
  7490. : $injector.invoke(interceptor)
  7491. );
  7492. });
  7493. /**
  7494. * @ngdoc function
  7495. * @name ng.$http
  7496. * @requires $httpBacked
  7497. * @requires $browser
  7498. * @requires $cacheFactory
  7499. * @requires $rootScope
  7500. * @requires $q
  7501. * @requires $injector
  7502. *
  7503. * @description
  7504. * The `$http` service is a core Angular service that facilitates communication with the remote
  7505. * HTTP servers via browser's {@link https://developer.mozilla.org/en/xmlhttprequest
  7506. * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}.
  7507. *
  7508. * For unit testing applications that use `$http` service, see
  7509. * {@link ngMock.$httpBackend $httpBackend mock}.
  7510. *
  7511. * For a higher level of abstraction, please check out the {@link ngResource.$resource
  7512. * $resource} service.
  7513. *
  7514. * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
  7515. * the $q service. While for simple usage patters this doesn't matter much, for advanced usage,
  7516. * it is important to familiarize yourself with these apis and guarantees they provide.
  7517. *
  7518. *
  7519. * # General usage
  7520. * The `$http` service is a function which takes a single argument — a configuration object —
  7521. * that is used to generate an http request and returns a {@link ng.$q promise}
  7522. * with two $http specific methods: `success` and `error`.
  7523. *
  7524. * <pre>
  7525. * $http({method: 'GET', url: '/someUrl'}).
  7526. * success(function(data, status, headers, config) {
  7527. * // this callback will be called asynchronously
  7528. * // when the response is available
  7529. * }).
  7530. * error(function(data, status, headers, config) {
  7531. * // called asynchronously if an error occurs
  7532. * // or server returns response with status
  7533. * // code outside of the <200, 400) range
  7534. * });
  7535. * </pre>
  7536. *
  7537. * Since the returned value of calling the $http function is a Promise object, you can also use
  7538. * the `then` method to register callbacks, and these callbacks will receive a single argument –
  7539. * an object representing the response. See the api signature and type info below for more
  7540. * details.
  7541. *
  7542. *
  7543. * # Shortcut methods
  7544. *
  7545. * Since all invocation of the $http service require definition of the http method and url and
  7546. * POST and PUT requests require response body/data to be provided as well, shortcut methods
  7547. * were created to simplify using the api:
  7548. *
  7549. * <pre>
  7550. * $http.get('/someUrl').success(successCallback);
  7551. * $http.post('/someUrl', data).success(successCallback);
  7552. * </pre>
  7553. *
  7554. * Complete list of shortcut methods:
  7555. *
  7556. * - {@link ng.$http#get $http.get}
  7557. * - {@link ng.$http#head $http.head}
  7558. * - {@link ng.$http#post $http.post}
  7559. * - {@link ng.$http#put $http.put}
  7560. * - {@link ng.$http#delete $http.delete}
  7561. * - {@link ng.$http#jsonp $http.jsonp}
  7562. *
  7563. *
  7564. * # Setting HTTP Headers
  7565. *
  7566. * The $http service will automatically add certain http headers to all requests. These defaults
  7567. * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
  7568. * object, which currently contains this default configuration:
  7569. *
  7570. * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
  7571. * - `Accept: application/json, text/plain, * / *`
  7572. * - `$httpProvider.defaults.headers.post`: (header defaults for HTTP POST requests)
  7573. * - `Content-Type: application/json`
  7574. * - `$httpProvider.defaults.headers.put` (header defaults for HTTP PUT requests)
  7575. * - `Content-Type: application/json`
  7576. *
  7577. * To add or overwrite these defaults, simply add or remove a property from this configuration
  7578. * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
  7579. * with name equal to the lower-cased http method name, e.g.
  7580. * `$httpProvider.defaults.headers.get['My-Header']='value'`.
  7581. *
  7582. * Additionally, the defaults can be set at runtime via the `$http.defaults` object in a similar
  7583. * fassion as described above.
  7584. *
  7585. *
  7586. * # Transforming Requests and Responses
  7587. *
  7588. * Both requests and responses can be transformed using transform functions. By default, Angular
  7589. * applies these transformations:
  7590. *
  7591. * Request transformations:
  7592. *
  7593. * - if the `data` property of the request config object contains an object, serialize it into
  7594. * JSON format.
  7595. *
  7596. * Response transformations:
  7597. *
  7598. * - if XSRF prefix is detected, strip it (see Security Considerations section below)
  7599. * - if json response is detected, deserialize it using a JSON parser
  7600. *
  7601. * To override these transformation locally, specify transform functions as `transformRequest`
  7602. * and/or `transformResponse` properties of the config object. To globally override the default
  7603. * transforms, override the `$httpProvider.defaults.transformRequest` and
  7604. * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`.
  7605. *
  7606. *
  7607. * # Caching
  7608. *
  7609. * To enable caching set the configuration property `cache` to `true`. When the cache is
  7610. * enabled, `$http` stores the response from the server in local cache. Next time the
  7611. * response is served from the cache without sending a request to the server.
  7612. *
  7613. * Note that even if the response is served from cache, delivery of the data is asynchronous in
  7614. * the same way that real requests are.
  7615. *
  7616. * If there are multiple GET requests for the same url that should be cached using the same
  7617. * cache, but the cache is not populated yet, only one request to the server will be made and
  7618. * the remaining requests will be fulfilled using the response for the first request.
  7619. *
  7620. *
  7621. * # Response interceptors
  7622. *
  7623. * Before you start creating interceptors, be sure to understand the
  7624. * {@link ng.$q $q and deferred/promise APIs}.
  7625. *
  7626. * For purposes of global error handling, authentication or any kind of synchronous or
  7627. * asynchronous preprocessing of received responses, it is desirable to be able to intercept
  7628. * responses for http requests before they are handed over to the application code that
  7629. * initiated these requests. The response interceptors leverage the {@link ng.$q
  7630. * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing.
  7631. *
  7632. * The interceptors are service factories that are registered with the $httpProvider by
  7633. * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
  7634. * injected with dependencies (if specified) and returns the interceptor — a function that
  7635. * takes a {@link ng.$q promise} and returns the original or a new promise.
  7636. *
  7637. * <pre>
  7638. * // register the interceptor as a service
  7639. * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
  7640. * return function(promise) {
  7641. * return promise.then(function(response) {
  7642. * // do something on success
  7643. * }, function(response) {
  7644. * // do something on error
  7645. * if (canRecover(response)) {
  7646. * return responseOrNewPromise
  7647. * }
  7648. * return $q.reject(response);
  7649. * });
  7650. * }
  7651. * });
  7652. *
  7653. * $httpProvider.responseInterceptors.push('myHttpInterceptor');
  7654. *
  7655. *
  7656. * // register the interceptor via an anonymous factory
  7657. * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
  7658. * return function(promise) {
  7659. * // same as above
  7660. * }
  7661. * });
  7662. * </pre>
  7663. *
  7664. *
  7665. * # Security Considerations
  7666. *
  7667. * When designing web applications, consider security threats from:
  7668. *
  7669. * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
  7670. * JSON Vulnerability}
  7671. * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}
  7672. *
  7673. * Both server and the client must cooperate in order to eliminate these threats. Angular comes
  7674. * pre-configured with strategies that address these issues, but for this to work backend server
  7675. * cooperation is required.
  7676. *
  7677. * ## JSON Vulnerability Protection
  7678. *
  7679. * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
  7680. * JSON Vulnerability} allows third party web-site to turn your JSON resource URL into
  7681. * {@link http://en.wikipedia.org/wiki/JSON#JSONP JSONP} request under some conditions. To
  7682. * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
  7683. * Angular will automatically strip the prefix before processing it as JSON.
  7684. *
  7685. * For example if your server needs to return:
  7686. * <pre>
  7687. * ['one','two']
  7688. * </pre>
  7689. *
  7690. * which is vulnerable to attack, your server can return:
  7691. * <pre>
  7692. * )]}',
  7693. * ['one','two']
  7694. * </pre>
  7695. *
  7696. * Angular will strip the prefix, before processing the JSON.
  7697. *
  7698. *
  7699. * ## Cross Site Request Forgery (XSRF) Protection
  7700. *
  7701. * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
  7702. * an unauthorized site can gain your user's private data. Angular provides following mechanism
  7703. * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
  7704. * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
  7705. * runs on your domain could read the cookie, your server can be assured that the XHR came from
  7706. * JavaScript running on your domain. The header will not be set for cross-domain requests.
  7707. *
  7708. * To take advantage of this, your server needs to set a token in a JavaScript readable session
  7709. * cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
  7710. * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
  7711. * that only JavaScript running on your domain could have read the token. The token must be
  7712. * unique for each user and must be verifiable by the server (to prevent the JavaScript making
  7713. * up its own tokens). We recommend that the token is a digest of your site's authentication
  7714. * cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
  7715. *
  7716. *
  7717. * @param {object} config Object describing the request to be made and how it should be
  7718. * processed. The object has following properties:
  7719. *
  7720. * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
  7721. * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
  7722. * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be turned to
  7723. * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
  7724. * - **data** – `{string|Object}` – Data to be sent as the request message data.
  7725. * - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
  7726. * - **transformRequest** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
  7727. * transform function or an array of such functions. The transform function takes the http
  7728. * request body and headers and returns its transformed (typically serialized) version.
  7729. * - **transformResponse** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
  7730. * transform function or an array of such functions. The transform function takes the http
  7731. * response body and headers and returns its transformed (typically deserialized) version.
  7732. * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
  7733. * GET request, otherwise if a cache instance built with
  7734. * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
  7735. * caching.
  7736. * - **timeout** – `{number}` – timeout in milliseconds.
  7737. * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
  7738. * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
  7739. * requests with credentials} for more information.
  7740. * - **responseType** - `{string}` - see {@link
  7741. * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
  7742. *
  7743. * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
  7744. * standard `then` method and two http specific methods: `success` and `error`. The `then`
  7745. * method takes two arguments a success and an error callback which will be called with a
  7746. * response object. The `success` and `error` methods take a single argument - a function that
  7747. * will be called when the request succeeds or fails respectively. The arguments passed into
  7748. * these functions are destructured representation of the response object passed into the
  7749. * `then` method. The response object has these properties:
  7750. *
  7751. * - **data** – `{string|Object}` – The response body transformed with the transform functions.
  7752. * - **status** – `{number}` – HTTP status code of the response.
  7753. * - **headers** – `{function([headerName])}` – Header getter function.
  7754. * - **config** – `{Object}` – The configuration object that was used to generate the request.
  7755. *
  7756. * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
  7757. * requests. This is primarily meant to be used for debugging purposes.
  7758. *
  7759. *
  7760. * @example
  7761. <example>
  7762. <file name="index.html">
  7763. <div ng-controller="FetchCtrl">
  7764. <select ng-model="method">
  7765. <option>GET</option>
  7766. <option>JSONP</option>
  7767. </select>
  7768. <input type="text" ng-model="url" size="80"/>
  7769. <button ng-click="fetch()">fetch</button><br>
  7770. <button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
  7771. <button ng-click="updateModel('JSONP', 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">Sample JSONP</button>
  7772. <button ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">Invalid JSONP</button>
  7773. <pre>http status code: {{status}}</pre>
  7774. <pre>http response data: {{data}}</pre>
  7775. </div>
  7776. </file>
  7777. <file name="script.js">
  7778. function FetchCtrl($scope, $http, $templateCache) {
  7779. $scope.method = 'GET';
  7780. $scope.url = 'http-hello.html';
  7781. $scope.fetch = function() {
  7782. $scope.code = null;
  7783. $scope.response = null;
  7784. $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
  7785. success(function(data, status) {
  7786. $scope.status = status;
  7787. $scope.data = data;
  7788. }).
  7789. error(function(data, status) {
  7790. $scope.data = data || "Request failed";
  7791. $scope.status = status;
  7792. });
  7793. };
  7794. $scope.updateModel = function(method, url) {
  7795. $scope.method = method;
  7796. $scope.url = url;
  7797. };
  7798. }
  7799. </file>
  7800. <file name="http-hello.html">
  7801. Hello, $http!
  7802. </file>
  7803. <file name="scenario.js">
  7804. it('should make an xhr GET request', function() {
  7805. element(':button:contains("Sample GET")').click();
  7806. element(':button:contains("fetch")').click();
  7807. expect(binding('status')).toBe('200');
  7808. expect(binding('data')).toMatch(/Hello, \$http!/);
  7809. });
  7810. it('should make a JSONP request to angularjs.org', function() {
  7811. element(':button:contains("Sample JSONP")').click();
  7812. element(':button:contains("fetch")').click();
  7813. expect(binding('status')).toBe('200');
  7814. expect(binding('data')).toMatch(/Super Hero!/);
  7815. });
  7816. it('should make JSONP request to invalid URL and invoke the error handler',
  7817. function() {
  7818. element(':button:contains("Invalid JSONP")').click();
  7819. element(':button:contains("fetch")').click();
  7820. expect(binding('status')).toBe('0');
  7821. expect(binding('data')).toBe('Request failed');
  7822. });
  7823. </file>
  7824. </example>
  7825. */
  7826. function $http(config) {
  7827. config.method = uppercase(config.method);
  7828. var reqTransformFn = config.transformRequest || defaults.transformRequest,
  7829. respTransformFn = config.transformResponse || defaults.transformResponse,
  7830. defHeaders = defaults.headers,
  7831. xsrfToken = isSameDomain(config.url, $browser.url()) ?
  7832. $browser.cookies()['XSRF-TOKEN'] : undefined,
  7833. reqHeaders = extend({'X-XSRF-TOKEN': xsrfToken},
  7834. defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
  7835. reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
  7836. promise;
  7837. // strip content-type if data is undefined
  7838. if (isUndefined(config.data)) {
  7839. delete reqHeaders['Content-Type'];
  7840. }
  7841. if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
  7842. config.withCredentials = defaults.withCredentials;
  7843. }
  7844. // send request
  7845. promise = sendReq(config, reqData, reqHeaders);
  7846. // transform future response
  7847. promise = promise.then(transformResponse, transformResponse);
  7848. // apply interceptors
  7849. forEach(responseInterceptors, function(interceptor) {
  7850. promise = interceptor(promise);
  7851. });
  7852. promise.success = function(fn) {
  7853. promise.then(function(response) {
  7854. fn(response.data, response.status, response.headers, config);
  7855. });
  7856. return promise;
  7857. };
  7858. promise.error = function(fn) {
  7859. promise.then(null, function(response) {
  7860. fn(response.data, response.status, response.headers, config);
  7861. });
  7862. return promise;
  7863. };
  7864. return promise;
  7865. function transformResponse(response) {
  7866. // make a copy since the response must be cacheable
  7867. var resp = extend({}, response, {
  7868. data: transformData(response.data, response.headers, respTransformFn)
  7869. });
  7870. return (isSuccess(response.status))
  7871. ? resp
  7872. : $q.reject(resp);
  7873. }
  7874. }
  7875. $http.pendingRequests = [];
  7876. /**
  7877. * @ngdoc method
  7878. * @name ng.$http#get
  7879. * @methodOf ng.$http
  7880. *
  7881. * @description
  7882. * Shortcut method to perform `GET` request
  7883. *
  7884. * @param {string} url Relative or absolute URL specifying the destination of the request
  7885. * @param {Object=} config Optional configuration object
  7886. * @returns {HttpPromise} Future object
  7887. */
  7888. /**
  7889. * @ngdoc method
  7890. * @name ng.$http#delete
  7891. * @methodOf ng.$http
  7892. *
  7893. * @description
  7894. * Shortcut method to perform `DELETE` request
  7895. *
  7896. * @param {string} url Relative or absolute URL specifying the destination of the request
  7897. * @param {Object=} config Optional configuration object
  7898. * @returns {HttpPromise} Future object
  7899. */
  7900. /**
  7901. * @ngdoc method
  7902. * @name ng.$http#head
  7903. * @methodOf ng.$http
  7904. *
  7905. * @description
  7906. * Shortcut method to perform `HEAD` request
  7907. *
  7908. * @param {string} url Relative or absolute URL specifying the destination of the request
  7909. * @param {Object=} config Optional configuration object
  7910. * @returns {HttpPromise} Future object
  7911. */
  7912. /**
  7913. * @ngdoc method
  7914. * @name ng.$http#jsonp
  7915. * @methodOf ng.$http
  7916. *
  7917. * @description
  7918. * Shortcut method to perform `JSONP` request
  7919. *
  7920. * @param {string} url Relative or absolute URL specifying the destination of the request.
  7921. * Should contain `JSON_CALLBACK` string.
  7922. * @param {Object=} config Optional configuration object
  7923. * @returns {HttpPromise} Future object
  7924. */
  7925. createShortMethods('get', 'delete', 'head', 'jsonp');
  7926. /**
  7927. * @ngdoc method
  7928. * @name ng.$http#post
  7929. * @methodOf ng.$http
  7930. *
  7931. * @description
  7932. * Shortcut method to perform `POST` request
  7933. *
  7934. * @param {string} url Relative or absolute URL specifying the destination of the request
  7935. * @param {*} data Request content
  7936. * @param {Object=} config Optional configuration object
  7937. * @returns {HttpPromise} Future object
  7938. */
  7939. /**
  7940. * @ngdoc method
  7941. * @name ng.$http#put
  7942. * @methodOf ng.$http
  7943. *
  7944. * @description
  7945. * Shortcut method to perform `PUT` request
  7946. *
  7947. * @param {string} url Relative or absolute URL specifying the destination of the request
  7948. * @param {*} data Request content
  7949. * @param {Object=} config Optional configuration object
  7950. * @returns {HttpPromise} Future object
  7951. */
  7952. createShortMethodsWithData('post', 'put');
  7953. /**
  7954. * @ngdoc property
  7955. * @name ng.$http#defaults
  7956. * @propertyOf ng.$http
  7957. *
  7958. * @description
  7959. * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
  7960. * default headers, withCredentials as well as request and response transformations.
  7961. *
  7962. * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
  7963. */
  7964. $http.defaults = defaults;
  7965. return $http;
  7966. function createShortMethods(names) {
  7967. forEach(arguments, function(name) {
  7968. $http[name] = function(url, config) {
  7969. return $http(extend(config || {}, {
  7970. method: name,
  7971. url: url
  7972. }));
  7973. };
  7974. });
  7975. }
  7976. function createShortMethodsWithData(name) {
  7977. forEach(arguments, function(name) {
  7978. $http[name] = function(url, data, config) {
  7979. return $http(extend(config || {}, {
  7980. method: name,
  7981. url: url,
  7982. data: data
  7983. }));
  7984. };
  7985. });
  7986. }
  7987. /**
  7988. * Makes the request
  7989. *
  7990. * !!! ACCESSES CLOSURE VARS:
  7991. * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
  7992. */
  7993. function sendReq(config, reqData, reqHeaders) {
  7994. var deferred = $q.defer(),
  7995. promise = deferred.promise,
  7996. cache,
  7997. cachedResp,
  7998. url = buildUrl(config.url, config.params);
  7999. $http.pendingRequests.push(config);
  8000. promise.then(removePendingReq, removePendingReq);
  8001. if (config.cache && config.method == 'GET') {
  8002. cache = isObject(config.cache) ? config.cache : defaultCache;
  8003. }
  8004. if (cache) {
  8005. cachedResp = cache.get(url);
  8006. if (cachedResp) {
  8007. if (cachedResp.then) {
  8008. // cached request has already been sent, but there is no response yet
  8009. cachedResp.then(removePendingReq, removePendingReq);
  8010. return cachedResp;
  8011. } else {
  8012. // serving from cache
  8013. if (isArray(cachedResp)) {
  8014. resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
  8015. } else {
  8016. resolvePromise(cachedResp, 200, {});
  8017. }
  8018. }
  8019. } else {
  8020. // put the promise for the non-transformed response into cache as a placeholder
  8021. cache.put(url, promise);
  8022. }
  8023. }
  8024. // if we won't have the response in cache, send the request to the backend
  8025. if (!cachedResp) {
  8026. $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
  8027. config.withCredentials, config.responseType);
  8028. }
  8029. return promise;
  8030. /**
  8031. * Callback registered to $httpBackend():
  8032. * - caches the response if desired
  8033. * - resolves the raw $http promise
  8034. * - calls $apply
  8035. */
  8036. function done(status, response, headersString) {
  8037. if (cache) {
  8038. if (isSuccess(status)) {
  8039. cache.put(url, [status, response, parseHeaders(headersString)]);
  8040. } else {
  8041. // remove promise from the cache
  8042. cache.remove(url);
  8043. }
  8044. }
  8045. resolvePromise(response, status, headersString);
  8046. $rootScope.$apply();
  8047. }
  8048. /**
  8049. * Resolves the raw $http promise.
  8050. */
  8051. function resolvePromise(response, status, headers) {
  8052. // normalize internal statuses to 0
  8053. status = Math.max(status, 0);
  8054. (isSuccess(status) ? deferred.resolve : deferred.reject)({
  8055. data: response,
  8056. status: status,
  8057. headers: headersGetter(headers),
  8058. config: config
  8059. });
  8060. }
  8061. function removePendingReq() {
  8062. var idx = indexOf($http.pendingRequests, config);
  8063. if (idx !== -1) $http.pendingRequests.splice(idx, 1);
  8064. }
  8065. }
  8066. function buildUrl(url, params) {
  8067. if (!params) return url;
  8068. var parts = [];
  8069. forEachSorted(params, function(value, key) {
  8070. if (value == null || value == undefined) return;
  8071. if (!isArray(value)) value = [value];
  8072. forEach(value, function(v) {
  8073. if (isObject(v)) {
  8074. v = toJson(v);
  8075. }
  8076. parts.push(encodeURIComponent(key) + '=' +
  8077. encodeURIComponent(v));
  8078. });
  8079. });
  8080. return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
  8081. }
  8082. }];
  8083. }
  8084. var XHR = window.XMLHttpRequest || function() {
  8085. try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
  8086. try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
  8087. try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
  8088. throw new Error("This browser does not support XMLHttpRequest.");
  8089. };
  8090. /**
  8091. * @ngdoc object
  8092. * @name ng.$httpBackend
  8093. * @requires $browser
  8094. * @requires $window
  8095. * @requires $document
  8096. *
  8097. * @description
  8098. * HTTP backend used by the {@link ng.$http service} that delegates to
  8099. * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
  8100. *
  8101. * You should never need to use this service directly, instead use the higher-level abstractions:
  8102. * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
  8103. *
  8104. * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
  8105. * $httpBackend} which can be trained with responses.
  8106. */
  8107. function $HttpBackendProvider() {
  8108. this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
  8109. return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks,
  8110. $document[0], $window.location.protocol.replace(':', ''));
  8111. }];
  8112. }
  8113. function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
  8114. // TODO(vojta): fix the signature
  8115. return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
  8116. $browser.$$incOutstandingRequestCount();
  8117. url = url || $browser.url();
  8118. if (lowercase(method) == 'jsonp') {
  8119. var callbackId = '_' + (callbacks.counter++).toString(36);
  8120. callbacks[callbackId] = function(data) {
  8121. callbacks[callbackId].data = data;
  8122. };
  8123. jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
  8124. function() {
  8125. if (callbacks[callbackId].data) {
  8126. completeRequest(callback, 200, callbacks[callbackId].data);
  8127. } else {
  8128. completeRequest(callback, -2);
  8129. }
  8130. delete callbacks[callbackId];
  8131. });
  8132. } else {
  8133. var xhr = new XHR();
  8134. xhr.open(method, url, true);
  8135. forEach(headers, function(value, key) {
  8136. if (value) xhr.setRequestHeader(key, value);
  8137. });
  8138. var status;
  8139. // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
  8140. // response is in the cache. the promise api will ensure that to the app code the api is
  8141. // always async
  8142. xhr.onreadystatechange = function() {
  8143. if (xhr.readyState == 4) {
  8144. completeRequest(callback, status || xhr.status, xhr.response || xhr.responseText,
  8145. xhr.getAllResponseHeaders());
  8146. }
  8147. };
  8148. if (withCredentials) {
  8149. xhr.withCredentials = true;
  8150. }
  8151. if (responseType) {
  8152. xhr.responseType = responseType;
  8153. }
  8154. xhr.send(post || '');
  8155. if (timeout > 0) {
  8156. $browserDefer(function() {
  8157. status = -1;
  8158. xhr.abort();
  8159. }, timeout);
  8160. }
  8161. }
  8162. function completeRequest(callback, status, response, headersString) {
  8163. // URL_MATCH is defined in src/service/location.js
  8164. var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1];
  8165. // fix status code for file protocol (it's always 0)
  8166. status = (protocol == 'file') ? (response ? 200 : 404) : status;
  8167. // normalize IE bug (http://bugs.jquery.com/ticket/1450)
  8168. status = status == 1223 ? 204 : status;
  8169. callback(status, response, headersString);
  8170. $browser.$$completeOutstandingRequest(noop);
  8171. }
  8172. };
  8173. function jsonpReq(url, done) {
  8174. // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
  8175. // - fetches local scripts via XHR and evals them
  8176. // - adds and immediately removes script elements from the document
  8177. var script = rawDocument.createElement('script'),
  8178. doneWrapper = function() {
  8179. rawDocument.body.removeChild(script);
  8180. if (done) done();
  8181. };
  8182. script.type = 'text/javascript';
  8183. script.src = url;
  8184. if (msie) {
  8185. script.onreadystatechange = function() {
  8186. if (/loaded|complete/.test(script.readyState)) doneWrapper();
  8187. };
  8188. } else {
  8189. script.onload = script.onerror = doneWrapper;
  8190. }
  8191. rawDocument.body.appendChild(script);
  8192. }
  8193. }
  8194. /**
  8195. * @ngdoc object
  8196. * @name ng.$locale
  8197. *
  8198. * @description
  8199. * $locale service provides localization rules for various Angular components. As of right now the
  8200. * only public api is:
  8201. *
  8202. * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
  8203. */
  8204. function $LocaleProvider(){
  8205. this.$get = function() {
  8206. return {
  8207. id: 'en-us',
  8208. NUMBER_FORMATS: {
  8209. DECIMAL_SEP: '.',
  8210. GROUP_SEP: ',',
  8211. PATTERNS: [
  8212. { // Decimal Pattern
  8213. minInt: 1,
  8214. minFrac: 0,
  8215. maxFrac: 3,
  8216. posPre: '',
  8217. posSuf: '',
  8218. negPre: '-',
  8219. negSuf: '',
  8220. gSize: 3,
  8221. lgSize: 3
  8222. },{ //Currency Pattern
  8223. minInt: 1,
  8224. minFrac: 2,
  8225. maxFrac: 2,
  8226. posPre: '\u00A4',
  8227. posSuf: '',
  8228. negPre: '(\u00A4',
  8229. negSuf: ')',
  8230. gSize: 3,
  8231. lgSize: 3
  8232. }
  8233. ],
  8234. CURRENCY_SYM: '$'
  8235. },
  8236. DATETIME_FORMATS: {
  8237. MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
  8238. .split(','),
  8239. SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
  8240. DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
  8241. SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
  8242. AMPMS: ['AM','PM'],
  8243. medium: 'MMM d, y h:mm:ss a',
  8244. short: 'M/d/yy h:mm a',
  8245. fullDate: 'EEEE, MMMM d, y',
  8246. longDate: 'MMMM d, y',
  8247. mediumDate: 'MMM d, y',
  8248. shortDate: 'M/d/yy',
  8249. mediumTime: 'h:mm:ss a',
  8250. shortTime: 'h:mm a'
  8251. },
  8252. pluralCat: function(num) {
  8253. if (num === 1) {
  8254. return 'one';
  8255. }
  8256. return 'other';
  8257. }
  8258. };
  8259. };
  8260. }
  8261. function $TimeoutProvider() {
  8262. this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
  8263. function($rootScope, $browser, $q, $exceptionHandler) {
  8264. var deferreds = {};
  8265. /**
  8266. * @ngdoc function
  8267. * @name ng.$timeout
  8268. * @requires $browser
  8269. *
  8270. * @description
  8271. * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
  8272. * block and delegates any exceptions to
  8273. * {@link ng.$exceptionHandler $exceptionHandler} service.
  8274. *
  8275. * The return value of registering a timeout function is a promise which will be resolved when
  8276. * the timeout is reached and the timeout function is executed.
  8277. *
  8278. * To cancel a the timeout request, call `$timeout.cancel(promise)`.
  8279. *
  8280. * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
  8281. * synchronously flush the queue of deferred functions.
  8282. *
  8283. * @param {function()} fn A function, who's execution should be delayed.
  8284. * @param {number=} [delay=0] Delay in milliseconds.
  8285. * @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise
  8286. * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
  8287. * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
  8288. * promise will be resolved with is the return value of the `fn` function.
  8289. */
  8290. function timeout(fn, delay, invokeApply) {
  8291. var deferred = $q.defer(),
  8292. promise = deferred.promise,
  8293. skipApply = (isDefined(invokeApply) && !invokeApply),
  8294. timeoutId, cleanup;
  8295. timeoutId = $browser.defer(function() {
  8296. try {
  8297. deferred.resolve(fn());
  8298. } catch(e) {
  8299. deferred.reject(e);
  8300. $exceptionHandler(e);
  8301. }
  8302. if (!skipApply) $rootScope.$apply();
  8303. }, delay);
  8304. cleanup = function() {
  8305. delete deferreds[promise.$$timeoutId];
  8306. };
  8307. promise.$$timeoutId = timeoutId;
  8308. deferreds[timeoutId] = deferred;
  8309. promise.then(cleanup, cleanup);
  8310. return promise;
  8311. }
  8312. /**
  8313. * @ngdoc function
  8314. * @name ng.$timeout#cancel
  8315. * @methodOf ng.$timeout
  8316. *
  8317. * @description
  8318. * Cancels a task associated with the `promise`. As a result of this the promise will be
  8319. * resolved with a rejection.
  8320. *
  8321. * @param {Promise=} promise Promise returned by the `$timeout` function.
  8322. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
  8323. * canceled.
  8324. */
  8325. timeout.cancel = function(promise) {
  8326. if (promise && promise.$$timeoutId in deferreds) {
  8327. deferreds[promise.$$timeoutId].reject('canceled');
  8328. return $browser.defer.cancel(promise.$$timeoutId);
  8329. }
  8330. return false;
  8331. };
  8332. return timeout;
  8333. }];
  8334. }
  8335. /**
  8336. * @ngdoc object
  8337. * @name ng.$filterProvider
  8338. * @description
  8339. *
  8340. * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To
  8341. * achieve this a filter definition consists of a factory function which is annotated with dependencies and is
  8342. * responsible for creating a the filter function.
  8343. *
  8344. * <pre>
  8345. * // Filter registration
  8346. * function MyModule($provide, $filterProvider) {
  8347. * // create a service to demonstrate injection (not always needed)
  8348. * $provide.value('greet', function(name){
  8349. * return 'Hello ' + name + '!';
  8350. * });
  8351. *
  8352. * // register a filter factory which uses the
  8353. * // greet service to demonstrate DI.
  8354. * $filterProvider.register('greet', function(greet){
  8355. * // return the filter function which uses the greet service
  8356. * // to generate salutation
  8357. * return function(text) {
  8358. * // filters need to be forgiving so check input validity
  8359. * return text && greet(text) || text;
  8360. * };
  8361. * });
  8362. * }
  8363. * </pre>
  8364. *
  8365. * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`.
  8366. * <pre>
  8367. * it('should be the same instance', inject(
  8368. * function($filterProvider) {
  8369. * $filterProvider.register('reverse', function(){
  8370. * return ...;
  8371. * });
  8372. * },
  8373. * function($filter, reverseFilter) {
  8374. * expect($filter('reverse')).toBe(reverseFilter);
  8375. * });
  8376. * </pre>
  8377. *
  8378. *
  8379. * For more information about how angular filters work, and how to create your own filters, see
  8380. * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer
  8381. * Guide.
  8382. */
  8383. /**
  8384. * @ngdoc method
  8385. * @name ng.$filterProvider#register
  8386. * @methodOf ng.$filterProvider
  8387. * @description
  8388. * Register filter factory function.
  8389. *
  8390. * @param {String} name Name of the filter.
  8391. * @param {function} fn The filter factory function which is injectable.
  8392. */
  8393. /**
  8394. * @ngdoc function
  8395. * @name ng.$filter
  8396. * @function
  8397. * @description
  8398. * Filters are used for formatting data displayed to the user.
  8399. *
  8400. * The general syntax in templates is as follows:
  8401. *
  8402. * {{ expression | [ filter_name ] }}
  8403. *
  8404. * @param {String} name Name of the filter function to retrieve
  8405. * @return {Function} the filter function
  8406. */
  8407. $FilterProvider.$inject = ['$provide'];
  8408. function $FilterProvider($provide) {
  8409. var suffix = 'Filter';
  8410. function register(name, factory) {
  8411. return $provide.factory(name + suffix, factory);
  8412. }
  8413. this.register = register;
  8414. this.$get = ['$injector', function($injector) {
  8415. return function(name) {
  8416. return $injector.get(name + suffix);
  8417. }
  8418. }];
  8419. ////////////////////////////////////////
  8420. register('currency', currencyFilter);
  8421. register('date', dateFilter);
  8422. register('filter', filterFilter);
  8423. register('json', jsonFilter);
  8424. register('limitTo', limitToFilter);
  8425. register('lowercase', lowercaseFilter);
  8426. register('number', numberFilter);
  8427. register('orderBy', orderByFilter);
  8428. register('uppercase', uppercaseFilter);
  8429. }
  8430. /**
  8431. * @ngdoc filter
  8432. * @name ng.filter:filter
  8433. * @function
  8434. *
  8435. * @description
  8436. * Selects a subset of items from `array` and returns it as a new array.
  8437. *
  8438. * Note: This function is used to augment the `Array` type in Angular expressions. See
  8439. * {@link ng.$filter} for more information about Angular arrays.
  8440. *
  8441. * @param {Array} array The source array.
  8442. * @param {string|Object|function()} expression The predicate to be used for selecting items from
  8443. * `array`.
  8444. *
  8445. * Can be one of:
  8446. *
  8447. * - `string`: Predicate that results in a substring match using the value of `expression`
  8448. * string. All strings or objects with string properties in `array` that contain this string
  8449. * will be returned. The predicate can be negated by prefixing the string with `!`.
  8450. *
  8451. * - `Object`: A pattern object can be used to filter specific properties on objects contained
  8452. * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
  8453. * which have property `name` containing "M" and property `phone` containing "1". A special
  8454. * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
  8455. * property of the object. That's equivalent to the simple substring match with a `string`
  8456. * as described above.
  8457. *
  8458. * - `function`: A predicate function can be used to write arbitrary filters. The function is
  8459. * called for each element of `array`. The final result is an array of those elements that
  8460. * the predicate returned true for.
  8461. *
  8462. * @example
  8463. <doc:example>
  8464. <doc:source>
  8465. <div ng-init="friends = [{name:'John', phone:'555-1276'},
  8466. {name:'Mary', phone:'800-BIG-MARY'},
  8467. {name:'Mike', phone:'555-4321'},
  8468. {name:'Adam', phone:'555-5678'},
  8469. {name:'Julie', phone:'555-8765'}]"></div>
  8470. Search: <input ng-model="searchText">
  8471. <table id="searchTextResults">
  8472. <tr><th>Name</th><th>Phone</th><tr>
  8473. <tr ng-repeat="friend in friends | filter:searchText">
  8474. <td>{{friend.name}}</td>
  8475. <td>{{friend.phone}}</td>
  8476. <tr>
  8477. </table>
  8478. <hr>
  8479. Any: <input ng-model="search.$"> <br>
  8480. Name only <input ng-model="search.name"><br>
  8481. Phone only <input ng-model="search.phone"å><br>
  8482. <table id="searchObjResults">
  8483. <tr><th>Name</th><th>Phone</th><tr>
  8484. <tr ng-repeat="friend in friends | filter:search">
  8485. <td>{{friend.name}}</td>
  8486. <td>{{friend.phone}}</td>
  8487. <tr>
  8488. </table>
  8489. </doc:source>
  8490. <doc:scenario>
  8491. it('should search across all fields when filtering with a string', function() {
  8492. input('searchText').enter('m');
  8493. expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
  8494. toEqual(['Mary', 'Mike', 'Adam']);
  8495. input('searchText').enter('76');
  8496. expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
  8497. toEqual(['John', 'Julie']);
  8498. });
  8499. it('should search in specific fields when filtering with a predicate object', function() {
  8500. input('search.$').enter('i');
  8501. expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
  8502. toEqual(['Mary', 'Mike', 'Julie']);
  8503. });
  8504. </doc:scenario>
  8505. </doc:example>
  8506. */
  8507. function filterFilter() {
  8508. return function(array, expression) {
  8509. if (!(array instanceof Array)) return array;
  8510. var predicates = [];
  8511. predicates.check = function(value) {
  8512. for (var j = 0; j < predicates.length; j++) {
  8513. if(!predicates[j](value)) {
  8514. return false;
  8515. }
  8516. }
  8517. return true;
  8518. };
  8519. var search = function(obj, text){
  8520. if (text.charAt(0) === '!') {
  8521. return !search(obj, text.substr(1));
  8522. }
  8523. switch (typeof obj) {
  8524. case "boolean":
  8525. case "number":
  8526. case "string":
  8527. return ('' + obj).toLowerCase().indexOf(text) > -1;
  8528. case "object":
  8529. for ( var objKey in obj) {
  8530. if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
  8531. return true;
  8532. }
  8533. }
  8534. return false;
  8535. case "array":
  8536. for ( var i = 0; i < obj.length; i++) {
  8537. if (search(obj[i], text)) {
  8538. return true;
  8539. }
  8540. }
  8541. return false;
  8542. default:
  8543. return false;
  8544. }
  8545. };
  8546. switch (typeof expression) {
  8547. case "boolean":
  8548. case "number":
  8549. case "string":
  8550. expression = {$:expression};
  8551. case "object":
  8552. for (var key in expression) {
  8553. if (key == '$') {
  8554. (function() {
  8555. var text = (''+expression[key]).toLowerCase();
  8556. if (!text) return;
  8557. predicates.push(function(value) {
  8558. return search(value, text);
  8559. });
  8560. })();
  8561. } else {
  8562. (function() {
  8563. var path = key;
  8564. var text = (''+expression[key]).toLowerCase();
  8565. if (!text) return;
  8566. predicates.push(function(value) {
  8567. return search(getter(value, path), text);
  8568. });
  8569. })();
  8570. }
  8571. }
  8572. break;
  8573. case 'function':
  8574. predicates.push(expression);
  8575. break;
  8576. default:
  8577. return array;
  8578. }
  8579. var filtered = [];
  8580. for ( var j = 0; j < array.length; j++) {
  8581. var value = array[j];
  8582. if (predicates.check(value)) {
  8583. filtered.push(value);
  8584. }
  8585. }
  8586. return filtered;
  8587. }
  8588. }
  8589. /**
  8590. * @ngdoc filter
  8591. * @name ng.filter:currency
  8592. * @function
  8593. *
  8594. * @description
  8595. * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
  8596. * symbol for current locale is used.
  8597. *
  8598. * @param {number} amount Input to filter.
  8599. * @param {string=} symbol Currency symbol or identifier to be displayed.
  8600. * @returns {string} Formatted number.
  8601. *
  8602. *
  8603. * @example
  8604. <doc:example>
  8605. <doc:source>
  8606. <script>
  8607. function Ctrl($scope) {
  8608. $scope.amount = 1234.56;
  8609. }
  8610. </script>
  8611. <div ng-controller="Ctrl">
  8612. <input type="number" ng-model="amount"> <br>
  8613. default currency symbol ($): {{amount | currency}}<br>
  8614. custom currency identifier (USD$): {{amount | currency:"USD$"}}
  8615. </div>
  8616. </doc:source>
  8617. <doc:scenario>
  8618. it('should init with 1234.56', function() {
  8619. expect(binding('amount | currency')).toBe('$1,234.56');
  8620. expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
  8621. });
  8622. it('should update', function() {
  8623. input('amount').enter('-1234');
  8624. expect(binding('amount | currency')).toBe('($1,234.00)');
  8625. expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
  8626. });
  8627. </doc:scenario>
  8628. </doc:example>
  8629. */
  8630. currencyFilter.$inject = ['$locale'];
  8631. function currencyFilter($locale) {
  8632. var formats = $locale.NUMBER_FORMATS;
  8633. return function(amount, currencySymbol){
  8634. if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
  8635. return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
  8636. replace(/\u00A4/g, currencySymbol);
  8637. };
  8638. }
  8639. /**
  8640. * @ngdoc filter
  8641. * @name ng.filter:number
  8642. * @function
  8643. *
  8644. * @description
  8645. * Formats a number as text.
  8646. *
  8647. * If the input is not a number an empty string is returned.
  8648. *
  8649. * @param {number|string} number Number to format.
  8650. * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
  8651. * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
  8652. *
  8653. * @example
  8654. <doc:example>
  8655. <doc:source>
  8656. <script>
  8657. function Ctrl($scope) {
  8658. $scope.val = 1234.56789;
  8659. }
  8660. </script>
  8661. <div ng-controller="Ctrl">
  8662. Enter number: <input ng-model='val'><br>
  8663. Default formatting: {{val | number}}<br>
  8664. No fractions: {{val | number:0}}<br>
  8665. Negative number: {{-val | number:4}}
  8666. </div>
  8667. </doc:source>
  8668. <doc:scenario>
  8669. it('should format numbers', function() {
  8670. expect(binding('val | number')).toBe('1,234.568');
  8671. expect(binding('val | number:0')).toBe('1,235');
  8672. expect(binding('-val | number:4')).toBe('-1,234.5679');
  8673. });
  8674. it('should update', function() {
  8675. input('val').enter('3374.333');
  8676. expect(binding('val | number')).toBe('3,374.333');
  8677. expect(binding('val | number:0')).toBe('3,374');
  8678. expect(binding('-val | number:4')).toBe('-3,374.3330');
  8679. });
  8680. </doc:scenario>
  8681. </doc:example>
  8682. */
  8683. numberFilter.$inject = ['$locale'];
  8684. function numberFilter($locale) {
  8685. var formats = $locale.NUMBER_FORMATS;
  8686. return function(number, fractionSize) {
  8687. return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
  8688. fractionSize);
  8689. };
  8690. }
  8691. var DECIMAL_SEP = '.';
  8692. function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
  8693. if (isNaN(number) || !isFinite(number)) return '';
  8694. var isNegative = number < 0;
  8695. number = Math.abs(number);
  8696. var numStr = number + '',
  8697. formatedText = '',
  8698. parts = [];
  8699. var hasExponent = false;
  8700. if (numStr.indexOf('e') !== -1) {
  8701. var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
  8702. if (match && match[2] == '-' && match[3] > fractionSize + 1) {
  8703. numStr = '0';
  8704. } else {
  8705. formatedText = numStr;
  8706. hasExponent = true;
  8707. }
  8708. }
  8709. if (!hasExponent) {
  8710. var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
  8711. // determine fractionSize if it is not specified
  8712. if (isUndefined(fractionSize)) {
  8713. fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
  8714. }
  8715. var pow = Math.pow(10, fractionSize);
  8716. number = Math.round(number * pow) / pow;
  8717. var fraction = ('' + number).split(DECIMAL_SEP);
  8718. var whole = fraction[0];
  8719. fraction = fraction[1] || '';
  8720. var pos = 0,
  8721. lgroup = pattern.lgSize,
  8722. group = pattern.gSize;
  8723. if (whole.length >= (lgroup + group)) {
  8724. pos = whole.length - lgroup;
  8725. for (var i = 0; i < pos; i++) {
  8726. if ((pos - i)%group === 0 && i !== 0) {
  8727. formatedText += groupSep;
  8728. }
  8729. formatedText += whole.charAt(i);
  8730. }
  8731. }
  8732. for (i = pos; i < whole.length; i++) {
  8733. if ((whole.length - i)%lgroup === 0 && i !== 0) {
  8734. formatedText += groupSep;
  8735. }
  8736. formatedText += whole.charAt(i);
  8737. }
  8738. // format fraction part.
  8739. while(fraction.length < fractionSize) {
  8740. fraction += '0';
  8741. }
  8742. if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize);
  8743. }
  8744. parts.push(isNegative ? pattern.negPre : pattern.posPre);
  8745. parts.push(formatedText);
  8746. parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
  8747. return parts.join('');
  8748. }
  8749. function padNumber(num, digits, trim) {
  8750. var neg = '';
  8751. if (num < 0) {
  8752. neg = '-';
  8753. num = -num;
  8754. }
  8755. num = '' + num;
  8756. while(num.length < digits) num = '0' + num;
  8757. if (trim)
  8758. num = num.substr(num.length - digits);
  8759. return neg + num;
  8760. }
  8761. function dateGetter(name, size, offset, trim) {
  8762. return function(date) {
  8763. var value = date['get' + name]();
  8764. if (offset > 0 || value > -offset)
  8765. value += offset;
  8766. if (value === 0 && offset == -12 ) value = 12;
  8767. return padNumber(value, size, trim);
  8768. };
  8769. }
  8770. function dateStrGetter(name, shortForm) {
  8771. return function(date, formats) {
  8772. var value = date['get' + name]();
  8773. var get = uppercase(shortForm ? ('SHORT' + name) : name);
  8774. return formats[get][value];
  8775. };
  8776. }
  8777. function timeZoneGetter(date) {
  8778. var offset = date.getTimezoneOffset();
  8779. return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
  8780. }
  8781. function ampmGetter(date, formats) {
  8782. return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
  8783. }
  8784. var DATE_FORMATS = {
  8785. yyyy: dateGetter('FullYear', 4),
  8786. yy: dateGetter('FullYear', 2, 0, true),
  8787. y: dateGetter('FullYear', 1),
  8788. MMMM: dateStrGetter('Month'),
  8789. MMM: dateStrGetter('Month', true),
  8790. MM: dateGetter('Month', 2, 1),
  8791. M: dateGetter('Month', 1, 1),
  8792. dd: dateGetter('Date', 2),
  8793. d: dateGetter('Date', 1),
  8794. HH: dateGetter('Hours', 2),
  8795. H: dateGetter('Hours', 1),
  8796. hh: dateGetter('Hours', 2, -12),
  8797. h: dateGetter('Hours', 1, -12),
  8798. mm: dateGetter('Minutes', 2),
  8799. m: dateGetter('Minutes', 1),
  8800. ss: dateGetter('Seconds', 2),
  8801. s: dateGetter('Seconds', 1),
  8802. EEEE: dateStrGetter('Day'),
  8803. EEE: dateStrGetter('Day', true),
  8804. a: ampmGetter,
  8805. Z: timeZoneGetter
  8806. };
  8807. var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
  8808. NUMBER_STRING = /^\d+$/;
  8809. /**
  8810. * @ngdoc filter
  8811. * @name ng.filter:date
  8812. * @function
  8813. *
  8814. * @description
  8815. * Formats `date` to a string based on the requested `format`.
  8816. *
  8817. * `format` string can be composed of the following elements:
  8818. *
  8819. * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
  8820. * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
  8821. * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
  8822. * * `'MMMM'`: Month in year (January-December)
  8823. * * `'MMM'`: Month in year (Jan-Dec)
  8824. * * `'MM'`: Month in year, padded (01-12)
  8825. * * `'M'`: Month in year (1-12)
  8826. * * `'dd'`: Day in month, padded (01-31)
  8827. * * `'d'`: Day in month (1-31)
  8828. * * `'EEEE'`: Day in Week,(Sunday-Saturday)
  8829. * * `'EEE'`: Day in Week, (Sun-Sat)
  8830. * * `'HH'`: Hour in day, padded (00-23)
  8831. * * `'H'`: Hour in day (0-23)
  8832. * * `'hh'`: Hour in am/pm, padded (01-12)
  8833. * * `'h'`: Hour in am/pm, (1-12)
  8834. * * `'mm'`: Minute in hour, padded (00-59)
  8835. * * `'m'`: Minute in hour (0-59)
  8836. * * `'ss'`: Second in minute, padded (00-59)
  8837. * * `'s'`: Second in minute (0-59)
  8838. * * `'a'`: am/pm marker
  8839. * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
  8840. *
  8841. * `format` string can also be one of the following predefined
  8842. * {@link guide/i18n localizable formats}:
  8843. *
  8844. * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
  8845. * (e.g. Sep 3, 2010 12:05:08 pm)
  8846. * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
  8847. * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
  8848. * (e.g. Friday, September 3, 2010)
  8849. * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010
  8850. * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
  8851. * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
  8852. * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
  8853. * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
  8854. *
  8855. * `format` string can contain literal values. These need to be quoted with single quotes (e.g.
  8856. * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
  8857. * (e.g. `"h o''clock"`).
  8858. *
  8859. * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
  8860. * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
  8861. * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
  8862. * @param {string=} format Formatting rules (see Description). If not specified,
  8863. * `mediumDate` is used.
  8864. * @returns {string} Formatted string or the input if input is not recognized as date/millis.
  8865. *
  8866. * @example
  8867. <doc:example>
  8868. <doc:source>
  8869. <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
  8870. {{1288323623006 | date:'medium'}}<br>
  8871. <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
  8872. {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
  8873. <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
  8874. {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
  8875. </doc:source>
  8876. <doc:scenario>
  8877. it('should format date', function() {
  8878. expect(binding("1288323623006 | date:'medium'")).
  8879. toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
  8880. expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
  8881. toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
  8882. expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
  8883. toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
  8884. });
  8885. </doc:scenario>
  8886. </doc:example>
  8887. */
  8888. dateFilter.$inject = ['$locale'];
  8889. function dateFilter($locale) {
  8890. var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
  8891. function jsonStringToDate(string){
  8892. var match;
  8893. if (match = string.match(R_ISO8601_STR)) {
  8894. var date = new Date(0),
  8895. tzHour = 0,
  8896. tzMin = 0;
  8897. if (match[9]) {
  8898. tzHour = int(match[9] + match[10]);
  8899. tzMin = int(match[9] + match[11]);
  8900. }
  8901. date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
  8902. date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
  8903. return date;
  8904. }
  8905. return string;
  8906. }
  8907. return function(date, format) {
  8908. var text = '',
  8909. parts = [],
  8910. fn, match;
  8911. format = format || 'mediumDate';
  8912. format = $locale.DATETIME_FORMATS[format] || format;
  8913. if (isString(date)) {
  8914. if (NUMBER_STRING.test(date)) {
  8915. date = int(date);
  8916. } else {
  8917. date = jsonStringToDate(date);
  8918. }
  8919. }
  8920. if (isNumber(date)) {
  8921. date = new Date(date);
  8922. }
  8923. if (!isDate(date)) {
  8924. return date;
  8925. }
  8926. while(format) {
  8927. match = DATE_FORMATS_SPLIT.exec(format);
  8928. if (match) {
  8929. parts = concat(parts, match, 1);
  8930. format = parts.pop();
  8931. } else {
  8932. parts.push(format);
  8933. format = null;
  8934. }
  8935. }
  8936. forEach(parts, function(value){
  8937. fn = DATE_FORMATS[value];
  8938. text += fn ? fn(date, $locale.DATETIME_FORMATS)
  8939. : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
  8940. });
  8941. return text;
  8942. };
  8943. }
  8944. /**
  8945. * @ngdoc filter
  8946. * @name ng.filter:json
  8947. * @function
  8948. *
  8949. * @description
  8950. * Allows you to convert a JavaScript object into JSON string.
  8951. *
  8952. * This filter is mostly useful for debugging. When using the double curly {{value}} notation
  8953. * the binding is automatically converted to JSON.
  8954. *
  8955. * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
  8956. * @returns {string} JSON string.
  8957. *
  8958. *
  8959. * @example:
  8960. <doc:example>
  8961. <doc:source>
  8962. <pre>{{ {'name':'value'} | json }}</pre>
  8963. </doc:source>
  8964. <doc:scenario>
  8965. it('should jsonify filtered objects', function() {
  8966. expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
  8967. });
  8968. </doc:scenario>
  8969. </doc:example>
  8970. *
  8971. */
  8972. function jsonFilter() {
  8973. return function(object) {
  8974. return toJson(object, true);
  8975. };
  8976. }
  8977. /**
  8978. * @ngdoc filter
  8979. * @name ng.filter:lowercase
  8980. * @function
  8981. * @description
  8982. * Converts string to lowercase.
  8983. * @see angular.lowercase
  8984. */
  8985. var lowercaseFilter = valueFn(lowercase);
  8986. /**
  8987. * @ngdoc filter
  8988. * @name ng.filter:uppercase
  8989. * @function
  8990. * @description
  8991. * Converts string to uppercase.
  8992. * @see angular.uppercase
  8993. */
  8994. var uppercaseFilter = valueFn(uppercase);
  8995. /**
  8996. * @ngdoc function
  8997. * @name ng.filter:limitTo
  8998. * @function
  8999. *
  9000. * @description
  9001. * Creates a new array containing only a specified number of elements in an array. The elements
  9002. * are taken from either the beginning or the end of the source array, as specified by the
  9003. * value and sign (positive or negative) of `limit`.
  9004. *
  9005. * Note: This function is used to augment the `Array` type in Angular expressions. See
  9006. * {@link ng.$filter} for more information about Angular arrays.
  9007. *
  9008. * @param {Array} array Source array to be limited.
  9009. * @param {string|Number} limit The length of the returned array. If the `limit` number is
  9010. * positive, `limit` number of items from the beginning of the source array are copied.
  9011. * If the number is negative, `limit` number of items from the end of the source array are
  9012. * copied. The `limit` will be trimmed if it exceeds `array.length`
  9013. * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
  9014. * elements.
  9015. *
  9016. * @example
  9017. <doc:example>
  9018. <doc:source>
  9019. <script>
  9020. function Ctrl($scope) {
  9021. $scope.numbers = [1,2,3,4,5,6,7,8,9];
  9022. $scope.limit = 3;
  9023. }
  9024. </script>
  9025. <div ng-controller="Ctrl">
  9026. Limit {{numbers}} to: <input type="integer" ng-model="limit">
  9027. <p>Output: {{ numbers | limitTo:limit }}</p>
  9028. </div>
  9029. </doc:source>
  9030. <doc:scenario>
  9031. it('should limit the numer array to first three items', function() {
  9032. expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
  9033. expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
  9034. });
  9035. it('should update the output when -3 is entered', function() {
  9036. input('limit').enter(-3);
  9037. expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
  9038. });
  9039. it('should not exceed the maximum size of input array', function() {
  9040. input('limit').enter(100);
  9041. expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
  9042. });
  9043. </doc:scenario>
  9044. </doc:example>
  9045. */
  9046. function limitToFilter(){
  9047. return function(array, limit) {
  9048. if (!(array instanceof Array)) return array;
  9049. limit = int(limit);
  9050. var out = [],
  9051. i, n;
  9052. // check that array is iterable
  9053. if (!array || !(array instanceof Array))
  9054. return out;
  9055. // if abs(limit) exceeds maximum length, trim it
  9056. if (limit > array.length)
  9057. limit = array.length;
  9058. else if (limit < -array.length)
  9059. limit = -array.length;
  9060. if (limit > 0) {
  9061. i = 0;
  9062. n = limit;
  9063. } else {
  9064. i = array.length + limit;
  9065. n = array.length;
  9066. }
  9067. for (; i<n; i++) {
  9068. out.push(array[i]);
  9069. }
  9070. return out;
  9071. }
  9072. }
  9073. /**
  9074. * @ngdoc function
  9075. * @name ng.filter:orderBy
  9076. * @function
  9077. *
  9078. * @description
  9079. * Orders a specified `array` by the `expression` predicate.
  9080. *
  9081. * Note: this function is used to augment the `Array` type in Angular expressions. See
  9082. * {@link ng.$filter} for more informaton about Angular arrays.
  9083. *
  9084. * @param {Array} array The array to sort.
  9085. * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
  9086. * used by the comparator to determine the order of elements.
  9087. *
  9088. * Can be one of:
  9089. *
  9090. * - `function`: Getter function. The result of this function will be sorted using the
  9091. * `<`, `=`, `>` operator.
  9092. * - `string`: An Angular expression which evaluates to an object to order by, such as 'name'
  9093. * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control
  9094. * ascending or descending sort order (for example, +name or -name).
  9095. * - `Array`: An array of function or string predicates. The first predicate in the array
  9096. * is used for sorting, but when two items are equivalent, the next predicate is used.
  9097. *
  9098. * @param {boolean=} reverse Reverse the order the array.
  9099. * @returns {Array} Sorted copy of the source array.
  9100. *
  9101. * @example
  9102. <doc:example>
  9103. <doc:source>
  9104. <script>
  9105. function Ctrl($scope) {
  9106. $scope.friends =
  9107. [{name:'John', phone:'555-1212', age:10},
  9108. {name:'Mary', phone:'555-9876', age:19},
  9109. {name:'Mike', phone:'555-4321', age:21},
  9110. {name:'Adam', phone:'555-5678', age:35},
  9111. {name:'Julie', phone:'555-8765', age:29}]
  9112. $scope.predicate = '-age';
  9113. }
  9114. </script>
  9115. <div ng-controller="Ctrl">
  9116. <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
  9117. <hr/>
  9118. [ <a href="" ng-click="predicate=''">unsorted</a> ]
  9119. <table class="friend">
  9120. <tr>
  9121. <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a>
  9122. (<a href ng-click="predicate = '-name'; reverse=false">^</a>)</th>
  9123. <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
  9124. <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th>
  9125. <tr>
  9126. <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
  9127. <td>{{friend.name}}</td>
  9128. <td>{{friend.phone}}</td>
  9129. <td>{{friend.age}}</td>
  9130. <tr>
  9131. </table>
  9132. </div>
  9133. </doc:source>
  9134. <doc:scenario>
  9135. it('should be reverse ordered by aged', function() {
  9136. expect(binding('predicate')).toBe('-age');
  9137. expect(repeater('table.friend', 'friend in friends').column('friend.age')).
  9138. toEqual(['35', '29', '21', '19', '10']);
  9139. expect(repeater('table.friend', 'friend in friends').column('friend.name')).
  9140. toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
  9141. });
  9142. it('should reorder the table when user selects different predicate', function() {
  9143. element('.doc-example-live a:contains("Name")').click();
  9144. expect(repeater('table.friend', 'friend in friends').column('friend.name')).
  9145. toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
  9146. expect(repeater('table.friend', 'friend in friends').column('friend.age')).
  9147. toEqual(['35', '10', '29', '19', '21']);
  9148. element('.doc-example-live a:contains("Phone")').click();
  9149. expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
  9150. toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
  9151. expect(repeater('table.friend', 'friend in friends').column('friend.name')).
  9152. toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
  9153. });
  9154. </doc:scenario>
  9155. </doc:example>
  9156. */
  9157. orderByFilter.$inject = ['$parse'];
  9158. function orderByFilter($parse){
  9159. return function(array, sortPredicate, reverseOrder) {
  9160. if (!(array instanceof Array)) return array;
  9161. if (!sortPredicate) return array;
  9162. sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
  9163. sortPredicate = map(sortPredicate, function(predicate){
  9164. var descending = false, get = predicate || identity;
  9165. if (isString(predicate)) {
  9166. if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
  9167. descending = predicate.charAt(0) == '-';
  9168. predicate = predicate.substring(1);
  9169. }
  9170. get = $parse(predicate);
  9171. }
  9172. return reverseComparator(function(a,b){
  9173. return compare(get(a),get(b));
  9174. }, descending);
  9175. });
  9176. var arrayCopy = [];
  9177. for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
  9178. return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
  9179. function comparator(o1, o2){
  9180. for ( var i = 0; i < sortPredicate.length; i++) {
  9181. var comp = sortPredicate[i](o1, o2);
  9182. if (comp !== 0) return comp;
  9183. }
  9184. return 0;
  9185. }
  9186. function reverseComparator(comp, descending) {
  9187. return toBoolean(descending)
  9188. ? function(a,b){return comp(b,a);}
  9189. : comp;
  9190. }
  9191. function compare(v1, v2){
  9192. var t1 = typeof v1;
  9193. var t2 = typeof v2;
  9194. if (t1 == t2) {
  9195. if (t1 == "string") v1 = v1.toLowerCase();
  9196. if (t1 == "string") v2 = v2.toLowerCase();
  9197. if (v1 === v2) return 0;
  9198. return v1 < v2 ? -1 : 1;
  9199. } else {
  9200. return t1 < t2 ? -1 : 1;
  9201. }
  9202. }
  9203. }
  9204. }
  9205. function ngDirective(directive) {
  9206. if (isFunction(directive)) {
  9207. directive = {
  9208. link: directive
  9209. }
  9210. }
  9211. directive.restrict = directive.restrict || 'AC';
  9212. return valueFn(directive);
  9213. }
  9214. /**
  9215. * @ngdoc directive
  9216. * @name ng.directive:a
  9217. * @restrict E
  9218. *
  9219. * @description
  9220. * Modifies the default behavior of html A tag, so that the default action is prevented when href
  9221. * attribute is empty.
  9222. *
  9223. * The reasoning for this change is to allow easy creation of action links with `ngClick` directive
  9224. * without changing the location or causing page reloads, e.g.:
  9225. * <a href="" ng-click="model.$save()">Save</a>
  9226. */
  9227. var htmlAnchorDirective = valueFn({
  9228. restrict: 'E',
  9229. compile: function(element, attr) {
  9230. // turn <a href ng-click="..">link</a> into a link in IE
  9231. // but only if it doesn't have name attribute, in which case it's an anchor
  9232. if (!attr.href) {
  9233. attr.$set('href', '');
  9234. }
  9235. return function(scope, element) {
  9236. element.bind('click', function(event){
  9237. // if we have no href url, then don't navigate anywhere.
  9238. if (!element.attr('href')) {
  9239. event.preventDefault();
  9240. return false; // Needed for opera
  9241. }
  9242. });
  9243. }
  9244. }
  9245. });
  9246. /**
  9247. * @ngdoc directive
  9248. * @name ng.directive:ngHref
  9249. * @restrict A
  9250. *
  9251. * @description
  9252. * Using Angular markup like {{hash}} in an href attribute makes
  9253. * the page open to a wrong URL, if the user clicks that link before
  9254. * angular has a chance to replace the {{hash}} with actual URL, the
  9255. * link will be broken and will most likely return a 404 error.
  9256. * The `ngHref` directive solves this problem.
  9257. *
  9258. * The buggy way to write it:
  9259. * <pre>
  9260. * <a href="http://www.gravatar.com/avatar/{{hash}}"/>
  9261. * </pre>
  9262. *
  9263. * The correct way to write it:
  9264. * <pre>
  9265. * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
  9266. * </pre>
  9267. *
  9268. * @element A
  9269. * @param {template} ngHref any string which can contain `{{}}` markup.
  9270. *
  9271. * @example
  9272. * This example uses `link` variable inside `href` attribute:
  9273. <doc:example>
  9274. <doc:source>
  9275. <input ng-model="value" /><br />
  9276. <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
  9277. <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
  9278. <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
  9279. <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
  9280. <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
  9281. <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
  9282. </doc:source>
  9283. <doc:scenario>
  9284. it('should execute ng-click but not reload when href without value', function() {
  9285. element('#link-1').click();
  9286. expect(input('value').val()).toEqual('1');
  9287. expect(element('#link-1').attr('href')).toBe("");
  9288. });
  9289. it('should execute ng-click but not reload when href empty string', function() {
  9290. element('#link-2').click();
  9291. expect(input('value').val()).toEqual('2');
  9292. expect(element('#link-2').attr('href')).toBe("");
  9293. });
  9294. it('should execute ng-click and change url when ng-href specified', function() {
  9295. expect(element('#link-3').attr('href')).toBe("/123");
  9296. element('#link-3').click();
  9297. expect(browser().window().path()).toEqual('/123');
  9298. });
  9299. it('should execute ng-click but not reload when href empty string and name specified', function() {
  9300. element('#link-4').click();
  9301. expect(input('value').val()).toEqual('4');
  9302. expect(element('#link-4').attr('href')).toBe('');
  9303. });
  9304. it('should execute ng-click but not reload when no href but name specified', function() {
  9305. element('#link-5').click();
  9306. expect(input('value').val()).toEqual('5');
  9307. expect(element('#link-5').attr('href')).toBe('');
  9308. });
  9309. it('should only change url when only ng-href', function() {
  9310. input('value').enter('6');
  9311. expect(element('#link-6').attr('href')).toBe('6');
  9312. element('#link-6').click();
  9313. expect(browser().location().url()).toEqual('/6');
  9314. });
  9315. </doc:scenario>
  9316. </doc:example>
  9317. */
  9318. /**
  9319. * @ngdoc directive
  9320. * @name ng.directive:ngSrc
  9321. * @restrict A
  9322. *
  9323. * @description
  9324. * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
  9325. * work right: The browser will fetch from the URL with the literal
  9326. * text `{{hash}}` until Angular replaces the expression inside
  9327. * `{{hash}}`. The `ngSrc` directive solves this problem.
  9328. *
  9329. * The buggy way to write it:
  9330. * <pre>
  9331. * <img src="http://www.gravatar.com/avatar/{{hash}}"/>
  9332. * </pre>
  9333. *
  9334. * The correct way to write it:
  9335. * <pre>
  9336. * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
  9337. * </pre>
  9338. *
  9339. * @element IMG
  9340. * @param {template} ngSrc any string which can contain `{{}}` markup.
  9341. */
  9342. /**
  9343. * @ngdoc directive
  9344. * @name ng.directive:ngDisabled
  9345. * @restrict A
  9346. *
  9347. * @description
  9348. *
  9349. * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
  9350. * <pre>
  9351. * <div ng-init="scope = { isDisabled: false }">
  9352. * <button disabled="{{scope.isDisabled}}">Disabled</button>
  9353. * </div>
  9354. * </pre>
  9355. *
  9356. * The HTML specs do not require browsers to preserve the special attributes such as disabled.
  9357. * (The presence of them means true and absence means false)
  9358. * This prevents the angular compiler from correctly retrieving the binding expression.
  9359. * To solve this problem, we introduce the `ngDisabled` directive.
  9360. *
  9361. * @example
  9362. <doc:example>
  9363. <doc:source>
  9364. Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
  9365. <button ng-model="button" ng-disabled="checked">Button</button>
  9366. </doc:source>
  9367. <doc:scenario>
  9368. it('should toggle button', function() {
  9369. expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
  9370. input('checked').check();
  9371. expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
  9372. });
  9373. </doc:scenario>
  9374. </doc:example>
  9375. *
  9376. * @element INPUT
  9377. * @param {expression} ngDisabled Angular expression that will be evaluated.
  9378. */
  9379. /**
  9380. * @ngdoc directive
  9381. * @name ng.directive:ngChecked
  9382. * @restrict A
  9383. *
  9384. * @description
  9385. * The HTML specs do not require browsers to preserve the special attributes such as checked.
  9386. * (The presence of them means true and absence means false)
  9387. * This prevents the angular compiler from correctly retrieving the binding expression.
  9388. * To solve this problem, we introduce the `ngChecked` directive.
  9389. * @example
  9390. <doc:example>
  9391. <doc:source>
  9392. Check me to check both: <input type="checkbox" ng-model="master"><br/>
  9393. <input id="checkSlave" type="checkbox" ng-checked="master">
  9394. </doc:source>
  9395. <doc:scenario>
  9396. it('should check both checkBoxes', function() {
  9397. expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
  9398. input('master').check();
  9399. expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
  9400. });
  9401. </doc:scenario>
  9402. </doc:example>
  9403. *
  9404. * @element INPUT
  9405. * @param {expression} ngChecked Angular expression that will be evaluated.
  9406. */
  9407. /**
  9408. * @ngdoc directive
  9409. * @name ng.directive:ngMultiple
  9410. * @restrict A
  9411. *
  9412. * @description
  9413. * The HTML specs do not require browsers to preserve the special attributes such as multiple.
  9414. * (The presence of them means true and absence means false)
  9415. * This prevents the angular compiler from correctly retrieving the binding expression.
  9416. * To solve this problem, we introduce the `ngMultiple` directive.
  9417. *
  9418. * @example
  9419. <doc:example>
  9420. <doc:source>
  9421. Check me check multiple: <input type="checkbox" ng-model="checked"><br/>
  9422. <select id="select" ng-multiple="checked">
  9423. <option>Misko</option>
  9424. <option>Igor</option>
  9425. <option>Vojta</option>
  9426. <option>Di</option>
  9427. </select>
  9428. </doc:source>
  9429. <doc:scenario>
  9430. it('should toggle multiple', function() {
  9431. expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
  9432. input('checked').check();
  9433. expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
  9434. });
  9435. </doc:scenario>
  9436. </doc:example>
  9437. *
  9438. * @element SELECT
  9439. * @param {expression} ngMultiple Angular expression that will be evaluated.
  9440. */
  9441. /**
  9442. * @ngdoc directive
  9443. * @name ng.directive:ngReadonly
  9444. * @restrict A
  9445. *
  9446. * @description
  9447. * The HTML specs do not require browsers to preserve the special attributes such as readonly.
  9448. * (The presence of them means true and absence means false)
  9449. * This prevents the angular compiler from correctly retrieving the binding expression.
  9450. * To solve this problem, we introduce the `ngReadonly` directive.
  9451. * @example
  9452. <doc:example>
  9453. <doc:source>
  9454. Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
  9455. <input type="text" ng-readonly="checked" value="I'm Angular"/>
  9456. </doc:source>
  9457. <doc:scenario>
  9458. it('should toggle readonly attr', function() {
  9459. expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
  9460. input('checked').check();
  9461. expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
  9462. });
  9463. </doc:scenario>
  9464. </doc:example>
  9465. *
  9466. * @element INPUT
  9467. * @param {string} expression Angular expression that will be evaluated.
  9468. */
  9469. /**
  9470. * @ngdoc directive
  9471. * @name ng.directive:ngSelected
  9472. * @restrict A
  9473. *
  9474. * @description
  9475. * The HTML specs do not require browsers to preserve the special attributes such as selected.
  9476. * (The presence of them means true and absence means false)
  9477. * This prevents the angular compiler from correctly retrieving the binding expression.
  9478. * To solve this problem, we introduced the `ngSelected` directive.
  9479. * @example
  9480. <doc:example>
  9481. <doc:source>
  9482. Check me to select: <input type="checkbox" ng-model="selected"><br/>
  9483. <select>
  9484. <option>Hello!</option>
  9485. <option id="greet" ng-selected="selected">Greetings!</option>
  9486. </select>
  9487. </doc:source>
  9488. <doc:scenario>
  9489. it('should select Greetings!', function() {
  9490. expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
  9491. input('selected').check();
  9492. expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
  9493. });
  9494. </doc:scenario>
  9495. </doc:example>
  9496. *
  9497. * @element OPTION
  9498. * @param {string} expression Angular expression that will be evaluated.
  9499. */
  9500. var ngAttributeAliasDirectives = {};
  9501. // boolean attrs are evaluated
  9502. forEach(BOOLEAN_ATTR, function(propName, attrName) {
  9503. var normalized = directiveNormalize('ng-' + attrName);
  9504. ngAttributeAliasDirectives[normalized] = function() {
  9505. return {
  9506. priority: 100,
  9507. compile: function() {
  9508. return function(scope, element, attr) {
  9509. scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
  9510. attr.$set(attrName, !!value);
  9511. });
  9512. };
  9513. }
  9514. };
  9515. };
  9516. });
  9517. // ng-src, ng-href are interpolated
  9518. forEach(['src', 'href'], function(attrName) {
  9519. var normalized = directiveNormalize('ng-' + attrName);
  9520. ngAttributeAliasDirectives[normalized] = function() {
  9521. return {
  9522. priority: 99, // it needs to run after the attributes are interpolated
  9523. link: function(scope, element, attr) {
  9524. attr.$observe(normalized, function(value) {
  9525. if (!value)
  9526. return;
  9527. attr.$set(attrName, value);
  9528. // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
  9529. // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
  9530. // to set the property as well to achieve the desired effect
  9531. if (msie) element.prop(attrName, value);
  9532. });
  9533. }
  9534. };
  9535. };
  9536. });
  9537. var nullFormCtrl = {
  9538. $addControl: noop,
  9539. $removeControl: noop,
  9540. $setValidity: noop,
  9541. $setDirty: noop,
  9542. $setPristine: noop
  9543. };
  9544. /**
  9545. * @ngdoc object
  9546. * @name ng.directive:form.FormController
  9547. *
  9548. * @property {boolean} $pristine True if user has not interacted with the form yet.
  9549. * @property {boolean} $dirty True if user has already interacted with the form.
  9550. * @property {boolean} $valid True if all of the containg forms and controls are valid.
  9551. * @property {boolean} $invalid True if at least one containing control or form is invalid.
  9552. *
  9553. * @property {Object} $error Is an object hash, containing references to all invalid controls or
  9554. * forms, where:
  9555. *
  9556. * - keys are validation tokens (error names) — such as `REQUIRED`, `URL` or `EMAIL`),
  9557. * - values are arrays of controls or forms that are invalid with given error.
  9558. *
  9559. * @description
  9560. * `FormController` keeps track of all its controls and nested forms as well as state of them,
  9561. * such as being valid/invalid or dirty/pristine.
  9562. *
  9563. * Each {@link ng.directive:form form} directive creates an instance
  9564. * of `FormController`.
  9565. *
  9566. */
  9567. //asks for $scope to fool the BC controller module
  9568. FormController.$inject = ['$element', '$attrs', '$scope'];
  9569. function FormController(element, attrs) {
  9570. var form = this,
  9571. parentForm = element.parent().controller('form') || nullFormCtrl,
  9572. invalidCount = 0, // used to easily determine if we are valid
  9573. errors = form.$error = {},
  9574. controls = [];
  9575. // init state
  9576. form.$name = attrs.name;
  9577. form.$dirty = false;
  9578. form.$pristine = true;
  9579. form.$valid = true;
  9580. form.$invalid = false;
  9581. parentForm.$addControl(form);
  9582. // Setup initial state of the control
  9583. element.addClass(PRISTINE_CLASS);
  9584. toggleValidCss(true);
  9585. // convenience method for easy toggling of classes
  9586. function toggleValidCss(isValid, validationErrorKey) {
  9587. validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
  9588. element.
  9589. removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
  9590. addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
  9591. }
  9592. form.$addControl = function(control) {
  9593. controls.push(control);
  9594. if (control.$name && !form.hasOwnProperty(control.$name)) {
  9595. form[control.$name] = control;
  9596. }
  9597. };
  9598. form.$removeControl = function(control) {
  9599. if (control.$name && form[control.$name] === control) {
  9600. delete form[control.$name];
  9601. }
  9602. forEach(errors, function(queue, validationToken) {
  9603. form.$setValidity(validationToken, true, control);
  9604. });
  9605. arrayRemove(controls, control);
  9606. };
  9607. form.$setValidity = function(validationToken, isValid, control) {
  9608. var queue = errors[validationToken];
  9609. if (isValid) {
  9610. if (queue) {
  9611. arrayRemove(queue, control);
  9612. if (!queue.length) {
  9613. invalidCount--;
  9614. if (!invalidCount) {
  9615. toggleValidCss(isValid);
  9616. form.$valid = true;
  9617. form.$invalid = false;
  9618. }
  9619. errors[validationToken] = false;
  9620. toggleValidCss(true, validationToken);
  9621. parentForm.$setValidity(validationToken, true, form);
  9622. }
  9623. }
  9624. } else {
  9625. if (!invalidCount) {
  9626. toggleValidCss(isValid);
  9627. }
  9628. if (queue) {
  9629. if (includes(queue, control)) return;
  9630. } else {
  9631. errors[validationToken] = queue = [];
  9632. invalidCount++;
  9633. toggleValidCss(false, validationToken);
  9634. parentForm.$setValidity(validationToken, false, form);
  9635. }
  9636. queue.push(control);
  9637. form.$valid = false;
  9638. form.$invalid = true;
  9639. }
  9640. };
  9641. form.$setDirty = function() {
  9642. element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
  9643. form.$dirty = true;
  9644. form.$pristine = false;
  9645. parentForm.$setDirty();
  9646. };
  9647. /**
  9648. * @ngdoc function
  9649. * @name ng.directive:form.FormController#$setPristine
  9650. * @methodOf ng.directive:form.FormController
  9651. *
  9652. * @description
  9653. * Sets the form to its pristine state.
  9654. *
  9655. * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
  9656. * state (ng-pristine class). This method will also propagate to all the controls contained
  9657. * in this form.
  9658. *
  9659. * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
  9660. * saving or resetting it.
  9661. */
  9662. form.$setPristine = function () {
  9663. element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
  9664. form.$dirty = false;
  9665. form.$pristine = true;
  9666. forEach(controls, function(control) {
  9667. control.$setPristine();
  9668. });
  9669. };
  9670. }
  9671. /**
  9672. * @ngdoc directive
  9673. * @name ng.directive:ngForm
  9674. * @restrict EAC
  9675. *
  9676. * @description
  9677. * Nestable alias of {@link ng.directive:form `form`} directive. HTML
  9678. * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
  9679. * sub-group of controls needs to be determined.
  9680. *
  9681. * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
  9682. * related scope, under this name.
  9683. *
  9684. */
  9685. /**
  9686. * @ngdoc directive
  9687. * @name ng.directive:form
  9688. * @restrict E
  9689. *
  9690. * @description
  9691. * Directive that instantiates
  9692. * {@link ng.directive:form.FormController FormController}.
  9693. *
  9694. * If `name` attribute is specified, the form controller is published onto the current scope under
  9695. * this name.
  9696. *
  9697. * # Alias: {@link ng.directive:ngForm `ngForm`}
  9698. *
  9699. * In angular forms can be nested. This means that the outer form is valid when all of the child
  9700. * forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this
  9701. * reason angular provides {@link ng.directive:ngForm `ngForm`} alias
  9702. * which behaves identical to `<form>` but allows form nesting.
  9703. *
  9704. *
  9705. * # CSS classes
  9706. * - `ng-valid` Is set if the form is valid.
  9707. * - `ng-invalid` Is set if the form is invalid.
  9708. * - `ng-pristine` Is set if the form is pristine.
  9709. * - `ng-dirty` Is set if the form is dirty.
  9710. *
  9711. *
  9712. * # Submitting a form and preventing default action
  9713. *
  9714. * Since the role of forms in client-side Angular applications is different than in classical
  9715. * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
  9716. * page reload that sends the data to the server. Instead some javascript logic should be triggered
  9717. * to handle the form submission in application specific way.
  9718. *
  9719. * For this reason, Angular prevents the default action (form submission to the server) unless the
  9720. * `<form>` element has an `action` attribute specified.
  9721. *
  9722. * You can use one of the following two ways to specify what javascript method should be called when
  9723. * a form is submitted:
  9724. *
  9725. * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
  9726. * - {@link ng.directive:ngClick ngClick} directive on the first
  9727. * button or input field of type submit (input[type=submit])
  9728. *
  9729. * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This
  9730. * is because of the following form submission rules coming from the html spec:
  9731. *
  9732. * - If a form has only one input field then hitting enter in this field triggers form submit
  9733. * (`ngSubmit`)
  9734. * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
  9735. * doesn't trigger submit
  9736. * - if a form has one or more input fields and one or more buttons or input[type=submit] then
  9737. * hitting enter in any of the input fields will trigger the click handler on the *first* button or
  9738. * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
  9739. *
  9740. * @param {string=} name Name of the form. If specified, the form controller will be published into
  9741. * related scope, under this name.
  9742. *
  9743. * @example
  9744. <doc:example>
  9745. <doc:source>
  9746. <script>
  9747. function Ctrl($scope) {
  9748. $scope.userType = 'guest';
  9749. }
  9750. </script>
  9751. <form name="myForm" ng-controller="Ctrl">
  9752. userType: <input name="input" ng-model="userType" required>
  9753. <span class="error" ng-show="myForm.input.$error.REQUIRED">Required!</span><br>
  9754. <tt>userType = {{userType}}</tt><br>
  9755. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
  9756. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
  9757. <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
  9758. <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br>
  9759. </form>
  9760. </doc:source>
  9761. <doc:scenario>
  9762. it('should initialize to model', function() {
  9763. expect(binding('userType')).toEqual('guest');
  9764. expect(binding('myForm.input.$valid')).toEqual('true');
  9765. });
  9766. it('should be invalid if empty', function() {
  9767. input('userType').enter('');
  9768. expect(binding('userType')).toEqual('');
  9769. expect(binding('myForm.input.$valid')).toEqual('false');
  9770. });
  9771. </doc:scenario>
  9772. </doc:example>
  9773. */
  9774. var formDirectiveFactory = function(isNgForm) {
  9775. return ['$timeout', function($timeout) {
  9776. var formDirective = {
  9777. name: 'form',
  9778. restrict: 'E',
  9779. controller: FormController,
  9780. compile: function() {
  9781. return {
  9782. pre: function(scope, formElement, attr, controller) {
  9783. if (!attr.action) {
  9784. // we can't use jq events because if a form is destroyed during submission the default
  9785. // action is not prevented. see #1238
  9786. //
  9787. // IE 9 is not affected because it doesn't fire a submit event and try to do a full
  9788. // page reload if the form was destroyed by submission of the form via a click handler
  9789. // on a button in the form. Looks like an IE9 specific bug.
  9790. var preventDefaultListener = function(event) {
  9791. event.preventDefault
  9792. ? event.preventDefault()
  9793. : event.returnValue = false; // IE
  9794. };
  9795. addEventListenerFn(formElement[0], 'submit', preventDefaultListener);
  9796. // unregister the preventDefault listener so that we don't not leak memory but in a
  9797. // way that will achieve the prevention of the default action.
  9798. formElement.bind('$destroy', function() {
  9799. $timeout(function() {
  9800. removeEventListenerFn(formElement[0], 'submit', preventDefaultListener);
  9801. }, 0, false);
  9802. });
  9803. }
  9804. var parentFormCtrl = formElement.parent().controller('form'),
  9805. alias = attr.name || attr.ngForm;
  9806. if (alias) {
  9807. scope[alias] = controller;
  9808. }
  9809. if (parentFormCtrl) {
  9810. formElement.bind('$destroy', function() {
  9811. parentFormCtrl.$removeControl(controller);
  9812. if (alias) {
  9813. scope[alias] = undefined;
  9814. }
  9815. extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
  9816. });
  9817. }
  9818. }
  9819. };
  9820. }
  9821. };
  9822. return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective;
  9823. }];
  9824. };
  9825. var formDirective = formDirectiveFactory();
  9826. var ngFormDirective = formDirectiveFactory(true);
  9827. var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
  9828. var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
  9829. var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
  9830. var inputType = {
  9831. /**
  9832. * @ngdoc inputType
  9833. * @name ng.directive:input.text
  9834. *
  9835. * @description
  9836. * Standard HTML text input with angular data binding.
  9837. *
  9838. * @param {string} ngModel Assignable angular expression to data-bind to.
  9839. * @param {string=} name Property name of the form under which the control is published.
  9840. * @param {string=} required Adds `required` validation error key if the value is not entered.
  9841. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
  9842. * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
  9843. * `required` when you want to data-bind to the `required` attribute.
  9844. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  9845. * minlength.
  9846. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  9847. * maxlength.
  9848. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  9849. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  9850. * patterns defined as scope expressions.
  9851. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  9852. * interaction with the input element.
  9853. * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trimming the
  9854. * input.
  9855. *
  9856. * @example
  9857. <doc:example>
  9858. <doc:source>
  9859. <script>
  9860. function Ctrl($scope) {
  9861. $scope.text = 'guest';
  9862. $scope.word = /^\s*\w*\s*$/;
  9863. }
  9864. </script>
  9865. <form name="myForm" ng-controller="Ctrl">
  9866. Single word: <input type="text" name="input" ng-model="text"
  9867. ng-pattern="word" required ng-trim="false">
  9868. <span class="error" ng-show="myForm.input.$error.required">
  9869. Required!</span>
  9870. <span class="error" ng-show="myForm.input.$error.pattern">
  9871. Single word only!</span>
  9872. <tt>text = {{text}}</tt><br/>
  9873. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
  9874. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
  9875. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  9876. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  9877. </form>
  9878. </doc:source>
  9879. <doc:scenario>
  9880. it('should initialize to model', function() {
  9881. expect(binding('text')).toEqual('guest');
  9882. expect(binding('myForm.input.$valid')).toEqual('true');
  9883. });
  9884. it('should be invalid if empty', function() {
  9885. input('text').enter('');
  9886. expect(binding('text')).toEqual('');
  9887. expect(binding('myForm.input.$valid')).toEqual('false');
  9888. });
  9889. it('should be invalid if multi word', function() {
  9890. input('text').enter('hello world');
  9891. expect(binding('myForm.input.$valid')).toEqual('false');
  9892. });
  9893. it('should not be trimmed', function() {
  9894. input('text').enter('untrimmed ');
  9895. expect(binding('text')).toEqual('untrimmed ');
  9896. expect(binding('myForm.input.$valid')).toEqual('true');
  9897. });
  9898. </doc:scenario>
  9899. </doc:example>
  9900. */
  9901. 'text': textInputType,
  9902. /**
  9903. * @ngdoc inputType
  9904. * @name ng.directive:input.number
  9905. *
  9906. * @description
  9907. * Text input with number validation and transformation. Sets the `number` validation
  9908. * error if not a valid number.
  9909. *
  9910. * @param {string} ngModel Assignable angular expression to data-bind to.
  9911. * @param {string=} name Property name of the form under which the control is published.
  9912. * @param {string=} min Sets the `min` validation error key if the value entered is less then `min`.
  9913. * @param {string=} max Sets the `max` validation error key if the value entered is greater then `min`.
  9914. * @param {string=} required Sets `required` validation error key if the value is not entered.
  9915. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
  9916. * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
  9917. * `required` when you want to data-bind to the `required` attribute.
  9918. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  9919. * minlength.
  9920. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  9921. * maxlength.
  9922. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  9923. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  9924. * patterns defined as scope expressions.
  9925. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  9926. * interaction with the input element.
  9927. *
  9928. * @example
  9929. <doc:example>
  9930. <doc:source>
  9931. <script>
  9932. function Ctrl($scope) {
  9933. $scope.value = 12;
  9934. }
  9935. </script>
  9936. <form name="myForm" ng-controller="Ctrl">
  9937. Number: <input type="number" name="input" ng-model="value"
  9938. min="0" max="99" required>
  9939. <span class="error" ng-show="myForm.list.$error.required">
  9940. Required!</span>
  9941. <span class="error" ng-show="myForm.list.$error.number">
  9942. Not valid number!</span>
  9943. <tt>value = {{value}}</tt><br/>
  9944. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
  9945. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
  9946. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  9947. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  9948. </form>
  9949. </doc:source>
  9950. <doc:scenario>
  9951. it('should initialize to model', function() {
  9952. expect(binding('value')).toEqual('12');
  9953. expect(binding('myForm.input.$valid')).toEqual('true');
  9954. });
  9955. it('should be invalid if empty', function() {
  9956. input('value').enter('');
  9957. expect(binding('value')).toEqual('');
  9958. expect(binding('myForm.input.$valid')).toEqual('false');
  9959. });
  9960. it('should be invalid if over max', function() {
  9961. input('value').enter('123');
  9962. expect(binding('value')).toEqual('');
  9963. expect(binding('myForm.input.$valid')).toEqual('false');
  9964. });
  9965. </doc:scenario>
  9966. </doc:example>
  9967. */
  9968. 'number': numberInputType,
  9969. /**
  9970. * @ngdoc inputType
  9971. * @name ng.directive:input.url
  9972. *
  9973. * @description
  9974. * Text input with URL validation. Sets the `url` validation error key if the content is not a
  9975. * valid URL.
  9976. *
  9977. * @param {string} ngModel Assignable angular expression to data-bind to.
  9978. * @param {string=} name Property name of the form under which the control is published.
  9979. * @param {string=} required Sets `required` validation error key if the value is not entered.
  9980. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
  9981. * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
  9982. * `required` when you want to data-bind to the `required` attribute.
  9983. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  9984. * minlength.
  9985. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  9986. * maxlength.
  9987. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  9988. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  9989. * patterns defined as scope expressions.
  9990. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  9991. * interaction with the input element.
  9992. *
  9993. * @example
  9994. <doc:example>
  9995. <doc:source>
  9996. <script>
  9997. function Ctrl($scope) {
  9998. $scope.text = 'http://google.com';
  9999. }
  10000. </script>
  10001. <form name="myForm" ng-controller="Ctrl">
  10002. URL: <input type="url" name="input" ng-model="text" required>
  10003. <span class="error" ng-show="myForm.input.$error.required">
  10004. Required!</span>
  10005. <span class="error" ng-show="myForm.input.$error.url">
  10006. Not valid url!</span>
  10007. <tt>text = {{text}}</tt><br/>
  10008. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
  10009. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
  10010. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  10011. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  10012. <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
  10013. </form>
  10014. </doc:source>
  10015. <doc:scenario>
  10016. it('should initialize to model', function() {
  10017. expect(binding('text')).toEqual('http://google.com');
  10018. expect(binding('myForm.input.$valid')).toEqual('true');
  10019. });
  10020. it('should be invalid if empty', function() {
  10021. input('text').enter('');
  10022. expect(binding('text')).toEqual('');
  10023. expect(binding('myForm.input.$valid')).toEqual('false');
  10024. });
  10025. it('should be invalid if not url', function() {
  10026. input('text').enter('xxx');
  10027. expect(binding('myForm.input.$valid')).toEqual('false');
  10028. });
  10029. </doc:scenario>
  10030. </doc:example>
  10031. */
  10032. 'url': urlInputType,
  10033. /**
  10034. * @ngdoc inputType
  10035. * @name ng.directive:input.email
  10036. *
  10037. * @description
  10038. * Text input with email validation. Sets the `email` validation error key if not a valid email
  10039. * address.
  10040. *
  10041. * @param {string} ngModel Assignable angular expression to data-bind to.
  10042. * @param {string=} name Property name of the form under which the control is published.
  10043. * @param {string=} required Sets `required` validation error key if the value is not entered.
  10044. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
  10045. * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
  10046. * `required` when you want to data-bind to the `required` attribute.
  10047. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  10048. * minlength.
  10049. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  10050. * maxlength.
  10051. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  10052. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  10053. * patterns defined as scope expressions.
  10054. *
  10055. * @example
  10056. <doc:example>
  10057. <doc:source>
  10058. <script>
  10059. function Ctrl($scope) {
  10060. $scope.text = 'me@example.com';
  10061. }
  10062. </script>
  10063. <form name="myForm" ng-controller="Ctrl">
  10064. Email: <input type="email" name="input" ng-model="text" required>
  10065. <span class="error" ng-show="myForm.input.$error.required">
  10066. Required!</span>
  10067. <span class="error" ng-show="myForm.input.$error.email">
  10068. Not valid email!</span>
  10069. <tt>text = {{text}}</tt><br/>
  10070. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
  10071. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
  10072. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  10073. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  10074. <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
  10075. </form>
  10076. </doc:source>
  10077. <doc:scenario>
  10078. it('should initialize to model', function() {
  10079. expect(binding('text')).toEqual('me@example.com');
  10080. expect(binding('myForm.input.$valid')).toEqual('true');
  10081. });
  10082. it('should be invalid if empty', function() {
  10083. input('text').enter('');
  10084. expect(binding('text')).toEqual('');
  10085. expect(binding('myForm.input.$valid')).toEqual('false');
  10086. });
  10087. it('should be invalid if not email', function() {
  10088. input('text').enter('xxx');
  10089. expect(binding('myForm.input.$valid')).toEqual('false');
  10090. });
  10091. </doc:scenario>
  10092. </doc:example>
  10093. */
  10094. 'email': emailInputType,
  10095. /**
  10096. * @ngdoc inputType
  10097. * @name ng.directive:input.radio
  10098. *
  10099. * @description
  10100. * HTML radio button.
  10101. *
  10102. * @param {string} ngModel Assignable angular expression to data-bind to.
  10103. * @param {string} value The value to which the expression should be set when selected.
  10104. * @param {string=} name Property name of the form under which the control is published.
  10105. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  10106. * interaction with the input element.
  10107. *
  10108. * @example
  10109. <doc:example>
  10110. <doc:source>
  10111. <script>
  10112. function Ctrl($scope) {
  10113. $scope.color = 'blue';
  10114. }
  10115. </script>
  10116. <form name="myForm" ng-controller="Ctrl">
  10117. <input type="radio" ng-model="color" value="red"> Red <br/>
  10118. <input type="radio" ng-model="color" value="green"> Green <br/>
  10119. <input type="radio" ng-model="color" value="blue"> Blue <br/>
  10120. <tt>color = {{color}}</tt><br/>
  10121. </form>
  10122. </doc:source>
  10123. <doc:scenario>
  10124. it('should change state', function() {
  10125. expect(binding('color')).toEqual('blue');
  10126. input('color').select('red');
  10127. expect(binding('color')).toEqual('red');
  10128. });
  10129. </doc:scenario>
  10130. </doc:example>
  10131. */
  10132. 'radio': radioInputType,
  10133. /**
  10134. * @ngdoc inputType
  10135. * @name ng.directive:input.checkbox
  10136. *
  10137. * @description
  10138. * HTML checkbox.
  10139. *
  10140. * @param {string} ngModel Assignable angular expression to data-bind to.
  10141. * @param {string=} name Property name of the form under which the control is published.
  10142. * @param {string=} ngTrueValue The value to which the expression should be set when selected.
  10143. * @param {string=} ngFalseValue The value to which the expression should be set when not selected.
  10144. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  10145. * interaction with the input element.
  10146. *
  10147. * @example
  10148. <doc:example>
  10149. <doc:source>
  10150. <script>
  10151. function Ctrl($scope) {
  10152. $scope.value1 = true;
  10153. $scope.value2 = 'YES'
  10154. }
  10155. </script>
  10156. <form name="myForm" ng-controller="Ctrl">
  10157. Value1: <input type="checkbox" ng-model="value1"> <br/>
  10158. Value2: <input type="checkbox" ng-model="value2"
  10159. ng-true-value="YES" ng-false-value="NO"> <br/>
  10160. <tt>value1 = {{value1}}</tt><br/>
  10161. <tt>value2 = {{value2}}</tt><br/>
  10162. </form>
  10163. </doc:source>
  10164. <doc:scenario>
  10165. it('should change state', function() {
  10166. expect(binding('value1')).toEqual('true');
  10167. expect(binding('value2')).toEqual('YES');
  10168. input('value1').check();
  10169. input('value2').check();
  10170. expect(binding('value1')).toEqual('false');
  10171. expect(binding('value2')).toEqual('NO');
  10172. });
  10173. </doc:scenario>
  10174. </doc:example>
  10175. */
  10176. 'checkbox': checkboxInputType,
  10177. 'hidden': noop,
  10178. 'button': noop,
  10179. 'submit': noop,
  10180. 'reset': noop
  10181. };
  10182. function isEmpty(value) {
  10183. return isUndefined(value) || value === '' || value === null || value !== value;
  10184. }
  10185. function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  10186. var listener = function() {
  10187. var value = element.val();
  10188. // By default we will trim the value
  10189. // If the attribute ng-trim exists we will avoid trimming
  10190. // e.g. <input ng-model="foo" ng-trim="false">
  10191. if (toBoolean(attr.ngTrim || 'T')) {
  10192. value = trim(value);
  10193. }
  10194. if (ctrl.$viewValue !== value) {
  10195. scope.$apply(function() {
  10196. ctrl.$setViewValue(value);
  10197. });
  10198. }
  10199. };
  10200. // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
  10201. // input event on backspace, delete or cut
  10202. if ($sniffer.hasEvent('input')) {
  10203. element.bind('input', listener);
  10204. } else {
  10205. var timeout;
  10206. element.bind('keydown', function(event) {
  10207. var key = event.keyCode;
  10208. // ignore
  10209. // command modifiers arrows
  10210. if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
  10211. if (!timeout) {
  10212. timeout = $browser.defer(function() {
  10213. listener();
  10214. timeout = null;
  10215. });
  10216. }
  10217. });
  10218. // if user paste into input using mouse, we need "change" event to catch it
  10219. element.bind('change', listener);
  10220. }
  10221. ctrl.$render = function() {
  10222. element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
  10223. };
  10224. // pattern validator
  10225. var pattern = attr.ngPattern,
  10226. patternValidator;
  10227. var validate = function(regexp, value) {
  10228. if (isEmpty(value) || regexp.test(value)) {
  10229. ctrl.$setValidity('pattern', true);
  10230. return value;
  10231. } else {
  10232. ctrl.$setValidity('pattern', false);
  10233. return undefined;
  10234. }
  10235. };
  10236. if (pattern) {
  10237. if (pattern.match(/^\/(.*)\/$/)) {
  10238. pattern = new RegExp(pattern.substr(1, pattern.length - 2));
  10239. patternValidator = function(value) {
  10240. return validate(pattern, value)
  10241. };
  10242. } else {
  10243. patternValidator = function(value) {
  10244. var patternObj = scope.$eval(pattern);
  10245. if (!patternObj || !patternObj.test) {
  10246. throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj);
  10247. }
  10248. return validate(patternObj, value);
  10249. };
  10250. }
  10251. ctrl.$formatters.push(patternValidator);
  10252. ctrl.$parsers.push(patternValidator);
  10253. }
  10254. // min length validator
  10255. if (attr.ngMinlength) {
  10256. var minlength = int(attr.ngMinlength);
  10257. var minLengthValidator = function(value) {
  10258. if (!isEmpty(value) && value.length < minlength) {
  10259. ctrl.$setValidity('minlength', false);
  10260. return undefined;
  10261. } else {
  10262. ctrl.$setValidity('minlength', true);
  10263. return value;
  10264. }
  10265. };
  10266. ctrl.$parsers.push(minLengthValidator);
  10267. ctrl.$formatters.push(minLengthValidator);
  10268. }
  10269. // max length validator
  10270. if (attr.ngMaxlength) {
  10271. var maxlength = int(attr.ngMaxlength);
  10272. var maxLengthValidator = function(value) {
  10273. if (!isEmpty(value) && value.length > maxlength) {
  10274. ctrl.$setValidity('maxlength', false);
  10275. return undefined;
  10276. } else {
  10277. ctrl.$setValidity('maxlength', true);
  10278. return value;
  10279. }
  10280. };
  10281. ctrl.$parsers.push(maxLengthValidator);
  10282. ctrl.$formatters.push(maxLengthValidator);
  10283. }
  10284. }
  10285. function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  10286. textInputType(scope, element, attr, ctrl, $sniffer, $browser);
  10287. ctrl.$parsers.push(function(value) {
  10288. var empty = isEmpty(value);
  10289. if (empty || NUMBER_REGEXP.test(value)) {
  10290. ctrl.$setValidity('number', true);
  10291. return value === '' ? null : (empty ? value : parseFloat(value));
  10292. } else {
  10293. ctrl.$setValidity('number', false);
  10294. return undefined;
  10295. }
  10296. });
  10297. ctrl.$formatters.push(function(value) {
  10298. return isEmpty(value) ? '' : '' + value;
  10299. });
  10300. if (attr.min) {
  10301. var min = parseFloat(attr.min);
  10302. var minValidator = function(value) {
  10303. if (!isEmpty(value) && value < min) {
  10304. ctrl.$setValidity('min', false);
  10305. return undefined;
  10306. } else {
  10307. ctrl.$setValidity('min', true);
  10308. return value;
  10309. }
  10310. };
  10311. ctrl.$parsers.push(minValidator);
  10312. ctrl.$formatters.push(minValidator);
  10313. }
  10314. if (attr.max) {
  10315. var max = parseFloat(attr.max);
  10316. var maxValidator = function(value) {
  10317. if (!isEmpty(value) && value > max) {
  10318. ctrl.$setValidity('max', false);
  10319. return undefined;
  10320. } else {
  10321. ctrl.$setValidity('max', true);
  10322. return value;
  10323. }
  10324. };
  10325. ctrl.$parsers.push(maxValidator);
  10326. ctrl.$formatters.push(maxValidator);
  10327. }
  10328. ctrl.$formatters.push(function(value) {
  10329. if (isEmpty(value) || isNumber(value)) {
  10330. ctrl.$setValidity('number', true);
  10331. return value;
  10332. } else {
  10333. ctrl.$setValidity('number', false);
  10334. return undefined;
  10335. }
  10336. });
  10337. }
  10338. function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  10339. textInputType(scope, element, attr, ctrl, $sniffer, $browser);
  10340. var urlValidator = function(value) {
  10341. if (isEmpty(value) || URL_REGEXP.test(value)) {
  10342. ctrl.$setValidity('url', true);
  10343. return value;
  10344. } else {
  10345. ctrl.$setValidity('url', false);
  10346. return undefined;
  10347. }
  10348. };
  10349. ctrl.$formatters.push(urlValidator);
  10350. ctrl.$parsers.push(urlValidator);
  10351. }
  10352. function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  10353. textInputType(scope, element, attr, ctrl, $sniffer, $browser);
  10354. var emailValidator = function(value) {
  10355. if (isEmpty(value) || EMAIL_REGEXP.test(value)) {
  10356. ctrl.$setValidity('email', true);
  10357. return value;
  10358. } else {
  10359. ctrl.$setValidity('email', false);
  10360. return undefined;
  10361. }
  10362. };
  10363. ctrl.$formatters.push(emailValidator);
  10364. ctrl.$parsers.push(emailValidator);
  10365. }
  10366. function radioInputType(scope, element, attr, ctrl) {
  10367. // make the name unique, if not defined
  10368. if (isUndefined(attr.name)) {
  10369. element.attr('name', nextUid());
  10370. }
  10371. element.bind('click', function() {
  10372. if (element[0].checked) {
  10373. scope.$apply(function() {
  10374. ctrl.$setViewValue(attr.value);
  10375. });
  10376. }
  10377. });
  10378. ctrl.$render = function() {
  10379. var value = attr.value;
  10380. element[0].checked = (value == ctrl.$viewValue);
  10381. };
  10382. attr.$observe('value', ctrl.$render);
  10383. }
  10384. function checkboxInputType(scope, element, attr, ctrl) {
  10385. var trueValue = attr.ngTrueValue,
  10386. falseValue = attr.ngFalseValue;
  10387. if (!isString(trueValue)) trueValue = true;
  10388. if (!isString(falseValue)) falseValue = false;
  10389. element.bind('click', function() {
  10390. scope.$apply(function() {
  10391. ctrl.$setViewValue(element[0].checked);
  10392. });
  10393. });
  10394. ctrl.$render = function() {
  10395. element[0].checked = ctrl.$viewValue;
  10396. };
  10397. ctrl.$formatters.push(function(value) {
  10398. return value === trueValue;
  10399. });
  10400. ctrl.$parsers.push(function(value) {
  10401. return value ? trueValue : falseValue;
  10402. });
  10403. }
  10404. /**
  10405. * @ngdoc directive
  10406. * @name ng.directive:textarea
  10407. * @restrict E
  10408. *
  10409. * @description
  10410. * HTML textarea element control with angular data-binding. The data-binding and validation
  10411. * properties of this element are exactly the same as those of the
  10412. * {@link ng.directive:input input element}.
  10413. *
  10414. * @param {string} ngModel Assignable angular expression to data-bind to.
  10415. * @param {string=} name Property name of the form under which the control is published.
  10416. * @param {string=} required Sets `required` validation error key if the value is not entered.
  10417. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
  10418. * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
  10419. * `required` when you want to data-bind to the `required` attribute.
  10420. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  10421. * minlength.
  10422. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  10423. * maxlength.
  10424. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  10425. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  10426. * patterns defined as scope expressions.
  10427. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  10428. * interaction with the input element.
  10429. */
  10430. /**
  10431. * @ngdoc directive
  10432. * @name ng.directive:input
  10433. * @restrict E
  10434. *
  10435. * @description
  10436. * HTML input element control with angular data-binding. Input control follows HTML5 input types
  10437. * and polyfills the HTML5 validation behavior for older browsers.
  10438. *
  10439. * @param {string} ngModel Assignable angular expression to data-bind to.
  10440. * @param {string=} name Property name of the form under which the control is published.
  10441. * @param {string=} required Sets `required` validation error key if the value is not entered.
  10442. * @param {boolean=} ngRequired Sets `required` attribute if set to true
  10443. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  10444. * minlength.
  10445. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  10446. * maxlength.
  10447. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  10448. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  10449. * patterns defined as scope expressions.
  10450. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  10451. * interaction with the input element.
  10452. *
  10453. * @example
  10454. <doc:example>
  10455. <doc:source>
  10456. <script>
  10457. function Ctrl($scope) {
  10458. $scope.user = {name: 'guest', last: 'visitor'};
  10459. }
  10460. </script>
  10461. <div ng-controller="Ctrl">
  10462. <form name="myForm">
  10463. User name: <input type="text" name="userName" ng-model="user.name" required>
  10464. <span class="error" ng-show="myForm.userName.$error.required">
  10465. Required!</span><br>
  10466. Last name: <input type="text" name="lastName" ng-model="user.last"
  10467. ng-minlength="3" ng-maxlength="10">
  10468. <span class="error" ng-show="myForm.lastName.$error.minlength">
  10469. Too short!</span>
  10470. <span class="error" ng-show="myForm.lastName.$error.maxlength">
  10471. Too long!</span><br>
  10472. </form>
  10473. <hr>
  10474. <tt>user = {{user}}</tt><br/>
  10475. <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br>
  10476. <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br>
  10477. <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br>
  10478. <tt>myForm.userName.$error = {{myForm.lastName.$error}}</tt><br>
  10479. <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
  10480. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
  10481. <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br>
  10482. <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
  10483. </div>
  10484. </doc:source>
  10485. <doc:scenario>
  10486. it('should initialize to model', function() {
  10487. expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
  10488. expect(binding('myForm.userName.$valid')).toEqual('true');
  10489. expect(binding('myForm.$valid')).toEqual('true');
  10490. });
  10491. it('should be invalid if empty when required', function() {
  10492. input('user.name').enter('');
  10493. expect(binding('user')).toEqual('{"last":"visitor"}');
  10494. expect(binding('myForm.userName.$valid')).toEqual('false');
  10495. expect(binding('myForm.$valid')).toEqual('false');
  10496. });
  10497. it('should be valid if empty when min length is set', function() {
  10498. input('user.last').enter('');
  10499. expect(binding('user')).toEqual('{"name":"guest","last":""}');
  10500. expect(binding('myForm.lastName.$valid')).toEqual('true');
  10501. expect(binding('myForm.$valid')).toEqual('true');
  10502. });
  10503. it('should be invalid if less than required min length', function() {
  10504. input('user.last').enter('xx');
  10505. expect(binding('user')).toEqual('{"name":"guest"}');
  10506. expect(binding('myForm.lastName.$valid')).toEqual('false');
  10507. expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
  10508. expect(binding('myForm.$valid')).toEqual('false');
  10509. });
  10510. it('should be invalid if longer than max length', function() {
  10511. input('user.last').enter('some ridiculously long name');
  10512. expect(binding('user'))
  10513. .toEqual('{"name":"guest"}');
  10514. expect(binding('myForm.lastName.$valid')).toEqual('false');
  10515. expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
  10516. expect(binding('myForm.$valid')).toEqual('false');
  10517. });
  10518. </doc:scenario>
  10519. </doc:example>
  10520. */
  10521. var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
  10522. return {
  10523. restrict: 'E',
  10524. require: '?ngModel',
  10525. link: function(scope, element, attr, ctrl) {
  10526. if (ctrl) {
  10527. (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,
  10528. $browser);
  10529. }
  10530. }
  10531. };
  10532. }];
  10533. var VALID_CLASS = 'ng-valid',
  10534. INVALID_CLASS = 'ng-invalid',
  10535. PRISTINE_CLASS = 'ng-pristine',
  10536. DIRTY_CLASS = 'ng-dirty';
  10537. /**
  10538. * @ngdoc object
  10539. * @name ng.directive:ngModel.NgModelController
  10540. *
  10541. * @property {string} $viewValue Actual string value in the view.
  10542. * @property {*} $modelValue The value in the model, that the control is bound to.
  10543. * @property {Array.<Function>} $parsers Whenever the control reads value from the DOM, it executes
  10544. * all of these functions to sanitize / convert the value as well as validate.
  10545. *
  10546. * @property {Array.<Function>} $formatters Whenever the model value changes, it executes all of
  10547. * these functions to convert the value as well as validate.
  10548. *
  10549. * @property {Object} $error An bject hash with all errors as keys.
  10550. *
  10551. * @property {boolean} $pristine True if user has not interacted with the control yet.
  10552. * @property {boolean} $dirty True if user has already interacted with the control.
  10553. * @property {boolean} $valid True if there is no error.
  10554. * @property {boolean} $invalid True if at least one error on the control.
  10555. *
  10556. * @description
  10557. *
  10558. * `NgModelController` provides API for the `ng-model` directive. The controller contains
  10559. * services for data-binding, validation, CSS update, value formatting and parsing. It
  10560. * specifically does not contain any logic which deals with DOM rendering or listening to
  10561. * DOM events. The `NgModelController` is meant to be extended by other directives where, the
  10562. * directive provides DOM manipulation and the `NgModelController` provides the data-binding.
  10563. *
  10564. * This example shows how to use `NgModelController` with a custom control to achieve
  10565. * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
  10566. * collaborate together to achieve the desired result.
  10567. *
  10568. * <example module="customControl">
  10569. <file name="style.css">
  10570. [contenteditable] {
  10571. border: 1px solid black;
  10572. background-color: white;
  10573. min-height: 20px;
  10574. }
  10575. .ng-invalid {
  10576. border: 1px solid red;
  10577. }
  10578. </file>
  10579. <file name="script.js">
  10580. angular.module('customControl', []).
  10581. directive('contenteditable', function() {
  10582. return {
  10583. restrict: 'A', // only activate on element attribute
  10584. require: '?ngModel', // get a hold of NgModelController
  10585. link: function(scope, element, attrs, ngModel) {
  10586. if(!ngModel) return; // do nothing if no ng-model
  10587. // Specify how UI should be updated
  10588. ngModel.$render = function() {
  10589. element.html(ngModel.$viewValue || '');
  10590. };
  10591. // Listen for change events to enable binding
  10592. element.bind('blur keyup change', function() {
  10593. scope.$apply(read);
  10594. });
  10595. read(); // initialize
  10596. // Write data to the model
  10597. function read() {
  10598. ngModel.$setViewValue(element.html());
  10599. }
  10600. }
  10601. };
  10602. });
  10603. </file>
  10604. <file name="index.html">
  10605. <form name="myForm">
  10606. <div contenteditable
  10607. name="myWidget" ng-model="userContent"
  10608. required>Change me!</div>
  10609. <span ng-show="myForm.myWidget.$error.required">Required!</span>
  10610. <hr>
  10611. <textarea ng-model="userContent"></textarea>
  10612. </form>
  10613. </file>
  10614. <file name="scenario.js">
  10615. it('should data-bind and become invalid', function() {
  10616. var contentEditable = element('[contenteditable]');
  10617. expect(contentEditable.text()).toEqual('Change me!');
  10618. input('userContent').enter('');
  10619. expect(contentEditable.text()).toEqual('');
  10620. expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
  10621. });
  10622. </file>
  10623. * </example>
  10624. *
  10625. */
  10626. var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
  10627. function($scope, $exceptionHandler, $attr, $element, $parse) {
  10628. this.$viewValue = Number.NaN;
  10629. this.$modelValue = Number.NaN;
  10630. this.$parsers = [];
  10631. this.$formatters = [];
  10632. this.$viewChangeListeners = [];
  10633. this.$pristine = true;
  10634. this.$dirty = false;
  10635. this.$valid = true;
  10636. this.$invalid = false;
  10637. this.$name = $attr.name;
  10638. var ngModelGet = $parse($attr.ngModel),
  10639. ngModelSet = ngModelGet.assign;
  10640. if (!ngModelSet) {
  10641. throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel +
  10642. ' (' + startingTag($element) + ')');
  10643. }
  10644. /**
  10645. * @ngdoc function
  10646. * @name ng.directive:ngModel.NgModelController#$render
  10647. * @methodOf ng.directive:ngModel.NgModelController
  10648. *
  10649. * @description
  10650. * Called when the view needs to be updated. It is expected that the user of the ng-model
  10651. * directive will implement this method.
  10652. */
  10653. this.$render = noop;
  10654. var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
  10655. invalidCount = 0, // used to easily determine if we are valid
  10656. $error = this.$error = {}; // keep invalid keys here
  10657. // Setup initial state of the control
  10658. $element.addClass(PRISTINE_CLASS);
  10659. toggleValidCss(true);
  10660. // convenience method for easy toggling of classes
  10661. function toggleValidCss(isValid, validationErrorKey) {
  10662. validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
  10663. $element.
  10664. removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
  10665. addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
  10666. }
  10667. /**
  10668. * @ngdoc function
  10669. * @name ng.directive:ngModel.NgModelController#$setValidity
  10670. * @methodOf ng.directive:ngModel.NgModelController
  10671. *
  10672. * @description
  10673. * Change the validity state, and notifies the form when the control changes validity. (i.e. it
  10674. * does not notify form if given validator is already marked as invalid).
  10675. *
  10676. * This method should be called by validators - i.e. the parser or formatter functions.
  10677. *
  10678. * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
  10679. * to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
  10680. * The `validationErrorKey` should be in camelCase and will get converted into dash-case
  10681. * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
  10682. * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
  10683. * @param {boolean} isValid Whether the current state is valid (true) or invalid (false).
  10684. */
  10685. this.$setValidity = function(validationErrorKey, isValid) {
  10686. if ($error[validationErrorKey] === !isValid) return;
  10687. if (isValid) {
  10688. if ($error[validationErrorKey]) invalidCount--;
  10689. if (!invalidCount) {
  10690. toggleValidCss(true);
  10691. this.$valid = true;
  10692. this.$invalid = false;
  10693. }
  10694. } else {
  10695. toggleValidCss(false);
  10696. this.$invalid = true;
  10697. this.$valid = false;
  10698. invalidCount++;
  10699. }
  10700. $error[validationErrorKey] = !isValid;
  10701. toggleValidCss(isValid, validationErrorKey);
  10702. parentForm.$setValidity(validationErrorKey, isValid, this);
  10703. };
  10704. /**
  10705. * @ngdoc function
  10706. * @name ng.directive:ngModel.NgModelController#$setPristine
  10707. * @methodOf ng.directive:ngModel.NgModelController
  10708. *
  10709. * @description
  10710. * Sets the control to its pristine state.
  10711. *
  10712. * This method can be called to remove the 'ng-dirty' class and set the control to its pristine
  10713. * state (ng-pristine class).
  10714. */
  10715. this.$setPristine = function () {
  10716. this.$dirty = false;
  10717. this.$pristine = true;
  10718. $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
  10719. };
  10720. /**
  10721. * @ngdoc function
  10722. * @name ng.directive:ngModel.NgModelController#$setViewValue
  10723. * @methodOf ng.directive:ngModel.NgModelController
  10724. *
  10725. * @description
  10726. * Read a value from view.
  10727. *
  10728. * This method should be called from within a DOM event handler.
  10729. * For example {@link ng.directive:input input} or
  10730. * {@link ng.directive:select select} directives call it.
  10731. *
  10732. * It internally calls all `formatters` and if resulted value is valid, updates the model and
  10733. * calls all registered change listeners.
  10734. *
  10735. * @param {string} value Value from the view.
  10736. */
  10737. this.$setViewValue = function(value) {
  10738. this.$viewValue = value;
  10739. // change to dirty
  10740. if (this.$pristine) {
  10741. this.$dirty = true;
  10742. this.$pristine = false;
  10743. $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
  10744. parentForm.$setDirty();
  10745. }
  10746. forEach(this.$parsers, function(fn) {
  10747. value = fn(value);
  10748. });
  10749. if (this.$modelValue !== value) {
  10750. this.$modelValue = value;
  10751. ngModelSet($scope, value);
  10752. forEach(this.$viewChangeListeners, function(listener) {
  10753. try {
  10754. listener();
  10755. } catch(e) {
  10756. $exceptionHandler(e);
  10757. }
  10758. })
  10759. }
  10760. };
  10761. // model -> value
  10762. var ctrl = this;
  10763. $scope.$watch(function ngModelWatch() {
  10764. var value = ngModelGet($scope);
  10765. // if scope model value and ngModel value are out of sync
  10766. if (ctrl.$modelValue !== value) {
  10767. var formatters = ctrl.$formatters,
  10768. idx = formatters.length;
  10769. ctrl.$modelValue = value;
  10770. while(idx--) {
  10771. value = formatters[idx](value);
  10772. }
  10773. if (ctrl.$viewValue !== value) {
  10774. ctrl.$viewValue = value;
  10775. ctrl.$render();
  10776. }
  10777. }
  10778. });
  10779. }];
  10780. /**
  10781. * @ngdoc directive
  10782. * @name ng.directive:ngModel
  10783. *
  10784. * @element input
  10785. *
  10786. * @description
  10787. * Is directive that tells Angular to do two-way data binding. It works together with `input`,
  10788. * `select`, `textarea`. You can easily write your own directives to use `ngModel` as well.
  10789. *
  10790. * `ngModel` is responsible for:
  10791. *
  10792. * - binding the view into the model, which other directives such as `input`, `textarea` or `select`
  10793. * require,
  10794. * - providing validation behavior (i.e. required, number, email, url),
  10795. * - keeping state of the control (valid/invalid, dirty/pristine, validation errors),
  10796. * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`),
  10797. * - register the control with parent {@link ng.directive:form form}.
  10798. *
  10799. * For basic examples, how to use `ngModel`, see:
  10800. *
  10801. * - {@link ng.directive:input input}
  10802. * - {@link ng.directive:input.text text}
  10803. * - {@link ng.directive:input.checkbox checkbox}
  10804. * - {@link ng.directive:input.radio radio}
  10805. * - {@link ng.directive:input.number number}
  10806. * - {@link ng.directive:input.email email}
  10807. * - {@link ng.directive:input.url url}
  10808. * - {@link ng.directive:select select}
  10809. * - {@link ng.directive:textarea textarea}
  10810. *
  10811. */
  10812. var ngModelDirective = function() {
  10813. return {
  10814. require: ['ngModel', '^?form'],
  10815. controller: NgModelController,
  10816. link: function(scope, element, attr, ctrls) {
  10817. // notify others, especially parent forms
  10818. var modelCtrl = ctrls[0],
  10819. formCtrl = ctrls[1] || nullFormCtrl;
  10820. formCtrl.$addControl(modelCtrl);
  10821. element.bind('$destroy', function() {
  10822. formCtrl.$removeControl(modelCtrl);
  10823. });
  10824. }
  10825. };
  10826. };
  10827. /**
  10828. * @ngdoc directive
  10829. * @name ng.directive:ngChange
  10830. * @restrict E
  10831. *
  10832. * @description
  10833. * Evaluate given expression when user changes the input.
  10834. * The expression is not evaluated when the value change is coming from the model.
  10835. *
  10836. * Note, this directive requires `ngModel` to be present.
  10837. *
  10838. * @element input
  10839. *
  10840. * @example
  10841. * <doc:example>
  10842. * <doc:source>
  10843. * <script>
  10844. * function Controller($scope) {
  10845. * $scope.counter = 0;
  10846. * $scope.change = function() {
  10847. * $scope.counter++;
  10848. * };
  10849. * }
  10850. * </script>
  10851. * <div ng-controller="Controller">
  10852. * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
  10853. * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
  10854. * <label for="ng-change-example2">Confirmed</label><br />
  10855. * debug = {{confirmed}}<br />
  10856. * counter = {{counter}}
  10857. * </div>
  10858. * </doc:source>
  10859. * <doc:scenario>
  10860. * it('should evaluate the expression if changing from view', function() {
  10861. * expect(binding('counter')).toEqual('0');
  10862. * element('#ng-change-example1').click();
  10863. * expect(binding('counter')).toEqual('1');
  10864. * expect(binding('confirmed')).toEqual('true');
  10865. * });
  10866. *
  10867. * it('should not evaluate the expression if changing from model', function() {
  10868. * element('#ng-change-example2').click();
  10869. * expect(binding('counter')).toEqual('0');
  10870. * expect(binding('confirmed')).toEqual('true');
  10871. * });
  10872. * </doc:scenario>
  10873. * </doc:example>
  10874. */
  10875. var ngChangeDirective = valueFn({
  10876. require: 'ngModel',
  10877. link: function(scope, element, attr, ctrl) {
  10878. ctrl.$viewChangeListeners.push(function() {
  10879. scope.$eval(attr.ngChange);
  10880. });
  10881. }
  10882. });
  10883. var requiredDirective = function() {
  10884. return {
  10885. require: '?ngModel',
  10886. link: function(scope, elm, attr, ctrl) {
  10887. if (!ctrl) return;
  10888. attr.required = true; // force truthy in case we are on non input element
  10889. var validator = function(value) {
  10890. if (attr.required && (isEmpty(value) || value === false)) {
  10891. ctrl.$setValidity('required', false);
  10892. return;
  10893. } else {
  10894. ctrl.$setValidity('required', true);
  10895. return value;
  10896. }
  10897. };
  10898. ctrl.$formatters.push(validator);
  10899. ctrl.$parsers.unshift(validator);
  10900. attr.$observe('required', function() {
  10901. validator(ctrl.$viewValue);
  10902. });
  10903. }
  10904. };
  10905. };
  10906. /**
  10907. * @ngdoc directive
  10908. * @name ng.directive:ngList
  10909. *
  10910. * @description
  10911. * Text input that converts between comma-separated string into an array of strings.
  10912. *
  10913. * @element input
  10914. * @param {string=} ngList optional delimiter that should be used to split the value. If
  10915. * specified in form `/something/` then the value will be converted into a regular expression.
  10916. *
  10917. * @example
  10918. <doc:example>
  10919. <doc:source>
  10920. <script>
  10921. function Ctrl($scope) {
  10922. $scope.names = ['igor', 'misko', 'vojta'];
  10923. }
  10924. </script>
  10925. <form name="myForm" ng-controller="Ctrl">
  10926. List: <input name="namesInput" ng-model="names" ng-list required>
  10927. <span class="error" ng-show="myForm.list.$error.required">
  10928. Required!</span>
  10929. <tt>names = {{names}}</tt><br/>
  10930. <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
  10931. <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
  10932. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  10933. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  10934. </form>
  10935. </doc:source>
  10936. <doc:scenario>
  10937. it('should initialize to model', function() {
  10938. expect(binding('names')).toEqual('["igor","misko","vojta"]');
  10939. expect(binding('myForm.namesInput.$valid')).toEqual('true');
  10940. });
  10941. it('should be invalid if empty', function() {
  10942. input('names').enter('');
  10943. expect(binding('names')).toEqual('[]');
  10944. expect(binding('myForm.namesInput.$valid')).toEqual('false');
  10945. });
  10946. </doc:scenario>
  10947. </doc:example>
  10948. */
  10949. var ngListDirective = function() {
  10950. return {
  10951. require: 'ngModel',
  10952. link: function(scope, element, attr, ctrl) {
  10953. var match = /\/(.*)\//.exec(attr.ngList),
  10954. separator = match && new RegExp(match[1]) || attr.ngList || ',';
  10955. var parse = function(viewValue) {
  10956. var list = [];
  10957. if (viewValue) {
  10958. forEach(viewValue.split(separator), function(value) {
  10959. if (value) list.push(trim(value));
  10960. });
  10961. }
  10962. return list;
  10963. };
  10964. ctrl.$parsers.push(parse);
  10965. ctrl.$formatters.push(function(value) {
  10966. if (isArray(value)) {
  10967. return value.join(', ');
  10968. }
  10969. return undefined;
  10970. });
  10971. }
  10972. };
  10973. };
  10974. var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
  10975. var ngValueDirective = function() {
  10976. return {
  10977. priority: 100,
  10978. compile: function(tpl, tplAttr) {
  10979. if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
  10980. return function(scope, elm, attr) {
  10981. attr.$set('value', scope.$eval(attr.ngValue));
  10982. };
  10983. } else {
  10984. return function(scope, elm, attr) {
  10985. scope.$watch(attr.ngValue, function valueWatchAction(value) {
  10986. attr.$set('value', value, false);
  10987. });
  10988. };
  10989. }
  10990. }
  10991. };
  10992. };
  10993. /**
  10994. * @ngdoc directive
  10995. * @name ng.directive:ngBind
  10996. *
  10997. * @description
  10998. * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
  10999. * with the value of a given expression, and to update the text content when the value of that
  11000. * expression changes.
  11001. *
  11002. * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
  11003. * `{{ expression }}` which is similar but less verbose.
  11004. *
  11005. * Once scenario in which the use of `ngBind` is prefered over `{{ expression }}` binding is when
  11006. * it's desirable to put bindings into template that is momentarily displayed by the browser in its
  11007. * raw state before Angular compiles it. Since `ngBind` is an element attribute, it makes the
  11008. * bindings invisible to the user while the page is loading.
  11009. *
  11010. * An alternative solution to this problem would be using the
  11011. * {@link ng.directive:ngCloak ngCloak} directive.
  11012. *
  11013. *
  11014. * @element ANY
  11015. * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
  11016. *
  11017. * @example
  11018. * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
  11019. <doc:example>
  11020. <doc:source>
  11021. <script>
  11022. function Ctrl($scope) {
  11023. $scope.name = 'Whirled';
  11024. }
  11025. </script>
  11026. <div ng-controller="Ctrl">
  11027. Enter name: <input type="text" ng-model="name"><br>
  11028. Hello <span ng-bind="name"></span>!
  11029. </div>
  11030. </doc:source>
  11031. <doc:scenario>
  11032. it('should check ng-bind', function() {
  11033. expect(using('.doc-example-live').binding('name')).toBe('Whirled');
  11034. using('.doc-example-live').input('name').enter('world');
  11035. expect(using('.doc-example-live').binding('name')).toBe('world');
  11036. });
  11037. </doc:scenario>
  11038. </doc:example>
  11039. */
  11040. var ngBindDirective = ngDirective(function(scope, element, attr) {
  11041. element.addClass('ng-binding').data('$binding', attr.ngBind);
  11042. scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
  11043. element.text(value == undefined ? '' : value);
  11044. });
  11045. });
  11046. /**
  11047. * @ngdoc directive
  11048. * @name ng.directive:ngBindTemplate
  11049. *
  11050. * @description
  11051. * The `ngBindTemplate` directive specifies that the element
  11052. * text should be replaced with the template in ngBindTemplate.
  11053. * Unlike ngBind the ngBindTemplate can contain multiple `{{` `}}`
  11054. * expressions. (This is required since some HTML elements
  11055. * can not have SPAN elements such as TITLE, or OPTION to name a few.)
  11056. *
  11057. * @element ANY
  11058. * @param {string} ngBindTemplate template of form
  11059. * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
  11060. *
  11061. * @example
  11062. * Try it here: enter text in text box and watch the greeting change.
  11063. <doc:example>
  11064. <doc:source>
  11065. <script>
  11066. function Ctrl($scope) {
  11067. $scope.salutation = 'Hello';
  11068. $scope.name = 'World';
  11069. }
  11070. </script>
  11071. <div ng-controller="Ctrl">
  11072. Salutation: <input type="text" ng-model="salutation"><br>
  11073. Name: <input type="text" ng-model="name"><br>
  11074. <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
  11075. </div>
  11076. </doc:source>
  11077. <doc:scenario>
  11078. it('should check ng-bind', function() {
  11079. expect(using('.doc-example-live').binding('salutation')).
  11080. toBe('Hello');
  11081. expect(using('.doc-example-live').binding('name')).
  11082. toBe('World');
  11083. using('.doc-example-live').input('salutation').enter('Greetings');
  11084. using('.doc-example-live').input('name').enter('user');
  11085. expect(using('.doc-example-live').binding('salutation')).
  11086. toBe('Greetings');
  11087. expect(using('.doc-example-live').binding('name')).
  11088. toBe('user');
  11089. });
  11090. </doc:scenario>
  11091. </doc:example>
  11092. */
  11093. var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
  11094. return function(scope, element, attr) {
  11095. // TODO: move this to scenario runner
  11096. var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
  11097. element.addClass('ng-binding').data('$binding', interpolateFn);
  11098. attr.$observe('ngBindTemplate', function(value) {
  11099. element.text(value);
  11100. });
  11101. }
  11102. }];
  11103. /**
  11104. * @ngdoc directive
  11105. * @name ng.directive:ngBindHtmlUnsafe
  11106. *
  11107. * @description
  11108. * Creates a binding that will innerHTML the result of evaluating the `expression` into the current
  11109. * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if
  11110. * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too
  11111. * restrictive and when you absolutely trust the source of the content you are binding to.
  11112. *
  11113. * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
  11114. *
  11115. * @element ANY
  11116. * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate.
  11117. */
  11118. var ngBindHtmlUnsafeDirective = [function() {
  11119. return function(scope, element, attr) {
  11120. element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe);
  11121. scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) {
  11122. element.html(value || '');
  11123. });
  11124. };
  11125. }];
  11126. function classDirective(name, selector) {
  11127. name = 'ngClass' + name;
  11128. return ngDirective(function(scope, element, attr) {
  11129. scope.$watch(attr[name], ngClassWatchAction, true);
  11130. attr.$observe('class', function(value) {
  11131. var ngClass = scope.$eval(attr[name]);
  11132. ngClassWatchAction(ngClass, ngClass);
  11133. });
  11134. if (name !== 'ngClass') {
  11135. scope.$watch('$index', function($index, old$index) {
  11136. var mod = $index % 2;
  11137. if (mod !== old$index % 2) {
  11138. if (mod == selector) {
  11139. addClass(scope.$eval(attr[name]));
  11140. } else {
  11141. removeClass(scope.$eval(attr[name]));
  11142. }
  11143. }
  11144. });
  11145. }
  11146. function ngClassWatchAction(newVal, oldVal) {
  11147. if (selector === true || scope.$index % 2 === selector) {
  11148. if (oldVal && (newVal !== oldVal)) {
  11149. removeClass(oldVal);
  11150. }
  11151. addClass(newVal);
  11152. }
  11153. }
  11154. function removeClass(classVal) {
  11155. if (isObject(classVal) && !isArray(classVal)) {
  11156. classVal = map(classVal, function(v, k) { if (v) return k });
  11157. }
  11158. element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal);
  11159. }
  11160. function addClass(classVal) {
  11161. if (isObject(classVal) && !isArray(classVal)) {
  11162. classVal = map(classVal, function(v, k) { if (v) return k });
  11163. }
  11164. if (classVal) {
  11165. element.addClass(isArray(classVal) ? classVal.join(' ') : classVal);
  11166. }
  11167. }
  11168. });
  11169. }
  11170. /**
  11171. * @ngdoc directive
  11172. * @name ng.directive:ngClass
  11173. *
  11174. * @description
  11175. * The `ngClass` allows you to set CSS class on HTML element dynamically by databinding an
  11176. * expression that represents all classes to be added.
  11177. *
  11178. * The directive won't add duplicate classes if a particular class was already set.
  11179. *
  11180. * When the expression changes, the previously added classes are removed and only then the classes
  11181. * new classes are added.
  11182. *
  11183. * @element ANY
  11184. * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
  11185. * of the evaluation can be a string representing space delimited class
  11186. * names, an array, or a map of class names to boolean values.
  11187. *
  11188. * @example
  11189. <example>
  11190. <file name="index.html">
  11191. <input type="button" value="set" ng-click="myVar='my-class'">
  11192. <input type="button" value="clear" ng-click="myVar=''">
  11193. <br>
  11194. <span ng-class="myVar">Sample Text</span>
  11195. </file>
  11196. <file name="style.css">
  11197. .my-class {
  11198. color: red;
  11199. }
  11200. </file>
  11201. <file name="scenario.js">
  11202. it('should check ng-class', function() {
  11203. expect(element('.doc-example-live span').prop('className')).not().
  11204. toMatch(/my-class/);
  11205. using('.doc-example-live').element(':button:first').click();
  11206. expect(element('.doc-example-live span').prop('className')).
  11207. toMatch(/my-class/);
  11208. using('.doc-example-live').element(':button:last').click();
  11209. expect(element('.doc-example-live span').prop('className')).not().
  11210. toMatch(/my-class/);
  11211. });
  11212. </file>
  11213. </example>
  11214. */
  11215. var ngClassDirective = classDirective('', true);
  11216. /**
  11217. * @ngdoc directive
  11218. * @name ng.directive:ngClassOdd
  11219. *
  11220. * @description
  11221. * The `ngClassOdd` and `ngClassEven` directives work exactly as
  11222. * {@link ng.directive:ngClass ngClass}, except it works in
  11223. * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
  11224. *
  11225. * This directive can be applied only within a scope of an
  11226. * {@link ng.directive:ngRepeat ngRepeat}.
  11227. *
  11228. * @element ANY
  11229. * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
  11230. * of the evaluation can be a string representing space delimited class names or an array.
  11231. *
  11232. * @example
  11233. <example>
  11234. <file name="index.html">
  11235. <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
  11236. <li ng-repeat="name in names">
  11237. <span ng-class-odd="'odd'" ng-class-even="'even'">
  11238. {{name}}
  11239. </span>
  11240. </li>
  11241. </ol>
  11242. </file>
  11243. <file name="style.css">
  11244. .odd {
  11245. color: red;
  11246. }
  11247. .even {
  11248. color: blue;
  11249. }
  11250. </file>
  11251. <file name="scenario.js">
  11252. it('should check ng-class-odd and ng-class-even', function() {
  11253. expect(element('.doc-example-live li:first span').prop('className')).
  11254. toMatch(/odd/);
  11255. expect(element('.doc-example-live li:last span').prop('className')).
  11256. toMatch(/even/);
  11257. });
  11258. </file>
  11259. </example>
  11260. */
  11261. var ngClassOddDirective = classDirective('Odd', 0);
  11262. /**
  11263. * @ngdoc directive
  11264. * @name ng.directive:ngClassEven
  11265. *
  11266. * @description
  11267. * The `ngClassOdd` and `ngClassEven` works exactly as
  11268. * {@link ng.directive:ngClass ngClass}, except it works in
  11269. * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
  11270. *
  11271. * This directive can be applied only within a scope of an
  11272. * {@link ng.directive:ngRepeat ngRepeat}.
  11273. *
  11274. * @element ANY
  11275. * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
  11276. * result of the evaluation can be a string representing space delimited class names or an array.
  11277. *
  11278. * @example
  11279. <example>
  11280. <file name="index.html">
  11281. <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
  11282. <li ng-repeat="name in names">
  11283. <span ng-class-odd="'odd'" ng-class-even="'even'">
  11284. {{name}} &nbsp; &nbsp; &nbsp;
  11285. </span>
  11286. </li>
  11287. </ol>
  11288. </file>
  11289. <file name="style.css">
  11290. .odd {
  11291. color: red;
  11292. }
  11293. .even {
  11294. color: blue;
  11295. }
  11296. </file>
  11297. <file name="scenario.js">
  11298. it('should check ng-class-odd and ng-class-even', function() {
  11299. expect(element('.doc-example-live li:first span').prop('className')).
  11300. toMatch(/odd/);
  11301. expect(element('.doc-example-live li:last span').prop('className')).
  11302. toMatch(/even/);
  11303. });
  11304. </file>
  11305. </example>
  11306. */
  11307. var ngClassEvenDirective = classDirective('Even', 1);
  11308. /**
  11309. * @ngdoc directive
  11310. * @name ng.directive:ngCloak
  11311. *
  11312. * @description
  11313. * The `ngCloak` directive is used to prevent the Angular html template from being briefly
  11314. * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
  11315. * directive to avoid the undesirable flicker effect caused by the html template display.
  11316. *
  11317. * The directive can be applied to the `<body>` element, but typically a fine-grained application is
  11318. * prefered in order to benefit from progressive rendering of the browser view.
  11319. *
  11320. * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and
  11321. * `angular.min.js` files. Following is the css rule:
  11322. *
  11323. * <pre>
  11324. * [ng\:cloak], [ng-cloak], .ng-cloak {
  11325. * display: none;
  11326. * }
  11327. * </pre>
  11328. *
  11329. * When this css rule is loaded by the browser, all html elements (including their children) that
  11330. * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive
  11331. * during the compilation of the template it deletes the `ngCloak` element attribute, which
  11332. * makes the compiled element visible.
  11333. *
  11334. * For the best result, `angular.js` script must be loaded in the head section of the html file;
  11335. * alternatively, the css rule (above) must be included in the external stylesheet of the
  11336. * application.
  11337. *
  11338. * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
  11339. * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
  11340. * class `ngCloak` in addition to `ngCloak` directive as shown in the example below.
  11341. *
  11342. * @element ANY
  11343. *
  11344. * @example
  11345. <doc:example>
  11346. <doc:source>
  11347. <div id="template1" ng-cloak>{{ 'hello' }}</div>
  11348. <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
  11349. </doc:source>
  11350. <doc:scenario>
  11351. it('should remove the template directive and css class', function() {
  11352. expect(element('.doc-example-live #template1').attr('ng-cloak')).
  11353. not().toBeDefined();
  11354. expect(element('.doc-example-live #template2').attr('ng-cloak')).
  11355. not().toBeDefined();
  11356. });
  11357. </doc:scenario>
  11358. </doc:example>
  11359. *
  11360. */
  11361. var ngCloakDirective = ngDirective({
  11362. compile: function(element, attr) {
  11363. attr.$set('ngCloak', undefined);
  11364. element.removeClass('ng-cloak');
  11365. }
  11366. });
  11367. /**
  11368. * @ngdoc directive
  11369. * @name ng.directive:ngController
  11370. *
  11371. * @description
  11372. * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular
  11373. * supports the principles behind the Model-View-Controller design pattern.
  11374. *
  11375. * MVC components in angular:
  11376. *
  11377. * * Model — The Model is data in scope properties; scopes are attached to the DOM.
  11378. * * View — The template (HTML with data bindings) is rendered into the View.
  11379. * * Controller — The `ngController` directive specifies a Controller class; the class has
  11380. * methods that typically express the business logic behind the application.
  11381. *
  11382. * Note that an alternative way to define controllers is via the `{@link ng.$route}`
  11383. * service.
  11384. *
  11385. * @element ANY
  11386. * @scope
  11387. * @param {expression} ngController Name of a globally accessible constructor function or an
  11388. * {@link guide/expression expression} that on the current scope evaluates to a
  11389. * constructor function.
  11390. *
  11391. * @example
  11392. * Here is a simple form for editing user contact information. Adding, removing, clearing, and
  11393. * greeting are methods declared on the controller (see source tab). These methods can
  11394. * easily be called from the angular markup. Notice that the scope becomes the `this` for the
  11395. * controller's instance. This allows for easy access to the view data from the controller. Also
  11396. * notice that any changes to the data are automatically reflected in the View without the need
  11397. * for a manual update.
  11398. <doc:example>
  11399. <doc:source>
  11400. <script>
  11401. function SettingsController($scope) {
  11402. $scope.name = "John Smith";
  11403. $scope.contacts = [
  11404. {type:'phone', value:'408 555 1212'},
  11405. {type:'email', value:'john.smith@example.org'} ];
  11406. $scope.greet = function() {
  11407. alert(this.name);
  11408. };
  11409. $scope.addContact = function() {
  11410. this.contacts.push({type:'email', value:'yourname@example.org'});
  11411. };
  11412. $scope.removeContact = function(contactToRemove) {
  11413. var index = this.contacts.indexOf(contactToRemove);
  11414. this.contacts.splice(index, 1);
  11415. };
  11416. $scope.clearContact = function(contact) {
  11417. contact.type = 'phone';
  11418. contact.value = '';
  11419. };
  11420. }
  11421. </script>
  11422. <div ng-controller="SettingsController">
  11423. Name: <input type="text" ng-model="name"/>
  11424. [ <a href="" ng-click="greet()">greet</a> ]<br/>
  11425. Contact:
  11426. <ul>
  11427. <li ng-repeat="contact in contacts">
  11428. <select ng-model="contact.type">
  11429. <option>phone</option>
  11430. <option>email</option>
  11431. </select>
  11432. <input type="text" ng-model="contact.value"/>
  11433. [ <a href="" ng-click="clearContact(contact)">clear</a>
  11434. | <a href="" ng-click="removeContact(contact)">X</a> ]
  11435. </li>
  11436. <li>[ <a href="" ng-click="addContact()">add</a> ]</li>
  11437. </ul>
  11438. </div>
  11439. </doc:source>
  11440. <doc:scenario>
  11441. it('should check controller', function() {
  11442. expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
  11443. expect(element('.doc-example-live li:nth-child(1) input').val())
  11444. .toBe('408 555 1212');
  11445. expect(element('.doc-example-live li:nth-child(2) input').val())
  11446. .toBe('john.smith@example.org');
  11447. element('.doc-example-live li:first a:contains("clear")').click();
  11448. expect(element('.doc-example-live li:first input').val()).toBe('');
  11449. element('.doc-example-live li:last a:contains("add")').click();
  11450. expect(element('.doc-example-live li:nth-child(3) input').val())
  11451. .toBe('yourname@example.org');
  11452. });
  11453. </doc:scenario>
  11454. </doc:example>
  11455. */
  11456. var ngControllerDirective = [function() {
  11457. return {
  11458. scope: true,
  11459. controller: '@'
  11460. };
  11461. }];
  11462. /**
  11463. * @ngdoc directive
  11464. * @name ng.directive:ngCsp
  11465. * @priority 1000
  11466. *
  11467. * @description
  11468. * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
  11469. * This directive should be used on the root element of the application (typically the `<html>`
  11470. * element or other element with the {@link ng.directive:ngApp ngApp}
  11471. * directive).
  11472. *
  11473. * If enabled the performance of template expression evaluator will suffer slightly, so don't enable
  11474. * this mode unless you need it.
  11475. *
  11476. * @element html
  11477. */
  11478. var ngCspDirective = ['$sniffer', function($sniffer) {
  11479. return {
  11480. priority: 1000,
  11481. compile: function() {
  11482. $sniffer.csp = true;
  11483. }
  11484. };
  11485. }];
  11486. /**
  11487. * @ngdoc directive
  11488. * @name ng.directive:ngClick
  11489. *
  11490. * @description
  11491. * The ngClick allows you to specify custom behavior when
  11492. * element is clicked.
  11493. *
  11494. * @element ANY
  11495. * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
  11496. * click. (Event object is available as `$event`)
  11497. *
  11498. * @example
  11499. <doc:example>
  11500. <doc:source>
  11501. <button ng-click="count = count + 1" ng-init="count=0">
  11502. Increment
  11503. </button>
  11504. count: {{count}}
  11505. </doc:source>
  11506. <doc:scenario>
  11507. it('should check ng-click', function() {
  11508. expect(binding('count')).toBe('0');
  11509. element('.doc-example-live :button').click();
  11510. expect(binding('count')).toBe('1');
  11511. });
  11512. </doc:scenario>
  11513. </doc:example>
  11514. */
  11515. /*
  11516. * A directive that allows creation of custom onclick handlers that are defined as angular
  11517. * expressions and are compiled and executed within the current scope.
  11518. *
  11519. * Events that are handled via these handler are always configured not to propagate further.
  11520. */
  11521. var ngEventDirectives = {};
  11522. forEach(
  11523. 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '),
  11524. function(name) {
  11525. var directiveName = directiveNormalize('ng-' + name);
  11526. ngEventDirectives[directiveName] = ['$parse', function($parse) {
  11527. return function(scope, element, attr) {
  11528. var fn = $parse(attr[directiveName]);
  11529. element.bind(lowercase(name), function(event) {
  11530. scope.$apply(function() {
  11531. fn(scope, {$event:event});
  11532. });
  11533. });
  11534. };
  11535. }];
  11536. }
  11537. );
  11538. /**
  11539. * @ngdoc directive
  11540. * @name ng.directive:ngDblclick
  11541. *
  11542. * @description
  11543. * The `ngDblclick` directive allows you to specify custom behavior on dblclick event.
  11544. *
  11545. * @element ANY
  11546. * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
  11547. * dblclick. (Event object is available as `$event`)
  11548. *
  11549. * @example
  11550. * See {@link ng.directive:ngClick ngClick}
  11551. */
  11552. /**
  11553. * @ngdoc directive
  11554. * @name ng.directive:ngMousedown
  11555. *
  11556. * @description
  11557. * The ngMousedown directive allows you to specify custom behavior on mousedown event.
  11558. *
  11559. * @element ANY
  11560. * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
  11561. * mousedown. (Event object is available as `$event`)
  11562. *
  11563. * @example
  11564. * See {@link ng.directive:ngClick ngClick}
  11565. */
  11566. /**
  11567. * @ngdoc directive
  11568. * @name ng.directive:ngMouseup
  11569. *
  11570. * @description
  11571. * Specify custom behavior on mouseup event.
  11572. *
  11573. * @element ANY
  11574. * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
  11575. * mouseup. (Event object is available as `$event`)
  11576. *
  11577. * @example
  11578. * See {@link ng.directive:ngClick ngClick}
  11579. */
  11580. /**
  11581. * @ngdoc directive
  11582. * @name ng.directive:ngMouseover
  11583. *
  11584. * @description
  11585. * Specify custom behavior on mouseover event.
  11586. *
  11587. * @element ANY
  11588. * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
  11589. * mouseover. (Event object is available as `$event`)
  11590. *
  11591. * @example
  11592. * See {@link ng.directive:ngClick ngClick}
  11593. */
  11594. /**
  11595. * @ngdoc directive
  11596. * @name ng.directive:ngMouseenter
  11597. *
  11598. * @description
  11599. * Specify custom behavior on mouseenter event.
  11600. *
  11601. * @element ANY
  11602. * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
  11603. * mouseenter. (Event object is available as `$event`)
  11604. *
  11605. * @example
  11606. * See {@link ng.directive:ngClick ngClick}
  11607. */
  11608. /**
  11609. * @ngdoc directive
  11610. * @name ng.directive:ngMouseleave
  11611. *
  11612. * @description
  11613. * Specify custom behavior on mouseleave event.
  11614. *
  11615. * @element ANY
  11616. * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
  11617. * mouseleave. (Event object is available as `$event`)
  11618. *
  11619. * @example
  11620. * See {@link ng.directive:ngClick ngClick}
  11621. */
  11622. /**
  11623. * @ngdoc directive
  11624. * @name ng.directive:ngMousemove
  11625. *
  11626. * @description
  11627. * Specify custom behavior on mousemove event.
  11628. *
  11629. * @element ANY
  11630. * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
  11631. * mousemove. (Event object is available as `$event`)
  11632. *
  11633. * @example
  11634. * See {@link ng.directive:ngClick ngClick}
  11635. */
  11636. /**
  11637. * @ngdoc directive
  11638. * @name ng.directive:ngSubmit
  11639. *
  11640. * @description
  11641. * Enables binding angular expressions to onsubmit events.
  11642. *
  11643. * Additionally it prevents the default action (which for form means sending the request to the
  11644. * server and reloading the current page).
  11645. *
  11646. * @element form
  11647. * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
  11648. *
  11649. * @example
  11650. <doc:example>
  11651. <doc:source>
  11652. <script>
  11653. function Ctrl($scope) {
  11654. $scope.list = [];
  11655. $scope.text = 'hello';
  11656. $scope.submit = function() {
  11657. if (this.text) {
  11658. this.list.push(this.text);
  11659. this.text = '';
  11660. }
  11661. };
  11662. }
  11663. </script>
  11664. <form ng-submit="submit()" ng-controller="Ctrl">
  11665. Enter text and hit enter:
  11666. <input type="text" ng-model="text" name="text" />
  11667. <input type="submit" id="submit" value="Submit" />
  11668. <pre>list={{list}}</pre>
  11669. </form>
  11670. </doc:source>
  11671. <doc:scenario>
  11672. it('should check ng-submit', function() {
  11673. expect(binding('list')).toBe('[]');
  11674. element('.doc-example-live #submit').click();
  11675. expect(binding('list')).toBe('["hello"]');
  11676. expect(input('text').val()).toBe('');
  11677. });
  11678. it('should ignore empty strings', function() {
  11679. expect(binding('list')).toBe('[]');
  11680. element('.doc-example-live #submit').click();
  11681. element('.doc-example-live #submit').click();
  11682. expect(binding('list')).toBe('["hello"]');
  11683. });
  11684. </doc:scenario>
  11685. </doc:example>
  11686. */
  11687. var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
  11688. element.bind('submit', function() {
  11689. scope.$apply(attrs.ngSubmit);
  11690. });
  11691. });
  11692. /**
  11693. * @ngdoc directive
  11694. * @name ng.directive:ngInclude
  11695. * @restrict ECA
  11696. *
  11697. * @description
  11698. * Fetches, compiles and includes an external HTML fragment.
  11699. *
  11700. * Keep in mind that Same Origin Policy applies to included resources
  11701. * (e.g. ngInclude won't work for cross-domain requests on all browsers and for
  11702. * file:// access on some browsers).
  11703. *
  11704. * @scope
  11705. *
  11706. * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
  11707. * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`.
  11708. * @param {string=} onload Expression to evaluate when a new partial is loaded.
  11709. *
  11710. * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
  11711. * $anchorScroll} to scroll the viewport after the content is loaded.
  11712. *
  11713. * - If the attribute is not set, disable scrolling.
  11714. * - If the attribute is set without value, enable scrolling.
  11715. * - Otherwise enable scrolling only if the expression evaluates to truthy value.
  11716. *
  11717. * @example
  11718. <example>
  11719. <file name="index.html">
  11720. <div ng-controller="Ctrl">
  11721. <select ng-model="template" ng-options="t.name for t in templates">
  11722. <option value="">(blank)</option>
  11723. </select>
  11724. url of the template: <tt>{{template.url}}</tt>
  11725. <hr/>
  11726. <div ng-include src="template.url"></div>
  11727. </div>
  11728. </file>
  11729. <file name="script.js">
  11730. function Ctrl($scope) {
  11731. $scope.templates =
  11732. [ { name: 'template1.html', url: 'template1.html'}
  11733. , { name: 'template2.html', url: 'template2.html'} ];
  11734. $scope.template = $scope.templates[0];
  11735. }
  11736. </file>
  11737. <file name="template1.html">
  11738. Content of template1.html
  11739. </file>
  11740. <file name="template2.html">
  11741. Content of template2.html
  11742. </file>
  11743. <file name="scenario.js">
  11744. it('should load template1.html', function() {
  11745. expect(element('.doc-example-live [ng-include]').text()).
  11746. toMatch(/Content of template1.html/);
  11747. });
  11748. it('should load template2.html', function() {
  11749. select('template').option('1');
  11750. expect(element('.doc-example-live [ng-include]').text()).
  11751. toMatch(/Content of template2.html/);
  11752. });
  11753. it('should change to blank', function() {
  11754. select('template').option('');
  11755. expect(element('.doc-example-live [ng-include]').text()).toEqual('');
  11756. });
  11757. </file>
  11758. </example>
  11759. */
  11760. /**
  11761. * @ngdoc event
  11762. * @name ng.directive:ngInclude#$includeContentLoaded
  11763. * @eventOf ng.directive:ngInclude
  11764. * @eventType emit on the current ngInclude scope
  11765. * @description
  11766. * Emitted every time the ngInclude content is reloaded.
  11767. */
  11768. var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
  11769. function($http, $templateCache, $anchorScroll, $compile) {
  11770. return {
  11771. restrict: 'ECA',
  11772. terminal: true,
  11773. compile: function(element, attr) {
  11774. var srcExp = attr.ngInclude || attr.src,
  11775. onloadExp = attr.onload || '',
  11776. autoScrollExp = attr.autoscroll;
  11777. return function(scope, element) {
  11778. var changeCounter = 0,
  11779. childScope;
  11780. var clearContent = function() {
  11781. if (childScope) {
  11782. childScope.$destroy();
  11783. childScope = null;
  11784. }
  11785. element.html('');
  11786. };
  11787. scope.$watch(srcExp, function ngIncludeWatchAction(src) {
  11788. var thisChangeId = ++changeCounter;
  11789. if (src) {
  11790. $http.get(src, {cache: $templateCache}).success(function(response) {
  11791. if (thisChangeId !== changeCounter) return;
  11792. if (childScope) childScope.$destroy();
  11793. childScope = scope.$new();
  11794. element.html(response);
  11795. $compile(element.contents())(childScope);
  11796. if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
  11797. $anchorScroll();
  11798. }
  11799. childScope.$emit('$includeContentLoaded');
  11800. scope.$eval(onloadExp);
  11801. }).error(function() {
  11802. if (thisChangeId === changeCounter) clearContent();
  11803. });
  11804. } else clearContent();
  11805. });
  11806. };
  11807. }
  11808. };
  11809. }];
  11810. /**
  11811. * @ngdoc directive
  11812. * @name ng.directive:ngInit
  11813. *
  11814. * @description
  11815. * The `ngInit` directive specifies initialization tasks to be executed
  11816. * before the template enters execution mode during bootstrap.
  11817. *
  11818. * @element ANY
  11819. * @param {expression} ngInit {@link guide/expression Expression} to eval.
  11820. *
  11821. * @example
  11822. <doc:example>
  11823. <doc:source>
  11824. <div ng-init="greeting='Hello'; person='World'">
  11825. {{greeting}} {{person}}!
  11826. </div>
  11827. </doc:source>
  11828. <doc:scenario>
  11829. it('should check greeting', function() {
  11830. expect(binding('greeting')).toBe('Hello');
  11831. expect(binding('person')).toBe('World');
  11832. });
  11833. </doc:scenario>
  11834. </doc:example>
  11835. */
  11836. var ngInitDirective = ngDirective({
  11837. compile: function() {
  11838. return {
  11839. pre: function(scope, element, attrs) {
  11840. scope.$eval(attrs.ngInit);
  11841. }
  11842. }
  11843. }
  11844. });
  11845. /**
  11846. * @ngdoc directive
  11847. * @name ng.directive:ngNonBindable
  11848. * @priority 1000
  11849. *
  11850. * @description
  11851. * Sometimes it is necessary to write code which looks like bindings but which should be left alone
  11852. * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML.
  11853. *
  11854. * @element ANY
  11855. *
  11856. * @example
  11857. * In this example there are two location where a simple binding (`{{}}`) is present, but the one
  11858. * wrapped in `ngNonBindable` is left alone.
  11859. *
  11860. * @example
  11861. <doc:example>
  11862. <doc:source>
  11863. <div>Normal: {{1 + 2}}</div>
  11864. <div ng-non-bindable>Ignored: {{1 + 2}}</div>
  11865. </doc:source>
  11866. <doc:scenario>
  11867. it('should check ng-non-bindable', function() {
  11868. expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
  11869. expect(using('.doc-example-live').element('div:last').text()).
  11870. toMatch(/1 \+ 2/);
  11871. });
  11872. </doc:scenario>
  11873. </doc:example>
  11874. */
  11875. var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
  11876. /**
  11877. * @ngdoc directive
  11878. * @name ng.directive:ngPluralize
  11879. * @restrict EA
  11880. *
  11881. * @description
  11882. * # Overview
  11883. * `ngPluralize` is a directive that displays messages according to en-US localization rules.
  11884. * These rules are bundled with angular.js and the rules can be overridden
  11885. * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
  11886. * by specifying the mappings between
  11887. * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
  11888. * plural categories} and the strings to be displayed.
  11889. *
  11890. * # Plural categories and explicit number rules
  11891. * There are two
  11892. * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
  11893. * plural categories} in Angular's default en-US locale: "one" and "other".
  11894. *
  11895. * While a pural category may match many numbers (for example, in en-US locale, "other" can match
  11896. * any number that is not 1), an explicit number rule can only match one number. For example, the
  11897. * explicit number rule for "3" matches the number 3. You will see the use of plural categories
  11898. * and explicit number rules throughout later parts of this documentation.
  11899. *
  11900. * # Configuring ngPluralize
  11901. * You configure ngPluralize by providing 2 attributes: `count` and `when`.
  11902. * You can also provide an optional attribute, `offset`.
  11903. *
  11904. * The value of the `count` attribute can be either a string or an {@link guide/expression
  11905. * Angular expression}; these are evaluated on the current scope for its bound value.
  11906. *
  11907. * The `when` attribute specifies the mappings between plural categories and the actual
  11908. * string to be displayed. The value of the attribute should be a JSON object so that Angular
  11909. * can interpret it correctly.
  11910. *
  11911. * The following example shows how to configure ngPluralize:
  11912. *
  11913. * <pre>
  11914. * <ng-pluralize count="personCount"
  11915. when="{'0': 'Nobody is viewing.',
  11916. * 'one': '1 person is viewing.',
  11917. * 'other': '{} people are viewing.'}">
  11918. * </ng-pluralize>
  11919. *</pre>
  11920. *
  11921. * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
  11922. * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
  11923. * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
  11924. * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
  11925. * show "a dozen people are viewing".
  11926. *
  11927. * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
  11928. * into pluralized strings. In the previous example, Angular will replace `{}` with
  11929. * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
  11930. * for <span ng-non-bindable>{{numberExpression}}</span>.
  11931. *
  11932. * # Configuring ngPluralize with offset
  11933. * The `offset` attribute allows further customization of pluralized text, which can result in
  11934. * a better user experience. For example, instead of the message "4 people are viewing this document",
  11935. * you might display "John, Kate and 2 others are viewing this document".
  11936. * The offset attribute allows you to offset a number by any desired value.
  11937. * Let's take a look at an example:
  11938. *
  11939. * <pre>
  11940. * <ng-pluralize count="personCount" offset=2
  11941. * when="{'0': 'Nobody is viewing.',
  11942. * '1': '{{person1}} is viewing.',
  11943. * '2': '{{person1}} and {{person2}} are viewing.',
  11944. * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
  11945. * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
  11946. * </ng-pluralize>
  11947. * </pre>
  11948. *
  11949. * Notice that we are still using two plural categories(one, other), but we added
  11950. * three explicit number rules 0, 1 and 2.
  11951. * When one person, perhaps John, views the document, "John is viewing" will be shown.
  11952. * When three people view the document, no explicit number rule is found, so
  11953. * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
  11954. * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
  11955. * is shown.
  11956. *
  11957. * Note that when you specify offsets, you must provide explicit number rules for
  11958. * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
  11959. * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
  11960. * plural categories "one" and "other".
  11961. *
  11962. * @param {string|expression} count The variable to be bounded to.
  11963. * @param {string} when The mapping between plural category to its correspoding strings.
  11964. * @param {number=} offset Offset to deduct from the total number.
  11965. *
  11966. * @example
  11967. <doc:example>
  11968. <doc:source>
  11969. <script>
  11970. function Ctrl($scope) {
  11971. $scope.person1 = 'Igor';
  11972. $scope.person2 = 'Misko';
  11973. $scope.personCount = 1;
  11974. }
  11975. </script>
  11976. <div ng-controller="Ctrl">
  11977. Person 1:<input type="text" ng-model="person1" value="Igor" /><br/>
  11978. Person 2:<input type="text" ng-model="person2" value="Misko" /><br/>
  11979. Number of People:<input type="text" ng-model="personCount" value="1" /><br/>
  11980. <!--- Example with simple pluralization rules for en locale --->
  11981. Without Offset:
  11982. <ng-pluralize count="personCount"
  11983. when="{'0': 'Nobody is viewing.',
  11984. 'one': '1 person is viewing.',
  11985. 'other': '{} people are viewing.'}">
  11986. </ng-pluralize><br>
  11987. <!--- Example with offset --->
  11988. With Offset(2):
  11989. <ng-pluralize count="personCount" offset=2
  11990. when="{'0': 'Nobody is viewing.',
  11991. '1': '{{person1}} is viewing.',
  11992. '2': '{{person1}} and {{person2}} are viewing.',
  11993. 'one': '{{person1}}, {{person2}} and one other person are viewing.',
  11994. 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
  11995. </ng-pluralize>
  11996. </div>
  11997. </doc:source>
  11998. <doc:scenario>
  11999. it('should show correct pluralized string', function() {
  12000. expect(element('.doc-example-live ng-pluralize:first').text()).
  12001. toBe('1 person is viewing.');
  12002. expect(element('.doc-example-live ng-pluralize:last').text()).
  12003. toBe('Igor is viewing.');
  12004. using('.doc-example-live').input('personCount').enter('0');
  12005. expect(element('.doc-example-live ng-pluralize:first').text()).
  12006. toBe('Nobody is viewing.');
  12007. expect(element('.doc-example-live ng-pluralize:last').text()).
  12008. toBe('Nobody is viewing.');
  12009. using('.doc-example-live').input('personCount').enter('2');
  12010. expect(element('.doc-example-live ng-pluralize:first').text()).
  12011. toBe('2 people are viewing.');
  12012. expect(element('.doc-example-live ng-pluralize:last').text()).
  12013. toBe('Igor and Misko are viewing.');
  12014. using('.doc-example-live').input('personCount').enter('3');
  12015. expect(element('.doc-example-live ng-pluralize:first').text()).
  12016. toBe('3 people are viewing.');
  12017. expect(element('.doc-example-live ng-pluralize:last').text()).
  12018. toBe('Igor, Misko and one other person are viewing.');
  12019. using('.doc-example-live').input('personCount').enter('4');
  12020. expect(element('.doc-example-live ng-pluralize:first').text()).
  12021. toBe('4 people are viewing.');
  12022. expect(element('.doc-example-live ng-pluralize:last').text()).
  12023. toBe('Igor, Misko and 2 other people are viewing.');
  12024. });
  12025. it('should show data-binded names', function() {
  12026. using('.doc-example-live').input('personCount').enter('4');
  12027. expect(element('.doc-example-live ng-pluralize:last').text()).
  12028. toBe('Igor, Misko and 2 other people are viewing.');
  12029. using('.doc-example-live').input('person1').enter('Di');
  12030. using('.doc-example-live').input('person2').enter('Vojta');
  12031. expect(element('.doc-example-live ng-pluralize:last').text()).
  12032. toBe('Di, Vojta and 2 other people are viewing.');
  12033. });
  12034. </doc:scenario>
  12035. </doc:example>
  12036. */
  12037. var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
  12038. var BRACE = /{}/g;
  12039. return {
  12040. restrict: 'EA',
  12041. link: function(scope, element, attr) {
  12042. var numberExp = attr.count,
  12043. whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs
  12044. offset = attr.offset || 0,
  12045. whens = scope.$eval(whenExp),
  12046. whensExpFns = {},
  12047. startSymbol = $interpolate.startSymbol(),
  12048. endSymbol = $interpolate.endSymbol();
  12049. forEach(whens, function(expression, key) {
  12050. whensExpFns[key] =
  12051. $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' +
  12052. offset + endSymbol));
  12053. });
  12054. scope.$watch(function ngPluralizeWatch() {
  12055. var value = parseFloat(scope.$eval(numberExp));
  12056. if (!isNaN(value)) {
  12057. //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
  12058. //check it against pluralization rules in $locale service
  12059. if (!whens[value]) value = $locale.pluralCat(value - offset);
  12060. return whensExpFns[value](scope, element, true);
  12061. } else {
  12062. return '';
  12063. }
  12064. }, function ngPluralizeWatchAction(newVal) {
  12065. element.text(newVal);
  12066. });
  12067. }
  12068. };
  12069. }];
  12070. /**
  12071. * @ngdoc directive
  12072. * @name ng.directive:ngRepeat
  12073. *
  12074. * @description
  12075. * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
  12076. * instance gets its own scope, where the given loop variable is set to the current collection item,
  12077. * and `$index` is set to the item index or key.
  12078. *
  12079. * Special properties are exposed on the local scope of each template instance, including:
  12080. *
  12081. * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
  12082. * * `$first` – `{boolean}` – true if the repeated element is first in the iterator.
  12083. * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
  12084. * * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
  12085. *
  12086. *
  12087. * @element ANY
  12088. * @scope
  12089. * @priority 1000
  12090. * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two
  12091. * formats are currently supported:
  12092. *
  12093. * * `variable in expression` – where variable is the user defined loop variable and `expression`
  12094. * is a scope expression giving the collection to enumerate.
  12095. *
  12096. * For example: `track in cd.tracks`.
  12097. *
  12098. * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
  12099. * and `expression` is the scope expression giving the collection to enumerate.
  12100. *
  12101. * For example: `(name, age) in {'adam':10, 'amalie':12}`.
  12102. *
  12103. * @example
  12104. * This example initializes the scope to a list of names and
  12105. * then uses `ngRepeat` to display every person:
  12106. <doc:example>
  12107. <doc:source>
  12108. <div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
  12109. I have {{friends.length}} friends. They are:
  12110. <ul>
  12111. <li ng-repeat="friend in friends">
  12112. [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
  12113. </li>
  12114. </ul>
  12115. </div>
  12116. </doc:source>
  12117. <doc:scenario>
  12118. it('should check ng-repeat', function() {
  12119. var r = using('.doc-example-live').repeater('ul li');
  12120. expect(r.count()).toBe(2);
  12121. expect(r.row(0)).toEqual(["1","John","25"]);
  12122. expect(r.row(1)).toEqual(["2","Mary","28"]);
  12123. });
  12124. </doc:scenario>
  12125. </doc:example>
  12126. */
  12127. var ngRepeatDirective = ngDirective({
  12128. transclude: 'element',
  12129. priority: 1000,
  12130. terminal: true,
  12131. compile: function(element, attr, linker) {
  12132. return function(scope, iterStartElement, attr){
  12133. var expression = attr.ngRepeat;
  12134. var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
  12135. lhs, rhs, valueIdent, keyIdent;
  12136. if (! match) {
  12137. throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" +
  12138. expression + "'.");
  12139. }
  12140. lhs = match[1];
  12141. rhs = match[2];
  12142. match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
  12143. if (!match) {
  12144. throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
  12145. lhs + "'.");
  12146. }
  12147. valueIdent = match[3] || match[1];
  12148. keyIdent = match[2];
  12149. // Store a list of elements from previous run. This is a hash where key is the item from the
  12150. // iterator, and the value is an array of objects with following properties.
  12151. // - scope: bound scope
  12152. // - element: previous element.
  12153. // - index: position
  12154. // We need an array of these objects since the same object can be returned from the iterator.
  12155. // We expect this to be a rare case.
  12156. var lastOrder = new HashQueueMap();
  12157. scope.$watch(function ngRepeatWatch(scope){
  12158. var index, length,
  12159. collection = scope.$eval(rhs),
  12160. collectionLength = size(collection, true),
  12161. childScope,
  12162. // Same as lastOrder but it has the current state. It will become the
  12163. // lastOrder on the next iteration.
  12164. nextOrder = new HashQueueMap(),
  12165. key, value, // key/value of iteration
  12166. array, last, // last object information {scope, element, index}
  12167. cursor = iterStartElement; // current position of the node
  12168. if (!isArray(collection)) {
  12169. // if object, extract keys, sort them and use to determine order of iteration over obj props
  12170. array = [];
  12171. for(key in collection) {
  12172. if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
  12173. array.push(key);
  12174. }
  12175. }
  12176. array.sort();
  12177. } else {
  12178. array = collection || [];
  12179. }
  12180. // we are not using forEach for perf reasons (trying to avoid #call)
  12181. for (index = 0, length = array.length; index < length; index++) {
  12182. key = (collection === array) ? index : array[index];
  12183. value = collection[key];
  12184. last = lastOrder.shift(value);
  12185. if (last) {
  12186. // if we have already seen this object, then we need to reuse the
  12187. // associated scope/element
  12188. childScope = last.scope;
  12189. nextOrder.push(value, last);
  12190. if (index === last.index) {
  12191. // do nothing
  12192. cursor = last.element;
  12193. } else {
  12194. // existing item which got moved
  12195. last.index = index;
  12196. // This may be a noop, if the element is next, but I don't know of a good way to
  12197. // figure this out, since it would require extra DOM access, so let's just hope that
  12198. // the browsers realizes that it is noop, and treats it as such.
  12199. cursor.after(last.element);
  12200. cursor = last.element;
  12201. }
  12202. } else {
  12203. // new item which we don't know about
  12204. childScope = scope.$new();
  12205. }
  12206. childScope[valueIdent] = value;
  12207. if (keyIdent) childScope[keyIdent] = key;
  12208. childScope.$index = index;
  12209. childScope.$first = (index === 0);
  12210. childScope.$last = (index === (collectionLength - 1));
  12211. childScope.$middle = !(childScope.$first || childScope.$last);
  12212. if (!last) {
  12213. linker(childScope, function(clone){
  12214. cursor.after(clone);
  12215. last = {
  12216. scope: childScope,
  12217. element: (cursor = clone),
  12218. index: index
  12219. };
  12220. nextOrder.push(value, last);
  12221. });
  12222. }
  12223. }
  12224. //shrink children
  12225. for (key in lastOrder) {
  12226. if (lastOrder.hasOwnProperty(key)) {
  12227. array = lastOrder[key];
  12228. while(array.length) {
  12229. value = array.pop();
  12230. value.element.remove();
  12231. value.scope.$destroy();
  12232. }
  12233. }
  12234. }
  12235. lastOrder = nextOrder;
  12236. });
  12237. };
  12238. }
  12239. });
  12240. /**
  12241. * @ngdoc directive
  12242. * @name ng.directive:ngShow
  12243. *
  12244. * @description
  12245. * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
  12246. * conditionally.
  12247. *
  12248. * @element ANY
  12249. * @param {expression} ngShow If the {@link guide/expression expression} is truthy
  12250. * then the element is shown or hidden respectively.
  12251. *
  12252. * @example
  12253. <doc:example>
  12254. <doc:source>
  12255. Click me: <input type="checkbox" ng-model="checked"><br/>
  12256. Show: <span ng-show="checked">I show up when your checkbox is checked.</span> <br/>
  12257. Hide: <span ng-hide="checked">I hide when your checkbox is checked.</span>
  12258. </doc:source>
  12259. <doc:scenario>
  12260. it('should check ng-show / ng-hide', function() {
  12261. expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
  12262. expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
  12263. input('checked').check();
  12264. expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
  12265. expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
  12266. });
  12267. </doc:scenario>
  12268. </doc:example>
  12269. */
  12270. //TODO(misko): refactor to remove element from the DOM
  12271. var ngShowDirective = ngDirective(function(scope, element, attr){
  12272. scope.$watch(attr.ngShow, function ngShowWatchAction(value){
  12273. element.css('display', toBoolean(value) ? '' : 'none');
  12274. });
  12275. });
  12276. /**
  12277. * @ngdoc directive
  12278. * @name ng.directive:ngHide
  12279. *
  12280. * @description
  12281. * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML)
  12282. * conditionally.
  12283. *
  12284. * @element ANY
  12285. * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
  12286. * the element is shown or hidden respectively.
  12287. *
  12288. * @example
  12289. <doc:example>
  12290. <doc:source>
  12291. Click me: <input type="checkbox" ng-model="checked"><br/>
  12292. Show: <span ng-show="checked">I show up when you checkbox is checked?</span> <br/>
  12293. Hide: <span ng-hide="checked">I hide when you checkbox is checked?</span>
  12294. </doc:source>
  12295. <doc:scenario>
  12296. it('should check ng-show / ng-hide', function() {
  12297. expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
  12298. expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
  12299. input('checked').check();
  12300. expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
  12301. expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
  12302. });
  12303. </doc:scenario>
  12304. </doc:example>
  12305. */
  12306. //TODO(misko): refactor to remove element from the DOM
  12307. var ngHideDirective = ngDirective(function(scope, element, attr){
  12308. scope.$watch(attr.ngHide, function ngHideWatchAction(value){
  12309. element.css('display', toBoolean(value) ? 'none' : '');
  12310. });
  12311. });
  12312. /**
  12313. * @ngdoc directive
  12314. * @name ng.directive:ngStyle
  12315. *
  12316. * @description
  12317. * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
  12318. *
  12319. * @element ANY
  12320. * @param {expression} ngStyle {@link guide/expression Expression} which evals to an
  12321. * object whose keys are CSS style names and values are corresponding values for those CSS
  12322. * keys.
  12323. *
  12324. * @example
  12325. <example>
  12326. <file name="index.html">
  12327. <input type="button" value="set" ng-click="myStyle={color:'red'}">
  12328. <input type="button" value="clear" ng-click="myStyle={}">
  12329. <br/>
  12330. <span ng-style="myStyle">Sample Text</span>
  12331. <pre>myStyle={{myStyle}}</pre>
  12332. </file>
  12333. <file name="style.css">
  12334. span {
  12335. color: black;
  12336. }
  12337. </file>
  12338. <file name="scenario.js">
  12339. it('should check ng-style', function() {
  12340. expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
  12341. element('.doc-example-live :button[value=set]').click();
  12342. expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
  12343. element('.doc-example-live :button[value=clear]').click();
  12344. expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
  12345. });
  12346. </file>
  12347. </example>
  12348. */
  12349. var ngStyleDirective = ngDirective(function(scope, element, attr) {
  12350. scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
  12351. if (oldStyles && (newStyles !== oldStyles)) {
  12352. forEach(oldStyles, function(val, style) { element.css(style, '');});
  12353. }
  12354. if (newStyles) element.css(newStyles);
  12355. }, true);
  12356. });
  12357. /**
  12358. * @ngdoc directive
  12359. * @name ng.directive:ngSwitch
  12360. * @restrict EA
  12361. *
  12362. * @description
  12363. * Conditionally change the DOM structure.
  12364. *
  12365. * @usageContent
  12366. * <ANY ng-switch-when="matchValue1">...</ANY>
  12367. * <ANY ng-switch-when="matchValue2">...</ANY>
  12368. * ...
  12369. * <ANY ng-switch-default>...</ANY>
  12370. *
  12371. * @scope
  12372. * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
  12373. * @paramDescription
  12374. * On child elments add:
  12375. *
  12376. * * `ngSwitchWhen`: the case statement to match against. If match then this
  12377. * case will be displayed.
  12378. * * `ngSwitchDefault`: the default case when no other casses match.
  12379. *
  12380. * @example
  12381. <doc:example>
  12382. <doc:source>
  12383. <script>
  12384. function Ctrl($scope) {
  12385. $scope.items = ['settings', 'home', 'other'];
  12386. $scope.selection = $scope.items[0];
  12387. }
  12388. </script>
  12389. <div ng-controller="Ctrl">
  12390. <select ng-model="selection" ng-options="item for item in items">
  12391. </select>
  12392. <tt>selection={{selection}}</tt>
  12393. <hr/>
  12394. <div ng-switch on="selection" >
  12395. <div ng-switch-when="settings">Settings Div</div>
  12396. <span ng-switch-when="home">Home Span</span>
  12397. <span ng-switch-default>default</span>
  12398. </div>
  12399. </div>
  12400. </doc:source>
  12401. <doc:scenario>
  12402. it('should start in settings', function() {
  12403. expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
  12404. });
  12405. it('should change to home', function() {
  12406. select('selection').option('home');
  12407. expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
  12408. });
  12409. it('should select deafault', function() {
  12410. select('selection').option('other');
  12411. expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
  12412. });
  12413. </doc:scenario>
  12414. </doc:example>
  12415. */
  12416. var NG_SWITCH = 'ng-switch';
  12417. var ngSwitchDirective = valueFn({
  12418. restrict: 'EA',
  12419. compile: function(element, attr) {
  12420. var watchExpr = attr.ngSwitch || attr.on,
  12421. cases = {};
  12422. element.data(NG_SWITCH, cases);
  12423. return function(scope, element){
  12424. var selectedTransclude,
  12425. selectedElement,
  12426. selectedScope;
  12427. scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
  12428. if (selectedElement) {
  12429. selectedScope.$destroy();
  12430. selectedElement.remove();
  12431. selectedElement = selectedScope = null;
  12432. }
  12433. if ((selectedTransclude = cases['!' + value] || cases['?'])) {
  12434. scope.$eval(attr.change);
  12435. selectedScope = scope.$new();
  12436. selectedTransclude(selectedScope, function(caseElement) {
  12437. selectedElement = caseElement;
  12438. element.append(caseElement);
  12439. });
  12440. }
  12441. });
  12442. };
  12443. }
  12444. });
  12445. var ngSwitchWhenDirective = ngDirective({
  12446. transclude: 'element',
  12447. priority: 500,
  12448. compile: function(element, attrs, transclude) {
  12449. var cases = element.inheritedData(NG_SWITCH);
  12450. assertArg(cases);
  12451. cases['!' + attrs.ngSwitchWhen] = transclude;
  12452. }
  12453. });
  12454. var ngSwitchDefaultDirective = ngDirective({
  12455. transclude: 'element',
  12456. priority: 500,
  12457. compile: function(element, attrs, transclude) {
  12458. var cases = element.inheritedData(NG_SWITCH);
  12459. assertArg(cases);
  12460. cases['?'] = transclude;
  12461. }
  12462. });
  12463. /**
  12464. * @ngdoc directive
  12465. * @name ng.directive:ngTransclude
  12466. *
  12467. * @description
  12468. * Insert the transcluded DOM here.
  12469. *
  12470. * @element ANY
  12471. *
  12472. * @example
  12473. <doc:example module="transclude">
  12474. <doc:source>
  12475. <script>
  12476. function Ctrl($scope) {
  12477. $scope.title = 'Lorem Ipsum';
  12478. $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
  12479. }
  12480. angular.module('transclude', [])
  12481. .directive('pane', function(){
  12482. return {
  12483. restrict: 'E',
  12484. transclude: true,
  12485. scope: 'isolate',
  12486. locals: { title:'bind' },
  12487. template: '<div style="border: 1px solid black;">' +
  12488. '<div style="background-color: gray">{{title}}</div>' +
  12489. '<div ng-transclude></div>' +
  12490. '</div>'
  12491. };
  12492. });
  12493. </script>
  12494. <div ng-controller="Ctrl">
  12495. <input ng-model="title"><br>
  12496. <textarea ng-model="text"></textarea> <br/>
  12497. <pane title="{{title}}">{{text}}</pane>
  12498. </div>
  12499. </doc:source>
  12500. <doc:scenario>
  12501. it('should have transcluded', function() {
  12502. input('title').enter('TITLE');
  12503. input('text').enter('TEXT');
  12504. expect(binding('title')).toEqual('TITLE');
  12505. expect(binding('text')).toEqual('TEXT');
  12506. });
  12507. </doc:scenario>
  12508. </doc:example>
  12509. *
  12510. */
  12511. var ngTranscludeDirective = ngDirective({
  12512. controller: ['$transclude', '$element', function($transclude, $element) {
  12513. $transclude(function(clone) {
  12514. $element.append(clone);
  12515. });
  12516. }]
  12517. });
  12518. /**
  12519. * @ngdoc directive
  12520. * @name ng.directive:ngView
  12521. * @restrict ECA
  12522. *
  12523. * @description
  12524. * # Overview
  12525. * `ngView` is a directive that complements the {@link ng.$route $route} service by
  12526. * including the rendered template of the current route into the main layout (`index.html`) file.
  12527. * Every time the current route changes, the included view changes with it according to the
  12528. * configuration of the `$route` service.
  12529. *
  12530. * @scope
  12531. * @example
  12532. <example module="ngView">
  12533. <file name="index.html">
  12534. <div ng-controller="MainCntl">
  12535. Choose:
  12536. <a href="Book/Moby">Moby</a> |
  12537. <a href="Book/Moby/ch/1">Moby: Ch1</a> |
  12538. <a href="Book/Gatsby">Gatsby</a> |
  12539. <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
  12540. <a href="Book/Scarlet">Scarlet Letter</a><br/>
  12541. <div ng-view></div>
  12542. <hr />
  12543. <pre>$location.path() = {{$location.path()}}</pre>
  12544. <pre>$route.current.template = {{$route.current.template}}</pre>
  12545. <pre>$route.current.params = {{$route.current.params}}</pre>
  12546. <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
  12547. <pre>$routeParams = {{$routeParams}}</pre>
  12548. </div>
  12549. </file>
  12550. <file name="book.html">
  12551. controller: {{name}}<br />
  12552. Book Id: {{params.bookId}}<br />
  12553. </file>
  12554. <file name="chapter.html">
  12555. controller: {{name}}<br />
  12556. Book Id: {{params.bookId}}<br />
  12557. Chapter Id: {{params.chapterId}}
  12558. </file>
  12559. <file name="script.js">
  12560. angular.module('ngView', [], function($routeProvider, $locationProvider) {
  12561. $routeProvider.when('/Book/:bookId', {
  12562. templateUrl: 'book.html',
  12563. controller: BookCntl
  12564. });
  12565. $routeProvider.when('/Book/:bookId/ch/:chapterId', {
  12566. templateUrl: 'chapter.html',
  12567. controller: ChapterCntl
  12568. });
  12569. // configure html5 to get links working on jsfiddle
  12570. $locationProvider.html5Mode(true);
  12571. });
  12572. function MainCntl($scope, $route, $routeParams, $location) {
  12573. $scope.$route = $route;
  12574. $scope.$location = $location;
  12575. $scope.$routeParams = $routeParams;
  12576. }
  12577. function BookCntl($scope, $routeParams) {
  12578. $scope.name = "BookCntl";
  12579. $scope.params = $routeParams;
  12580. }
  12581. function ChapterCntl($scope, $routeParams) {
  12582. $scope.name = "ChapterCntl";
  12583. $scope.params = $routeParams;
  12584. }
  12585. </file>
  12586. <file name="scenario.js">
  12587. it('should load and compile correct template', function() {
  12588. element('a:contains("Moby: Ch1")').click();
  12589. var content = element('.doc-example-live [ng-view]').text();
  12590. expect(content).toMatch(/controller\: ChapterCntl/);
  12591. expect(content).toMatch(/Book Id\: Moby/);
  12592. expect(content).toMatch(/Chapter Id\: 1/);
  12593. element('a:contains("Scarlet")').click();
  12594. content = element('.doc-example-live [ng-view]').text();
  12595. expect(content).toMatch(/controller\: BookCntl/);
  12596. expect(content).toMatch(/Book Id\: Scarlet/);
  12597. });
  12598. </file>
  12599. </example>
  12600. */
  12601. /**
  12602. * @ngdoc event
  12603. * @name ng.directive:ngView#$viewContentLoaded
  12604. * @eventOf ng.directive:ngView
  12605. * @eventType emit on the current ngView scope
  12606. * @description
  12607. * Emitted every time the ngView content is reloaded.
  12608. */
  12609. var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
  12610. '$controller',
  12611. function($http, $templateCache, $route, $anchorScroll, $compile,
  12612. $controller) {
  12613. return {
  12614. restrict: 'ECA',
  12615. terminal: true,
  12616. link: function(scope, element, attr) {
  12617. var lastScope,
  12618. onloadExp = attr.onload || '';
  12619. scope.$on('$routeChangeSuccess', update);
  12620. update();
  12621. function destroyLastScope() {
  12622. if (lastScope) {
  12623. lastScope.$destroy();
  12624. lastScope = null;
  12625. }
  12626. }
  12627. function clearContent() {
  12628. element.html('');
  12629. destroyLastScope();
  12630. }
  12631. function update() {
  12632. var locals = $route.current && $route.current.locals,
  12633. template = locals && locals.$template;
  12634. if (template) {
  12635. element.html(template);
  12636. destroyLastScope();
  12637. var link = $compile(element.contents()),
  12638. current = $route.current,
  12639. controller;
  12640. lastScope = current.scope = scope.$new();
  12641. if (current.controller) {
  12642. locals.$scope = lastScope;
  12643. controller = $controller(current.controller, locals);
  12644. element.contents().data('$ngControllerController', controller);
  12645. }
  12646. link(lastScope);
  12647. lastScope.$emit('$viewContentLoaded');
  12648. lastScope.$eval(onloadExp);
  12649. // $anchorScroll might listen on event...
  12650. $anchorScroll();
  12651. } else {
  12652. clearContent();
  12653. }
  12654. }
  12655. }
  12656. };
  12657. }];
  12658. /**
  12659. * @ngdoc directive
  12660. * @name ng.directive:script
  12661. *
  12662. * @description
  12663. * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
  12664. * template can be used by `ngInclude`, `ngView` or directive templates.
  12665. *
  12666. * @restrict E
  12667. * @param {'text/ng-template'} type must be set to `'text/ng-template'`
  12668. *
  12669. * @example
  12670. <doc:example>
  12671. <doc:source>
  12672. <script type="text/ng-template" id="/tpl.html">
  12673. Content of the template.
  12674. </script>
  12675. <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
  12676. <div id="tpl-content" ng-include src="currentTpl"></div>
  12677. </doc:source>
  12678. <doc:scenario>
  12679. it('should load template defined inside script tag', function() {
  12680. element('#tpl-link').click();
  12681. expect(element('#tpl-content').text()).toMatch(/Content of the template/);
  12682. });
  12683. </doc:scenario>
  12684. </doc:example>
  12685. */
  12686. var scriptDirective = ['$templateCache', function($templateCache) {
  12687. return {
  12688. restrict: 'E',
  12689. terminal: true,
  12690. compile: function(element, attr) {
  12691. if (attr.type == 'text/ng-template') {
  12692. var templateUrl = attr.id,
  12693. // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
  12694. text = element[0].text;
  12695. $templateCache.put(templateUrl, text);
  12696. }
  12697. }
  12698. };
  12699. }];
  12700. /**
  12701. * @ngdoc directive
  12702. * @name ng.directive:select
  12703. * @restrict E
  12704. *
  12705. * @description
  12706. * HTML `SELECT` element with angular data-binding.
  12707. *
  12708. * # `ngOptions`
  12709. *
  12710. * Optionally `ngOptions` attribute can be used to dynamically generate a list of `<option>`
  12711. * elements for a `<select>` element using an array or an object obtained by evaluating the
  12712. * `ngOptions` expression.
  12713. *??
  12714. * When an item in the select menu is select, the value of array element or object property
  12715. * represented by the selected option will be bound to the model identified by the `ngModel`
  12716. * directive of the parent select element.
  12717. *
  12718. * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
  12719. * be nested into the `<select>` element. This element will then represent `null` or "not selected"
  12720. * option. See example below for demonstration.
  12721. *
  12722. * Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead
  12723. * of {@link ng.directive:ngRepeat ngRepeat} when you want the
  12724. * `select` model to be bound to a non-string value. This is because an option element can currently
  12725. * be bound to string values only.
  12726. *
  12727. * @param {string} name assignable expression to data-bind to.
  12728. * @param {string=} required The control is considered valid only if value is entered.
  12729. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
  12730. * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
  12731. * `required` when you want to data-bind to the `required` attribute.
  12732. * @param {comprehension_expression=} ngOptions in one of the following forms:
  12733. *
  12734. * * for array data sources:
  12735. * * `label` **`for`** `value` **`in`** `array`
  12736. * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
  12737. * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
  12738. * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array`
  12739. * * for object data sources:
  12740. * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
  12741. * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
  12742. * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
  12743. * * `select` **`as`** `label` **`group by`** `group`
  12744. * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
  12745. *
  12746. * Where:
  12747. *
  12748. * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
  12749. * * `value`: local variable which will refer to each item in the `array` or each property value
  12750. * of `object` during iteration.
  12751. * * `key`: local variable which will refer to a property name in `object` during iteration.
  12752. * * `label`: The result of this expression will be the label for `<option>` element. The
  12753. * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
  12754. * * `select`: The result of this expression will be bound to the model of the parent `<select>`
  12755. * element. If not specified, `select` expression will default to `value`.
  12756. * * `group`: The result of this expression will be used to group options using the `<optgroup>`
  12757. * DOM element.
  12758. *
  12759. * @example
  12760. <doc:example>
  12761. <doc:source>
  12762. <script>
  12763. function MyCntrl($scope) {
  12764. $scope.colors = [
  12765. {name:'black', shade:'dark'},
  12766. {name:'white', shade:'light'},
  12767. {name:'red', shade:'dark'},
  12768. {name:'blue', shade:'dark'},
  12769. {name:'yellow', shade:'light'}
  12770. ];
  12771. $scope.color = $scope.colors[2]; // red
  12772. }
  12773. </script>
  12774. <div ng-controller="MyCntrl">
  12775. <ul>
  12776. <li ng-repeat="color in colors">
  12777. Name: <input ng-model="color.name">
  12778. [<a href ng-click="colors.splice($index, 1)">X</a>]
  12779. </li>
  12780. <li>
  12781. [<a href ng-click="colors.push({})">add</a>]
  12782. </li>
  12783. </ul>
  12784. <hr/>
  12785. Color (null not allowed):
  12786. <select ng-model="color" ng-options="c.name for c in colors"></select><br>
  12787. Color (null allowed):
  12788. <span class="nullable">
  12789. <select ng-model="color" ng-options="c.name for c in colors">
  12790. <option value="">-- chose color --</option>
  12791. </select>
  12792. </span><br/>
  12793. Color grouped by shade:
  12794. <select ng-model="color" ng-options="c.name group by c.shade for c in colors">
  12795. </select><br/>
  12796. Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br>
  12797. <hr/>
  12798. Currently selected: {{ {selected_color:color} }}
  12799. <div style="border:solid 1px black; height:20px"
  12800. ng-style="{'background-color':color.name}">
  12801. </div>
  12802. </div>
  12803. </doc:source>
  12804. <doc:scenario>
  12805. it('should check ng-options', function() {
  12806. expect(binding('{selected_color:color}')).toMatch('red');
  12807. select('color').option('0');
  12808. expect(binding('{selected_color:color}')).toMatch('black');
  12809. using('.nullable').select('color').option('');
  12810. expect(binding('{selected_color:color}')).toMatch('null');
  12811. });
  12812. </doc:scenario>
  12813. </doc:example>
  12814. */
  12815. var ngOptionsDirective = valueFn({ terminal: true });
  12816. var selectDirective = ['$compile', '$parse', function($compile, $parse) {
  12817. //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
  12818. var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,
  12819. nullModelCtrl = {$setViewValue: noop};
  12820. return {
  12821. restrict: 'E',
  12822. require: ['select', '?ngModel'],
  12823. controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
  12824. var self = this,
  12825. optionsMap = {},
  12826. ngModelCtrl = nullModelCtrl,
  12827. nullOption,
  12828. unknownOption;
  12829. self.databound = $attrs.ngModel;
  12830. self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
  12831. ngModelCtrl = ngModelCtrl_;
  12832. nullOption = nullOption_;
  12833. unknownOption = unknownOption_;
  12834. }
  12835. self.addOption = function(value) {
  12836. optionsMap[value] = true;
  12837. if (ngModelCtrl.$viewValue == value) {
  12838. $element.val(value);
  12839. if (unknownOption.parent()) unknownOption.remove();
  12840. }
  12841. };
  12842. self.removeOption = function(value) {
  12843. if (this.hasOption(value)) {
  12844. delete optionsMap[value];
  12845. if (ngModelCtrl.$viewValue == value) {
  12846. this.renderUnknownOption(value);
  12847. }
  12848. }
  12849. };
  12850. self.renderUnknownOption = function(val) {
  12851. var unknownVal = '? ' + hashKey(val) + ' ?';
  12852. unknownOption.val(unknownVal);
  12853. $element.prepend(unknownOption);
  12854. $element.val(unknownVal);
  12855. unknownOption.prop('selected', true); // needed for IE
  12856. }
  12857. self.hasOption = function(value) {
  12858. return optionsMap.hasOwnProperty(value);
  12859. }
  12860. $scope.$on('$destroy', function() {
  12861. // disable unknown option so that we don't do work when the whole select is being destroyed
  12862. self.renderUnknownOption = noop;
  12863. });
  12864. }],
  12865. link: function(scope, element, attr, ctrls) {
  12866. // if ngModel is not defined, we don't need to do anything
  12867. if (!ctrls[1]) return;
  12868. var selectCtrl = ctrls[0],
  12869. ngModelCtrl = ctrls[1],
  12870. multiple = attr.multiple,
  12871. optionsExp = attr.ngOptions,
  12872. nullOption = false, // if false, user will not be able to select it (used by ngOptions)
  12873. emptyOption,
  12874. // we can't just jqLite('<option>') since jqLite is not smart enough
  12875. // to create it in <select> and IE barfs otherwise.
  12876. optionTemplate = jqLite(document.createElement('option')),
  12877. optGroupTemplate =jqLite(document.createElement('optgroup')),
  12878. unknownOption = optionTemplate.clone();
  12879. // find "null" option
  12880. for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
  12881. if (children[i].value == '') {
  12882. emptyOption = nullOption = children.eq(i);
  12883. break;
  12884. }
  12885. }
  12886. selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
  12887. // required validator
  12888. if (multiple && (attr.required || attr.ngRequired)) {
  12889. var requiredValidator = function(value) {
  12890. ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
  12891. return value;
  12892. };
  12893. ngModelCtrl.$parsers.push(requiredValidator);
  12894. ngModelCtrl.$formatters.unshift(requiredValidator);
  12895. attr.$observe('required', function() {
  12896. requiredValidator(ngModelCtrl.$viewValue);
  12897. });
  12898. }
  12899. if (optionsExp) Options(scope, element, ngModelCtrl);
  12900. else if (multiple) Multiple(scope, element, ngModelCtrl);
  12901. else Single(scope, element, ngModelCtrl, selectCtrl);
  12902. ////////////////////////////
  12903. function Single(scope, selectElement, ngModelCtrl, selectCtrl) {
  12904. ngModelCtrl.$render = function() {
  12905. var viewValue = ngModelCtrl.$viewValue;
  12906. if (selectCtrl.hasOption(viewValue)) {
  12907. if (unknownOption.parent()) unknownOption.remove();
  12908. selectElement.val(viewValue);
  12909. if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
  12910. } else {
  12911. if (isUndefined(viewValue) && emptyOption) {
  12912. selectElement.val('');
  12913. } else {
  12914. selectCtrl.renderUnknownOption(viewValue);
  12915. }
  12916. }
  12917. };
  12918. selectElement.bind('change', function() {
  12919. scope.$apply(function() {
  12920. if (unknownOption.parent()) unknownOption.remove();
  12921. ngModelCtrl.$setViewValue(selectElement.val());
  12922. });
  12923. });
  12924. }
  12925. function Multiple(scope, selectElement, ctrl) {
  12926. var lastView;
  12927. ctrl.$render = function() {
  12928. var items = new HashMap(ctrl.$viewValue);
  12929. forEach(selectElement.children(), function(option) {
  12930. option.selected = isDefined(items.get(option.value));
  12931. });
  12932. };
  12933. // we have to do it on each watch since ngModel watches reference, but
  12934. // we need to work of an array, so we need to see if anything was inserted/removed
  12935. scope.$watch(function selectMultipleWatch() {
  12936. if (!equals(lastView, ctrl.$viewValue)) {
  12937. lastView = copy(ctrl.$viewValue);
  12938. ctrl.$render();
  12939. }
  12940. });
  12941. selectElement.bind('change', function() {
  12942. scope.$apply(function() {
  12943. var array = [];
  12944. forEach(selectElement.children(), function(option) {
  12945. if (option.selected) {
  12946. array.push(option.value);
  12947. }
  12948. });
  12949. ctrl.$setViewValue(array);
  12950. });
  12951. });
  12952. }
  12953. function Options(scope, selectElement, ctrl) {
  12954. var match;
  12955. if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
  12956. throw Error(
  12957. "Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
  12958. " but got '" + optionsExp + "'.");
  12959. }
  12960. var displayFn = $parse(match[2] || match[1]),
  12961. valueName = match[4] || match[6],
  12962. keyName = match[5],
  12963. groupByFn = $parse(match[3] || ''),
  12964. valueFn = $parse(match[2] ? match[1] : valueName),
  12965. valuesFn = $parse(match[7]),
  12966. // This is an array of array of existing option groups in DOM. We try to reuse these if possible
  12967. // optionGroupsCache[0] is the options with no option group
  12968. // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
  12969. optionGroupsCache = [[{element: selectElement, label:''}]];
  12970. if (nullOption) {
  12971. // compile the element since there might be bindings in it
  12972. $compile(nullOption)(scope);
  12973. // remove the class, which is added automatically because we recompile the element and it
  12974. // becomes the compilation root
  12975. nullOption.removeClass('ng-scope');
  12976. // we need to remove it before calling selectElement.html('') because otherwise IE will
  12977. // remove the label from the element. wtf?
  12978. nullOption.remove();
  12979. }
  12980. // clear contents, we'll add what's needed based on the model
  12981. selectElement.html('');
  12982. selectElement.bind('change', function() {
  12983. scope.$apply(function() {
  12984. var optionGroup,
  12985. collection = valuesFn(scope) || [],
  12986. locals = {},
  12987. key, value, optionElement, index, groupIndex, length, groupLength;
  12988. if (multiple) {
  12989. value = [];
  12990. for (groupIndex = 0, groupLength = optionGroupsCache.length;
  12991. groupIndex < groupLength;
  12992. groupIndex++) {
  12993. // list of options for that group. (first item has the parent)
  12994. optionGroup = optionGroupsCache[groupIndex];
  12995. for(index = 1, length = optionGroup.length; index < length; index++) {
  12996. if ((optionElement = optionGroup[index].element)[0].selected) {
  12997. key = optionElement.val();
  12998. if (keyName) locals[keyName] = key;
  12999. locals[valueName] = collection[key];
  13000. value.push(valueFn(scope, locals));
  13001. }
  13002. }
  13003. }
  13004. } else {
  13005. key = selectElement.val();
  13006. if (key == '?') {
  13007. value = undefined;
  13008. } else if (key == ''){
  13009. value = null;
  13010. } else {
  13011. locals[valueName] = collection[key];
  13012. if (keyName) locals[keyName] = key;
  13013. value = valueFn(scope, locals);
  13014. }
  13015. }
  13016. ctrl.$setViewValue(value);
  13017. });
  13018. });
  13019. ctrl.$render = render;
  13020. // TODO(vojta): can't we optimize this ?
  13021. scope.$watch(render);
  13022. function render() {
  13023. var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
  13024. optionGroupNames = [''],
  13025. optionGroupName,
  13026. optionGroup,
  13027. option,
  13028. existingParent, existingOptions, existingOption,
  13029. modelValue = ctrl.$modelValue,
  13030. values = valuesFn(scope) || [],
  13031. keys = keyName ? sortedKeys(values) : values,
  13032. groupLength, length,
  13033. groupIndex, index,
  13034. locals = {},
  13035. selected,
  13036. selectedSet = false, // nothing is selected yet
  13037. lastElement,
  13038. element,
  13039. label;
  13040. if (multiple) {
  13041. selectedSet = new HashMap(modelValue);
  13042. } else if (modelValue === null || nullOption) {
  13043. // if we are not multiselect, and we are null then we have to add the nullOption
  13044. optionGroups[''].push({selected:modelValue === null, id:'', label:''});
  13045. selectedSet = true;
  13046. }
  13047. // We now build up the list of options we need (we merge later)
  13048. for (index = 0; length = keys.length, index < length; index++) {
  13049. locals[valueName] = values[keyName ? locals[keyName]=keys[index]:index];
  13050. optionGroupName = groupByFn(scope, locals) || '';
  13051. if (!(optionGroup = optionGroups[optionGroupName])) {
  13052. optionGroup = optionGroups[optionGroupName] = [];
  13053. optionGroupNames.push(optionGroupName);
  13054. }
  13055. if (multiple) {
  13056. selected = selectedSet.remove(valueFn(scope, locals)) != undefined;
  13057. } else {
  13058. selected = modelValue === valueFn(scope, locals);
  13059. selectedSet = selectedSet || selected; // see if at least one item is selected
  13060. }
  13061. label = displayFn(scope, locals); // what will be seen by the user
  13062. label = label === undefined ? '' : label; // doing displayFn(scope, locals) || '' overwrites zero values
  13063. optionGroup.push({
  13064. id: keyName ? keys[index] : index, // either the index into array or key from object
  13065. label: label,
  13066. selected: selected // determine if we should be selected
  13067. });
  13068. }
  13069. if (!multiple && !selectedSet) {
  13070. // nothing was selected, we have to insert the undefined item
  13071. optionGroups[''].unshift({id:'?', label:'', selected:true});
  13072. }
  13073. // Now we need to update the list of DOM nodes to match the optionGroups we computed above
  13074. for (groupIndex = 0, groupLength = optionGroupNames.length;
  13075. groupIndex < groupLength;
  13076. groupIndex++) {
  13077. // current option group name or '' if no group
  13078. optionGroupName = optionGroupNames[groupIndex];
  13079. // list of options for that group. (first item has the parent)
  13080. optionGroup = optionGroups[optionGroupName];
  13081. if (optionGroupsCache.length <= groupIndex) {
  13082. // we need to grow the optionGroups
  13083. existingParent = {
  13084. element: optGroupTemplate.clone().attr('label', optionGroupName),
  13085. label: optionGroup.label
  13086. };
  13087. existingOptions = [existingParent];
  13088. optionGroupsCache.push(existingOptions);
  13089. selectElement.append(existingParent.element);
  13090. } else {
  13091. existingOptions = optionGroupsCache[groupIndex];
  13092. existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
  13093. // update the OPTGROUP label if not the same.
  13094. if (existingParent.label != optionGroupName) {
  13095. existingParent.element.attr('label', existingParent.label = optionGroupName);
  13096. }
  13097. }
  13098. lastElement = null; // start at the beginning
  13099. for(index = 0, length = optionGroup.length; index < length; index++) {
  13100. option = optionGroup[index];
  13101. if ((existingOption = existingOptions[index+1])) {
  13102. // reuse elements
  13103. lastElement = existingOption.element;
  13104. if (existingOption.label !== option.label) {
  13105. lastElement.text(existingOption.label = option.label);
  13106. }
  13107. if (existingOption.id !== option.id) {
  13108. lastElement.val(existingOption.id = option.id);
  13109. }
  13110. if (existingOption.element.selected !== option.selected) {
  13111. lastElement.prop('selected', (existingOption.selected = option.selected));
  13112. }
  13113. } else {
  13114. // grow elements
  13115. // if it's a null option
  13116. if (option.id === '' && nullOption) {
  13117. // put back the pre-compiled element
  13118. element = nullOption;
  13119. } else {
  13120. // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
  13121. // in this version of jQuery on some browser the .text() returns a string
  13122. // rather then the element.
  13123. (element = optionTemplate.clone())
  13124. .val(option.id)
  13125. .attr('selected', option.selected)
  13126. .text(option.label);
  13127. }
  13128. existingOptions.push(existingOption = {
  13129. element: element,
  13130. label: option.label,
  13131. id: option.id,
  13132. selected: option.selected
  13133. });
  13134. if (lastElement) {
  13135. lastElement.after(element);
  13136. } else {
  13137. existingParent.element.append(element);
  13138. }
  13139. lastElement = element;
  13140. }
  13141. }
  13142. // remove any excessive OPTIONs in a group
  13143. index++; // increment since the existingOptions[0] is parent element not OPTION
  13144. while(existingOptions.length > index) {
  13145. existingOptions.pop().element.remove();
  13146. }
  13147. }
  13148. // remove any excessive OPTGROUPs from select
  13149. while(optionGroupsCache.length > groupIndex) {
  13150. optionGroupsCache.pop()[0].element.remove();
  13151. }
  13152. }
  13153. }
  13154. }
  13155. }
  13156. }];
  13157. var optionDirective = ['$interpolate', function($interpolate) {
  13158. var nullSelectCtrl = {
  13159. addOption: noop,
  13160. removeOption: noop
  13161. };
  13162. return {
  13163. restrict: 'E',
  13164. priority: 100,
  13165. compile: function(element, attr) {
  13166. if (isUndefined(attr.value)) {
  13167. var interpolateFn = $interpolate(element.text(), true);
  13168. if (!interpolateFn) {
  13169. attr.$set('value', element.text());
  13170. }
  13171. }
  13172. return function (scope, element, attr) {
  13173. var selectCtrlName = '$selectController',
  13174. parent = element.parent(),
  13175. selectCtrl = parent.data(selectCtrlName) ||
  13176. parent.parent().data(selectCtrlName); // in case we are in optgroup
  13177. if (selectCtrl && selectCtrl.databound) {
  13178. // For some reason Opera defaults to true and if not overridden this messes up the repeater.
  13179. // We don't want the view to drive the initialization of the model anyway.
  13180. element.prop('selected', false);
  13181. } else {
  13182. selectCtrl = nullSelectCtrl;
  13183. }
  13184. if (interpolateFn) {
  13185. scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
  13186. attr.$set('value', newVal);
  13187. if (newVal !== oldVal) selectCtrl.removeOption(oldVal);
  13188. selectCtrl.addOption(newVal);
  13189. });
  13190. } else {
  13191. selectCtrl.addOption(attr.value);
  13192. }
  13193. element.bind('$destroy', function() {
  13194. selectCtrl.removeOption(attr.value);
  13195. });
  13196. };
  13197. }
  13198. }
  13199. }];
  13200. var styleDirective = valueFn({
  13201. restrict: 'E',
  13202. terminal: true
  13203. });
  13204. //try to bind to jquery now so that one can write angular.element().read()
  13205. //but we will rebind on bootstrap again.
  13206. bindJQuery();
  13207. publishExternalAPI(angular);
  13208. jqLite(document).ready(function() {
  13209. angularInit(document, bootstrap);
  13210. });
  13211. })(window, document);
  13212. angular.element(document).find('head').append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');