PageRenderTime 123ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 2ms

/app/lib/angular-1.0.1.js

https://bitbucket.org/deshartman/containerdirective
JavaScript | 14327 lines | 7833 code | 1073 blank | 5421 comment | 1082 complexity | 5e5b7510dafb031f7893cf091bc20559 MD5 | raw file
  1. /**
  2. * @license AngularJS v1.0.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 mehtod 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 on 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 configure 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 Option 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 needs to be performed when the injector with
  1071. * with the current module is finished loading.
  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.0.1', // all of these placeholder strings will be replaced by rake's
  1113. major: 1, // compile task
  1114. minor: 0,
  1115. dot: 1,
  1116. codeName: 'thorium-shielding'
  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. * - [unbind()](http://api.jquery.com/unbind/)
  1278. * - [val()](http://api.jquery.com/val/)
  1279. * - [wrap()](http://api.jquery.com/wrap/)
  1280. *
  1281. * ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
  1282. *
  1283. * - `controller(name)` - retrieves the controller of the current element or its parent. By default
  1284. * retrieves controller associated with the `ngController` directive. If `name` is provided as
  1285. * camelCase directive name, then the controller for this directive will be retrieved (e.g.
  1286. * `'ngModel'`).
  1287. * - `injector()` - retrieves the injector of the current element or its parent.
  1288. * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current
  1289. * element or its parent.
  1290. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
  1291. * parent element is reached.
  1292. *
  1293. * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
  1294. * @returns {Object} jQuery object.
  1295. */
  1296. var jqCache = JQLite.cache = {},
  1297. jqName = JQLite.expando = 'ng-' + new Date().getTime(),
  1298. jqId = 1,
  1299. addEventListenerFn = (window.document.addEventListener
  1300. ? function(element, type, fn) {element.addEventListener(type, fn, false);}
  1301. : function(element, type, fn) {element.attachEvent('on' + type, fn);}),
  1302. removeEventListenerFn = (window.document.removeEventListener
  1303. ? function(element, type, fn) {element.removeEventListener(type, fn, false); }
  1304. : function(element, type, fn) {element.detachEvent('on' + type, fn); });
  1305. function jqNextId() { return ++jqId; }
  1306. var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
  1307. var MOZ_HACK_REGEXP = /^moz([A-Z])/;
  1308. /**
  1309. * Converts snake_case to camelCase.
  1310. * Also there is special case for Moz prefix starting with upper case letter.
  1311. * @param name Name to normalize
  1312. */
  1313. function camelCase(name) {
  1314. return name.
  1315. replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
  1316. return offset ? letter.toUpperCase() : letter;
  1317. }).
  1318. replace(MOZ_HACK_REGEXP, 'Moz$1');
  1319. }
  1320. /////////////////////////////////////////////
  1321. // jQuery mutation patch
  1322. //
  1323. // In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
  1324. // $destroy event on all DOM nodes being removed.
  1325. //
  1326. /////////////////////////////////////////////
  1327. function JQLitePatchJQueryRemove(name, dispatchThis) {
  1328. var originalJqFn = jQuery.fn[name];
  1329. originalJqFn = originalJqFn.$original || originalJqFn;
  1330. removePatch.$original = originalJqFn;
  1331. jQuery.fn[name] = removePatch;
  1332. function removePatch() {
  1333. var list = [this],
  1334. fireEvent = dispatchThis,
  1335. set, setIndex, setLength,
  1336. element, childIndex, childLength, children,
  1337. fns, events;
  1338. while(list.length) {
  1339. set = list.shift();
  1340. for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
  1341. element = jqLite(set[setIndex]);
  1342. if (fireEvent) {
  1343. events = element.data('events');
  1344. if ( (fns = events && events.$destroy) ) {
  1345. forEach(fns, function(fn){
  1346. fn.handler();
  1347. });
  1348. }
  1349. } else {
  1350. fireEvent = !fireEvent;
  1351. }
  1352. for(childIndex = 0, childLength = (children = element.children()).length;
  1353. childIndex < childLength;
  1354. childIndex++) {
  1355. list.push(jQuery(children[childIndex]));
  1356. }
  1357. }
  1358. }
  1359. return originalJqFn.apply(this, arguments);
  1360. }
  1361. }
  1362. /////////////////////////////////////////////
  1363. function JQLite(element) {
  1364. if (element instanceof JQLite) {
  1365. return element;
  1366. }
  1367. if (!(this instanceof JQLite)) {
  1368. if (isString(element) && element.charAt(0) != '<') {
  1369. throw Error('selectors not implemented');
  1370. }
  1371. return new JQLite(element);
  1372. }
  1373. if (isString(element)) {
  1374. var div = document.createElement('div');
  1375. // Read about the NoScope elements here:
  1376. // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
  1377. div.innerHTML = '<div>&nbsp;</div>' + element; // IE insanity to make NoScope elements work!
  1378. div.removeChild(div.firstChild); // remove the superfluous div
  1379. JQLiteAddNodes(this, div.childNodes);
  1380. this.remove(); // detach the elements from the temporary DOM div.
  1381. } else {
  1382. JQLiteAddNodes(this, element);
  1383. }
  1384. }
  1385. function JQLiteClone(element) {
  1386. return element.cloneNode(true);
  1387. }
  1388. function JQLiteDealoc(element){
  1389. JQLiteRemoveData(element);
  1390. for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
  1391. JQLiteDealoc(children[i]);
  1392. }
  1393. }
  1394. function JQLiteUnbind(element, type, fn) {
  1395. var events = JQLiteExpandoStore(element, 'events'),
  1396. handle = JQLiteExpandoStore(element, 'handle');
  1397. if (!handle) return; //no listeners registered
  1398. if (isUndefined(type)) {
  1399. forEach(events, function(eventHandler, type) {
  1400. removeEventListenerFn(element, type, eventHandler);
  1401. delete events[type];
  1402. });
  1403. } else {
  1404. if (isUndefined(fn)) {
  1405. removeEventListenerFn(element, type, events[type]);
  1406. delete events[type];
  1407. } else {
  1408. arrayRemove(events[type], fn);
  1409. }
  1410. }
  1411. }
  1412. function JQLiteRemoveData(element) {
  1413. var expandoId = element[jqName],
  1414. expandoStore = jqCache[expandoId];
  1415. if (expandoStore) {
  1416. if (expandoStore.handle) {
  1417. expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
  1418. JQLiteUnbind(element);
  1419. }
  1420. delete jqCache[expandoId];
  1421. element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
  1422. }
  1423. }
  1424. function JQLiteExpandoStore(element, key, value) {
  1425. var expandoId = element[jqName],
  1426. expandoStore = jqCache[expandoId || -1];
  1427. if (isDefined(value)) {
  1428. if (!expandoStore) {
  1429. element[jqName] = expandoId = jqNextId();
  1430. expandoStore = jqCache[expandoId] = {};
  1431. }
  1432. expandoStore[key] = value;
  1433. } else {
  1434. return expandoStore && expandoStore[key];
  1435. }
  1436. }
  1437. function JQLiteData(element, key, value) {
  1438. var data = JQLiteExpandoStore(element, 'data'),
  1439. isSetter = isDefined(value),
  1440. keyDefined = !isSetter && isDefined(key),
  1441. isSimpleGetter = keyDefined && !isObject(key);
  1442. if (!data && !isSimpleGetter) {
  1443. JQLiteExpandoStore(element, 'data', data = {});
  1444. }
  1445. if (isSetter) {
  1446. data[key] = value;
  1447. } else {
  1448. if (keyDefined) {
  1449. if (isSimpleGetter) {
  1450. // don't create data in this case.
  1451. return data && data[key];
  1452. } else {
  1453. extend(data, key);
  1454. }
  1455. } else {
  1456. return data;
  1457. }
  1458. }
  1459. }
  1460. function JQLiteHasClass(element, selector) {
  1461. return ((" " + element.className + " ").replace(/[\n\t]/g, " ").
  1462. indexOf( " " + selector + " " ) > -1);
  1463. }
  1464. function JQLiteRemoveClass(element, selector) {
  1465. if (selector) {
  1466. forEach(selector.split(' '), function(cssClass) {
  1467. element.className = trim(
  1468. (" " + element.className + " ")
  1469. .replace(/[\n\t]/g, " ")
  1470. .replace(" " + trim(cssClass) + " ", " ")
  1471. );
  1472. });
  1473. }
  1474. }
  1475. function JQLiteAddClass(element, selector) {
  1476. if (selector) {
  1477. forEach(selector.split(' '), function(cssClass) {
  1478. if (!JQLiteHasClass(element, cssClass)) {
  1479. element.className = trim(element.className + ' ' + trim(cssClass));
  1480. }
  1481. });
  1482. }
  1483. }
  1484. function JQLiteAddNodes(root, elements) {
  1485. if (elements) {
  1486. elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
  1487. ? elements
  1488. : [ elements ];
  1489. for(var i=0; i < elements.length; i++) {
  1490. root.push(elements[i]);
  1491. }
  1492. }
  1493. }
  1494. function JQLiteController(element, name) {
  1495. return JQLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
  1496. }
  1497. function JQLiteInheritedData(element, name, value) {
  1498. element = jqLite(element);
  1499. // if element is the document object work with the html element instead
  1500. // this makes $(document).scope() possible
  1501. if(element[0].nodeType == 9) {
  1502. element = element.find('html');
  1503. }
  1504. while (element.length) {
  1505. if (value = element.data(name)) return value;
  1506. element = element.parent();
  1507. }
  1508. }
  1509. //////////////////////////////////////////
  1510. // Functions which are declared directly.
  1511. //////////////////////////////////////////
  1512. var JQLitePrototype = JQLite.prototype = {
  1513. ready: function(fn) {
  1514. var fired = false;
  1515. function trigger() {
  1516. if (fired) return;
  1517. fired = true;
  1518. fn();
  1519. }
  1520. this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
  1521. // we can not use jqLite since we are not done loading and jQuery could be loaded later.
  1522. JQLite(window).bind('load', trigger); // fallback to window.onload for others
  1523. },
  1524. toString: function() {
  1525. var value = [];
  1526. forEach(this, function(e){ value.push('' + e);});
  1527. return '[' + value.join(', ') + ']';
  1528. },
  1529. eq: function(index) {
  1530. return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
  1531. },
  1532. length: 0,
  1533. push: push,
  1534. sort: [].sort,
  1535. splice: [].splice
  1536. };
  1537. //////////////////////////////////////////
  1538. // Functions iterating getter/setters.
  1539. // these functions return self on setter and
  1540. // value on get.
  1541. //////////////////////////////////////////
  1542. var BOOLEAN_ATTR = {};
  1543. forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) {
  1544. BOOLEAN_ATTR[lowercase(value)] = value;
  1545. });
  1546. var BOOLEAN_ELEMENTS = {};
  1547. forEach('input,select,option,textarea,button,form'.split(','), function(value) {
  1548. BOOLEAN_ELEMENTS[uppercase(value)] = true;
  1549. });
  1550. function getBooleanAttrName(element, name) {
  1551. // check dom last since we will most likely fail on name
  1552. var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
  1553. // booleanAttr is here twice to minimize DOM access
  1554. return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr;
  1555. }
  1556. forEach({
  1557. data: JQLiteData,
  1558. inheritedData: JQLiteInheritedData,
  1559. scope: function(element) {
  1560. return JQLiteInheritedData(element, '$scope');
  1561. },
  1562. controller: JQLiteController ,
  1563. injector: function(element) {
  1564. return JQLiteInheritedData(element, '$injector');
  1565. },
  1566. removeAttr: function(element,name) {
  1567. element.removeAttribute(name);
  1568. },
  1569. hasClass: JQLiteHasClass,
  1570. css: function(element, name, value) {
  1571. name = camelCase(name);
  1572. if (isDefined(value)) {
  1573. element.style[name] = value;
  1574. } else {
  1575. var val;
  1576. if (msie <= 8) {
  1577. // this is some IE specific weirdness that jQuery 1.6.4 does not sure why
  1578. val = element.currentStyle && element.currentStyle[name];
  1579. if (val === '') val = 'auto';
  1580. }
  1581. val = val || element.style[name];
  1582. if (msie <= 8) {
  1583. // jquery weirdness :-/
  1584. val = (val === '') ? undefined : val;
  1585. }
  1586. return val;
  1587. }
  1588. },
  1589. attr: function(element, name, value){
  1590. var lowercasedName = lowercase(name);
  1591. if (BOOLEAN_ATTR[lowercasedName]) {
  1592. if (isDefined(value)) {
  1593. if (!!value) {
  1594. element[name] = true;
  1595. element.setAttribute(name, lowercasedName);
  1596. } else {
  1597. element[name] = false;
  1598. element.removeAttribute(lowercasedName);
  1599. }
  1600. } else {
  1601. return (element[name] ||
  1602. (element.attributes.getNamedItem(name)|| noop).specified)
  1603. ? lowercasedName
  1604. : undefined;
  1605. }
  1606. } else if (isDefined(value)) {
  1607. element.setAttribute(name, value);
  1608. } else if (element.getAttribute) {
  1609. // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
  1610. // some elements (e.g. Document) don't have get attribute, so return undefined
  1611. var ret = element.getAttribute(name, 2);
  1612. // normalize non-existing attributes to undefined (as jQuery)
  1613. return ret === null ? undefined : ret;
  1614. }
  1615. },
  1616. prop: function(element, name, value) {
  1617. if (isDefined(value)) {
  1618. element[name] = value;
  1619. } else {
  1620. return element[name];
  1621. }
  1622. },
  1623. text: extend((msie < 9)
  1624. ? function(element, value) {
  1625. if (element.nodeType == 1 /** Element */) {
  1626. if (isUndefined(value))
  1627. return element.innerText;
  1628. element.innerText = value;
  1629. } else {
  1630. if (isUndefined(value))
  1631. return element.nodeValue;
  1632. element.nodeValue = value;
  1633. }
  1634. }
  1635. : function(element, value) {
  1636. if (isUndefined(value)) {
  1637. return element.textContent;
  1638. }
  1639. element.textContent = value;
  1640. }, {$dv:''}),
  1641. val: function(element, value) {
  1642. if (isUndefined(value)) {
  1643. return element.value;
  1644. }
  1645. element.value = value;
  1646. },
  1647. html: function(element, value) {
  1648. if (isUndefined(value)) {
  1649. return element.innerHTML;
  1650. }
  1651. for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
  1652. JQLiteDealoc(childNodes[i]);
  1653. }
  1654. element.innerHTML = value;
  1655. }
  1656. }, function(fn, name){
  1657. /**
  1658. * Properties: writes return selection, reads return first value
  1659. */
  1660. JQLite.prototype[name] = function(arg1, arg2) {
  1661. var i, key;
  1662. // JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
  1663. // in a way that survives minification.
  1664. if (((fn.length == 2 && (fn !== JQLiteHasClass && fn !== JQLiteController)) ? arg1 : arg2) === undefined) {
  1665. if (isObject(arg1)) {
  1666. // we are a write, but the object properties are the key/values
  1667. for(i=0; i < this.length; i++) {
  1668. if (fn === JQLiteData) {
  1669. // data() takes the whole object in jQuery
  1670. fn(this[i], arg1);
  1671. } else {
  1672. for (key in arg1) {
  1673. fn(this[i], key, arg1[key]);
  1674. }
  1675. }
  1676. }
  1677. // return self for chaining
  1678. return this;
  1679. } else {
  1680. // we are a read, so read the first child.
  1681. if (this.length)
  1682. return fn(this[0], arg1, arg2);
  1683. }
  1684. } else {
  1685. // we are a write, so apply to all children
  1686. for(i=0; i < this.length; i++) {
  1687. fn(this[i], arg1, arg2);
  1688. }
  1689. // return self for chaining
  1690. return this;
  1691. }
  1692. return fn.$dv;
  1693. };
  1694. });
  1695. function createEventHandler(element, events) {
  1696. var eventHandler = function (event, type) {
  1697. if (!event.preventDefault) {
  1698. event.preventDefault = function() {
  1699. event.returnValue = false; //ie
  1700. };
  1701. }
  1702. if (!event.stopPropagation) {
  1703. event.stopPropagation = function() {
  1704. event.cancelBubble = true; //ie
  1705. };
  1706. }
  1707. if (!event.target) {
  1708. event.target = event.srcElement || document;
  1709. }
  1710. if (isUndefined(event.defaultPrevented)) {
  1711. var prevent = event.preventDefault;
  1712. event.preventDefault = function() {
  1713. event.defaultPrevented = true;
  1714. prevent.call(event);
  1715. };
  1716. event.defaultPrevented = false;
  1717. }
  1718. event.isDefaultPrevented = function() {
  1719. return event.defaultPrevented;
  1720. };
  1721. forEach(events[type || event.type], function(fn) {
  1722. fn.call(element, event);
  1723. });
  1724. // Remove monkey-patched methods (IE),
  1725. // as they would cause memory leaks in IE8.
  1726. if (msie <= 8) {
  1727. // IE7/8 does not allow to delete property on native object
  1728. event.preventDefault = null;
  1729. event.stopPropagation = null;
  1730. event.isDefaultPrevented = null;
  1731. } else {
  1732. // It shouldn't affect normal browsers (native methods are defined on prototype).
  1733. delete event.preventDefault;
  1734. delete event.stopPropagation;
  1735. delete event.isDefaultPrevented;
  1736. }
  1737. };
  1738. eventHandler.elem = element;
  1739. return eventHandler;
  1740. }
  1741. //////////////////////////////////////////
  1742. // Functions iterating traversal.
  1743. // These functions chain results into a single
  1744. // selector.
  1745. //////////////////////////////////////////
  1746. forEach({
  1747. removeData: JQLiteRemoveData,
  1748. dealoc: JQLiteDealoc,
  1749. bind: function bindFn(element, type, fn){
  1750. var events = JQLiteExpandoStore(element, 'events'),
  1751. handle = JQLiteExpandoStore(element, 'handle');
  1752. if (!events) JQLiteExpandoStore(element, 'events', events = {});
  1753. if (!handle) JQLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
  1754. forEach(type.split(' '), function(type){
  1755. var eventFns = events[type];
  1756. if (!eventFns) {
  1757. if (type == 'mouseenter' || type == 'mouseleave') {
  1758. var counter = 0;
  1759. events.mouseenter = [];
  1760. events.mouseleave = [];
  1761. bindFn(element, 'mouseover', function(event) {
  1762. counter++;
  1763. if (counter == 1) {
  1764. handle(event, 'mouseenter');
  1765. }
  1766. });
  1767. bindFn(element, 'mouseout', function(event) {
  1768. counter --;
  1769. if (counter == 0) {
  1770. handle(event, 'mouseleave');
  1771. }
  1772. });
  1773. } else {
  1774. addEventListenerFn(element, type, handle);
  1775. events[type] = [];
  1776. }
  1777. eventFns = events[type]
  1778. }
  1779. eventFns.push(fn);
  1780. });
  1781. },
  1782. unbind: JQLiteUnbind,
  1783. replaceWith: function(element, replaceNode) {
  1784. var index, parent = element.parentNode;
  1785. JQLiteDealoc(element);
  1786. forEach(new JQLite(replaceNode), function(node){
  1787. if (index) {
  1788. parent.insertBefore(node, index.nextSibling);
  1789. } else {
  1790. parent.replaceChild(node, element);
  1791. }
  1792. index = node;
  1793. });
  1794. },
  1795. children: function(element) {
  1796. var children = [];
  1797. forEach(element.childNodes, function(element){
  1798. if (element.nodeName != '#text')
  1799. children.push(element);
  1800. });
  1801. return children;
  1802. },
  1803. contents: function(element) {
  1804. return element.childNodes;
  1805. },
  1806. append: function(element, node) {
  1807. forEach(new JQLite(node), function(child){
  1808. if (element.nodeType === 1)
  1809. element.appendChild(child);
  1810. });
  1811. },
  1812. prepend: function(element, node) {
  1813. if (element.nodeType === 1) {
  1814. var index = element.firstChild;
  1815. forEach(new JQLite(node), function(child){
  1816. if (index) {
  1817. element.insertBefore(child, index);
  1818. } else {
  1819. element.appendChild(child);
  1820. index = child;
  1821. }
  1822. });
  1823. }
  1824. },
  1825. wrap: function(element, wrapNode) {
  1826. wrapNode = jqLite(wrapNode)[0];
  1827. var parent = element.parentNode;
  1828. if (parent) {
  1829. parent.replaceChild(wrapNode, element);
  1830. }
  1831. wrapNode.appendChild(element);
  1832. },
  1833. remove: function(element) {
  1834. JQLiteDealoc(element);
  1835. var parent = element.parentNode;
  1836. if (parent) parent.removeChild(element);
  1837. },
  1838. after: function(element, newElement) {
  1839. var index = element, parent = element.parentNode;
  1840. forEach(new JQLite(newElement), function(node){
  1841. parent.insertBefore(node, index.nextSibling);
  1842. index = node;
  1843. });
  1844. },
  1845. addClass: JQLiteAddClass,
  1846. removeClass: JQLiteRemoveClass,
  1847. toggleClass: function(element, selector, condition) {
  1848. if (isUndefined(condition)) {
  1849. condition = !JQLiteHasClass(element, selector);
  1850. }
  1851. (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector);
  1852. },
  1853. parent: function(element) {
  1854. var parent = element.parentNode;
  1855. return parent && parent.nodeType !== 11 ? parent : null;
  1856. },
  1857. next: function(element) {
  1858. return element.nextSibling;
  1859. },
  1860. find: function(element, selector) {
  1861. return element.getElementsByTagName(selector);
  1862. },
  1863. clone: JQLiteClone
  1864. }, function(fn, name){
  1865. /**
  1866. * chaining functions
  1867. */
  1868. JQLite.prototype[name] = function(arg1, arg2) {
  1869. var value;
  1870. for(var i=0; i < this.length; i++) {
  1871. if (value == undefined) {
  1872. value = fn(this[i], arg1, arg2);
  1873. if (value !== undefined) {
  1874. // any function which returns a value needs to be wrapped
  1875. value = jqLite(value);
  1876. }
  1877. } else {
  1878. JQLiteAddNodes(value, fn(this[i], arg1, arg2));
  1879. }
  1880. }
  1881. return value == undefined ? this : value;
  1882. };
  1883. });
  1884. /**
  1885. * Computes a hash of an 'obj'.
  1886. * Hash of a:
  1887. * string is string
  1888. * number is number as string
  1889. * object is either result of calling $$hashKey function on the object or uniquely generated id,
  1890. * that is also assigned to the $$hashKey property of the object.
  1891. *
  1892. * @param obj
  1893. * @returns {string} hash string such that the same input will have the same hash string.
  1894. * The resulting string key is in 'type:hashKey' format.
  1895. */
  1896. function hashKey(obj) {
  1897. var objType = typeof obj,
  1898. key;
  1899. if (objType == 'object' && obj !== null) {
  1900. if (typeof (key = obj.$$hashKey) == 'function') {
  1901. // must invoke on object to keep the right this
  1902. key = obj.$$hashKey();
  1903. } else if (key === undefined) {
  1904. key = obj.$$hashKey = nextUid();
  1905. }
  1906. } else {
  1907. key = obj;
  1908. }
  1909. return objType + ':' + key;
  1910. }
  1911. /**
  1912. * HashMap which can use objects as keys
  1913. */
  1914. function HashMap(array){
  1915. forEach(array, this.put, this);
  1916. }
  1917. HashMap.prototype = {
  1918. /**
  1919. * Store key value pair
  1920. * @param key key to store can be any type
  1921. * @param value value to store can be any type
  1922. */
  1923. put: function(key, value) {
  1924. this[hashKey(key)] = value;
  1925. },
  1926. /**
  1927. * @param key
  1928. * @returns the value for the key
  1929. */
  1930. get: function(key) {
  1931. return this[hashKey(key)];
  1932. },
  1933. /**
  1934. * Remove the key/value pair
  1935. * @param key
  1936. */
  1937. remove: function(key) {
  1938. var value = this[key = hashKey(key)];
  1939. delete this[key];
  1940. return value;
  1941. }
  1942. };
  1943. /**
  1944. * A map where multiple values can be added to the same key such that they form a queue.
  1945. * @returns {HashQueueMap}
  1946. */
  1947. function HashQueueMap() {}
  1948. HashQueueMap.prototype = {
  1949. /**
  1950. * Same as array push, but using an array as the value for the hash
  1951. */
  1952. push: function(key, value) {
  1953. var array = this[key = hashKey(key)];
  1954. if (!array) {
  1955. this[key] = [value];
  1956. } else {
  1957. array.push(value);
  1958. }
  1959. },
  1960. /**
  1961. * Same as array shift, but using an array as the value for the hash
  1962. */
  1963. shift: function(key) {
  1964. var array = this[key = hashKey(key)];
  1965. if (array) {
  1966. if (array.length == 1) {
  1967. delete this[key];
  1968. return array[0];
  1969. } else {
  1970. return array.shift();
  1971. }
  1972. }
  1973. }
  1974. };
  1975. /**
  1976. * @ngdoc function
  1977. * @name angular.injector
  1978. * @function
  1979. *
  1980. * @description
  1981. * Creates an injector function that can be used for retrieving services as well as for
  1982. * dependency injection (see {@link guide/di dependency injection}).
  1983. *
  1984. * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
  1985. * {@link angular.module}. The `ng` module must be explicitly added.
  1986. * @returns {function()} Injector function. See {@link AUTO.$injector $injector}.
  1987. *
  1988. * @example
  1989. * Typical usage
  1990. * <pre>
  1991. * // create an injector
  1992. * var $injector = angular.injector(['ng']);
  1993. *
  1994. * // use the injector to kick of your application
  1995. * // use the type inference to auto inject arguments, or use implicit injection
  1996. * $injector.invoke(function($rootScope, $compile, $document){
  1997. * $compile($document)($rootScope);
  1998. * $rootScope.$digest();
  1999. * });
  2000. * </pre>
  2001. */
  2002. /**
  2003. * @ngdoc overview
  2004. * @name AUTO
  2005. * @description
  2006. *
  2007. * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
  2008. */
  2009. var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  2010. var FN_ARG_SPLIT = /,/;
  2011. var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
  2012. var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  2013. function annotate(fn) {
  2014. var $inject,
  2015. fnText,
  2016. argDecl,
  2017. last;
  2018. if (typeof fn == 'function') {
  2019. if (!($inject = fn.$inject)) {
  2020. $inject = [];
  2021. fnText = fn.toString().replace(STRIP_COMMENTS, '');
  2022. argDecl = fnText.match(FN_ARGS);
  2023. forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
  2024. arg.replace(FN_ARG, function(all, underscore, name){
  2025. $inject.push(name);
  2026. });
  2027. });
  2028. fn.$inject = $inject;
  2029. }
  2030. } else if (isArray(fn)) {
  2031. last = fn.length - 1;
  2032. assertArgFn(fn[last], 'fn')
  2033. $inject = fn.slice(0, last);
  2034. } else {
  2035. assertArgFn(fn, 'fn', true);
  2036. }
  2037. return $inject;
  2038. }
  2039. ///////////////////////////////////////
  2040. /**
  2041. * @ngdoc object
  2042. * @name AUTO.$injector
  2043. * @function
  2044. *
  2045. * @description
  2046. *
  2047. * `$injector` is used to retrieve object instances as defined by
  2048. * {@link AUTO.$provide provider}, instantiate types, invoke methods,
  2049. * and load modules.
  2050. *
  2051. * The following always holds true:
  2052. *
  2053. * <pre>
  2054. * var $injector = angular.injector();
  2055. * expect($injector.get('$injector')).toBe($injector);
  2056. * expect($injector.invoke(function($injector){
  2057. * return $injector;
  2058. * }).toBe($injector);
  2059. * </pre>
  2060. *
  2061. * # Injection Function Annotation
  2062. *
  2063. * JavaScript does not have annotations, and annotations are needed for dependency injection. The
  2064. * following ways are all valid way of annotating function with injection arguments and are equivalent.
  2065. *
  2066. * <pre>
  2067. * // inferred (only works if code not minified/obfuscated)
  2068. * $inject.invoke(function(serviceA){});
  2069. *
  2070. * // annotated
  2071. * function explicit(serviceA) {};
  2072. * explicit.$inject = ['serviceA'];
  2073. * $inject.invoke(explicit);
  2074. *
  2075. * // inline
  2076. * $inject.invoke(['serviceA', function(serviceA){}]);
  2077. * </pre>
  2078. *
  2079. * ## Inference
  2080. *
  2081. * In JavaScript calling `toString()` on a function returns the function definition. The definition can then be
  2082. * parsed and the function arguments can be extracted. *NOTE:* This does not work with minification, and obfuscation
  2083. * tools since these tools change the argument names.
  2084. *
  2085. * ## `$inject` Annotation
  2086. * By adding a `$inject` property onto a function the injection parameters can be specified.
  2087. *
  2088. * ## Inline
  2089. * As an array of injection names, where the last item in the array is the function to call.
  2090. */
  2091. /**
  2092. * @ngdoc method
  2093. * @name AUTO.$injector#get
  2094. * @methodOf AUTO.$injector
  2095. *
  2096. * @description
  2097. * Return an instance of the service.
  2098. *
  2099. * @param {string} name The name of the instance to retrieve.
  2100. * @return {*} The instance.
  2101. */
  2102. /**
  2103. * @ngdoc method
  2104. * @name AUTO.$injector#invoke
  2105. * @methodOf AUTO.$injector
  2106. *
  2107. * @description
  2108. * Invoke the method and supply the method arguments from the `$injector`.
  2109. *
  2110. * @param {!function} fn The function to invoke. The function arguments come form the function annotation.
  2111. * @param {Object=} self The `this` for the invoked method.
  2112. * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
  2113. * the `$injector` is consulted.
  2114. * @returns {*} the value returned by the invoked `fn` function.
  2115. */
  2116. /**
  2117. * @ngdoc method
  2118. * @name AUTO.$injector#instantiate
  2119. * @methodOf AUTO.$injector
  2120. * @description
  2121. * Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies
  2122. * all of the arguments to the constructor function as specified by the constructor annotation.
  2123. *
  2124. * @param {function} Type Annotated constructor function.
  2125. * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
  2126. * the `$injector` is consulted.
  2127. * @returns {Object} new instance of `Type`.
  2128. */
  2129. /**
  2130. * @ngdoc method
  2131. * @name AUTO.$injector#annotate
  2132. * @methodOf AUTO.$injector
  2133. *
  2134. * @description
  2135. * Returns an array of service names which the function is requesting for injection. This API is used by the injector
  2136. * to determine which services need to be injected into the function when the function is invoked. There are three
  2137. * ways in which the function can be annotated with the needed dependencies.
  2138. *
  2139. * # Argument names
  2140. *
  2141. * The simplest form is to extract the dependencies from the arguments of the function. This is done by converting
  2142. * the function into a string using `toString()` method and extracting the argument names.
  2143. * <pre>
  2144. * // Given
  2145. * function MyController($scope, $route) {
  2146. * // ...
  2147. * }
  2148. *
  2149. * // Then
  2150. * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
  2151. * </pre>
  2152. *
  2153. * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
  2154. * are supported.
  2155. *
  2156. * # The `$injector` property
  2157. *
  2158. * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
  2159. * services to be injected into the function.
  2160. * <pre>
  2161. * // Given
  2162. * var MyController = function(obfuscatedScope, obfuscatedRoute) {
  2163. * // ...
  2164. * }
  2165. * // Define function dependencies
  2166. * MyController.$inject = ['$scope', '$route'];
  2167. *
  2168. * // Then
  2169. * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
  2170. * </pre>
  2171. *
  2172. * # The array notation
  2173. *
  2174. * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very
  2175. * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives
  2176. * minification is a better choice:
  2177. *
  2178. * <pre>
  2179. * // We wish to write this (not minification / obfuscation safe)
  2180. * injector.invoke(function($compile, $rootScope) {
  2181. * // ...
  2182. * });
  2183. *
  2184. * // We are forced to write break inlining
  2185. * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
  2186. * // ...
  2187. * };
  2188. * tmpFn.$inject = ['$compile', '$rootScope'];
  2189. * injector.invoke(tempFn);
  2190. *
  2191. * // To better support inline function the inline annotation is supported
  2192. * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
  2193. * // ...
  2194. * }]);
  2195. *
  2196. * // Therefore
  2197. * expect(injector.annotate(
  2198. * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
  2199. * ).toEqual(['$compile', '$rootScope']);
  2200. * </pre>
  2201. *
  2202. * @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described
  2203. * above.
  2204. *
  2205. * @returns {Array.<string>} The names of the services which the function requires.
  2206. */
  2207. /**
  2208. * @ngdoc object
  2209. * @name AUTO.$provide
  2210. *
  2211. * @description
  2212. *
  2213. * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance.
  2214. * The providers share the same name as the instance they create with the `Provider` suffixed to them.
  2215. *
  2216. * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of
  2217. * a service. The Provider can have additional methods which would allow for configuration of the provider.
  2218. *
  2219. * <pre>
  2220. * function GreetProvider() {
  2221. * var salutation = 'Hello';
  2222. *
  2223. * this.salutation = function(text) {
  2224. * salutation = text;
  2225. * };
  2226. *
  2227. * this.$get = function() {
  2228. * return function (name) {
  2229. * return salutation + ' ' + name + '!';
  2230. * };
  2231. * };
  2232. * }
  2233. *
  2234. * describe('Greeter', function(){
  2235. *
  2236. * beforeEach(module(function($provide) {
  2237. * $provide.provider('greet', GreetProvider);
  2238. * });
  2239. *
  2240. * it('should greet', inject(function(greet) {
  2241. * expect(greet('angular')).toEqual('Hello angular!');
  2242. * }));
  2243. *
  2244. * it('should allow configuration of salutation', function() {
  2245. * module(function(greetProvider) {
  2246. * greetProvider.salutation('Ahoj');
  2247. * });
  2248. * inject(function(greet) {
  2249. * expect(greet('angular')).toEqual('Ahoj angular!');
  2250. * });
  2251. * )};
  2252. *
  2253. * });
  2254. * </pre>
  2255. */
  2256. /**
  2257. * @ngdoc method
  2258. * @name AUTO.$provide#provider
  2259. * @methodOf AUTO.$provide
  2260. * @description
  2261. *
  2262. * Register a provider for a service. The providers can be retrieved and can have additional configuration methods.
  2263. *
  2264. * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key.
  2265. * @param {(Object|function())} provider If the provider is:
  2266. *
  2267. * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
  2268. * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created.
  2269. * - `Constructor`: a new instance of the provider will be created using
  2270. * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.
  2271. *
  2272. * @returns {Object} registered provider instance
  2273. */
  2274. /**
  2275. * @ngdoc method
  2276. * @name AUTO.$provide#factory
  2277. * @methodOf AUTO.$provide
  2278. * @description
  2279. *
  2280. * A short hand for configuring services if only `$get` method is required.
  2281. *
  2282. * @param {string} name The name of the instance.
  2283. * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for
  2284. * `$provide.provider(name, {$get: $getFn})`.
  2285. * @returns {Object} registered provider instance
  2286. */
  2287. /**
  2288. * @ngdoc method
  2289. * @name AUTO.$provide#service
  2290. * @methodOf AUTO.$provide
  2291. * @description
  2292. *
  2293. * A short hand for registering service of given class.
  2294. *
  2295. * @param {string} name The name of the instance.
  2296. * @param {Function} constructor A class (constructor function) that will be instantiated.
  2297. * @returns {Object} registered provider instance
  2298. */
  2299. /**
  2300. * @ngdoc method
  2301. * @name AUTO.$provide#value
  2302. * @methodOf AUTO.$provide
  2303. * @description
  2304. *
  2305. * A short hand for configuring services if the `$get` method is a constant.
  2306. *
  2307. * @param {string} name The name of the instance.
  2308. * @param {*} value The value.
  2309. * @returns {Object} registered provider instance
  2310. */
  2311. /**
  2312. * @ngdoc method
  2313. * @name AUTO.$provide#constant
  2314. * @methodOf AUTO.$provide
  2315. * @description
  2316. *
  2317. * A constant value, but unlike {@link AUTO.$provide#value value} it can be injected
  2318. * into configuration function (other modules) and it is not interceptable by
  2319. * {@link AUTO.$provide#decorator decorator}.
  2320. *
  2321. * @param {string} name The name of the constant.
  2322. * @param {*} value The constant value.
  2323. * @returns {Object} registered instance
  2324. */
  2325. /**
  2326. * @ngdoc method
  2327. * @name AUTO.$provide#decorator
  2328. * @methodOf AUTO.$provide
  2329. * @description
  2330. *
  2331. * Decoration of service, allows the decorator to intercept the service instance creation. The
  2332. * returned instance may be the original instance, or a new instance which delegates to the
  2333. * original instance.
  2334. *
  2335. * @param {string} name The name of the service to decorate.
  2336. * @param {function()} decorator This function will be invoked when the service needs to be
  2337. * instanciated. The function is called using the {@link AUTO.$injector#invoke
  2338. * injector.invoke} method and is therefore fully injectable. Local injection arguments:
  2339. *
  2340. * * `$delegate` - The original service instance, which can be monkey patched, configured,
  2341. * decorated or delegated to.
  2342. */
  2343. function createInjector(modulesToLoad) {
  2344. var INSTANTIATING = {},
  2345. providerSuffix = 'Provider',
  2346. path = [],
  2347. loadedModules = new HashMap(),
  2348. providerCache = {
  2349. $provide: {
  2350. provider: supportObject(provider),
  2351. factory: supportObject(factory),
  2352. service: supportObject(service),
  2353. value: supportObject(value),
  2354. constant: supportObject(constant),
  2355. decorator: decorator
  2356. }
  2357. },
  2358. providerInjector = createInternalInjector(providerCache, function() {
  2359. throw Error("Unknown provider: " + path.join(' <- '));
  2360. }),
  2361. instanceCache = {},
  2362. instanceInjector = (instanceCache.$injector =
  2363. createInternalInjector(instanceCache, function(servicename) {
  2364. var provider = providerInjector.get(servicename + providerSuffix);
  2365. return instanceInjector.invoke(provider.$get, provider);
  2366. }));
  2367. forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
  2368. return instanceInjector;
  2369. ////////////////////////////////////
  2370. // $provider
  2371. ////////////////////////////////////
  2372. function supportObject(delegate) {
  2373. return function(key, value) {
  2374. if (isObject(key)) {
  2375. forEach(key, reverseParams(delegate));
  2376. } else {
  2377. return delegate(key, value);
  2378. }
  2379. }
  2380. }
  2381. function provider(name, provider_) {
  2382. if (isFunction(provider_)) {
  2383. provider_ = providerInjector.instantiate(provider_);
  2384. }
  2385. if (!provider_.$get) {
  2386. throw Error('Provider ' + name + ' must define $get factory method.');
  2387. }
  2388. return providerCache[name + providerSuffix] = provider_;
  2389. }
  2390. function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
  2391. function service(name, constructor) {
  2392. return factory(name, ['$injector', function($injector) {
  2393. return $injector.instantiate(constructor);
  2394. }]);
  2395. }
  2396. function value(name, value) { return factory(name, valueFn(value)); }
  2397. function constant(name, value) {
  2398. providerCache[name] = value;
  2399. instanceCache[name] = value;
  2400. }
  2401. function decorator(serviceName, decorFn) {
  2402. var origProvider = providerInjector.get(serviceName + providerSuffix),
  2403. orig$get = origProvider.$get;
  2404. origProvider.$get = function() {
  2405. var origInstance = instanceInjector.invoke(orig$get, origProvider);
  2406. return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
  2407. };
  2408. }
  2409. ////////////////////////////////////
  2410. // Module Loading
  2411. ////////////////////////////////////
  2412. function loadModules(modulesToLoad){
  2413. var runBlocks = [];
  2414. forEach(modulesToLoad, function(module) {
  2415. if (loadedModules.get(module)) return;
  2416. loadedModules.put(module, true);
  2417. if (isString(module)) {
  2418. var moduleFn = angularModule(module);
  2419. runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
  2420. try {
  2421. for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
  2422. var invokeArgs = invokeQueue[i],
  2423. provider = invokeArgs[0] == '$injector'
  2424. ? providerInjector
  2425. : providerInjector.get(invokeArgs[0]);
  2426. provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
  2427. }
  2428. } catch (e) {
  2429. if (e.message) e.message += ' from ' + module;
  2430. throw e;
  2431. }
  2432. } else if (isFunction(module)) {
  2433. try {
  2434. runBlocks.push(providerInjector.invoke(module));
  2435. } catch (e) {
  2436. if (e.message) e.message += ' from ' + module;
  2437. throw e;
  2438. }
  2439. } else if (isArray(module)) {
  2440. try {
  2441. runBlocks.push(providerInjector.invoke(module));
  2442. } catch (e) {
  2443. if (e.message) e.message += ' from ' + String(module[module.length - 1]);
  2444. throw e;
  2445. }
  2446. } else {
  2447. assertArgFn(module, 'module');
  2448. }
  2449. });
  2450. return runBlocks;
  2451. }
  2452. ////////////////////////////////////
  2453. // internal Injector
  2454. ////////////////////////////////////
  2455. function createInternalInjector(cache, factory) {
  2456. function getService(serviceName) {
  2457. if (typeof serviceName !== 'string') {
  2458. throw Error('Service name expected');
  2459. }
  2460. if (cache.hasOwnProperty(serviceName)) {
  2461. if (cache[serviceName] === INSTANTIATING) {
  2462. throw Error('Circular dependency: ' + path.join(' <- '));
  2463. }
  2464. return cache[serviceName];
  2465. } else {
  2466. try {
  2467. path.unshift(serviceName);
  2468. cache[serviceName] = INSTANTIATING;
  2469. return cache[serviceName] = factory(serviceName);
  2470. } finally {
  2471. path.shift();
  2472. }
  2473. }
  2474. }
  2475. function invoke(fn, self, locals){
  2476. var args = [],
  2477. $inject = annotate(fn),
  2478. length, i,
  2479. key;
  2480. for(i = 0, length = $inject.length; i < length; i++) {
  2481. key = $inject[i];
  2482. args.push(
  2483. locals && locals.hasOwnProperty(key)
  2484. ? locals[key]
  2485. : getService(key, path)
  2486. );
  2487. }
  2488. if (!fn.$inject) {
  2489. // this means that we must be an array.
  2490. fn = fn[length];
  2491. }
  2492. // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
  2493. switch (self ? -1 : args.length) {
  2494. case 0: return fn();
  2495. case 1: return fn(args[0]);
  2496. case 2: return fn(args[0], args[1]);
  2497. case 3: return fn(args[0], args[1], args[2]);
  2498. case 4: return fn(args[0], args[1], args[2], args[3]);
  2499. case 5: return fn(args[0], args[1], args[2], args[3], args[4]);
  2500. case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
  2501. case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
  2502. case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
  2503. case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
  2504. case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
  2505. default: return fn.apply(self, args);
  2506. }
  2507. }
  2508. function instantiate(Type, locals) {
  2509. var Constructor = function() {},
  2510. instance, returnedValue;
  2511. Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
  2512. instance = new Constructor();
  2513. returnedValue = invoke(Type, instance, locals);
  2514. return isObject(returnedValue) ? returnedValue : instance;
  2515. }
  2516. return {
  2517. invoke: invoke,
  2518. instantiate: instantiate,
  2519. get: getService,
  2520. annotate: annotate
  2521. };
  2522. }
  2523. }
  2524. /**
  2525. * @ngdoc function
  2526. * @name ng.$anchorScroll
  2527. * @requires $window
  2528. * @requires $location
  2529. * @requires $rootScope
  2530. *
  2531. * @description
  2532. * When called, it checks current value of `$location.hash()` and scroll to related element,
  2533. * according to rules specified in
  2534. * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}.
  2535. *
  2536. * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor.
  2537. * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
  2538. */
  2539. function $AnchorScrollProvider() {
  2540. var autoScrollingEnabled = true;
  2541. this.disableAutoScrolling = function() {
  2542. autoScrollingEnabled = false;
  2543. };
  2544. this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
  2545. var document = $window.document;
  2546. // helper function to get first anchor from a NodeList
  2547. // can't use filter.filter, as it accepts only instances of Array
  2548. // and IE can't convert NodeList to an array using [].slice
  2549. // TODO(vojta): use filter if we change it to accept lists as well
  2550. function getFirstAnchor(list) {
  2551. var result = null;
  2552. forEach(list, function(element) {
  2553. if (!result && lowercase(element.nodeName) === 'a') result = element;
  2554. });
  2555. return result;
  2556. }
  2557. function scroll() {
  2558. var hash = $location.hash(), elm;
  2559. // empty hash, scroll to the top of the page
  2560. if (!hash) $window.scrollTo(0, 0);
  2561. // element with given id
  2562. else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
  2563. // first anchor with given name :-D
  2564. else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
  2565. // no element and hash == 'top', scroll to the top of the page
  2566. else if (hash === 'top') $window.scrollTo(0, 0);
  2567. }
  2568. // does not scroll when user clicks on anchor link that is currently on
  2569. // (no url change, no $locaiton.hash() change), browser native does scroll
  2570. if (autoScrollingEnabled) {
  2571. $rootScope.$watch(function() {return $location.hash();}, function() {
  2572. $rootScope.$evalAsync(scroll);
  2573. });
  2574. }
  2575. return scroll;
  2576. }];
  2577. }
  2578. /**
  2579. * ! This is a private undocumented service !
  2580. *
  2581. * @name ng.$browser
  2582. * @requires $log
  2583. * @description
  2584. * This object has two goals:
  2585. *
  2586. * - hide all the global state in the browser caused by the window object
  2587. * - abstract away all the browser specific features and inconsistencies
  2588. *
  2589. * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
  2590. * service, which can be used for convenient testing of the application without the interaction with
  2591. * the real browser apis.
  2592. */
  2593. /**
  2594. * @param {object} window The global window object.
  2595. * @param {object} document jQuery wrapped document.
  2596. * @param {function()} XHR XMLHttpRequest constructor.
  2597. * @param {object} $log console.log or an object with the same interface.
  2598. * @param {object} $sniffer $sniffer service
  2599. */
  2600. function Browser(window, document, $log, $sniffer) {
  2601. var self = this,
  2602. rawDocument = document[0],
  2603. location = window.location,
  2604. history = window.history,
  2605. setTimeout = window.setTimeout,
  2606. clearTimeout = window.clearTimeout,
  2607. pendingDeferIds = {};
  2608. self.isMock = false;
  2609. var outstandingRequestCount = 0;
  2610. var outstandingRequestCallbacks = [];
  2611. // TODO(vojta): remove this temporary api
  2612. self.$$completeOutstandingRequest = completeOutstandingRequest;
  2613. self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
  2614. /**
  2615. * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
  2616. * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
  2617. */
  2618. function completeOutstandingRequest(fn) {
  2619. try {
  2620. fn.apply(null, sliceArgs(arguments, 1));
  2621. } finally {
  2622. outstandingRequestCount--;
  2623. if (outstandingRequestCount === 0) {
  2624. while(outstandingRequestCallbacks.length) {
  2625. try {
  2626. outstandingRequestCallbacks.pop()();
  2627. } catch (e) {
  2628. $log.error(e);
  2629. }
  2630. }
  2631. }
  2632. }
  2633. }
  2634. /**
  2635. * @private
  2636. * Note: this method is used only by scenario runner
  2637. * TODO(vojta): prefix this method with $$ ?
  2638. * @param {function()} callback Function that will be called when no outstanding request
  2639. */
  2640. self.notifyWhenNoOutstandingRequests = function(callback) {
  2641. // force browser to execute all pollFns - this is needed so that cookies and other pollers fire
  2642. // at some deterministic time in respect to the test runner's actions. Leaving things up to the
  2643. // regular poller would result in flaky tests.
  2644. forEach(pollFns, function(pollFn){ pollFn(); });
  2645. if (outstandingRequestCount === 0) {
  2646. callback();
  2647. } else {
  2648. outstandingRequestCallbacks.push(callback);
  2649. }
  2650. };
  2651. //////////////////////////////////////////////////////////////
  2652. // Poll Watcher API
  2653. //////////////////////////////////////////////////////////////
  2654. var pollFns = [],
  2655. pollTimeout;
  2656. /**
  2657. * @name ng.$browser#addPollFn
  2658. * @methodOf ng.$browser
  2659. *
  2660. * @param {function()} fn Poll function to add
  2661. *
  2662. * @description
  2663. * Adds a function to the list of functions that poller periodically executes,
  2664. * and starts polling if not started yet.
  2665. *
  2666. * @returns {function()} the added function
  2667. */
  2668. self.addPollFn = function(fn) {
  2669. if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
  2670. pollFns.push(fn);
  2671. return fn;
  2672. };
  2673. /**
  2674. * @param {number} interval How often should browser call poll functions (ms)
  2675. * @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
  2676. *
  2677. * @description
  2678. * Configures the poller to run in the specified intervals, using the specified
  2679. * setTimeout fn and kicks it off.
  2680. */
  2681. function startPoller(interval, setTimeout) {
  2682. (function check() {
  2683. forEach(pollFns, function(pollFn){ pollFn(); });
  2684. pollTimeout = setTimeout(check, interval);
  2685. })();
  2686. }
  2687. //////////////////////////////////////////////////////////////
  2688. // URL API
  2689. //////////////////////////////////////////////////////////////
  2690. var lastBrowserUrl = location.href,
  2691. baseElement = document.find('base');
  2692. /**
  2693. * @name ng.$browser#url
  2694. * @methodOf ng.$browser
  2695. *
  2696. * @description
  2697. * GETTER:
  2698. * Without any argument, this method just returns current value of location.href.
  2699. *
  2700. * SETTER:
  2701. * With at least one argument, this method sets url to new value.
  2702. * If html5 history api supported, pushState/replaceState is used, otherwise
  2703. * location.href/location.replace is used.
  2704. * Returns its own instance to allow chaining
  2705. *
  2706. * NOTE: this api is intended for use only by the $location service. Please use the
  2707. * {@link ng.$location $location service} to change url.
  2708. *
  2709. * @param {string} url New url (when used as setter)
  2710. * @param {boolean=} replace Should new url replace current history record ?
  2711. */
  2712. self.url = function(url, replace) {
  2713. // setter
  2714. if (url) {
  2715. if (lastBrowserUrl == url) return;
  2716. lastBrowserUrl = url;
  2717. if ($sniffer.history) {
  2718. if (replace) history.replaceState(null, '', url);
  2719. else {
  2720. history.pushState(null, '', url);
  2721. // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
  2722. baseElement.attr('href', baseElement.attr('href'));
  2723. }
  2724. } else {
  2725. if (replace) location.replace(url);
  2726. else location.href = url;
  2727. }
  2728. return self;
  2729. // getter
  2730. } else {
  2731. // the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
  2732. return location.href.replace(/%27/g,"'");
  2733. }
  2734. };
  2735. var urlChangeListeners = [],
  2736. urlChangeInit = false;
  2737. function fireUrlChange() {
  2738. if (lastBrowserUrl == self.url()) return;
  2739. lastBrowserUrl = self.url();
  2740. forEach(urlChangeListeners, function(listener) {
  2741. listener(self.url());
  2742. });
  2743. }
  2744. /**
  2745. * @name ng.$browser#onUrlChange
  2746. * @methodOf ng.$browser
  2747. * @TODO(vojta): refactor to use node's syntax for events
  2748. *
  2749. * @description
  2750. * Register callback function that will be called, when url changes.
  2751. *
  2752. * It's only called when the url is changed by outside of angular:
  2753. * - user types different url into address bar
  2754. * - user clicks on history (forward/back) button
  2755. * - user clicks on a link
  2756. *
  2757. * It's not called when url is changed by $browser.url() method
  2758. *
  2759. * The listener gets called with new url as parameter.
  2760. *
  2761. * NOTE: this api is intended for use only by the $location service. Please use the
  2762. * {@link ng.$location $location service} to monitor url changes in angular apps.
  2763. *
  2764. * @param {function(string)} listener Listener function to be called when url changes.
  2765. * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
  2766. */
  2767. self.onUrlChange = function(callback) {
  2768. if (!urlChangeInit) {
  2769. // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
  2770. // don't fire popstate when user change the address bar and don't fire hashchange when url
  2771. // changed by push/replaceState
  2772. // html5 history api - popstate event
  2773. if ($sniffer.history) jqLite(window).bind('popstate', fireUrlChange);
  2774. // hashchange event
  2775. if ($sniffer.hashchange) jqLite(window).bind('hashchange', fireUrlChange);
  2776. // polling
  2777. else self.addPollFn(fireUrlChange);
  2778. urlChangeInit = true;
  2779. }
  2780. urlChangeListeners.push(callback);
  2781. return callback;
  2782. };
  2783. //////////////////////////////////////////////////////////////
  2784. // Misc API
  2785. //////////////////////////////////////////////////////////////
  2786. /**
  2787. * Returns current <base href>
  2788. * (always relative - without domain)
  2789. *
  2790. * @returns {string=}
  2791. */
  2792. self.baseHref = function() {
  2793. var href = baseElement.attr('href');
  2794. return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
  2795. };
  2796. //////////////////////////////////////////////////////////////
  2797. // Cookies API
  2798. //////////////////////////////////////////////////////////////
  2799. var lastCookies = {};
  2800. var lastCookieString = '';
  2801. var cookiePath = self.baseHref();
  2802. /**
  2803. * @name ng.$browser#cookies
  2804. * @methodOf ng.$browser
  2805. *
  2806. * @param {string=} name Cookie name
  2807. * @param {string=} value Cokkie value
  2808. *
  2809. * @description
  2810. * The cookies method provides a 'private' low level access to browser cookies.
  2811. * It is not meant to be used directly, use the $cookie service instead.
  2812. *
  2813. * The return values vary depending on the arguments that the method was called with as follows:
  2814. * <ul>
  2815. * <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li>
  2816. * <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li>
  2817. * <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li>
  2818. * </ul>
  2819. *
  2820. * @returns {Object} Hash of all cookies (if called without any parameter)
  2821. */
  2822. self.cookies = function(name, value) {
  2823. var cookieLength, cookieArray, cookie, i, index;
  2824. if (name) {
  2825. if (value === undefined) {
  2826. rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
  2827. } else {
  2828. if (isString(value)) {
  2829. cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1;
  2830. if (cookieLength > 4096) {
  2831. $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+
  2832. cookieLength + " > 4096 bytes)!");
  2833. }
  2834. if (lastCookies.length > 20) {
  2835. $log.warn("Cookie '"+ name +"' possibly not set or overflowed because too many cookies " +
  2836. "were already set (" + lastCookies.length + " > 20 )");
  2837. }
  2838. }
  2839. }
  2840. } else {
  2841. if (rawDocument.cookie !== lastCookieString) {
  2842. lastCookieString = rawDocument.cookie;
  2843. cookieArray = lastCookieString.split("; ");
  2844. lastCookies = {};
  2845. for (i = 0; i < cookieArray.length; i++) {
  2846. cookie = cookieArray[i];
  2847. index = cookie.indexOf('=');
  2848. if (index > 0) { //ignore nameless cookies
  2849. lastCookies[unescape(cookie.substring(0, index))] = unescape(cookie.substring(index + 1));
  2850. }
  2851. }
  2852. }
  2853. return lastCookies;
  2854. }
  2855. };
  2856. /**
  2857. * @name ng.$browser#defer
  2858. * @methodOf ng.$browser
  2859. * @param {function()} fn A function, who's execution should be defered.
  2860. * @param {number=} [delay=0] of milliseconds to defer the function execution.
  2861. * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
  2862. *
  2863. * @description
  2864. * Executes a fn asynchroniously via `setTimeout(fn, delay)`.
  2865. *
  2866. * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
  2867. * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
  2868. * via `$browser.defer.flush()`.
  2869. *
  2870. */
  2871. self.defer = function(fn, delay) {
  2872. var timeoutId;
  2873. outstandingRequestCount++;
  2874. timeoutId = setTimeout(function() {
  2875. delete pendingDeferIds[timeoutId];
  2876. completeOutstandingRequest(fn);
  2877. }, delay || 0);
  2878. pendingDeferIds[timeoutId] = true;
  2879. return timeoutId;
  2880. };
  2881. /**
  2882. * @name ng.$browser#defer.cancel
  2883. * @methodOf ng.$browser.defer
  2884. *
  2885. * @description
  2886. * Cancels a defered task identified with `deferId`.
  2887. *
  2888. * @param {*} deferId Token returned by the `$browser.defer` function.
  2889. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
  2890. */
  2891. self.defer.cancel = function(deferId) {
  2892. if (pendingDeferIds[deferId]) {
  2893. delete pendingDeferIds[deferId];
  2894. clearTimeout(deferId);
  2895. completeOutstandingRequest(noop);
  2896. return true;
  2897. }
  2898. return false;
  2899. };
  2900. }
  2901. function $BrowserProvider(){
  2902. this.$get = ['$window', '$log', '$sniffer', '$document',
  2903. function( $window, $log, $sniffer, $document){
  2904. return new Browser($window, $document, $log, $sniffer);
  2905. }];
  2906. }
  2907. /**
  2908. * @ngdoc object
  2909. * @name ng.$cacheFactory
  2910. *
  2911. * @description
  2912. * Factory that constructs cache objects.
  2913. *
  2914. *
  2915. * @param {string} cacheId Name or id of the newly created cache.
  2916. * @param {object=} options Options object that specifies the cache behavior. Properties:
  2917. *
  2918. * - `{number=}` `capacity` — turns the cache into LRU cache.
  2919. *
  2920. * @returns {object} Newly created cache object with the following set of methods:
  2921. *
  2922. * - `{object}` `info()` — Returns id, size, and options of cache.
  2923. * - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache.
  2924. * - `{{*}} `get({string} key) — Returns cached value for `key` or undefined for cache miss.
  2925. * - `{void}` `remove({string} key) — Removes a key-value pair from the cache.
  2926. * - `{void}` `removeAll() — Removes all cached values.
  2927. * - `{void}` `destroy() — Removes references to this cache from $cacheFactory.
  2928. *
  2929. */
  2930. function $CacheFactoryProvider() {
  2931. this.$get = function() {
  2932. var caches = {};
  2933. function cacheFactory(cacheId, options) {
  2934. if (cacheId in caches) {
  2935. throw Error('cacheId ' + cacheId + ' taken');
  2936. }
  2937. var size = 0,
  2938. stats = extend({}, options, {id: cacheId}),
  2939. data = {},
  2940. capacity = (options && options.capacity) || Number.MAX_VALUE,
  2941. lruHash = {},
  2942. freshEnd = null,
  2943. staleEnd = null;
  2944. return caches[cacheId] = {
  2945. put: function(key, value) {
  2946. var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
  2947. refresh(lruEntry);
  2948. if (isUndefined(value)) return;
  2949. if (!(key in data)) size++;
  2950. data[key] = value;
  2951. if (size > capacity) {
  2952. this.remove(staleEnd.key);
  2953. }
  2954. },
  2955. get: function(key) {
  2956. var lruEntry = lruHash[key];
  2957. if (!lruEntry) return;
  2958. refresh(lruEntry);
  2959. return data[key];
  2960. },
  2961. remove: function(key) {
  2962. var lruEntry = lruHash[key];
  2963. if (lruEntry == freshEnd) freshEnd = lruEntry.p;
  2964. if (lruEntry == staleEnd) staleEnd = lruEntry.n;
  2965. link(lruEntry.n,lruEntry.p);
  2966. delete lruHash[key];
  2967. delete data[key];
  2968. size--;
  2969. },
  2970. removeAll: function() {
  2971. data = {};
  2972. size = 0;
  2973. lruHash = {};
  2974. freshEnd = staleEnd = null;
  2975. },
  2976. destroy: function() {
  2977. data = null;
  2978. stats = null;
  2979. lruHash = null;
  2980. delete caches[cacheId];
  2981. },
  2982. info: function() {
  2983. return extend({}, stats, {size: size});
  2984. }
  2985. };
  2986. /**
  2987. * makes the `entry` the freshEnd of the LRU linked list
  2988. */
  2989. function refresh(entry) {
  2990. if (entry != freshEnd) {
  2991. if (!staleEnd) {
  2992. staleEnd = entry;
  2993. } else if (staleEnd == entry) {
  2994. staleEnd = entry.n;
  2995. }
  2996. link(entry.n, entry.p);
  2997. link(entry, freshEnd);
  2998. freshEnd = entry;
  2999. freshEnd.n = null;
  3000. }
  3001. }
  3002. /**
  3003. * bidirectionally links two entries of the LRU linked list
  3004. */
  3005. function link(nextEntry, prevEntry) {
  3006. if (nextEntry != prevEntry) {
  3007. if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
  3008. if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
  3009. }
  3010. }
  3011. }
  3012. cacheFactory.info = function() {
  3013. var info = {};
  3014. forEach(caches, function(cache, cacheId) {
  3015. info[cacheId] = cache.info();
  3016. });
  3017. return info;
  3018. };
  3019. cacheFactory.get = function(cacheId) {
  3020. return caches[cacheId];
  3021. };
  3022. return cacheFactory;
  3023. };
  3024. }
  3025. /**
  3026. * @ngdoc object
  3027. * @name ng.$templateCache
  3028. *
  3029. * @description
  3030. * Cache used for storing html templates.
  3031. *
  3032. * See {@link ng.$cacheFactory $cacheFactory}.
  3033. *
  3034. */
  3035. function $TemplateCacheProvider() {
  3036. this.$get = ['$cacheFactory', function($cacheFactory) {
  3037. return $cacheFactory('views');
  3038. }];
  3039. }
  3040. /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
  3041. *
  3042. * DOM-related variables:
  3043. *
  3044. * - "node" - DOM Node
  3045. * - "element" - DOM Element or Node
  3046. * - "$node" or "$element" - jqLite-wrapped node or element
  3047. *
  3048. *
  3049. * Compiler related stuff:
  3050. *
  3051. * - "linkFn" - linking fn of a single directive
  3052. * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
  3053. * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
  3054. * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
  3055. */
  3056. var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
  3057. /**
  3058. * @ngdoc function
  3059. * @name ng.$compile
  3060. * @function
  3061. *
  3062. * @description
  3063. * Compiles a piece of HTML string or DOM into a template and produces a template function, which
  3064. * can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
  3065. *
  3066. * The compilation is a process of walking the DOM tree and trying to match DOM elements to
  3067. * {@link ng.$compileProvider.directive directives}. For each match it
  3068. * executes corresponding template function and collects the
  3069. * instance functions into a single template function which is then returned.
  3070. *
  3071. * The template function can then be used once to produce the view or as it is the case with
  3072. * {@link ng.directive:ngRepeat repeater} many-times, in which
  3073. * case each call results in a view that is a DOM clone of the original template.
  3074. *
  3075. <doc:example module="compile">
  3076. <doc:source>
  3077. <script>
  3078. // declare a new module, and inject the $compileProvider
  3079. angular.module('compile', [], function($compileProvider) {
  3080. // configure new 'compile' directive by passing a directive
  3081. // factory function. The factory function injects the '$compile'
  3082. $compileProvider.directive('compile', function($compile) {
  3083. // directive factory creates a link function
  3084. return function(scope, element, attrs) {
  3085. scope.$watch(
  3086. function(scope) {
  3087. // watch the 'compile' expression for changes
  3088. return scope.$eval(attrs.compile);
  3089. },
  3090. function(value) {
  3091. // when the 'compile' expression changes
  3092. // assign it into the current DOM
  3093. element.html(value);
  3094. // compile the new DOM and link it to the current
  3095. // scope.
  3096. // NOTE: we only compile .childNodes so that
  3097. // we don't get into infinite loop compiling ourselves
  3098. $compile(element.contents())(scope);
  3099. }
  3100. );
  3101. };
  3102. })
  3103. });
  3104. function Ctrl($scope) {
  3105. $scope.name = 'Angular';
  3106. $scope.html = 'Hello {{name}}';
  3107. }
  3108. </script>
  3109. <div ng-controller="Ctrl">
  3110. <input ng-model="name"> <br>
  3111. <textarea ng-model="html"></textarea> <br>
  3112. <div compile="html"></div>
  3113. </div>
  3114. </doc:source>
  3115. <doc:scenario>
  3116. it('should auto compile', function() {
  3117. expect(element('div[compile]').text()).toBe('Hello Angular');
  3118. input('html').enter('{{name}}!');
  3119. expect(element('div[compile]').text()).toBe('Angular!');
  3120. });
  3121. </doc:scenario>
  3122. </doc:example>
  3123. *
  3124. *
  3125. * @param {string|DOMElement} element Element or HTML string to compile into a template function.
  3126. * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
  3127. * @param {number} maxPriority only apply directives lower then given priority (Only effects the
  3128. * root element(s), not their children)
  3129. * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
  3130. * (a DOM element/tree) to a scope. Where:
  3131. *
  3132. * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
  3133. * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
  3134. * `template` and call the `cloneAttachFn` function allowing the caller to attach the
  3135. * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
  3136. * called as: <br> `cloneAttachFn(clonedElement, scope)` where:
  3137. *
  3138. * * `clonedElement` - is a clone of the original `element` passed into the compiler.
  3139. * * `scope` - is the current scope with which the linking function is working with.
  3140. *
  3141. * Calling the linking function returns the element of the template. It is either the original element
  3142. * passed in, or the clone of the element if the `cloneAttachFn` is provided.
  3143. *
  3144. * After linking the view is not updated until after a call to $digest which typically is done by
  3145. * Angular automatically.
  3146. *
  3147. * If you need access to the bound view, there are two ways to do it:
  3148. *
  3149. * - If you are not asking the linking function to clone the template, create the DOM element(s)
  3150. * before you send them to the compiler and keep this reference around.
  3151. * <pre>
  3152. * var element = $compile('<p>{{total}}</p>')(scope);
  3153. * </pre>
  3154. *
  3155. * - if on the other hand, you need the element to be cloned, the view reference from the original
  3156. * example would not point to the clone, but rather to the original template that was cloned. In
  3157. * this case, you can access the clone via the cloneAttachFn:
  3158. * <pre>
  3159. * var templateHTML = angular.element('<p>{{total}}</p>'),
  3160. * scope = ....;
  3161. *
  3162. * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
  3163. * //attach the clone to DOM document at the right place
  3164. * });
  3165. *
  3166. * //now we have reference to the cloned DOM via `clone`
  3167. * </pre>
  3168. *
  3169. *
  3170. * For information on how the compiler works, see the
  3171. * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
  3172. */
  3173. /**
  3174. * @ngdoc service
  3175. * @name ng.$compileProvider
  3176. * @function
  3177. *
  3178. * @description
  3179. */
  3180. /**
  3181. * @ngdoc function
  3182. * @name ng.$compileProvider#directive
  3183. * @methodOf ng.$compileProvider
  3184. * @function
  3185. *
  3186. * @description
  3187. * Register a new directive with compiler
  3188. *
  3189. * @param {string} name name of the directive.
  3190. * @param {function} directiveFactory An injectable directive factory function.
  3191. * @returns {ng.$compileProvider} Self for chaining.
  3192. */
  3193. $CompileProvider.$inject = ['$provide'];
  3194. function $CompileProvider($provide) {
  3195. var hasDirectives = {},
  3196. Suffix = 'Directive',
  3197. COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
  3198. CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
  3199. MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
  3200. /**
  3201. * @ngdoc function
  3202. * @name ng.$compileProvider.directive
  3203. * @methodOf ng.$compileProvider
  3204. * @function
  3205. *
  3206. * @description
  3207. * Register directives with the compiler.
  3208. *
  3209. * @param {string} name Name of the directive in camel-case. (ie <code>ngBind</code> which will match as
  3210. * <code>ng-bind</code>).
  3211. * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more
  3212. * info.
  3213. */
  3214. this.directive = function registerDirective(name, directiveFactory) {
  3215. if (isString(name)) {
  3216. assertArg(directiveFactory, 'directive');
  3217. if (!hasDirectives.hasOwnProperty(name)) {
  3218. hasDirectives[name] = [];
  3219. $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
  3220. function($injector, $exceptionHandler) {
  3221. var directives = [];
  3222. forEach(hasDirectives[name], function(directiveFactory) {
  3223. try {
  3224. var directive = $injector.invoke(directiveFactory);
  3225. if (isFunction(directive)) {
  3226. directive = { compile: valueFn(directive) };
  3227. } else if (!directive.compile && directive.link) {
  3228. directive.compile = valueFn(directive.link);
  3229. }
  3230. directive.priority = directive.priority || 0;
  3231. directive.name = directive.name || name;
  3232. directive.require = directive.require || (directive.controller && directive.name);
  3233. directive.restrict = directive.restrict || 'A';
  3234. directives.push(directive);
  3235. } catch (e) {
  3236. $exceptionHandler(e);
  3237. }
  3238. });
  3239. return directives;
  3240. }]);
  3241. }
  3242. hasDirectives[name].push(directiveFactory);
  3243. } else {
  3244. forEach(name, reverseParams(registerDirective));
  3245. }
  3246. return this;
  3247. };
  3248. this.$get = [
  3249. '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
  3250. '$controller', '$rootScope',
  3251. function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
  3252. $controller, $rootScope) {
  3253. var Attributes = function(element, attr) {
  3254. this.$$element = element;
  3255. this.$attr = attr || {};
  3256. };
  3257. Attributes.prototype = {
  3258. $normalize: directiveNormalize,
  3259. /**
  3260. * Set a normalized attribute on the element in a way such that all directives
  3261. * can share the attribute. This function properly handles boolean attributes.
  3262. * @param {string} key Normalized key. (ie ngAttribute)
  3263. * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
  3264. * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
  3265. * Defaults to true.
  3266. * @param {string=} attrName Optional none normalized name. Defaults to key.
  3267. */
  3268. $set: function(key, value, writeAttr, attrName) {
  3269. var booleanKey = getBooleanAttrName(this.$$element[0], key),
  3270. $$observers = this.$$observers;
  3271. if (booleanKey) {
  3272. this.$$element.prop(key, value);
  3273. attrName = booleanKey;
  3274. }
  3275. this[key] = value;
  3276. // translate normalized key to actual key
  3277. if (attrName) {
  3278. this.$attr[key] = attrName;
  3279. } else {
  3280. attrName = this.$attr[key];
  3281. if (!attrName) {
  3282. this.$attr[key] = attrName = snake_case(key, '-');
  3283. }
  3284. }
  3285. if (writeAttr !== false) {
  3286. if (value === null || value === undefined) {
  3287. this.$$element.removeAttr(attrName);
  3288. } else {
  3289. this.$$element.attr(attrName, value);
  3290. }
  3291. }
  3292. // fire observers
  3293. $$observers && forEach($$observers[key], function(fn) {
  3294. try {
  3295. fn(value);
  3296. } catch (e) {
  3297. $exceptionHandler(e);
  3298. }
  3299. });
  3300. },
  3301. /**
  3302. * Observe an interpolated attribute.
  3303. * The observer will never be called, if given attribute is not interpolated.
  3304. *
  3305. * @param {string} key Normalized key. (ie ngAttribute) .
  3306. * @param {function(*)} fn Function that will be called whenever the attribute value changes.
  3307. * @returns {function(*)} the `fn` Function passed in.
  3308. */
  3309. $observe: function(key, fn) {
  3310. var attrs = this,
  3311. $$observers = (attrs.$$observers || (attrs.$$observers = {})),
  3312. listeners = ($$observers[key] || ($$observers[key] = []));
  3313. listeners.push(fn);
  3314. $rootScope.$evalAsync(function() {
  3315. if (!listeners.$$inter) {
  3316. // no one registered attribute interpolation function, so lets call it manually
  3317. fn(attrs[key]);
  3318. }
  3319. });
  3320. return fn;
  3321. }
  3322. };
  3323. return compile;
  3324. //================================
  3325. function compile($compileNode, transcludeFn, maxPriority) {
  3326. if (!($compileNode instanceof jqLite)) {
  3327. // jquery always rewraps, where as we need to preserve the original selector so that we can modify it.
  3328. $compileNode = jqLite($compileNode);
  3329. }
  3330. // We can not compile top level text elements since text nodes can be merged and we will
  3331. // not be able to attach scope data to them, so we will wrap them in <span>
  3332. forEach($compileNode, function(node, index){
  3333. if (node.nodeType == 3 /* text node */) {
  3334. $compileNode[index] = jqLite(node).wrap('<span>').parent()[0];
  3335. }
  3336. });
  3337. var compositeLinkFn = compileNodes($compileNode, transcludeFn, $compileNode, maxPriority);
  3338. return function(scope, cloneConnectFn){
  3339. assertArg(scope, 'scope');
  3340. // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
  3341. // and sometimes changes the structure of the DOM.
  3342. var $linkNode = cloneConnectFn
  3343. ? JQLitePrototype.clone.call($compileNode) // IMPORTANT!!!
  3344. : $compileNode;
  3345. $linkNode.data('$scope', scope);
  3346. safeAddClass($linkNode, 'ng-scope');
  3347. if (cloneConnectFn) cloneConnectFn($linkNode, scope);
  3348. if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
  3349. return $linkNode;
  3350. };
  3351. }
  3352. function wrongMode(localName, mode) {
  3353. throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
  3354. }
  3355. function safeAddClass($element, className) {
  3356. try {
  3357. $element.addClass(className);
  3358. } catch(e) {
  3359. // ignore, since it means that we are trying to set class on
  3360. // SVG element, where class name is read-only.
  3361. }
  3362. }
  3363. /**
  3364. * Compile function matches each node in nodeList against the directives. Once all directives
  3365. * for a particular node are collected their compile functions are executed. The compile
  3366. * functions return values - the linking functions - are combined into a composite linking
  3367. * function, which is the a linking function for the node.
  3368. *
  3369. * @param {NodeList} nodeList an array of nodes to compile
  3370. * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
  3371. * scope argument is auto-generated to the new child of the transcluded parent scope.
  3372. * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
  3373. * rootElement must be set the jqLite collection of the compile root. This is
  3374. * needed so that the jqLite collection items can be replaced with widgets.
  3375. * @param {number=} max directive priority
  3376. * @returns {?function} A composite linking function of all of the matched directives or null.
  3377. */
  3378. function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority) {
  3379. var linkFns = [],
  3380. nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
  3381. for(var i = 0; i < nodeList.length; i++) {
  3382. attrs = new Attributes();
  3383. // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
  3384. directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
  3385. nodeLinkFn = (directives.length)
  3386. ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
  3387. : null;
  3388. childLinkFn = (nodeLinkFn && nodeLinkFn.terminal)
  3389. ? null
  3390. : compileNodes(nodeList[i].childNodes,
  3391. nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
  3392. linkFns.push(nodeLinkFn);
  3393. linkFns.push(childLinkFn);
  3394. linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
  3395. }
  3396. // return a linking function if we have found anything, null otherwise
  3397. return linkFnFound ? compositeLinkFn : null;
  3398. function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
  3399. var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn;
  3400. for(var i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
  3401. node = nodeList[n];
  3402. nodeLinkFn = linkFns[i++];
  3403. childLinkFn = linkFns[i++];
  3404. if (nodeLinkFn) {
  3405. if (nodeLinkFn.scope) {
  3406. childScope = scope.$new(isObject(nodeLinkFn.scope));
  3407. jqLite(node).data('$scope', childScope);
  3408. } else {
  3409. childScope = scope;
  3410. }
  3411. childTranscludeFn = nodeLinkFn.transclude;
  3412. if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
  3413. nodeLinkFn(childLinkFn, childScope, node, $rootElement,
  3414. (function(transcludeFn) {
  3415. return function(cloneFn) {
  3416. var transcludeScope = scope.$new();
  3417. return transcludeFn(transcludeScope, cloneFn).
  3418. bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
  3419. };
  3420. })(childTranscludeFn || transcludeFn)
  3421. );
  3422. } else {
  3423. nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
  3424. }
  3425. } else if (childLinkFn) {
  3426. childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
  3427. }
  3428. }
  3429. }
  3430. }
  3431. /**
  3432. * Looks for directives on the given node ands them to the directive collection which is sorted.
  3433. *
  3434. * @param node node to search
  3435. * @param directives an array to which the directives are added to. This array is sorted before
  3436. * the function returns.
  3437. * @param attrs the shared attrs object which is used to populate the normalized attributes.
  3438. * @param {number=} max directive priority
  3439. */
  3440. function collectDirectives(node, directives, attrs, maxPriority) {
  3441. var nodeType = node.nodeType,
  3442. attrsMap = attrs.$attr,
  3443. match,
  3444. className;
  3445. switch(nodeType) {
  3446. case 1: /* Element */
  3447. // use the node name: <directive>
  3448. addDirective(directives,
  3449. directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
  3450. // iterate over the attributes
  3451. for (var attr, name, nName, value, nAttrs = node.attributes,
  3452. j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
  3453. attr = nAttrs[j];
  3454. if (attr.specified) {
  3455. name = attr.name;
  3456. nName = directiveNormalize(name.toLowerCase());
  3457. attrsMap[nName] = name;
  3458. attrs[nName] = value = trim((msie && name == 'href')
  3459. ? decodeURIComponent(node.getAttribute(name, 2))
  3460. : attr.value);
  3461. if (getBooleanAttrName(node, nName)) {
  3462. attrs[nName] = true; // presence means true
  3463. }
  3464. addAttrInterpolateDirective(node, directives, value, nName);
  3465. addDirective(directives, nName, 'A', maxPriority);
  3466. }
  3467. }
  3468. // use class as directive
  3469. className = node.className;
  3470. if (isString(className)) {
  3471. while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
  3472. nName = directiveNormalize(match[2]);
  3473. if (addDirective(directives, nName, 'C', maxPriority)) {
  3474. attrs[nName] = trim(match[3]);
  3475. }
  3476. className = className.substr(match.index + match[0].length);
  3477. }
  3478. }
  3479. break;
  3480. case 3: /* Text Node */
  3481. addTextInterpolateDirective(directives, node.nodeValue);
  3482. break;
  3483. case 8: /* Comment */
  3484. try {
  3485. match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
  3486. if (match) {
  3487. nName = directiveNormalize(match[1]);
  3488. if (addDirective(directives, nName, 'M', maxPriority)) {
  3489. attrs[nName] = trim(match[2]);
  3490. }
  3491. }
  3492. } catch (e) {
  3493. // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
  3494. // Just ignore it and continue. (Can't seem to reproduce in test case.)
  3495. }
  3496. break;
  3497. }
  3498. directives.sort(byPriority);
  3499. return directives;
  3500. }
  3501. /**
  3502. * Once the directives have been collected their compile functions is executed. This method
  3503. * is responsible for inlining directive templates as well as terminating the application
  3504. * of the directives if the terminal directive has been reached..
  3505. *
  3506. * @param {Array} directives Array of collected directives to execute their compile function.
  3507. * this needs to be pre-sorted by priority order.
  3508. * @param {Node} compileNode The raw DOM node to apply the compile functions to
  3509. * @param {Object} templateAttrs The shared attribute function
  3510. * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
  3511. * scope argument is auto-generated to the new child of the transcluded parent scope.
  3512. * @param {DOMElement} $rootElement If we are working on the root of the compile tree then this
  3513. * argument has the root jqLite array so that we can replace widgets on it.
  3514. * @returns linkFn
  3515. */
  3516. function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, $rootElement) {
  3517. var terminalPriority = -Number.MAX_VALUE,
  3518. preLinkFns = [],
  3519. postLinkFns = [],
  3520. newScopeDirective = null,
  3521. newIsolatedScopeDirective = null,
  3522. templateDirective = null,
  3523. $compileNode = templateAttrs.$$element = jqLite(compileNode),
  3524. directive,
  3525. directiveName,
  3526. $template,
  3527. transcludeDirective,
  3528. childTranscludeFn = transcludeFn,
  3529. controllerDirectives,
  3530. linkFn,
  3531. directiveValue;
  3532. // executes all directives on the current element
  3533. for(var i = 0, ii = directives.length; i < ii; i++) {
  3534. directive = directives[i];
  3535. $template = undefined;
  3536. if (terminalPriority > directive.priority) {
  3537. break; // prevent further processing of directives
  3538. }
  3539. if (directiveValue = directive.scope) {
  3540. assertNoDuplicate('isolated scope', newIsolatedScopeDirective, directive, $compileNode);
  3541. if (isObject(directiveValue)) {
  3542. safeAddClass($compileNode, 'ng-isolate-scope');
  3543. newIsolatedScopeDirective = directive;
  3544. }
  3545. safeAddClass($compileNode, 'ng-scope');
  3546. newScopeDirective = newScopeDirective || directive;
  3547. }
  3548. directiveName = directive.name;
  3549. if (directiveValue = directive.controller) {
  3550. controllerDirectives = controllerDirectives || {};
  3551. assertNoDuplicate("'" + directiveName + "' controller",
  3552. controllerDirectives[directiveName], directive, $compileNode);
  3553. controllerDirectives[directiveName] = directive;
  3554. }
  3555. if (directiveValue = directive.transclude) {
  3556. assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
  3557. transcludeDirective = directive;
  3558. terminalPriority = directive.priority;
  3559. if (directiveValue == 'element') {
  3560. $template = jqLite(compileNode);
  3561. $compileNode = templateAttrs.$$element =
  3562. jqLite('<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->');
  3563. compileNode = $compileNode[0];
  3564. replaceWith($rootElement, jqLite($template[0]), compileNode);
  3565. childTranscludeFn = compile($template, transcludeFn, terminalPriority);
  3566. } else {
  3567. $template = jqLite(JQLiteClone(compileNode)).contents();
  3568. $compileNode.html(''); // clear contents
  3569. childTranscludeFn = compile($template, transcludeFn);
  3570. }
  3571. }
  3572. if (directiveValue = directive.template) {
  3573. assertNoDuplicate('template', templateDirective, directive, $compileNode);
  3574. templateDirective = directive;
  3575. $template = jqLite('<div>' + trim(directiveValue) + '</div>').contents();
  3576. compileNode = $template[0];
  3577. if (directive.replace) {
  3578. if ($template.length != 1 || compileNode.nodeType !== 1) {
  3579. throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue);
  3580. }
  3581. replaceWith($rootElement, $compileNode, compileNode);
  3582. var newTemplateAttrs = {$attr: {}};
  3583. // combine directives from the original node and from the template:
  3584. // - take the array of directives for this element
  3585. // - split it into two parts, those that were already applied and those that weren't
  3586. // - collect directives from the template, add them to the second group and sort them
  3587. // - append the second group with new directives to the first group
  3588. directives = directives.concat(
  3589. collectDirectives(
  3590. compileNode,
  3591. directives.splice(i + 1, directives.length - (i + 1)),
  3592. newTemplateAttrs
  3593. )
  3594. );
  3595. mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
  3596. ii = directives.length;
  3597. } else {
  3598. $compileNode.html(directiveValue);
  3599. }
  3600. }
  3601. if (directive.templateUrl) {
  3602. assertNoDuplicate('template', templateDirective, directive, $compileNode);
  3603. templateDirective = directive;
  3604. nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i),
  3605. nodeLinkFn, $compileNode, templateAttrs, $rootElement, directive.replace,
  3606. childTranscludeFn);
  3607. ii = directives.length;
  3608. } else if (directive.compile) {
  3609. try {
  3610. linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
  3611. if (isFunction(linkFn)) {
  3612. addLinkFns(null, linkFn);
  3613. } else if (linkFn) {
  3614. addLinkFns(linkFn.pre, linkFn.post);
  3615. }
  3616. } catch (e) {
  3617. $exceptionHandler(e, startingTag($compileNode));
  3618. }
  3619. }
  3620. if (directive.terminal) {
  3621. nodeLinkFn.terminal = true;
  3622. terminalPriority = Math.max(terminalPriority, directive.priority);
  3623. }
  3624. }
  3625. nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
  3626. nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
  3627. // might be normal or delayed nodeLinkFn depending on if templateUrl is present
  3628. return nodeLinkFn;
  3629. ////////////////////
  3630. function addLinkFns(pre, post) {
  3631. if (pre) {
  3632. pre.require = directive.require;
  3633. preLinkFns.push(pre);
  3634. }
  3635. if (post) {
  3636. post.require = directive.require;
  3637. postLinkFns.push(post);
  3638. }
  3639. }
  3640. function getControllers(require, $element) {
  3641. var value, retrievalMethod = 'data', optional = false;
  3642. if (isString(require)) {
  3643. while((value = require.charAt(0)) == '^' || value == '?') {
  3644. require = require.substr(1);
  3645. if (value == '^') {
  3646. retrievalMethod = 'inheritedData';
  3647. }
  3648. optional = optional || value == '?';
  3649. }
  3650. value = $element[retrievalMethod]('$' + require + 'Controller');
  3651. if (!value && !optional) {
  3652. throw Error("No controller: " + require);
  3653. }
  3654. return value;
  3655. } else if (isArray(require)) {
  3656. value = [];
  3657. forEach(require, function(require) {
  3658. value.push(getControllers(require, $element));
  3659. });
  3660. }
  3661. return value;
  3662. }
  3663. function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
  3664. var attrs, $element, i, ii, linkFn, controller;
  3665. if (compileNode === linkNode) {
  3666. attrs = templateAttrs;
  3667. } else {
  3668. attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
  3669. }
  3670. $element = attrs.$$element;
  3671. if (newScopeDirective && isObject(newScopeDirective.scope)) {
  3672. var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
  3673. var parentScope = scope.$parent || scope;
  3674. forEach(newScopeDirective.scope, function(definiton, scopeName) {
  3675. var match = definiton.match(LOCAL_REGEXP) || [],
  3676. attrName = match[2]|| scopeName,
  3677. mode = match[1], // @, =, or &
  3678. lastValue,
  3679. parentGet, parentSet;
  3680. switch (mode) {
  3681. case '@': {
  3682. attrs.$observe(attrName, function(value) {
  3683. scope[scopeName] = value;
  3684. });
  3685. attrs.$$observers[attrName].$$scope = parentScope;
  3686. break;
  3687. }
  3688. case '=': {
  3689. parentGet = $parse(attrs[attrName]);
  3690. parentSet = parentGet.assign || function() {
  3691. // reset the change, or we will throw this exception on every $digest
  3692. lastValue = scope[scopeName] = parentGet(parentScope);
  3693. throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
  3694. ' (directive: ' + newScopeDirective.name + ')');
  3695. };
  3696. lastValue = scope[scopeName] = parentGet(parentScope);
  3697. scope.$watch(function() {
  3698. var parentValue = parentGet(parentScope);
  3699. if (parentValue !== scope[scopeName]) {
  3700. // we are out of sync and need to copy
  3701. if (parentValue !== lastValue) {
  3702. // parent changed and it has precedence
  3703. lastValue = scope[scopeName] = parentValue;
  3704. } else {
  3705. // if the parent can be assigned then do so
  3706. parentSet(parentScope, lastValue = scope[scopeName]);
  3707. }
  3708. }
  3709. return parentValue;
  3710. });
  3711. break;
  3712. }
  3713. case '&': {
  3714. parentGet = $parse(attrs[attrName]);
  3715. scope[scopeName] = function(locals) {
  3716. return parentGet(parentScope, locals);
  3717. }
  3718. break;
  3719. }
  3720. default: {
  3721. throw Error('Invalid isolate scope definition for directive ' +
  3722. newScopeDirective.name + ': ' + definiton);
  3723. }
  3724. }
  3725. });
  3726. }
  3727. if (controllerDirectives) {
  3728. forEach(controllerDirectives, function(directive) {
  3729. var locals = {
  3730. $scope: scope,
  3731. $element: $element,
  3732. $attrs: attrs,
  3733. $transclude: boundTranscludeFn
  3734. };
  3735. controller = directive.controller;
  3736. if (controller == '@') {
  3737. controller = attrs[directive.name];
  3738. }
  3739. $element.data(
  3740. '$' + directive.name + 'Controller',
  3741. $controller(controller, locals));
  3742. });
  3743. }
  3744. // PRELINKING
  3745. for(i = 0, ii = preLinkFns.length; i < ii; i++) {
  3746. try {
  3747. linkFn = preLinkFns[i];
  3748. linkFn(scope, $element, attrs,
  3749. linkFn.require && getControllers(linkFn.require, $element));
  3750. } catch (e) {
  3751. $exceptionHandler(e, startingTag($element));
  3752. }
  3753. }
  3754. // RECURSION
  3755. childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
  3756. // POSTLINKING
  3757. for(i = 0, ii = postLinkFns.length; i < ii; i++) {
  3758. try {
  3759. linkFn = postLinkFns[i];
  3760. linkFn(scope, $element, attrs,
  3761. linkFn.require && getControllers(linkFn.require, $element));
  3762. } catch (e) {
  3763. $exceptionHandler(e, startingTag($element));
  3764. }
  3765. }
  3766. }
  3767. }
  3768. /**
  3769. * looks up the directive and decorates it with exception handling and proper parameters. We
  3770. * call this the boundDirective.
  3771. *
  3772. * @param {string} name name of the directive to look up.
  3773. * @param {string} location The directive must be found in specific format.
  3774. * String containing any of theses characters:
  3775. *
  3776. * * `E`: element name
  3777. * * `A': attribute
  3778. * * `C`: class
  3779. * * `M`: comment
  3780. * @returns true if directive was added.
  3781. */
  3782. function addDirective(tDirectives, name, location, maxPriority) {
  3783. var match = false;
  3784. if (hasDirectives.hasOwnProperty(name)) {
  3785. for(var directive, directives = $injector.get(name + Suffix),
  3786. i = 0, ii = directives.length; i<ii; i++) {
  3787. try {
  3788. directive = directives[i];
  3789. if ( (maxPriority === undefined || maxPriority > directive.priority) &&
  3790. directive.restrict.indexOf(location) != -1) {
  3791. tDirectives.push(directive);
  3792. match = true;
  3793. }
  3794. } catch(e) { $exceptionHandler(e); }
  3795. }
  3796. }
  3797. return match;
  3798. }
  3799. /**
  3800. * When the element is replaced with HTML template then the new attributes
  3801. * on the template need to be merged with the existing attributes in the DOM.
  3802. * The desired effect is to have both of the attributes present.
  3803. *
  3804. * @param {object} dst destination attributes (original DOM)
  3805. * @param {object} src source attributes (from the directive template)
  3806. */
  3807. function mergeTemplateAttributes(dst, src) {
  3808. var srcAttr = src.$attr,
  3809. dstAttr = dst.$attr,
  3810. $element = dst.$$element;
  3811. // reapply the old attributes to the new element
  3812. forEach(dst, function(value, key) {
  3813. if (key.charAt(0) != '$') {
  3814. if (src[key]) {
  3815. value += (key === 'style' ? ';' : ' ') + src[key];
  3816. }
  3817. dst.$set(key, value, true, srcAttr[key]);
  3818. }
  3819. });
  3820. // copy the new attributes on the old attrs object
  3821. forEach(src, function(value, key) {
  3822. if (key == 'class') {
  3823. safeAddClass($element, value);
  3824. dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
  3825. } else if (key == 'style') {
  3826. $element.attr('style', $element.attr('style') + ';' + value);
  3827. } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
  3828. dst[key] = value;
  3829. dstAttr[key] = srcAttr[key];
  3830. }
  3831. });
  3832. }
  3833. function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
  3834. $rootElement, replace, childTranscludeFn) {
  3835. var linkQueue = [],
  3836. afterTemplateNodeLinkFn,
  3837. afterTemplateChildLinkFn,
  3838. beforeTemplateCompileNode = $compileNode[0],
  3839. origAsyncDirective = directives.shift(),
  3840. // The fact that we have to copy and patch the directive seems wrong!
  3841. derivedSyncDirective = extend({}, origAsyncDirective, {
  3842. controller: null, templateUrl: null, transclude: null
  3843. });
  3844. $compileNode.html('');
  3845. $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}).
  3846. success(function(content) {
  3847. var compileNode, tempTemplateAttrs, $template;
  3848. if (replace) {
  3849. $template = jqLite('<div>' + trim(content) + '</div>').contents();
  3850. compileNode = $template[0];
  3851. if ($template.length != 1 || compileNode.nodeType !== 1) {
  3852. throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content);
  3853. }
  3854. tempTemplateAttrs = {$attr: {}};
  3855. replaceWith($rootElement, $compileNode, compileNode);
  3856. collectDirectives(compileNode, directives, tempTemplateAttrs);
  3857. mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
  3858. } else {
  3859. compileNode = beforeTemplateCompileNode;
  3860. $compileNode.html(content);
  3861. }
  3862. directives.unshift(derivedSyncDirective);
  3863. afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
  3864. afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
  3865. while(linkQueue.length) {
  3866. var controller = linkQueue.pop(),
  3867. linkRootElement = linkQueue.pop(),
  3868. beforeTemplateLinkNode = linkQueue.pop(),
  3869. scope = linkQueue.pop(),
  3870. linkNode = compileNode;
  3871. if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
  3872. // it was cloned therefore we have to clone as well.
  3873. linkNode = JQLiteClone(compileNode);
  3874. replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
  3875. }
  3876. afterTemplateNodeLinkFn(function() {
  3877. beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
  3878. }, scope, linkNode, $rootElement, controller);
  3879. }
  3880. linkQueue = null;
  3881. }).
  3882. error(function(response, code, headers, config) {
  3883. throw Error('Failed to load template: ' + config.url);
  3884. });
  3885. return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
  3886. if (linkQueue) {
  3887. linkQueue.push(scope);
  3888. linkQueue.push(node);
  3889. linkQueue.push(rootElement);
  3890. linkQueue.push(controller);
  3891. } else {
  3892. afterTemplateNodeLinkFn(function() {
  3893. beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
  3894. }, scope, node, rootElement, controller);
  3895. }
  3896. };
  3897. }
  3898. /**
  3899. * Sorting function for bound directives.
  3900. */
  3901. function byPriority(a, b) {
  3902. return b.priority - a.priority;
  3903. }
  3904. function assertNoDuplicate(what, previousDirective, directive, element) {
  3905. if (previousDirective) {
  3906. throw Error('Multiple directives [' + previousDirective.name + ', ' +
  3907. directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
  3908. }
  3909. }
  3910. function addTextInterpolateDirective(directives, text) {
  3911. var interpolateFn = $interpolate(text, true);
  3912. if (interpolateFn) {
  3913. directives.push({
  3914. priority: 0,
  3915. compile: valueFn(function(scope, node) {
  3916. var parent = node.parent(),
  3917. bindings = parent.data('$binding') || [];
  3918. bindings.push(interpolateFn);
  3919. safeAddClass(parent.data('$binding', bindings), 'ng-binding');
  3920. scope.$watch(interpolateFn, function(value) {
  3921. node[0].nodeValue = value;
  3922. });
  3923. })
  3924. });
  3925. }
  3926. }
  3927. function addAttrInterpolateDirective(node, directives, value, name) {
  3928. var interpolateFn = $interpolate(value, true);
  3929. // no interpolation found -> ignore
  3930. if (!interpolateFn) return;
  3931. directives.push({
  3932. priority: 100,
  3933. compile: valueFn(function(scope, element, attr) {
  3934. var $$observers = (attr.$$observers || (attr.$$observers = {}));
  3935. if (name === 'class') {
  3936. // we need to interpolate classes again, in the case the element was replaced
  3937. // and therefore the two class attrs got merged - we want to interpolate the result
  3938. interpolateFn = $interpolate(attr[name], true);
  3939. }
  3940. attr[name] = undefined;
  3941. ($$observers[name] || ($$observers[name] = [])).$$inter = true;
  3942. (attr.$$observers && attr.$$observers[name].$$scope || scope).
  3943. $watch(interpolateFn, function(value) {
  3944. attr.$set(name, value);
  3945. });
  3946. })
  3947. });
  3948. }
  3949. /**
  3950. * This is a special jqLite.replaceWith, which can replace items which
  3951. * have no parents, provided that the containing jqLite collection is provided.
  3952. *
  3953. * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
  3954. * in the root of the tree.
  3955. * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell,
  3956. * but replace its DOM node reference.
  3957. * @param {Node} newNode The new DOM node.
  3958. */
  3959. function replaceWith($rootElement, $element, newNode) {
  3960. var oldNode = $element[0],
  3961. parent = oldNode.parentNode,
  3962. i, ii;
  3963. if ($rootElement) {
  3964. for(i = 0, ii = $rootElement.length; i < ii; i++) {
  3965. if ($rootElement[i] == oldNode) {
  3966. $rootElement[i] = newNode;
  3967. break;
  3968. }
  3969. }
  3970. }
  3971. if (parent) {
  3972. parent.replaceChild(newNode, oldNode);
  3973. }
  3974. newNode[jqLite.expando] = oldNode[jqLite.expando];
  3975. $element[0] = newNode;
  3976. }
  3977. }];
  3978. }
  3979. var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
  3980. /**
  3981. * Converts all accepted directives format into proper directive name.
  3982. * All of these will become 'myDirective':
  3983. * my:DiRective
  3984. * my-directive
  3985. * x-my-directive
  3986. * data-my:directive
  3987. *
  3988. * Also there is special case for Moz prefix starting with upper case letter.
  3989. * @param name Name to normalize
  3990. */
  3991. function directiveNormalize(name) {
  3992. return camelCase(name.replace(PREFIX_REGEXP, ''));
  3993. }
  3994. /**
  3995. * @ngdoc object
  3996. * @name ng.$compile.directive.Attributes
  3997. * @description
  3998. *
  3999. * A shared object between directive compile / linking functions which contains normalized DOM element
  4000. * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
  4001. * since all of these are treated as equivalent in Angular:
  4002. *
  4003. * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
  4004. */
  4005. /**
  4006. * @ngdoc property
  4007. * @name ng.$compile.directive.Attributes#$attr
  4008. * @propertyOf ng.$compile.directive.Attributes
  4009. * @returns {object} A map of DOM element attribute names to the normalized name. This is
  4010. * needed to do reverse lookup from normalized name back to actual name.
  4011. */
  4012. /**
  4013. * @ngdoc function
  4014. * @name ng.$compile.directive.Attributes#$set
  4015. * @methodOf ng.$compile.directive.Attributes
  4016. * @function
  4017. *
  4018. * @description
  4019. * Set DOM element attribute value.
  4020. *
  4021. *
  4022. * @param {string} name Normalized element attribute name of the property to modify. The name is
  4023. * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
  4024. * property to the original name.
  4025. * @param {string} value Value to set the attribute to.
  4026. */
  4027. /**
  4028. * Closure compiler type information
  4029. */
  4030. function nodesetLinkingFn(
  4031. /* angular.Scope */ scope,
  4032. /* NodeList */ nodeList,
  4033. /* Element */ rootElement,
  4034. /* function(Function) */ boundTranscludeFn
  4035. ){}
  4036. function directiveLinkingFn(
  4037. /* nodesetLinkingFn */ nodesetLinkingFn,
  4038. /* angular.Scope */ scope,
  4039. /* Node */ node,
  4040. /* Element */ rootElement,
  4041. /* function(Function) */ boundTranscludeFn
  4042. ){}
  4043. /**
  4044. * @ngdoc object
  4045. * @name ng.$controllerProvider
  4046. * @description
  4047. * The {@link ng.$controller $controller service} is used by Angular to create new
  4048. * controllers.
  4049. *
  4050. * This provider allows controller registration via the
  4051. * {@link ng.$controllerProvider#register register} method.
  4052. */
  4053. function $ControllerProvider() {
  4054. var controllers = {};
  4055. /**
  4056. * @ngdoc function
  4057. * @name ng.$controllerProvider#register
  4058. * @methodOf ng.$controllerProvider
  4059. * @param {string} name Controller name
  4060. * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
  4061. * annotations in the array notation).
  4062. */
  4063. this.register = function(name, constructor) {
  4064. if (isObject(name)) {
  4065. extend(controllers, name)
  4066. } else {
  4067. controllers[name] = constructor;
  4068. }
  4069. };
  4070. this.$get = ['$injector', '$window', function($injector, $window) {
  4071. /**
  4072. * @ngdoc function
  4073. * @name ng.$controller
  4074. * @requires $injector
  4075. *
  4076. * @param {Function|string} constructor If called with a function then it's considered to be the
  4077. * controller constructor function. Otherwise it's considered to be a string which is used
  4078. * to retrieve the controller constructor using the following steps:
  4079. *
  4080. * * check if a controller with given name is registered via `$controllerProvider`
  4081. * * check if evaluating the string on the current scope returns a constructor
  4082. * * check `window[constructor]` on the global `window` object
  4083. *
  4084. * @param {Object} locals Injection locals for Controller.
  4085. * @return {Object} Instance of given controller.
  4086. *
  4087. * @description
  4088. * `$controller` service is responsible for instantiating controllers.
  4089. *
  4090. * It's just simple call to {@link AUTO.$injector $injector}, but extracted into
  4091. * a service, so that one can override this service with {@link https://gist.github.com/1649788
  4092. * BC version}.
  4093. */
  4094. return function(constructor, locals) {
  4095. if(isString(constructor)) {
  4096. var name = constructor;
  4097. constructor = controllers.hasOwnProperty(name)
  4098. ? controllers[name]
  4099. : getter(locals.$scope, name, true) || getter($window, name, true);
  4100. assertArgFn(constructor, name, true);
  4101. }
  4102. return $injector.instantiate(constructor, locals);
  4103. };
  4104. }];
  4105. }
  4106. /**
  4107. * @ngdoc object
  4108. * @name ng.$document
  4109. * @requires $window
  4110. *
  4111. * @description
  4112. * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
  4113. * element.
  4114. */
  4115. function $DocumentProvider(){
  4116. this.$get = ['$window', function(window){
  4117. return jqLite(window.document);
  4118. }];
  4119. }
  4120. /**
  4121. * @ngdoc function
  4122. * @name ng.$exceptionHandler
  4123. * @requires $log
  4124. *
  4125. * @description
  4126. * Any uncaught exception in angular expressions is delegated to this service.
  4127. * The default implementation simply delegates to `$log.error` which logs it into
  4128. * the browser console.
  4129. *
  4130. * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
  4131. * {@link ngMock.$exceptionHandler mock $exceptionHandler}
  4132. *
  4133. * @param {Error} exception Exception associated with the error.
  4134. * @param {string=} cause optional information about the context in which
  4135. * the error was thrown.
  4136. */
  4137. function $ExceptionHandlerProvider() {
  4138. this.$get = ['$log', function($log){
  4139. return function(exception, cause) {
  4140. $log.error.apply($log, arguments);
  4141. };
  4142. }];
  4143. }
  4144. /**
  4145. * @ngdoc function
  4146. * @name ng.$interpolateProvider
  4147. * @function
  4148. *
  4149. * @description
  4150. *
  4151. * Used for configuring the interpolation markup. Deafults to `{{` and `}}`.
  4152. */
  4153. function $InterpolateProvider() {
  4154. var startSymbol = '{{';
  4155. var endSymbol = '}}';
  4156. /**
  4157. * @ngdoc method
  4158. * @name ng.$interpolateProvider#startSymbol
  4159. * @methodOf ng.$interpolateProvider
  4160. * @description
  4161. * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
  4162. *
  4163. * @prop {string=} value new value to set the starting symbol to.
  4164. */
  4165. this.startSymbol = function(value){
  4166. if (value) {
  4167. startSymbol = value;
  4168. return this;
  4169. } else {
  4170. return startSymbol;
  4171. }
  4172. };
  4173. /**
  4174. * @ngdoc method
  4175. * @name ng.$interpolateProvider#endSymbol
  4176. * @methodOf ng.$interpolateProvider
  4177. * @description
  4178. * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
  4179. *
  4180. * @prop {string=} value new value to set the ending symbol to.
  4181. */
  4182. this.endSymbol = function(value){
  4183. if (value) {
  4184. endSymbol = value;
  4185. return this;
  4186. } else {
  4187. return startSymbol;
  4188. }
  4189. };
  4190. this.$get = ['$parse', function($parse) {
  4191. var startSymbolLength = startSymbol.length,
  4192. endSymbolLength = endSymbol.length;
  4193. /**
  4194. * @ngdoc function
  4195. * @name ng.$interpolate
  4196. * @function
  4197. *
  4198. * @requires $parse
  4199. *
  4200. * @description
  4201. *
  4202. * Compiles a string with markup into an interpolation function. This service is used by the
  4203. * HTML {@link ng.$compile $compile} service for data binding. See
  4204. * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
  4205. * interpolation markup.
  4206. *
  4207. *
  4208. <pre>
  4209. var $interpolate = ...; // injected
  4210. var exp = $interpolate('Hello {{name}}!');
  4211. expect(exp({name:'Angular'}).toEqual('Hello Angular!');
  4212. </pre>
  4213. *
  4214. *
  4215. * @param {string} text The text with markup to interpolate.
  4216. * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
  4217. * embedded expression in order to return an interpolation function. Strings with no
  4218. * embedded expression will return null for the interpolation function.
  4219. * @returns {function(context)} an interpolation function which is used to compute the interpolated
  4220. * string. The function has these parameters:
  4221. *
  4222. * * `context`: an object against which any expressions embedded in the strings are evaluated
  4223. * against.
  4224. *
  4225. */
  4226. return function(text, mustHaveExpression) {
  4227. var startIndex,
  4228. endIndex,
  4229. index = 0,
  4230. parts = [],
  4231. length = text.length,
  4232. hasInterpolation = false,
  4233. fn,
  4234. exp,
  4235. concat = [];
  4236. while(index < length) {
  4237. if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
  4238. ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
  4239. (index != startIndex) && parts.push(text.substring(index, startIndex));
  4240. parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
  4241. fn.exp = exp;
  4242. index = endIndex + endSymbolLength;
  4243. hasInterpolation = true;
  4244. } else {
  4245. // we did not find anything, so we have to add the remainder to the parts array
  4246. (index != length) && parts.push(text.substring(index));
  4247. index = length;
  4248. }
  4249. }
  4250. if (!(length = parts.length)) {
  4251. // we added, nothing, must have been an empty string.
  4252. parts.push('');
  4253. length = 1;
  4254. }
  4255. if (!mustHaveExpression || hasInterpolation) {
  4256. concat.length = length;
  4257. fn = function(context) {
  4258. for(var i = 0, ii = length, part; i<ii; i++) {
  4259. if (typeof (part = parts[i]) == 'function') {
  4260. part = part(context);
  4261. if (part == null || part == undefined) {
  4262. part = '';
  4263. } else if (typeof part != 'string') {
  4264. part = toJson(part);
  4265. }
  4266. }
  4267. concat[i] = part;
  4268. }
  4269. return concat.join('');
  4270. };
  4271. fn.exp = text;
  4272. fn.parts = parts;
  4273. return fn;
  4274. }
  4275. };
  4276. }];
  4277. }
  4278. var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
  4279. PATH_MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
  4280. HASH_MATCH = PATH_MATCH,
  4281. DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
  4282. /**
  4283. * Encode path using encodeUriSegment, ignoring forward slashes
  4284. *
  4285. * @param {string} path Path to encode
  4286. * @returns {string}
  4287. */
  4288. function encodePath(path) {
  4289. var segments = path.split('/'),
  4290. i = segments.length;
  4291. while (i--) {
  4292. segments[i] = encodeUriSegment(segments[i]);
  4293. }
  4294. return segments.join('/');
  4295. }
  4296. function stripHash(url) {
  4297. return url.split('#')[0];
  4298. }
  4299. function matchUrl(url, obj) {
  4300. var match = URL_MATCH.exec(url);
  4301. match = {
  4302. protocol: match[1],
  4303. host: match[3],
  4304. port: int(match[5]) || DEFAULT_PORTS[match[1]] || null,
  4305. path: match[6] || '/',
  4306. search: match[8],
  4307. hash: match[10]
  4308. };
  4309. if (obj) {
  4310. obj.$$protocol = match.protocol;
  4311. obj.$$host = match.host;
  4312. obj.$$port = match.port;
  4313. }
  4314. return match;
  4315. }
  4316. function composeProtocolHostPort(protocol, host, port) {
  4317. return protocol + '://' + host + (port == DEFAULT_PORTS[protocol] ? '' : ':' + port);
  4318. }
  4319. function pathPrefixFromBase(basePath) {
  4320. return basePath.substr(0, basePath.lastIndexOf('/'));
  4321. }
  4322. function convertToHtml5Url(url, basePath, hashPrefix) {
  4323. var match = matchUrl(url);
  4324. // already html5 url
  4325. if (decodeURIComponent(match.path) != basePath || isUndefined(match.hash) ||
  4326. match.hash.indexOf(hashPrefix) !== 0) {
  4327. return url;
  4328. // convert hashbang url -> html5 url
  4329. } else {
  4330. return composeProtocolHostPort(match.protocol, match.host, match.port) +
  4331. pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length);
  4332. }
  4333. }
  4334. function convertToHashbangUrl(url, basePath, hashPrefix) {
  4335. var match = matchUrl(url);
  4336. // already hashbang url
  4337. if (decodeURIComponent(match.path) == basePath) {
  4338. return url;
  4339. // convert html5 url -> hashbang url
  4340. } else {
  4341. var search = match.search && '?' + match.search || '',
  4342. hash = match.hash && '#' + match.hash || '',
  4343. pathPrefix = pathPrefixFromBase(basePath),
  4344. path = match.path.substr(pathPrefix.length);
  4345. if (match.path.indexOf(pathPrefix) !== 0) {
  4346. throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
  4347. }
  4348. return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath +
  4349. '#' + hashPrefix + path + search + hash;
  4350. }
  4351. }
  4352. /**
  4353. * LocationUrl represents an url
  4354. * This object is exposed as $location service when HTML5 mode is enabled and supported
  4355. *
  4356. * @constructor
  4357. * @param {string} url HTML5 url
  4358. * @param {string} pathPrefix
  4359. */
  4360. function LocationUrl(url, pathPrefix, appBaseUrl) {
  4361. pathPrefix = pathPrefix || '';
  4362. /**
  4363. * Parse given html5 (regular) url string into properties
  4364. * @param {string} newAbsoluteUrl HTML5 url
  4365. * @private
  4366. */
  4367. this.$$parse = function(newAbsoluteUrl) {
  4368. var match = matchUrl(newAbsoluteUrl, this);
  4369. if (match.path.indexOf(pathPrefix) !== 0) {
  4370. throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
  4371. }
  4372. this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
  4373. this.$$search = parseKeyValue(match.search);
  4374. this.$$hash = match.hash && decodeURIComponent(match.hash) || '';
  4375. this.$$compose();
  4376. };
  4377. /**
  4378. * Compose url and update `absUrl` property
  4379. * @private
  4380. */
  4381. this.$$compose = function() {
  4382. var search = toKeyValue(this.$$search),
  4383. hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
  4384. this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
  4385. this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
  4386. pathPrefix + this.$$url;
  4387. };
  4388. this.$$rewriteAppUrl = function(absoluteLinkUrl) {
  4389. if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
  4390. return absoluteLinkUrl;
  4391. }
  4392. }
  4393. this.$$parse(url);
  4394. }
  4395. /**
  4396. * LocationHashbangUrl represents url
  4397. * This object is exposed as $location service when html5 history api is disabled or not supported
  4398. *
  4399. * @constructor
  4400. * @param {string} url Legacy url
  4401. * @param {string} hashPrefix Prefix for hash part (containing path and search)
  4402. */
  4403. function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
  4404. var basePath;
  4405. /**
  4406. * Parse given hashbang url into properties
  4407. * @param {string} url Hashbang url
  4408. * @private
  4409. */
  4410. this.$$parse = function(url) {
  4411. var match = matchUrl(url, this);
  4412. if (match.hash && match.hash.indexOf(hashPrefix) !== 0) {
  4413. throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !');
  4414. }
  4415. basePath = match.path + (match.search ? '?' + match.search : '');
  4416. match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length));
  4417. if (match[1]) {
  4418. this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]);
  4419. } else {
  4420. this.$$path = '';
  4421. }
  4422. this.$$search = parseKeyValue(match[3]);
  4423. this.$$hash = match[5] && decodeURIComponent(match[5]) || '';
  4424. this.$$compose();
  4425. };
  4426. /**
  4427. * Compose hashbang url and update `absUrl` property
  4428. * @private
  4429. */
  4430. this.$$compose = function() {
  4431. var search = toKeyValue(this.$$search),
  4432. hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
  4433. this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
  4434. this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
  4435. basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
  4436. };
  4437. this.$$rewriteAppUrl = function(absoluteLinkUrl) {
  4438. if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
  4439. return absoluteLinkUrl;
  4440. }
  4441. }
  4442. this.$$parse(url);
  4443. }
  4444. LocationUrl.prototype = {
  4445. /**
  4446. * Has any change been replacing ?
  4447. * @private
  4448. */
  4449. $$replace: false,
  4450. /**
  4451. * @ngdoc method
  4452. * @name ng.$location#absUrl
  4453. * @methodOf ng.$location
  4454. *
  4455. * @description
  4456. * This method is getter only.
  4457. *
  4458. * Return full url representation with all segments encoded according to rules specified in
  4459. * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}.
  4460. *
  4461. * @return {string} full url
  4462. */
  4463. absUrl: locationGetter('$$absUrl'),
  4464. /**
  4465. * @ngdoc method
  4466. * @name ng.$location#url
  4467. * @methodOf ng.$location
  4468. *
  4469. * @description
  4470. * This method is getter / setter.
  4471. *
  4472. * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
  4473. *
  4474. * Change path, search and hash, when called with parameter and return `$location`.
  4475. *
  4476. * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
  4477. * @return {string} url
  4478. */
  4479. url: function(url, replace) {
  4480. if (isUndefined(url))
  4481. return this.$$url;
  4482. var match = PATH_MATCH.exec(url);
  4483. if (match[1]) this.path(decodeURIComponent(match[1]));
  4484. if (match[2] || match[1]) this.search(match[3] || '');
  4485. this.hash(match[5] || '', replace);
  4486. return this;
  4487. },
  4488. /**
  4489. * @ngdoc method
  4490. * @name ng.$location#protocol
  4491. * @methodOf ng.$location
  4492. *
  4493. * @description
  4494. * This method is getter only.
  4495. *
  4496. * Return protocol of current url.
  4497. *
  4498. * @return {string} protocol of current url
  4499. */
  4500. protocol: locationGetter('$$protocol'),
  4501. /**
  4502. * @ngdoc method
  4503. * @name ng.$location#host
  4504. * @methodOf ng.$location
  4505. *
  4506. * @description
  4507. * This method is getter only.
  4508. *
  4509. * Return host of current url.
  4510. *
  4511. * @return {string} host of current url.
  4512. */
  4513. host: locationGetter('$$host'),
  4514. /**
  4515. * @ngdoc method
  4516. * @name ng.$location#port
  4517. * @methodOf ng.$location
  4518. *
  4519. * @description
  4520. * This method is getter only.
  4521. *
  4522. * Return port of current url.
  4523. *
  4524. * @return {Number} port
  4525. */
  4526. port: locationGetter('$$port'),
  4527. /**
  4528. * @ngdoc method
  4529. * @name ng.$location#path
  4530. * @methodOf ng.$location
  4531. *
  4532. * @description
  4533. * This method is getter / setter.
  4534. *
  4535. * Return path of current url when called without any parameter.
  4536. *
  4537. * Change path when called with parameter and return `$location`.
  4538. *
  4539. * Note: Path should always begin with forward slash (/), this method will add the forward slash
  4540. * if it is missing.
  4541. *
  4542. * @param {string=} path New path
  4543. * @return {string} path
  4544. */
  4545. path: locationGetterSetter('$$path', function(path) {
  4546. return path.charAt(0) == '/' ? path : '/' + path;
  4547. }),
  4548. /**
  4549. * @ngdoc method
  4550. * @name ng.$location#search
  4551. * @methodOf ng.$location
  4552. *
  4553. * @description
  4554. * This method is getter / setter.
  4555. *
  4556. * Return search part (as object) of current url when called without any parameter.
  4557. *
  4558. * Change search part when called with parameter and return `$location`.
  4559. *
  4560. * @param {string|object<string,string>=} search New search params - string or hash object
  4561. * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a
  4562. * single search parameter. If the value is `null`, the parameter will be deleted.
  4563. *
  4564. * @return {string} search
  4565. */
  4566. search: function(search, paramValue) {
  4567. if (isUndefined(search))
  4568. return this.$$search;
  4569. if (isDefined(paramValue)) {
  4570. if (paramValue === null) {
  4571. delete this.$$search[search];
  4572. } else {
  4573. this.$$search[search] = paramValue;
  4574. }
  4575. } else {
  4576. this.$$search = isString(search) ? parseKeyValue(search) : search;
  4577. }
  4578. this.$$compose();
  4579. return this;
  4580. },
  4581. /**
  4582. * @ngdoc method
  4583. * @name ng.$location#hash
  4584. * @methodOf ng.$location
  4585. *
  4586. * @description
  4587. * This method is getter / setter.
  4588. *
  4589. * Return hash fragment when called without any parameter.
  4590. *
  4591. * Change hash fragment when called with parameter and return `$location`.
  4592. *
  4593. * @param {string=} hash New hash fragment
  4594. * @return {string} hash
  4595. */
  4596. hash: locationGetterSetter('$$hash', identity),
  4597. /**
  4598. * @ngdoc method
  4599. * @name ng.$location#replace
  4600. * @methodOf ng.$location
  4601. *
  4602. * @description
  4603. * If called, all changes to $location during current `$digest` will be replacing current history
  4604. * record, instead of adding new one.
  4605. */
  4606. replace: function() {
  4607. this.$$replace = true;
  4608. return this;
  4609. }
  4610. };
  4611. LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
  4612. function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
  4613. LocationHashbangUrl.apply(this, arguments);
  4614. this.$$rewriteAppUrl = function(absoluteLinkUrl) {
  4615. if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
  4616. return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
  4617. }
  4618. }
  4619. }
  4620. LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
  4621. function locationGetter(property) {
  4622. return function() {
  4623. return this[property];
  4624. };
  4625. }
  4626. function locationGetterSetter(property, preprocess) {
  4627. return function(value) {
  4628. if (isUndefined(value))
  4629. return this[property];
  4630. this[property] = preprocess(value);
  4631. this.$$compose();
  4632. return this;
  4633. };
  4634. }
  4635. /**
  4636. * @ngdoc object
  4637. * @name ng.$location
  4638. *
  4639. * @requires $browser
  4640. * @requires $sniffer
  4641. * @requires $rootElement
  4642. *
  4643. * @description
  4644. * The $location service parses the URL in the browser address bar (based on the
  4645. * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
  4646. * available to your application. Changes to the URL in the address bar are reflected into
  4647. * $location service and changes to $location are reflected into the browser address bar.
  4648. *
  4649. * **The $location service:**
  4650. *
  4651. * - Exposes the current URL in the browser address bar, so you can
  4652. * - Watch and observe the URL.
  4653. * - Change the URL.
  4654. * - Synchronizes the URL with the browser when the user
  4655. * - Changes the address bar.
  4656. * - Clicks the back or forward button (or clicks a History link).
  4657. * - Clicks on a link.
  4658. * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
  4659. *
  4660. * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
  4661. * Services: Using $location}
  4662. */
  4663. /**
  4664. * @ngdoc object
  4665. * @name ng.$locationProvider
  4666. * @description
  4667. * Use the `$locationProvider` to configure how the application deep linking paths are stored.
  4668. */
  4669. function $LocationProvider(){
  4670. var hashPrefix = '',
  4671. html5Mode = false;
  4672. /**
  4673. * @ngdoc property
  4674. * @name ng.$locationProvider#hashPrefix
  4675. * @methodOf ng.$locationProvider
  4676. * @description
  4677. * @param {string=} prefix Prefix for hash part (containing path and search)
  4678. * @returns {*} current value if used as getter or itself (chaining) if used as setter
  4679. */
  4680. this.hashPrefix = function(prefix) {
  4681. if (isDefined(prefix)) {
  4682. hashPrefix = prefix;
  4683. return this;
  4684. } else {
  4685. return hashPrefix;
  4686. }
  4687. };
  4688. /**
  4689. * @ngdoc property
  4690. * @name ng.$locationProvider#html5Mode
  4691. * @methodOf ng.$locationProvider
  4692. * @description
  4693. * @param {string=} mode Use HTML5 strategy if available.
  4694. * @returns {*} current value if used as getter or itself (chaining) if used as setter
  4695. */
  4696. this.html5Mode = function(mode) {
  4697. if (isDefined(mode)) {
  4698. html5Mode = mode;
  4699. return this;
  4700. } else {
  4701. return html5Mode;
  4702. }
  4703. };
  4704. this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
  4705. function( $rootScope, $browser, $sniffer, $rootElement) {
  4706. var $location,
  4707. basePath,
  4708. pathPrefix,
  4709. initUrl = $browser.url(),
  4710. initUrlParts = matchUrl(initUrl),
  4711. appBaseUrl;
  4712. if (html5Mode) {
  4713. basePath = $browser.baseHref() || '/';
  4714. pathPrefix = pathPrefixFromBase(basePath);
  4715. appBaseUrl =
  4716. composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
  4717. pathPrefix + '/';
  4718. if ($sniffer.history) {
  4719. $location = new LocationUrl(
  4720. convertToHtml5Url(initUrl, basePath, hashPrefix),
  4721. pathPrefix, appBaseUrl);
  4722. } else {
  4723. $location = new LocationHashbangInHtml5Url(
  4724. convertToHashbangUrl(initUrl, basePath, hashPrefix),
  4725. hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
  4726. }
  4727. } else {
  4728. appBaseUrl =
  4729. composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
  4730. (initUrlParts.path || '') +
  4731. (initUrlParts.search ? ('?' + initUrlParts.search) : '') +
  4732. '#' + hashPrefix + '/';
  4733. $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
  4734. }
  4735. $rootElement.bind('click', function(event) {
  4736. // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
  4737. // currently we open nice url link and redirect then
  4738. if (event.ctrlKey || event.metaKey || event.which == 2) return;
  4739. var elm = jqLite(event.target);
  4740. // traverse the DOM up to find first A tag
  4741. while (lowercase(elm[0].nodeName) !== 'a') {
  4742. // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
  4743. if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
  4744. }
  4745. var absHref = elm.prop('href'),
  4746. rewrittenUrl = $location.$$rewriteAppUrl(absHref);
  4747. if (absHref && !elm.attr('target') && rewrittenUrl) {
  4748. // update location manually
  4749. $location.$$parse(rewrittenUrl);
  4750. $rootScope.$apply();
  4751. event.preventDefault();
  4752. // hack to work around FF6 bug 684208 when scenario runner clicks on links
  4753. window.angular['ff-684208-preventDefault'] = true;
  4754. }
  4755. });
  4756. // rewrite hashbang url <> html5 url
  4757. if ($location.absUrl() != initUrl) {
  4758. $browser.url($location.absUrl(), true);
  4759. }
  4760. // update $location when $browser url changes
  4761. $browser.onUrlChange(function(newUrl) {
  4762. if ($location.absUrl() != newUrl) {
  4763. $rootScope.$evalAsync(function() {
  4764. var oldUrl = $location.absUrl();
  4765. $location.$$parse(newUrl);
  4766. afterLocationChange(oldUrl);
  4767. });
  4768. if (!$rootScope.$$phase) $rootScope.$digest();
  4769. }
  4770. });
  4771. // update browser
  4772. var changeCounter = 0;
  4773. $rootScope.$watch(function $locationWatch() {
  4774. var oldUrl = $browser.url();
  4775. if (!changeCounter || oldUrl != $location.absUrl()) {
  4776. changeCounter++;
  4777. $rootScope.$evalAsync(function() {
  4778. if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
  4779. defaultPrevented) {
  4780. $location.$$parse(oldUrl);
  4781. } else {
  4782. $browser.url($location.absUrl(), $location.$$replace);
  4783. $location.$$replace = false;
  4784. afterLocationChange(oldUrl);
  4785. }
  4786. });
  4787. }
  4788. return changeCounter;
  4789. });
  4790. return $location;
  4791. function afterLocationChange(oldUrl) {
  4792. $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
  4793. }
  4794. }];
  4795. }
  4796. /**
  4797. * @ngdoc object
  4798. * @name ng.$log
  4799. * @requires $window
  4800. *
  4801. * @description
  4802. * Simple service for logging. Default implementation writes the message
  4803. * into the browser's console (if present).
  4804. *
  4805. * The main purpose of this service is to simplify debugging and troubleshooting.
  4806. *
  4807. * @example
  4808. <doc:example>
  4809. <doc:source>
  4810. <script>
  4811. function LogCtrl($log) {
  4812. this.$log = $log;
  4813. this.message = 'Hello World!';
  4814. }
  4815. </script>
  4816. <div ng-controller="LogCtrl">
  4817. <p>Reload this page with open console, enter text and hit the log button...</p>
  4818. Message:
  4819. <input type="text" ng-model="message"/>
  4820. <button ng-click="$log.log(message)">log</button>
  4821. <button ng-click="$log.warn(message)">warn</button>
  4822. <button ng-click="$log.info(message)">info</button>
  4823. <button ng-click="$log.error(message)">error</button>
  4824. </div>
  4825. </doc:source>
  4826. <doc:scenario>
  4827. </doc:scenario>
  4828. </doc:example>
  4829. */
  4830. function $LogProvider(){
  4831. this.$get = ['$window', function($window){
  4832. return {
  4833. /**
  4834. * @ngdoc method
  4835. * @name ng.$log#log
  4836. * @methodOf ng.$log
  4837. *
  4838. * @description
  4839. * Write a log message
  4840. */
  4841. log: consoleLog('log'),
  4842. /**
  4843. * @ngdoc method
  4844. * @name ng.$log#warn
  4845. * @methodOf ng.$log
  4846. *
  4847. * @description
  4848. * Write a warning message
  4849. */
  4850. warn: consoleLog('warn'),
  4851. /**
  4852. * @ngdoc method
  4853. * @name ng.$log#info
  4854. * @methodOf ng.$log
  4855. *
  4856. * @description
  4857. * Write an information message
  4858. */
  4859. info: consoleLog('info'),
  4860. /**
  4861. * @ngdoc method
  4862. * @name ng.$log#error
  4863. * @methodOf ng.$log
  4864. *
  4865. * @description
  4866. * Write an error message
  4867. */
  4868. error: consoleLog('error')
  4869. };
  4870. function formatError(arg) {
  4871. if (arg instanceof Error) {
  4872. if (arg.stack) {
  4873. arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
  4874. ? 'Error: ' + arg.message + '\n' + arg.stack
  4875. : arg.stack;
  4876. } else if (arg.sourceURL) {
  4877. arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
  4878. }
  4879. }
  4880. return arg;
  4881. }
  4882. function consoleLog(type) {
  4883. var console = $window.console || {},
  4884. logFn = console[type] || console.log || noop;
  4885. if (logFn.apply) {
  4886. return function() {
  4887. var args = [];
  4888. forEach(arguments, function(arg) {
  4889. args.push(formatError(arg));
  4890. });
  4891. return logFn.apply(console, args);
  4892. };
  4893. }
  4894. // we are IE which either doesn't have window.console => this is noop and we do nothing,
  4895. // or we are IE where console.log doesn't have apply so we log at least first 2 args
  4896. return function(arg1, arg2) {
  4897. logFn(arg1, arg2);
  4898. }
  4899. }
  4900. }];
  4901. }
  4902. var OPERATORS = {
  4903. 'null':function(){return null;},
  4904. 'true':function(){return true;},
  4905. 'false':function(){return false;},
  4906. undefined:noop,
  4907. '+':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
  4908. '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
  4909. '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
  4910. '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
  4911. '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
  4912. '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
  4913. '=':noop,
  4914. '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
  4915. '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
  4916. '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
  4917. '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
  4918. '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
  4919. '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
  4920. '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
  4921. '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
  4922. '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
  4923. // '|':function(self, locals, a,b){return a|b;},
  4924. '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
  4925. '!':function(self, locals, a){return !a(self, locals);}
  4926. };
  4927. var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
  4928. function lex(text, csp){
  4929. var tokens = [],
  4930. token,
  4931. index = 0,
  4932. json = [],
  4933. ch,
  4934. lastCh = ':'; // can start regexp
  4935. while (index < text.length) {
  4936. ch = text.charAt(index);
  4937. if (is('"\'')) {
  4938. readString(ch);
  4939. } else if (isNumber(ch) || is('.') && isNumber(peek())) {
  4940. readNumber();
  4941. } else if (isIdent(ch)) {
  4942. readIdent();
  4943. // identifiers can only be if the preceding char was a { or ,
  4944. if (was('{,') && json[0]=='{' &&
  4945. (token=tokens[tokens.length-1])) {
  4946. token.json = token.text.indexOf('.') == -1;
  4947. }
  4948. } else if (is('(){}[].,;:')) {
  4949. tokens.push({
  4950. index:index,
  4951. text:ch,
  4952. json:(was(':[,') && is('{[')) || is('}]:,')
  4953. });
  4954. if (is('{[')) json.unshift(ch);
  4955. if (is('}]')) json.shift();
  4956. index++;
  4957. } else if (isWhitespace(ch)) {
  4958. index++;
  4959. continue;
  4960. } else {
  4961. var ch2 = ch + peek(),
  4962. fn = OPERATORS[ch],
  4963. fn2 = OPERATORS[ch2];
  4964. if (fn2) {
  4965. tokens.push({index:index, text:ch2, fn:fn2});
  4966. index += 2;
  4967. } else if (fn) {
  4968. tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
  4969. index += 1;
  4970. } else {
  4971. throwError("Unexpected next character ", index, index+1);
  4972. }
  4973. }
  4974. lastCh = ch;
  4975. }
  4976. return tokens;
  4977. function is(chars) {
  4978. return chars.indexOf(ch) != -1;
  4979. }
  4980. function was(chars) {
  4981. return chars.indexOf(lastCh) != -1;
  4982. }
  4983. function peek() {
  4984. return index + 1 < text.length ? text.charAt(index + 1) : false;
  4985. }
  4986. function isNumber(ch) {
  4987. return '0' <= ch && ch <= '9';
  4988. }
  4989. function isWhitespace(ch) {
  4990. return ch == ' ' || ch == '\r' || ch == '\t' ||
  4991. ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
  4992. }
  4993. function isIdent(ch) {
  4994. return 'a' <= ch && ch <= 'z' ||
  4995. 'A' <= ch && ch <= 'Z' ||
  4996. '_' == ch || ch == '$';
  4997. }
  4998. function isExpOperator(ch) {
  4999. return ch == '-' || ch == '+' || isNumber(ch);
  5000. }
  5001. function throwError(error, start, end) {
  5002. end = end || index;
  5003. throw Error("Lexer Error: " + error + " at column" +
  5004. (isDefined(start)
  5005. ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]"
  5006. : " " + end) +
  5007. " in expression [" + text + "].");
  5008. }
  5009. function readNumber() {
  5010. var number = "";
  5011. var start = index;
  5012. while (index < text.length) {
  5013. var ch = lowercase(text.charAt(index));
  5014. if (ch == '.' || isNumber(ch)) {
  5015. number += ch;
  5016. } else {
  5017. var peekCh = peek();
  5018. if (ch == 'e' && isExpOperator(peekCh)) {
  5019. number += ch;
  5020. } else if (isExpOperator(ch) &&
  5021. peekCh && isNumber(peekCh) &&
  5022. number.charAt(number.length - 1) == 'e') {
  5023. number += ch;
  5024. } else if (isExpOperator(ch) &&
  5025. (!peekCh || !isNumber(peekCh)) &&
  5026. number.charAt(number.length - 1) == 'e') {
  5027. throwError('Invalid exponent');
  5028. } else {
  5029. break;
  5030. }
  5031. }
  5032. index++;
  5033. }
  5034. number = 1 * number;
  5035. tokens.push({index:start, text:number, json:true,
  5036. fn:function() {return number;}});
  5037. }
  5038. function readIdent() {
  5039. var ident = "",
  5040. start = index,
  5041. lastDot, peekIndex, methodName;
  5042. while (index < text.length) {
  5043. var ch = text.charAt(index);
  5044. if (ch == '.' || isIdent(ch) || isNumber(ch)) {
  5045. if (ch == '.') lastDot = index;
  5046. ident += ch;
  5047. } else {
  5048. break;
  5049. }
  5050. index++;
  5051. }
  5052. //check if this is not a method invocation and if it is back out to last dot
  5053. if (lastDot) {
  5054. peekIndex = index;
  5055. while(peekIndex < text.length) {
  5056. var ch = text.charAt(peekIndex);
  5057. if (ch == '(') {
  5058. methodName = ident.substr(lastDot - start + 1);
  5059. ident = ident.substr(0, lastDot - start);
  5060. index = peekIndex;
  5061. break;
  5062. }
  5063. if(isWhitespace(ch)) {
  5064. peekIndex++;
  5065. } else {
  5066. break;
  5067. }
  5068. }
  5069. }
  5070. var token = {
  5071. index:start,
  5072. text:ident
  5073. };
  5074. if (OPERATORS.hasOwnProperty(ident)) {
  5075. token.fn = token.json = OPERATORS[ident];
  5076. } else {
  5077. var getter = getterFn(ident, csp);
  5078. token.fn = extend(function(self, locals) {
  5079. return (getter(self, locals));
  5080. }, {
  5081. assign: function(self, value) {
  5082. return setter(self, ident, value);
  5083. }
  5084. });
  5085. }
  5086. tokens.push(token);
  5087. if (methodName) {
  5088. tokens.push({
  5089. index:lastDot,
  5090. text: '.',
  5091. json: false
  5092. });
  5093. tokens.push({
  5094. index: lastDot + 1,
  5095. text: methodName,
  5096. json: false
  5097. });
  5098. }
  5099. }
  5100. function readString(quote) {
  5101. var start = index;
  5102. index++;
  5103. var string = "";
  5104. var rawString = quote;
  5105. var escape = false;
  5106. while (index < text.length) {
  5107. var ch = text.charAt(index);
  5108. rawString += ch;
  5109. if (escape) {
  5110. if (ch == 'u') {
  5111. var hex = text.substring(index + 1, index + 5);
  5112. if (!hex.match(/[\da-f]{4}/i))
  5113. throwError( "Invalid unicode escape [\\u" + hex + "]");
  5114. index += 4;
  5115. string += String.fromCharCode(parseInt(hex, 16));
  5116. } else {
  5117. var rep = ESCAPE[ch];
  5118. if (rep) {
  5119. string += rep;
  5120. } else {
  5121. string += ch;
  5122. }
  5123. }
  5124. escape = false;
  5125. } else if (ch == '\\') {
  5126. escape = true;
  5127. } else if (ch == quote) {
  5128. index++;
  5129. tokens.push({
  5130. index:start,
  5131. text:rawString,
  5132. string:string,
  5133. json:true,
  5134. fn:function() { return string; }
  5135. });
  5136. return;
  5137. } else {
  5138. string += ch;
  5139. }
  5140. index++;
  5141. }
  5142. throwError("Unterminated quote", start);
  5143. }
  5144. }
  5145. /////////////////////////////////////////
  5146. function parser(text, json, $filter, csp){
  5147. var ZERO = valueFn(0),
  5148. value,
  5149. tokens = lex(text, csp),
  5150. assignment = _assignment,
  5151. functionCall = _functionCall,
  5152. fieldAccess = _fieldAccess,
  5153. objectIndex = _objectIndex,
  5154. filterChain = _filterChain;
  5155. if(json){
  5156. // The extra level of aliasing is here, just in case the lexer misses something, so that
  5157. // we prevent any accidental execution in JSON.
  5158. assignment = logicalOR;
  5159. functionCall =
  5160. fieldAccess =
  5161. objectIndex =
  5162. filterChain =
  5163. function() { throwError("is not valid json", {text:text, index:0}); };
  5164. value = primary();
  5165. } else {
  5166. value = statements();
  5167. }
  5168. if (tokens.length !== 0) {
  5169. throwError("is an unexpected token", tokens[0]);
  5170. }
  5171. return value;
  5172. ///////////////////////////////////
  5173. function throwError(msg, token) {
  5174. throw Error("Syntax Error: Token '" + token.text +
  5175. "' " + msg + " at column " +
  5176. (token.index + 1) + " of the expression [" +
  5177. text + "] starting at [" + text.substring(token.index) + "].");
  5178. }
  5179. function peekToken() {
  5180. if (tokens.length === 0)
  5181. throw Error("Unexpected end of expression: " + text);
  5182. return tokens[0];
  5183. }
  5184. function peek(e1, e2, e3, e4) {
  5185. if (tokens.length > 0) {
  5186. var token = tokens[0];
  5187. var t = token.text;
  5188. if (t==e1 || t==e2 || t==e3 || t==e4 ||
  5189. (!e1 && !e2 && !e3 && !e4)) {
  5190. return token;
  5191. }
  5192. }
  5193. return false;
  5194. }
  5195. function expect(e1, e2, e3, e4){
  5196. var token = peek(e1, e2, e3, e4);
  5197. if (token) {
  5198. if (json && !token.json) {
  5199. throwError("is not valid json", token);
  5200. }
  5201. tokens.shift();
  5202. return token;
  5203. }
  5204. return false;
  5205. }
  5206. function consume(e1){
  5207. if (!expect(e1)) {
  5208. throwError("is unexpected, expecting [" + e1 + "]", peek());
  5209. }
  5210. }
  5211. function unaryFn(fn, right) {
  5212. return function(self, locals) {
  5213. return fn(self, locals, right);
  5214. };
  5215. }
  5216. function binaryFn(left, fn, right) {
  5217. return function(self, locals) {
  5218. return fn(self, locals, left, right);
  5219. };
  5220. }
  5221. function statements() {
  5222. var statements = [];
  5223. while(true) {
  5224. if (tokens.length > 0 && !peek('}', ')', ';', ']'))
  5225. statements.push(filterChain());
  5226. if (!expect(';')) {
  5227. // optimize for the common case where there is only one statement.
  5228. // TODO(size): maybe we should not support multiple statements?
  5229. return statements.length == 1
  5230. ? statements[0]
  5231. : function(self, locals){
  5232. var value;
  5233. for ( var i = 0; i < statements.length; i++) {
  5234. var statement = statements[i];
  5235. if (statement)
  5236. value = statement(self, locals);
  5237. }
  5238. return value;
  5239. };
  5240. }
  5241. }
  5242. }
  5243. function _filterChain() {
  5244. var left = expression();
  5245. var token;
  5246. while(true) {
  5247. if ((token = expect('|'))) {
  5248. left = binaryFn(left, token.fn, filter());
  5249. } else {
  5250. return left;
  5251. }
  5252. }
  5253. }
  5254. function filter() {
  5255. var token = expect();
  5256. var fn = $filter(token.text);
  5257. var argsFn = [];
  5258. while(true) {
  5259. if ((token = expect(':'))) {
  5260. argsFn.push(expression());
  5261. } else {
  5262. var fnInvoke = function(self, locals, input){
  5263. var args = [input];
  5264. for ( var i = 0; i < argsFn.length; i++) {
  5265. args.push(argsFn[i](self, locals));
  5266. }
  5267. return fn.apply(self, args);
  5268. };
  5269. return function() {
  5270. return fnInvoke;
  5271. };
  5272. }
  5273. }
  5274. }
  5275. function expression() {
  5276. return assignment();
  5277. }
  5278. function _assignment() {
  5279. var left = logicalOR();
  5280. var right;
  5281. var token;
  5282. if ((token = expect('='))) {
  5283. if (!left.assign) {
  5284. throwError("implies assignment but [" +
  5285. text.substring(0, token.index) + "] can not be assigned to", token);
  5286. }
  5287. right = logicalOR();
  5288. return function(self, locals){
  5289. return left.assign(self, right(self, locals), locals);
  5290. };
  5291. } else {
  5292. return left;
  5293. }
  5294. }
  5295. function logicalOR() {
  5296. var left = logicalAND();
  5297. var token;
  5298. while(true) {
  5299. if ((token = expect('||'))) {
  5300. left = binaryFn(left, token.fn, logicalAND());
  5301. } else {
  5302. return left;
  5303. }
  5304. }
  5305. }
  5306. function logicalAND() {
  5307. var left = equality();
  5308. var token;
  5309. if ((token = expect('&&'))) {
  5310. left = binaryFn(left, token.fn, logicalAND());
  5311. }
  5312. return left;
  5313. }
  5314. function equality() {
  5315. var left = relational();
  5316. var token;
  5317. if ((token = expect('==','!='))) {
  5318. left = binaryFn(left, token.fn, equality());
  5319. }
  5320. return left;
  5321. }
  5322. function relational() {
  5323. var left = additive();
  5324. var token;
  5325. if ((token = expect('<', '>', '<=', '>='))) {
  5326. left = binaryFn(left, token.fn, relational());
  5327. }
  5328. return left;
  5329. }
  5330. function additive() {
  5331. var left = multiplicative();
  5332. var token;
  5333. while ((token = expect('+','-'))) {
  5334. left = binaryFn(left, token.fn, multiplicative());
  5335. }
  5336. return left;
  5337. }
  5338. function multiplicative() {
  5339. var left = unary();
  5340. var token;
  5341. while ((token = expect('*','/','%'))) {
  5342. left = binaryFn(left, token.fn, unary());
  5343. }
  5344. return left;
  5345. }
  5346. function unary() {
  5347. var token;
  5348. if (expect('+')) {
  5349. return primary();
  5350. } else if ((token = expect('-'))) {
  5351. return binaryFn(ZERO, token.fn, unary());
  5352. } else if ((token = expect('!'))) {
  5353. return unaryFn(token.fn, unary());
  5354. } else {
  5355. return primary();
  5356. }
  5357. }
  5358. function primary() {
  5359. var primary;
  5360. if (expect('(')) {
  5361. primary = filterChain();
  5362. consume(')');
  5363. } else if (expect('[')) {
  5364. primary = arrayDeclaration();
  5365. } else if (expect('{')) {
  5366. primary = object();
  5367. } else {
  5368. var token = expect();
  5369. primary = token.fn;
  5370. if (!primary) {
  5371. throwError("not a primary expression", token);
  5372. }
  5373. }
  5374. var next, context;
  5375. while ((next = expect('(', '[', '.'))) {
  5376. if (next.text === '(') {
  5377. primary = functionCall(primary, context);
  5378. context = null;
  5379. } else if (next.text === '[') {
  5380. context = primary;
  5381. primary = objectIndex(primary);
  5382. } else if (next.text === '.') {
  5383. context = primary;
  5384. primary = fieldAccess(primary);
  5385. } else {
  5386. throwError("IMPOSSIBLE");
  5387. }
  5388. }
  5389. return primary;
  5390. }
  5391. function _fieldAccess(object) {
  5392. var field = expect().text;
  5393. var getter = getterFn(field, csp);
  5394. return extend(
  5395. function(self, locals) {
  5396. return getter(object(self, locals), locals);
  5397. },
  5398. {
  5399. assign:function(self, value, locals) {
  5400. return setter(object(self, locals), field, value);
  5401. }
  5402. }
  5403. );
  5404. }
  5405. function _objectIndex(obj) {
  5406. var indexFn = expression();
  5407. consume(']');
  5408. return extend(
  5409. function(self, locals){
  5410. var o = obj(self, locals),
  5411. i = indexFn(self, locals),
  5412. v, p;
  5413. if (!o) return undefined;
  5414. v = o[i];
  5415. if (v && v.then) {
  5416. p = v;
  5417. if (!('$$v' in v)) {
  5418. p.$$v = undefined;
  5419. p.then(function(val) { p.$$v = val; });
  5420. }
  5421. v = v.$$v;
  5422. }
  5423. return v;
  5424. }, {
  5425. assign:function(self, value, locals){
  5426. return obj(self, locals)[indexFn(self, locals)] = value;
  5427. }
  5428. });
  5429. }
  5430. function _functionCall(fn, contextGetter) {
  5431. var argsFn = [];
  5432. if (peekToken().text != ')') {
  5433. do {
  5434. argsFn.push(expression());
  5435. } while (expect(','));
  5436. }
  5437. consume(')');
  5438. return function(self, locals){
  5439. var args = [],
  5440. context = contextGetter ? contextGetter(self, locals) : self;
  5441. for ( var i = 0; i < argsFn.length; i++) {
  5442. args.push(argsFn[i](self, locals));
  5443. }
  5444. var fnPtr = fn(self, locals) || noop;
  5445. // IE stupidity!
  5446. return fnPtr.apply
  5447. ? fnPtr.apply(context, args)
  5448. : fnPtr(args[0], args[1], args[2], args[3], args[4]);
  5449. };
  5450. }
  5451. // This is used with json array declaration
  5452. function arrayDeclaration () {
  5453. var elementFns = [];
  5454. if (peekToken().text != ']') {
  5455. do {
  5456. elementFns.push(expression());
  5457. } while (expect(','));
  5458. }
  5459. consume(']');
  5460. return function(self, locals){
  5461. var array = [];
  5462. for ( var i = 0; i < elementFns.length; i++) {
  5463. array.push(elementFns[i](self, locals));
  5464. }
  5465. return array;
  5466. };
  5467. }
  5468. function object () {
  5469. var keyValues = [];
  5470. if (peekToken().text != '}') {
  5471. do {
  5472. var token = expect(),
  5473. key = token.string || token.text;
  5474. consume(":");
  5475. var value = expression();
  5476. keyValues.push({key:key, value:value});
  5477. } while (expect(','));
  5478. }
  5479. consume('}');
  5480. return function(self, locals){
  5481. var object = {};
  5482. for ( var i = 0; i < keyValues.length; i++) {
  5483. var keyValue = keyValues[i];
  5484. var value = keyValue.value(self, locals);
  5485. object[keyValue.key] = value;
  5486. }
  5487. return object;
  5488. };
  5489. }
  5490. }
  5491. //////////////////////////////////////////////////
  5492. // Parser helper functions
  5493. //////////////////////////////////////////////////
  5494. function setter(obj, path, setValue) {
  5495. var element = path.split('.');
  5496. for (var i = 0; element.length > 1; i++) {
  5497. var key = element.shift();
  5498. var propertyObj = obj[key];
  5499. if (!propertyObj) {
  5500. propertyObj = {};
  5501. obj[key] = propertyObj;
  5502. }
  5503. obj = propertyObj;
  5504. }
  5505. obj[element.shift()] = setValue;
  5506. return setValue;
  5507. }
  5508. /**
  5509. * Return the value accesible from the object by path. Any undefined traversals are ignored
  5510. * @param {Object} obj starting object
  5511. * @param {string} path path to traverse
  5512. * @param {boolean=true} bindFnToScope
  5513. * @returns value as accesbile by path
  5514. */
  5515. //TODO(misko): this function needs to be removed
  5516. function getter(obj, path, bindFnToScope) {
  5517. if (!path) return obj;
  5518. var keys = path.split('.');
  5519. var key;
  5520. var lastInstance = obj;
  5521. var len = keys.length;
  5522. for (var i = 0; i < len; i++) {
  5523. key = keys[i];
  5524. if (obj) {
  5525. obj = (lastInstance = obj)[key];
  5526. }
  5527. }
  5528. if (!bindFnToScope && isFunction(obj)) {
  5529. return bind(lastInstance, obj);
  5530. }
  5531. return obj;
  5532. }
  5533. var getterFnCache = {};
  5534. /**
  5535. * Implementation of the "Black Hole" variant from:
  5536. * - http://jsperf.com/angularjs-parse-getter/4
  5537. * - http://jsperf.com/path-evaluation-simplified/7
  5538. */
  5539. function cspSafeGetterFn(key0, key1, key2, key3, key4) {
  5540. return function(scope, locals) {
  5541. var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
  5542. promise;
  5543. if (pathVal === null || pathVal === undefined) return pathVal;
  5544. pathVal = pathVal[key0];
  5545. if (pathVal && pathVal.then) {
  5546. if (!("$$v" in pathVal)) {
  5547. promise = pathVal;
  5548. promise.$$v = undefined;
  5549. promise.then(function(val) { promise.$$v = val; });
  5550. }
  5551. pathVal = pathVal.$$v;
  5552. }
  5553. if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
  5554. pathVal = pathVal[key1];
  5555. if (pathVal && pathVal.then) {
  5556. if (!("$$v" in pathVal)) {
  5557. promise = pathVal;
  5558. promise.$$v = undefined;
  5559. promise.then(function(val) { promise.$$v = val; });
  5560. }
  5561. pathVal = pathVal.$$v;
  5562. }
  5563. if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
  5564. pathVal = pathVal[key2];
  5565. if (pathVal && pathVal.then) {
  5566. if (!("$$v" in pathVal)) {
  5567. promise = pathVal;
  5568. promise.$$v = undefined;
  5569. promise.then(function(val) { promise.$$v = val; });
  5570. }
  5571. pathVal = pathVal.$$v;
  5572. }
  5573. if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
  5574. pathVal = pathVal[key3];
  5575. if (pathVal && pathVal.then) {
  5576. if (!("$$v" in pathVal)) {
  5577. promise = pathVal;
  5578. promise.$$v = undefined;
  5579. promise.then(function(val) { promise.$$v = val; });
  5580. }
  5581. pathVal = pathVal.$$v;
  5582. }
  5583. if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
  5584. pathVal = pathVal[key4];
  5585. if (pathVal && pathVal.then) {
  5586. if (!("$$v" in pathVal)) {
  5587. promise = pathVal;
  5588. promise.$$v = undefined;
  5589. promise.then(function(val) { promise.$$v = val; });
  5590. }
  5591. pathVal = pathVal.$$v;
  5592. }
  5593. return pathVal;
  5594. };
  5595. };
  5596. function getterFn(path, csp) {
  5597. if (getterFnCache.hasOwnProperty(path)) {
  5598. return getterFnCache[path];
  5599. }
  5600. var pathKeys = path.split('.'),
  5601. pathKeysLength = pathKeys.length,
  5602. fn;
  5603. if (csp) {
  5604. fn = (pathKeysLength < 6)
  5605. ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4])
  5606. : function(scope, locals) {
  5607. var i = 0, val
  5608. do {
  5609. val = cspSafeGetterFn(
  5610. pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
  5611. )(scope, locals);
  5612. locals = undefined; // clear after first iteration
  5613. scope = val;
  5614. } while (i < pathKeysLength);
  5615. return val;
  5616. }
  5617. } else {
  5618. var code = 'var l, fn, p;\n';
  5619. forEach(pathKeys, function(key, index) {
  5620. code += 'if(s === null || s === undefined) return s;\n' +
  5621. 'l=s;\n' +
  5622. 's='+ (index
  5623. // we simply dereference 's' on any .dot notation
  5624. ? 's'
  5625. // but if we are first then we check locals first, and if so read it first
  5626. : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
  5627. 'if (s && s.then) {\n' +
  5628. ' if (!("$$v" in s)) {\n' +
  5629. ' p=s;\n' +
  5630. ' p.$$v = undefined;\n' +
  5631. ' p.then(function(v) {p.$$v=v;});\n' +
  5632. '}\n' +
  5633. ' s=s.$$v\n' +
  5634. '}\n';
  5635. });
  5636. code += 'return s;';
  5637. fn = Function('s', 'k', code); // s=scope, k=locals
  5638. fn.toString = function() { return code; };
  5639. }
  5640. return getterFnCache[path] = fn;
  5641. }
  5642. ///////////////////////////////////
  5643. /**
  5644. * @ngdoc function
  5645. * @name ng.$parse
  5646. * @function
  5647. *
  5648. * @description
  5649. *
  5650. * Converts Angular {@link guide/expression expression} into a function.
  5651. *
  5652. * <pre>
  5653. * var getter = $parse('user.name');
  5654. * var setter = getter.assign;
  5655. * var context = {user:{name:'angular'}};
  5656. * var locals = {user:{name:'local'}};
  5657. *
  5658. * expect(getter(context)).toEqual('angular');
  5659. * setter(context, 'newValue');
  5660. * expect(context.user.name).toEqual('newValue');
  5661. * expect(getter(context, locals)).toEqual('local');
  5662. * </pre>
  5663. *
  5664. *
  5665. * @param {string} expression String expression to compile.
  5666. * @returns {function(context, locals)} a function which represents the compiled expression:
  5667. *
  5668. * * `context`: an object against which any expressions embedded in the strings are evaluated
  5669. * against (Topically a scope object).
  5670. * * `locals`: local variables context object, useful for overriding values in `context`.
  5671. *
  5672. * The return function also has an `assign` property, if the expression is assignable, which
  5673. * allows one to set values to expressions.
  5674. *
  5675. */
  5676. function $ParseProvider() {
  5677. var cache = {};
  5678. this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
  5679. return function(exp) {
  5680. switch(typeof exp) {
  5681. case 'string':
  5682. return cache.hasOwnProperty(exp)
  5683. ? cache[exp]
  5684. : cache[exp] = parser(exp, false, $filter, $sniffer.csp);
  5685. case 'function':
  5686. return exp;
  5687. default:
  5688. return noop;
  5689. }
  5690. };
  5691. }];
  5692. }
  5693. /**
  5694. * @ngdoc service
  5695. * @name ng.$q
  5696. * @requires $rootScope
  5697. *
  5698. * @description
  5699. * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
  5700. *
  5701. * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
  5702. * interface for interacting with an object that represents the result of an action that is
  5703. * performed asynchronously, and may or may not be finished at any given point in time.
  5704. *
  5705. * From the perspective of dealing with error handling, deferred and promise apis are to
  5706. * asynchronous programing what `try`, `catch` and `throw` keywords are to synchronous programing.
  5707. *
  5708. * <pre>
  5709. * // for the purpose of this example let's assume that variables `$q` and `scope` are
  5710. * // available in the current lexical scope (they could have been injected or passed in).
  5711. *
  5712. * function asyncGreet(name) {
  5713. * var deferred = $q.defer();
  5714. *
  5715. * setTimeout(function() {
  5716. * // since this fn executes async in a future turn of the event loop, we need to wrap
  5717. * // our code into an $apply call so that the model changes are properly observed.
  5718. * scope.$apply(function() {
  5719. * if (okToGreet(name)) {
  5720. * deferred.resolve('Hello, ' + name + '!');
  5721. * } else {
  5722. * deferred.reject('Greeting ' + name + ' is not allowed.');
  5723. * }
  5724. * });
  5725. * }, 1000);
  5726. *
  5727. * return deferred.promise;
  5728. * }
  5729. *
  5730. * var promise = asyncGreet('Robin Hood');
  5731. * promise.then(function(greeting) {
  5732. * alert('Success: ' + greeting);
  5733. * }, function(reason) {
  5734. * alert('Failed: ' + reason);
  5735. * );
  5736. * </pre>
  5737. *
  5738. * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
  5739. * comes in the way of
  5740. * [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
  5741. *
  5742. * Additionally the promise api allows for composition that is very hard to do with the
  5743. * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
  5744. * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
  5745. * section on serial or parallel joining of promises.
  5746. *
  5747. *
  5748. * # The Deferred API
  5749. *
  5750. * A new instance of deferred is constructed by calling `$q.defer()`.
  5751. *
  5752. * The purpose of the deferred object is to expose the associated Promise instance as well as apis
  5753. * that can be used for signaling the successful or unsuccessful completion of the task.
  5754. *
  5755. * **Methods**
  5756. *
  5757. * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
  5758. * constructed via `$q.reject`, the promise will be rejected instead.
  5759. * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
  5760. * resolving it with a rejection constructed via `$q.reject`.
  5761. *
  5762. * **Properties**
  5763. *
  5764. * - promise – `{Promise}` – promise object associated with this deferred.
  5765. *
  5766. *
  5767. * # The Promise API
  5768. *
  5769. * A new promise instance is created when a deferred instance is created and can be retrieved by
  5770. * calling `deferred.promise`.
  5771. *
  5772. * The purpose of the promise object is to allow for interested parties to get access to the result
  5773. * of the deferred task when it completes.
  5774. *
  5775. * **Methods**
  5776. *
  5777. * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved
  5778. * or rejected calls one of the success or error callbacks asynchronously as soon as the result
  5779. * is available. The callbacks are called with a single argument the result or rejection reason.
  5780. *
  5781. * This method *returns a new promise* which is resolved or rejected via the return value of the
  5782. * `successCallback` or `errorCallback`.
  5783. *
  5784. *
  5785. * # Chaining promises
  5786. *
  5787. * Because calling `then` api of a promise returns a new derived promise, it is easily possible
  5788. * to create a chain of promises:
  5789. *
  5790. * <pre>
  5791. * promiseB = promiseA.then(function(result) {
  5792. * return result + 1;
  5793. * });
  5794. *
  5795. * // promiseB will be resolved immediately after promiseA is resolved and it's value will be
  5796. * // the result of promiseA incremented by 1
  5797. * </pre>
  5798. *
  5799. * It is possible to create chains of any length and since a promise can be resolved with another
  5800. * promise (which will defer its resolution further), it is possible to pause/defer resolution of
  5801. * the promises at any point in the chain. This makes it possible to implement powerful apis like
  5802. * $http's response interceptors.
  5803. *
  5804. *
  5805. * # Differences between Kris Kowal's Q and $q
  5806. *
  5807. * There are three main differences:
  5808. *
  5809. * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
  5810. * mechanism in angular, which means faster propagation of resolution or rejection into your
  5811. * models and avoiding unnecessary browser repaints, which would result in flickering UI.
  5812. * - $q promises are recognized by the templating engine in angular, which means that in templates
  5813. * you can treat promises attached to a scope as if they were the resulting values.
  5814. * - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains
  5815. * all the important functionality needed for common async tasks.
  5816. */
  5817. function $QProvider() {
  5818. this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
  5819. return qFactory(function(callback) {
  5820. $rootScope.$evalAsync(callback);
  5821. }, $exceptionHandler);
  5822. }];
  5823. }
  5824. /**
  5825. * Constructs a promise manager.
  5826. *
  5827. * @param {function(function)} nextTick Function for executing functions in the next turn.
  5828. * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
  5829. * debugging purposes.
  5830. * @returns {object} Promise manager.
  5831. */
  5832. function qFactory(nextTick, exceptionHandler) {
  5833. /**
  5834. * @ngdoc
  5835. * @name ng.$q#defer
  5836. * @methodOf ng.$q
  5837. * @description
  5838. * Creates a `Deferred` object which represents a task which will finish in the future.
  5839. *
  5840. * @returns {Deferred} Returns a new instance of deferred.
  5841. */
  5842. var defer = function() {
  5843. var pending = [],
  5844. value, deferred;
  5845. deferred = {
  5846. resolve: function(val) {
  5847. if (pending) {
  5848. var callbacks = pending;
  5849. pending = undefined;
  5850. value = ref(val);
  5851. if (callbacks.length) {
  5852. nextTick(function() {
  5853. var callback;
  5854. for (var i = 0, ii = callbacks.length; i < ii; i++) {
  5855. callback = callbacks[i];
  5856. value.then(callback[0], callback[1]);
  5857. }
  5858. });
  5859. }
  5860. }
  5861. },
  5862. reject: function(reason) {
  5863. deferred.resolve(reject(reason));
  5864. },
  5865. promise: {
  5866. then: function(callback, errback) {
  5867. var result = defer();
  5868. var wrappedCallback = function(value) {
  5869. try {
  5870. result.resolve((callback || defaultCallback)(value));
  5871. } catch(e) {
  5872. exceptionHandler(e);
  5873. result.reject(e);
  5874. }
  5875. };
  5876. var wrappedErrback = function(reason) {
  5877. try {
  5878. result.resolve((errback || defaultErrback)(reason));
  5879. } catch(e) {
  5880. exceptionHandler(e);
  5881. result.reject(e);
  5882. }
  5883. };
  5884. if (pending) {
  5885. pending.push([wrappedCallback, wrappedErrback]);
  5886. } else {
  5887. value.then(wrappedCallback, wrappedErrback);
  5888. }
  5889. return result.promise;
  5890. }
  5891. }
  5892. };
  5893. return deferred;
  5894. };
  5895. var ref = function(value) {
  5896. if (value && value.then) return value;
  5897. return {
  5898. then: function(callback) {
  5899. var result = defer();
  5900. nextTick(function() {
  5901. result.resolve(callback(value));
  5902. });
  5903. return result.promise;
  5904. }
  5905. };
  5906. };
  5907. /**
  5908. * @ngdoc
  5909. * @name ng.$q#reject
  5910. * @methodOf ng.$q
  5911. * @description
  5912. * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
  5913. * used to forward rejection in a chain of promises. If you are dealing with the last promise in
  5914. * a promise chain, you don't need to worry about it.
  5915. *
  5916. * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
  5917. * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
  5918. * a promise error callback and you want to forward the error to the promise derived from the
  5919. * current promise, you have to "rethrow" the error by returning a rejection constructed via
  5920. * `reject`.
  5921. *
  5922. * <pre>
  5923. * promiseB = promiseA.then(function(result) {
  5924. * // success: do something and resolve promiseB
  5925. * // with the old or a new result
  5926. * return result;
  5927. * }, function(reason) {
  5928. * // error: handle the error if possible and
  5929. * // resolve promiseB with newPromiseOrValue,
  5930. * // otherwise forward the rejection to promiseB
  5931. * if (canHandle(reason)) {
  5932. * // handle the error and recover
  5933. * return newPromiseOrValue;
  5934. * }
  5935. * return $q.reject(reason);
  5936. * });
  5937. * </pre>
  5938. *
  5939. * @param {*} reason Constant, message, exception or an object representing the rejection reason.
  5940. * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
  5941. */
  5942. var reject = function(reason) {
  5943. return {
  5944. then: function(callback, errback) {
  5945. var result = defer();
  5946. nextTick(function() {
  5947. result.resolve((errback || defaultErrback)(reason));
  5948. });
  5949. return result.promise;
  5950. }
  5951. };
  5952. };
  5953. /**
  5954. * @ngdoc
  5955. * @name ng.$q#when
  5956. * @methodOf ng.$q
  5957. * @description
  5958. * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
  5959. * This is useful when you are dealing with on object that might or might not be a promise, or if
  5960. * the promise comes from a source that can't be trusted.
  5961. *
  5962. * @param {*} value Value or a promise
  5963. * @returns {Promise} Returns a single promise that will be resolved with an array of values,
  5964. * each value coresponding to the promise at the same index in the `promises` array. If any of
  5965. * the promises is resolved with a rejection, this resulting promise will be resolved with the
  5966. * same rejection.
  5967. */
  5968. var when = function(value, callback, errback) {
  5969. var result = defer(),
  5970. done;
  5971. var wrappedCallback = function(value) {
  5972. try {
  5973. return (callback || defaultCallback)(value);
  5974. } catch (e) {
  5975. exceptionHandler(e);
  5976. return reject(e);
  5977. }
  5978. };
  5979. var wrappedErrback = function(reason) {
  5980. try {
  5981. return (errback || defaultErrback)(reason);
  5982. } catch (e) {
  5983. exceptionHandler(e);
  5984. return reject(e);
  5985. }
  5986. };
  5987. nextTick(function() {
  5988. ref(value).then(function(value) {
  5989. if (done) return;
  5990. done = true;
  5991. result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
  5992. }, function(reason) {
  5993. if (done) return;
  5994. done = true;
  5995. result.resolve(wrappedErrback(reason));
  5996. });
  5997. });
  5998. return result.promise;
  5999. };
  6000. function defaultCallback(value) {
  6001. return value;
  6002. }
  6003. function defaultErrback(reason) {
  6004. return reject(reason);
  6005. }
  6006. /**
  6007. * @ngdoc
  6008. * @name ng.$q#all
  6009. * @methodOf ng.$q
  6010. * @description
  6011. * Combines multiple promises into a single promise that is resolved when all of the input
  6012. * promises are resolved.
  6013. *
  6014. * @param {Array.<Promise>} promises An array of promises.
  6015. * @returns {Promise} Returns a single promise that will be resolved with an array of values,
  6016. * each value coresponding to the promise at the same index in the `promises` array. If any of
  6017. * the promises is resolved with a rejection, this resulting promise will be resolved with the
  6018. * same rejection.
  6019. */
  6020. function all(promises) {
  6021. var deferred = defer(),
  6022. counter = promises.length,
  6023. results = [];
  6024. if (counter) {
  6025. forEach(promises, function(promise, index) {
  6026. ref(promise).then(function(value) {
  6027. if (index in results) return;
  6028. results[index] = value;
  6029. if (!(--counter)) deferred.resolve(results);
  6030. }, function(reason) {
  6031. if (index in results) return;
  6032. deferred.reject(reason);
  6033. });
  6034. });
  6035. } else {
  6036. deferred.resolve(results);
  6037. }
  6038. return deferred.promise;
  6039. }
  6040. return {
  6041. defer: defer,
  6042. reject: reject,
  6043. when: when,
  6044. all: all
  6045. };
  6046. }
  6047. /**
  6048. * @ngdoc object
  6049. * @name ng.$routeProvider
  6050. * @function
  6051. *
  6052. * @description
  6053. *
  6054. * Used for configuring routes. See {@link ng.$route $route} for an example.
  6055. */
  6056. function $RouteProvider(){
  6057. var routes = {};
  6058. /**
  6059. * @ngdoc method
  6060. * @name ng.$routeProvider#when
  6061. * @methodOf ng.$routeProvider
  6062. *
  6063. * @param {string} path Route path (matched against `$location.path`). If `$location.path`
  6064. * contains redundant trailing slash or is missing one, the route will still match and the
  6065. * `$location.path` will be updated to add or drop the trailing slash to exacly match the
  6066. * route definition.
  6067. * @param {Object} route Mapping information to be assigned to `$route.current` on route
  6068. * match.
  6069. *
  6070. * Object properties:
  6071. *
  6072. * - `controller` – `{function()=}` – Controller fn that should be associated with newly
  6073. * created scope.
  6074. * - `template` – `{string=}` – html template as a string that should be used by
  6075. * {@link ng.directive:ngView ngView} or
  6076. * {@link ng.directive:ngInclude ngInclude} directives.
  6077. * this property takes precedence over `templateUrl`.
  6078. * - `templateUrl` – `{string=}` – path to an html template that should be used by
  6079. * {@link ng.directive:ngView ngView}.
  6080. * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
  6081. * be injected into the controller. If any of these dependencies are promises, they will be
  6082. * resolved and converted to a value before the controller is instantiated and the
  6083. * `$aftreRouteChange` event is fired. The map object is:
  6084. *
  6085. * - `key` – `{string}`: a name of a dependency to be injected into the controller.
  6086. * - `factory` - `{string|function}`: If `string` then it is an alias for a service.
  6087. * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
  6088. * and the return value is treated as the dependency. If the result is a promise, it is resolved
  6089. * before its value is injected into the controller.
  6090. *
  6091. * - `redirectTo` – {(string|function())=} – value to update
  6092. * {@link ng.$location $location} path with and trigger route redirection.
  6093. *
  6094. * If `redirectTo` is a function, it will be called with the following parameters:
  6095. *
  6096. * - `{Object.<string>}` - route parameters extracted from the current
  6097. * `$location.path()` by applying the current route templateUrl.
  6098. * - `{string}` - current `$location.path()`
  6099. * - `{Object}` - current `$location.search()`
  6100. *
  6101. * The custom `redirectTo` function is expected to return a string which will be used
  6102. * to update `$location.path()` and `$location.search()`.
  6103. *
  6104. * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
  6105. * changes.
  6106. *
  6107. * If the option is set to `false` and url in the browser changes, then
  6108. * `$routeUpdate` event is broadcasted on the root scope.
  6109. *
  6110. * @returns {Object} self
  6111. *
  6112. * @description
  6113. * Adds a new route definition to the `$route` service.
  6114. */
  6115. this.when = function(path, route) {
  6116. routes[path] = extend({reloadOnSearch: true}, route);
  6117. // create redirection for trailing slashes
  6118. if (path) {
  6119. var redirectPath = (path[path.length-1] == '/')
  6120. ? path.substr(0, path.length-1)
  6121. : path +'/';
  6122. routes[redirectPath] = {redirectTo: path};
  6123. }
  6124. return this;
  6125. };
  6126. /**
  6127. * @ngdoc method
  6128. * @name ng.$routeProvider#otherwise
  6129. * @methodOf ng.$routeProvider
  6130. *
  6131. * @description
  6132. * Sets route definition that will be used on route change when no other route definition
  6133. * is matched.
  6134. *
  6135. * @param {Object} params Mapping information to be assigned to `$route.current`.
  6136. * @returns {Object} self
  6137. */
  6138. this.otherwise = function(params) {
  6139. this.when(null, params);
  6140. return this;
  6141. };
  6142. this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache',
  6143. function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) {
  6144. /**
  6145. * @ngdoc object
  6146. * @name ng.$route
  6147. * @requires $location
  6148. * @requires $routeParams
  6149. *
  6150. * @property {Object} current Reference to the current route definition.
  6151. * The route definition contains:
  6152. *
  6153. * - `controller`: The controller constructor as define in route definition.
  6154. * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
  6155. * controller instantiation. The `locals` contain
  6156. * the resolved values of the `resolve` map. Additionally the `locals` also contain:
  6157. *
  6158. * - `$scope` - The current route scope.
  6159. * - `$template` - The current route template HTML.
  6160. *
  6161. * @property {Array.<Object>} routes Array of all configured routes.
  6162. *
  6163. * @description
  6164. * Is used for deep-linking URLs to controllers and view (HTML view).
  6165. * It watches `$location.url()` and tries to map the path to an existing route definition.
  6166. *
  6167. * You can define routes through {@link ng.$routeProvider $routeProvider}'s API.
  6168. *
  6169. * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView}
  6170. * directive and the {@link ng.$routeParams $routeParams} service.
  6171. *
  6172. * @example
  6173. This example shows how changing the URL hash causes the `$route` to match a route against the
  6174. URL, and the `ngView` pulls in the partial.
  6175. Note that this example is using {@link ng.directive:script inlined templates}
  6176. to get it working on jsfiddle as well.
  6177. <example module="ngView">
  6178. <file name="index.html">
  6179. <div ng-controller="MainCntl">
  6180. Choose:
  6181. <a href="Book/Moby">Moby</a> |
  6182. <a href="Book/Moby/ch/1">Moby: Ch1</a> |
  6183. <a href="Book/Gatsby">Gatsby</a> |
  6184. <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
  6185. <a href="Book/Scarlet">Scarlet Letter</a><br/>
  6186. <div ng-view></div>
  6187. <hr />
  6188. <pre>$location.path() = {{$location.path()}}</pre>
  6189. <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
  6190. <pre>$route.current.params = {{$route.current.params}}</pre>
  6191. <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
  6192. <pre>$routeParams = {{$routeParams}}</pre>
  6193. </div>
  6194. </file>
  6195. <file name="book.html">
  6196. controller: {{name}}<br />
  6197. Book Id: {{params.bookId}}<br />
  6198. </file>
  6199. <file name="chapter.html">
  6200. controller: {{name}}<br />
  6201. Book Id: {{params.bookId}}<br />
  6202. Chapter Id: {{params.chapterId}}
  6203. </file>
  6204. <file name="script.js">
  6205. angular.module('ngView', [], function($routeProvider, $locationProvider) {
  6206. $routeProvider.when('/Book/:bookId', {
  6207. templateUrl: 'book.html',
  6208. controller: BookCntl,
  6209. resolve: {
  6210. // I will cause a 1 second delay
  6211. delay: function($q, $timeout) {
  6212. var delay = $q.defer();
  6213. $timeout(delay.resolve, 1000);
  6214. return delay.promise;
  6215. }
  6216. }
  6217. });
  6218. $routeProvider.when('/Book/:bookId/ch/:chapterId', {
  6219. templateUrl: 'chapter.html',
  6220. controller: ChapterCntl
  6221. });
  6222. // configure html5 to get links working on jsfiddle
  6223. $locationProvider.html5Mode(true);
  6224. });
  6225. function MainCntl($scope, $route, $routeParams, $location) {
  6226. $scope.$route = $route;
  6227. $scope.$location = $location;
  6228. $scope.$routeParams = $routeParams;
  6229. }
  6230. function BookCntl($scope, $routeParams) {
  6231. $scope.name = "BookCntl";
  6232. $scope.params = $routeParams;
  6233. }
  6234. function ChapterCntl($scope, $routeParams) {
  6235. $scope.name = "ChapterCntl";
  6236. $scope.params = $routeParams;
  6237. }
  6238. </file>
  6239. <file name="scenario.js">
  6240. it('should load and compile correct template', function() {
  6241. element('a:contains("Moby: Ch1")').click();
  6242. var content = element('.doc-example-live [ng-view]').text();
  6243. expect(content).toMatch(/controller\: ChapterCntl/);
  6244. expect(content).toMatch(/Book Id\: Moby/);
  6245. expect(content).toMatch(/Chapter Id\: 1/);
  6246. element('a:contains("Scarlet")').click();
  6247. sleep(2); // promises are not part of scenario waiting
  6248. content = element('.doc-example-live [ng-view]').text();
  6249. expect(content).toMatch(/controller\: BookCntl/);
  6250. expect(content).toMatch(/Book Id\: Scarlet/);
  6251. });
  6252. </file>
  6253. </example>
  6254. */
  6255. /**
  6256. * @ngdoc event
  6257. * @name ng.$route#$routeChangeStart
  6258. * @eventOf ng.$route
  6259. * @eventType broadcast on root scope
  6260. * @description
  6261. * Broadcasted before a route change. At this point the route services starts
  6262. * resolving all of the dependencies needed for the route change to occurs.
  6263. * Typically this involves fetching the view template as well as any dependencies
  6264. * defined in `resolve` route property. Once all of the dependencies are resolved
  6265. * `$routeChangeSuccess` is fired.
  6266. *
  6267. * @param {Route} next Future route information.
  6268. * @param {Route} current Current route information.
  6269. */
  6270. /**
  6271. * @ngdoc event
  6272. * @name ng.$route#$routeChangeSuccess
  6273. * @eventOf ng.$route
  6274. * @eventType broadcast on root scope
  6275. * @description
  6276. * Broadcasted after a route dependencies are resolved.
  6277. * {@link ng.directive:ngView ngView} listens for the directive
  6278. * to instantiate the controller and render the view.
  6279. *
  6280. * @param {Route} current Current route information.
  6281. * @param {Route} previous Previous route information.
  6282. */
  6283. /**
  6284. * @ngdoc event
  6285. * @name ng.$route#$routeChangeError
  6286. * @eventOf ng.$route
  6287. * @eventType broadcast on root scope
  6288. * @description
  6289. * Broadcasted if any of the resolve promises are rejected.
  6290. *
  6291. * @param {Route} current Current route information.
  6292. * @param {Route} previous Previous route information.
  6293. * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
  6294. */
  6295. /**
  6296. * @ngdoc event
  6297. * @name ng.$route#$routeUpdate
  6298. * @eventOf ng.$route
  6299. * @eventType broadcast on root scope
  6300. * @description
  6301. *
  6302. * The `reloadOnSearch` property has been set to false, and we are reusing the same
  6303. * instance of the Controller.
  6304. */
  6305. var matcher = switchRouteMatcher,
  6306. forceReload = false,
  6307. $route = {
  6308. routes: routes,
  6309. /**
  6310. * @ngdoc method
  6311. * @name ng.$route#reload
  6312. * @methodOf ng.$route
  6313. *
  6314. * @description
  6315. * Causes `$route` service to reload the current route even if
  6316. * {@link ng.$location $location} hasn't changed.
  6317. *
  6318. * As a result of that, {@link ng.directive:ngView ngView}
  6319. * creates new scope, reinstantiates the controller.
  6320. */
  6321. reload: function() {
  6322. forceReload = true;
  6323. $rootScope.$evalAsync(updateRoute);
  6324. }
  6325. };
  6326. $rootScope.$on('$locationChangeSuccess', updateRoute);
  6327. return $route;
  6328. /////////////////////////////////////////////////////
  6329. function switchRouteMatcher(on, when) {
  6330. // TODO(i): this code is convoluted and inefficient, we should construct the route matching
  6331. // regex only once and then reuse it
  6332. var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
  6333. params = [],
  6334. dst = {};
  6335. forEach(when.split(/\W/), function(param) {
  6336. if (param) {
  6337. var paramRegExp = new RegExp(":" + param + "([\\W])");
  6338. if (regex.match(paramRegExp)) {
  6339. regex = regex.replace(paramRegExp, "([^\\/]*)$1");
  6340. params.push(param);
  6341. }
  6342. }
  6343. });
  6344. var match = on.match(new RegExp(regex));
  6345. if (match) {
  6346. forEach(params, function(name, index) {
  6347. dst[name] = match[index + 1];
  6348. });
  6349. }
  6350. return match ? dst : null;
  6351. }
  6352. function updateRoute() {
  6353. var next = parseRoute(),
  6354. last = $route.current;
  6355. if (next && last && next.$route === last.$route
  6356. && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
  6357. last.params = next.params;
  6358. copy(last.params, $routeParams);
  6359. $rootScope.$broadcast('$routeUpdate', last);
  6360. } else if (next || last) {
  6361. forceReload = false;
  6362. $rootScope.$broadcast('$routeChangeStart', next, last);
  6363. $route.current = next;
  6364. if (next) {
  6365. if (next.redirectTo) {
  6366. if (isString(next.redirectTo)) {
  6367. $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
  6368. .replace();
  6369. } else {
  6370. $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
  6371. .replace();
  6372. }
  6373. }
  6374. }
  6375. $q.when(next).
  6376. then(function() {
  6377. if (next) {
  6378. var keys = [],
  6379. values = [],
  6380. template;
  6381. forEach(next.resolve || {}, function(value, key) {
  6382. keys.push(key);
  6383. values.push(isFunction(value) ? $injector.invoke(value) : $injector.get(value));
  6384. });
  6385. if (isDefined(template = next.template)) {
  6386. } else if (isDefined(template = next.templateUrl)) {
  6387. template = $http.get(template, {cache: $templateCache}).
  6388. then(function(response) { return response.data; });
  6389. }
  6390. if (isDefined(template)) {
  6391. keys.push('$template');
  6392. values.push(template);
  6393. }
  6394. return $q.all(values).then(function(values) {
  6395. var locals = {};
  6396. forEach(values, function(value, index) {
  6397. locals[keys[index]] = value;
  6398. });
  6399. return locals;
  6400. });
  6401. }
  6402. }).
  6403. // after route change
  6404. then(function(locals) {
  6405. if (next == $route.current) {
  6406. if (next) {
  6407. next.locals = locals;
  6408. copy(next.params, $routeParams);
  6409. }
  6410. $rootScope.$broadcast('$routeChangeSuccess', next, last);
  6411. }
  6412. }, function(error) {
  6413. if (next == $route.current) {
  6414. $rootScope.$broadcast('$routeChangeError', next, last, error);
  6415. }
  6416. });
  6417. }
  6418. }
  6419. /**
  6420. * @returns the current active route, by matching it against the URL
  6421. */
  6422. function parseRoute() {
  6423. // Match a route
  6424. var params, match;
  6425. forEach(routes, function(route, path) {
  6426. if (!match && (params = matcher($location.path(), path))) {
  6427. match = inherit(route, {
  6428. params: extend({}, $location.search(), params),
  6429. pathParams: params});
  6430. match.$route = route;
  6431. }
  6432. });
  6433. // No route matched; fallback to "otherwise" route
  6434. return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
  6435. }
  6436. /**
  6437. * @returns interpolation of the redirect path with the parametrs
  6438. */
  6439. function interpolate(string, params) {
  6440. var result = [];
  6441. forEach((string||'').split(':'), function(segment, i) {
  6442. if (i == 0) {
  6443. result.push(segment);
  6444. } else {
  6445. var segmentMatch = segment.match(/(\w+)(.*)/);
  6446. var key = segmentMatch[1];
  6447. result.push(params[key]);
  6448. result.push(segmentMatch[2] || '');
  6449. delete params[key];
  6450. }
  6451. });
  6452. return result.join('');
  6453. }
  6454. }];
  6455. }
  6456. /**
  6457. * @ngdoc object
  6458. * @name ng.$routeParams
  6459. * @requires $route
  6460. *
  6461. * @description
  6462. * Current set of route parameters. The route parameters are a combination of the
  6463. * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters
  6464. * are extracted when the {@link ng.$route $route} path is matched.
  6465. *
  6466. * In case of parameter name collision, `path` params take precedence over `search` params.
  6467. *
  6468. * The service guarantees that the identity of the `$routeParams` object will remain unchanged
  6469. * (but its properties will likely change) even when a route change occurs.
  6470. *
  6471. * @example
  6472. * <pre>
  6473. * // Given:
  6474. * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
  6475. * // Route: /Chapter/:chapterId/Section/:sectionId
  6476. * //
  6477. * // Then
  6478. * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
  6479. * </pre>
  6480. */
  6481. function $RouteParamsProvider() {
  6482. this.$get = valueFn({});
  6483. }
  6484. /**
  6485. * DESIGN NOTES
  6486. *
  6487. * The design decisions behind the scope ware heavily favored for speed and memory consumption.
  6488. *
  6489. * The typical use of scope is to watch the expressions, which most of the time return the same
  6490. * value as last time so we optimize the operation.
  6491. *
  6492. * Closures construction is expensive from speed as well as memory:
  6493. * - no closures, instead ups prototypical inheritance for API
  6494. * - Internal state needs to be stored on scope directly, which means that private state is
  6495. * exposed as $$____ properties
  6496. *
  6497. * Loop operations are optimized by using while(count--) { ... }
  6498. * - this means that in order to keep the same order of execution as addition we have to add
  6499. * items to the array at the begging (shift) instead of at the end (push)
  6500. *
  6501. * Child scopes are created and removed often
  6502. * - Using array would be slow since inserts in meddle are expensive so we use linked list
  6503. *
  6504. * There are few watches then a lot of observers. This is why you don't want the observer to be
  6505. * implemented in the same way as watch. Watch requires return of initialization function which
  6506. * are expensive to construct.
  6507. */
  6508. /**
  6509. * @ngdoc object
  6510. * @name ng.$rootScopeProvider
  6511. * @description
  6512. *
  6513. * Provider for the $rootScope service.
  6514. */
  6515. /**
  6516. * @ngdoc function
  6517. * @name ng.$rootScopeProvider#digestTtl
  6518. * @methodOf ng.$rootScopeProvider
  6519. * @description
  6520. *
  6521. * Sets the number of digest iteration the scope should attempt to execute before giving up and
  6522. * assuming that the model is unstable.
  6523. *
  6524. * The current default is 10 iterations.
  6525. *
  6526. * @param {number} limit The number of digest iterations.
  6527. */
  6528. /**
  6529. * @ngdoc object
  6530. * @name ng.$rootScope
  6531. * @description
  6532. *
  6533. * Every application has a single root {@link ng.$rootScope.Scope scope}.
  6534. * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide
  6535. * event processing life-cycle. See {@link guide/scope developer guide on scopes}.
  6536. */
  6537. function $RootScopeProvider(){
  6538. var TTL = 10;
  6539. this.digestTtl = function(value) {
  6540. if (arguments.length) {
  6541. TTL = value;
  6542. }
  6543. return TTL;
  6544. };
  6545. this.$get = ['$injector', '$exceptionHandler', '$parse',
  6546. function( $injector, $exceptionHandler, $parse) {
  6547. /**
  6548. * @ngdoc function
  6549. * @name ng.$rootScope.Scope
  6550. *
  6551. * @description
  6552. * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
  6553. * {@link AUTO.$injector $injector}. Child scopes are created using the
  6554. * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
  6555. * compiled HTML template is executed.)
  6556. *
  6557. * Here is a simple scope snippet to show how you can interact with the scope.
  6558. * <pre>
  6559. angular.injector(['ng']).invoke(function($rootScope) {
  6560. var scope = $rootScope.$new();
  6561. scope.salutation = 'Hello';
  6562. scope.name = 'World';
  6563. expect(scope.greeting).toEqual(undefined);
  6564. scope.$watch('name', function() {
  6565. this.greeting = this.salutation + ' ' + this.name + '!';
  6566. }); // initialize the watch
  6567. expect(scope.greeting).toEqual(undefined);
  6568. scope.name = 'Misko';
  6569. // still old value, since watches have not been called yet
  6570. expect(scope.greeting).toEqual(undefined);
  6571. scope.$digest(); // fire all the watches
  6572. expect(scope.greeting).toEqual('Hello Misko!');
  6573. });
  6574. * </pre>
  6575. *
  6576. * # Inheritance
  6577. * A scope can inherit from a parent scope, as in this example:
  6578. * <pre>
  6579. var parent = $rootScope;
  6580. var child = parent.$new();
  6581. parent.salutation = "Hello";
  6582. child.name = "World";
  6583. expect(child.salutation).toEqual('Hello');
  6584. child.salutation = "Welcome";
  6585. expect(child.salutation).toEqual('Welcome');
  6586. expect(parent.salutation).toEqual('Hello');
  6587. * </pre>
  6588. *
  6589. *
  6590. * @param {Object.<string, function()>=} providers Map of service factory which need to be provided
  6591. * for the current scope. Defaults to {@link ng}.
  6592. * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
  6593. * append/override services provided by `providers`. This is handy when unit-testing and having
  6594. * the need to override a default service.
  6595. * @returns {Object} Newly created scope.
  6596. *
  6597. */
  6598. function Scope() {
  6599. this.$id = nextUid();
  6600. this.$$phase = this.$parent = this.$$watchers =
  6601. this.$$nextSibling = this.$$prevSibling =
  6602. this.$$childHead = this.$$childTail = null;
  6603. this['this'] = this.$root = this;
  6604. this.$$asyncQueue = [];
  6605. this.$$listeners = {};
  6606. }
  6607. /**
  6608. * @ngdoc property
  6609. * @name ng.$rootScope.Scope#$id
  6610. * @propertyOf ng.$rootScope.Scope
  6611. * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
  6612. * debugging.
  6613. */
  6614. Scope.prototype = {
  6615. /**
  6616. * @ngdoc function
  6617. * @name ng.$rootScope.Scope#$new
  6618. * @methodOf ng.$rootScope.Scope
  6619. * @function
  6620. *
  6621. * @description
  6622. * Creates a new child {@link ng.$rootScope.Scope scope}.
  6623. *
  6624. * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and
  6625. * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope
  6626. * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
  6627. *
  6628. * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for
  6629. * the scope and its child scopes to be permanently detached from the parent and thus stop
  6630. * participating in model change detection and listener notification by invoking.
  6631. *
  6632. * @param {boolean} isolate if true then the scoped does not prototypically inherit from the
  6633. * parent scope. The scope is isolated, as it can not se parent scope properties.
  6634. * When creating widgets it is useful for the widget to not accidently read parent
  6635. * state.
  6636. *
  6637. * @returns {Object} The newly created child scope.
  6638. *
  6639. */
  6640. $new: function(isolate) {
  6641. var Child,
  6642. child;
  6643. if (isFunction(isolate)) {
  6644. // TODO: remove at some point
  6645. throw Error('API-CHANGE: Use $controller to instantiate controllers.');
  6646. }
  6647. if (isolate) {
  6648. child = new Scope();
  6649. child.$root = this.$root;
  6650. } else {
  6651. Child = function() {}; // should be anonymous; This is so that when the minifier munges
  6652. // the name it does not become random set of chars. These will then show up as class
  6653. // name in the debugger.
  6654. Child.prototype = this;
  6655. child = new Child();
  6656. child.$id = nextUid();
  6657. }
  6658. child['this'] = child;
  6659. child.$$listeners = {};
  6660. child.$parent = this;
  6661. child.$$asyncQueue = [];
  6662. child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
  6663. child.$$prevSibling = this.$$childTail;
  6664. if (this.$$childHead) {
  6665. this.$$childTail.$$nextSibling = child;
  6666. this.$$childTail = child;
  6667. } else {
  6668. this.$$childHead = this.$$childTail = child;
  6669. }
  6670. return child;
  6671. },
  6672. /**
  6673. * @ngdoc function
  6674. * @name ng.$rootScope.Scope#$watch
  6675. * @methodOf ng.$rootScope.Scope
  6676. * @function
  6677. *
  6678. * @description
  6679. * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
  6680. *
  6681. * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and
  6682. * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()}
  6683. * reruns when it detects changes the `watchExpression` can execute multiple times per
  6684. * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
  6685. * - The `listener` is called only when the value from the current `watchExpression` and the
  6686. * previous call to `watchExpression' are not equal (with the exception of the initial run
  6687. * see below). The inequality is determined according to
  6688. * {@link angular.equals} function. To save the value of the object for later comparison
  6689. * {@link angular.copy} function is used. It also means that watching complex options will
  6690. * have adverse memory and performance implications.
  6691. * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
  6692. * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
  6693. * limit is 100 to prevent infinity loop deadlock.
  6694. *
  6695. *
  6696. * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
  6697. * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`,
  6698. * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is
  6699. * detected, be prepared for multiple calls to your listener.)
  6700. *
  6701. * After a watcher is registered with the scope, the `listener` fn is called asynchronously
  6702. * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
  6703. * watcher. In rare cases, this is undesirable because the listener is called when the result
  6704. * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
  6705. * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
  6706. * listener was called due to initialization.
  6707. *
  6708. *
  6709. * # Example
  6710. * <pre>
  6711. // let's assume that scope was dependency injected as the $rootScope
  6712. var scope = $rootScope;
  6713. scope.name = 'misko';
  6714. scope.counter = 0;
  6715. expect(scope.counter).toEqual(0);
  6716. scope.$watch('name', function(newValue, oldValue) { counter = counter + 1; });
  6717. expect(scope.counter).toEqual(0);
  6718. scope.$digest();
  6719. // no variable change
  6720. expect(scope.counter).toEqual(0);
  6721. scope.name = 'adam';
  6722. scope.$digest();
  6723. expect(scope.counter).toEqual(1);
  6724. * </pre>
  6725. *
  6726. *
  6727. *
  6728. * @param {(function()|string)} watchExpression Expression that is evaluated on each
  6729. * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a
  6730. * call to the `listener`.
  6731. *
  6732. * - `string`: Evaluated as {@link guide/expression expression}
  6733. * - `function(scope)`: called with current `scope` as a parameter.
  6734. * @param {(function()|string)=} listener Callback called whenever the return value of
  6735. * the `watchExpression` changes.
  6736. *
  6737. * - `string`: Evaluated as {@link guide/expression expression}
  6738. * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
  6739. *
  6740. * @param {boolean=} objectEquality Compare object for equality rather then for refference.
  6741. * @returns {function()} Returns a deregistration function for this listener.
  6742. */
  6743. $watch: function(watchExp, listener, objectEquality) {
  6744. var scope = this,
  6745. get = compileToFn(watchExp, 'watch'),
  6746. array = scope.$$watchers,
  6747. watcher = {
  6748. fn: listener,
  6749. last: initWatchVal,
  6750. get: get,
  6751. exp: watchExp,
  6752. eq: !!objectEquality
  6753. };
  6754. // in the case user pass string, we need to compile it, do we really need this ?
  6755. if (!isFunction(listener)) {
  6756. var listenFn = compileToFn(listener || noop, 'listener');
  6757. watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
  6758. }
  6759. if (!array) {
  6760. array = scope.$$watchers = [];
  6761. }
  6762. // we use unshift since we use a while loop in $digest for speed.
  6763. // the while loop reads in reverse order.
  6764. array.unshift(watcher);
  6765. return function() {
  6766. arrayRemove(array, watcher);
  6767. };
  6768. },
  6769. /**
  6770. * @ngdoc function
  6771. * @name ng.$rootScope.Scope#$digest
  6772. * @methodOf ng.$rootScope.Scope
  6773. * @function
  6774. *
  6775. * @description
  6776. * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children.
  6777. * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the
  6778. * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are
  6779. * firing. This means that it is possible to get into an infinite loop. This function will throw
  6780. * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10.
  6781. *
  6782. * Usually you don't call `$digest()` directly in
  6783. * {@link ng.directive:ngController controllers} or in
  6784. * {@link ng.$compileProvider.directive directives}.
  6785. * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a
  6786. * {@link ng.$compileProvider.directive directives}) will force a `$digest()`.
  6787. *
  6788. * If you want to be notified whenever `$digest()` is called,
  6789. * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()}
  6790. * with no `listener`.
  6791. *
  6792. * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
  6793. * life-cycle.
  6794. *
  6795. * # Example
  6796. * <pre>
  6797. var scope = ...;
  6798. scope.name = 'misko';
  6799. scope.counter = 0;
  6800. expect(scope.counter).toEqual(0);
  6801. scope.$watch('name', function(newValue, oldValue) {
  6802. counter = counter + 1;
  6803. });
  6804. expect(scope.counter).toEqual(0);
  6805. scope.$digest();
  6806. // no variable change
  6807. expect(scope.counter).toEqual(0);
  6808. scope.name = 'adam';
  6809. scope.$digest();
  6810. expect(scope.counter).toEqual(1);
  6811. * </pre>
  6812. *
  6813. */
  6814. $digest: function() {
  6815. var watch, value, last,
  6816. watchers,
  6817. asyncQueue,
  6818. length,
  6819. dirty, ttl = TTL,
  6820. next, current, target = this,
  6821. watchLog = [],
  6822. logIdx, logMsg;
  6823. beginPhase('$digest');
  6824. do {
  6825. dirty = false;
  6826. current = target;
  6827. do {
  6828. asyncQueue = current.$$asyncQueue;
  6829. while(asyncQueue.length) {
  6830. try {
  6831. current.$eval(asyncQueue.shift());
  6832. } catch (e) {
  6833. $exceptionHandler(e);
  6834. }
  6835. }
  6836. if ((watchers = current.$$watchers)) {
  6837. // process our watches
  6838. length = watchers.length;
  6839. while (length--) {
  6840. try {
  6841. watch = watchers[length];
  6842. // Most common watches are on primitives, in which case we can short
  6843. // circuit it with === operator, only when === fails do we use .equals
  6844. if ((value = watch.get(current)) !== (last = watch.last) &&
  6845. !(watch.eq
  6846. ? equals(value, last)
  6847. : (typeof value == 'number' && typeof last == 'number'
  6848. && isNaN(value) && isNaN(last)))) {
  6849. dirty = true;
  6850. watch.last = watch.eq ? copy(value) : value;
  6851. watch.fn(value, ((last === initWatchVal) ? value : last), current);
  6852. if (ttl < 5) {
  6853. logIdx = 4 - ttl;
  6854. if (!watchLog[logIdx]) watchLog[logIdx] = [];
  6855. logMsg = (isFunction(watch.exp))
  6856. ? 'fn: ' + (watch.exp.name || watch.exp.toString())
  6857. : watch.exp;
  6858. logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
  6859. watchLog[logIdx].push(logMsg);
  6860. }
  6861. }
  6862. } catch (e) {
  6863. $exceptionHandler(e);
  6864. }
  6865. }
  6866. }
  6867. // Insanity Warning: scope depth-first traversal
  6868. // yes, this code is a bit crazy, but it works and we have tests to prove it!
  6869. // this piece should be kept in sync with the traversal in $broadcast
  6870. if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
  6871. while(current !== target && !(next = current.$$nextSibling)) {
  6872. current = current.$parent;
  6873. }
  6874. }
  6875. } while ((current = next));
  6876. if(dirty && !(ttl--)) {
  6877. clearPhase();
  6878. throw Error(TTL + ' $digest() iterations reached. Aborting!\n' +
  6879. 'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
  6880. }
  6881. } while (dirty || asyncQueue.length);
  6882. clearPhase();
  6883. },
  6884. /**
  6885. * @ngdoc event
  6886. * @name ng.$rootScope.Scope#$destroy
  6887. * @eventOf ng.$rootScope.Scope
  6888. * @eventType broadcast on scope being destroyed
  6889. *
  6890. * @description
  6891. * Broadcasted when a scope and its children are being destroyed.
  6892. */
  6893. /**
  6894. * @ngdoc function
  6895. * @name ng.$rootScope.Scope#$destroy
  6896. * @methodOf ng.$rootScope.Scope
  6897. * @function
  6898. *
  6899. * @description
  6900. * Remove the current scope (and all of its children) from the parent scope. Removal implies
  6901. * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
  6902. * propagate to the current scope and its children. Removal also implies that the current
  6903. * scope is eligible for garbage collection.
  6904. *
  6905. * The `$destroy()` is usually used by directives such as
  6906. * {@link ng.directive:ngRepeat ngRepeat} for managing the
  6907. * unrolling of the loop.
  6908. *
  6909. * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope.
  6910. * Application code can register a `$destroy` event handler that will give it chance to
  6911. * perform any necessary cleanup.
  6912. */
  6913. $destroy: function() {
  6914. if ($rootScope == this) return; // we can't remove the root node;
  6915. var parent = this.$parent;
  6916. this.$broadcast('$destroy');
  6917. if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
  6918. if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
  6919. if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
  6920. if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
  6921. },
  6922. /**
  6923. * @ngdoc function
  6924. * @name ng.$rootScope.Scope#$eval
  6925. * @methodOf ng.$rootScope.Scope
  6926. * @function
  6927. *
  6928. * @description
  6929. * Executes the `expression` on the current scope returning the result. Any exceptions in the
  6930. * expression are propagated (uncaught). This is useful when evaluating engular expressions.
  6931. *
  6932. * # Example
  6933. * <pre>
  6934. var scope = ng.$rootScope.Scope();
  6935. scope.a = 1;
  6936. scope.b = 2;
  6937. expect(scope.$eval('a+b')).toEqual(3);
  6938. expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
  6939. * </pre>
  6940. *
  6941. * @param {(string|function())=} expression An angular expression to be executed.
  6942. *
  6943. * - `string`: execute using the rules as defined in {@link guide/expression expression}.
  6944. * - `function(scope)`: execute the function with the current `scope` parameter.
  6945. *
  6946. * @returns {*} The result of evaluating the expression.
  6947. */
  6948. $eval: function(expr, locals) {
  6949. return $parse(expr)(this, locals);
  6950. },
  6951. /**
  6952. * @ngdoc function
  6953. * @name ng.$rootScope.Scope#$evalAsync
  6954. * @methodOf ng.$rootScope.Scope
  6955. * @function
  6956. *
  6957. * @description
  6958. * Executes the expression on the current scope at a later point in time.
  6959. *
  6960. * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
  6961. *
  6962. * - it will execute in the current script execution context (before any DOM rendering).
  6963. * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
  6964. * `expression` execution.
  6965. *
  6966. * Any exceptions from the execution of the expression are forwarded to the
  6967. * {@link ng.$exceptionHandler $exceptionHandler} service.
  6968. *
  6969. * @param {(string|function())=} expression An angular expression to be executed.
  6970. *
  6971. * - `string`: execute using the rules as defined in {@link guide/expression expression}.
  6972. * - `function(scope)`: execute the function with the current `scope` parameter.
  6973. *
  6974. */
  6975. $evalAsync: function(expr) {
  6976. this.$$asyncQueue.push(expr);
  6977. },
  6978. /**
  6979. * @ngdoc function
  6980. * @name ng.$rootScope.Scope#$apply
  6981. * @methodOf ng.$rootScope.Scope
  6982. * @function
  6983. *
  6984. * @description
  6985. * `$apply()` is used to execute an expression in angular from outside of the angular framework.
  6986. * (For example from browser DOM events, setTimeout, XHR or third party libraries).
  6987. * Because we are calling into the angular framework we need to perform proper scope life-cycle
  6988. * of {@link ng.$exceptionHandler exception handling},
  6989. * {@link ng.$rootScope.Scope#$digest executing watches}.
  6990. *
  6991. * ## Life cycle
  6992. *
  6993. * # Pseudo-Code of `$apply()`
  6994. * <pre>
  6995. function $apply(expr) {
  6996. try {
  6997. return $eval(expr);
  6998. } catch (e) {
  6999. $exceptionHandler(e);
  7000. } finally {
  7001. $root.$digest();
  7002. }
  7003. }
  7004. * </pre>
  7005. *
  7006. *
  7007. * Scope's `$apply()` method transitions through the following stages:
  7008. *
  7009. * 1. The {@link guide/expression expression} is executed using the
  7010. * {@link ng.$rootScope.Scope#$eval $eval()} method.
  7011. * 2. Any exceptions from the execution of the expression are forwarded to the
  7012. * {@link ng.$exceptionHandler $exceptionHandler} service.
  7013. * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression
  7014. * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
  7015. *
  7016. *
  7017. * @param {(string|function())=} exp An angular expression to be executed.
  7018. *
  7019. * - `string`: execute using the rules as defined in {@link guide/expression expression}.
  7020. * - `function(scope)`: execute the function with current `scope` parameter.
  7021. *
  7022. * @returns {*} The result of evaluating the expression.
  7023. */
  7024. $apply: function(expr) {
  7025. try {
  7026. beginPhase('$apply');
  7027. return this.$eval(expr);
  7028. } catch (e) {
  7029. $exceptionHandler(e);
  7030. } finally {
  7031. clearPhase();
  7032. try {
  7033. $rootScope.$digest();
  7034. } catch (e) {
  7035. $exceptionHandler(e);
  7036. throw e;
  7037. }
  7038. }
  7039. },
  7040. /**
  7041. * @ngdoc function
  7042. * @name ng.$rootScope.Scope#$on
  7043. * @methodOf ng.$rootScope.Scope
  7044. * @function
  7045. *
  7046. * @description
  7047. * Listen on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
  7048. * event life cycle.
  7049. *
  7050. * @param {string} name Event name to listen on.
  7051. * @param {function(event)} listener Function to call when the event is emitted.
  7052. * @returns {function()} Returns a deregistration function for this listener.
  7053. *
  7054. * The event listener function format is: `function(event)`. The `event` object passed into the
  7055. * listener has the following attributes
  7056. *
  7057. * - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
  7058. * - `currentScope` - {Scope}: the current scope which is handling the event.
  7059. * - `name` - {string}: Name of the event.
  7060. * - `stopPropagation` - {function=}: calling `stopPropagation` function will cancel further event propagation
  7061. * (available only for events that were `$emit`-ed).
  7062. * - `preventDefault` - {function}: calling `preventDefault` sets `defaultPrevented` flag to true.
  7063. * - `defaultPrevented` - {boolean}: true if `preventDefault` was called.
  7064. */
  7065. $on: function(name, listener) {
  7066. var namedListeners = this.$$listeners[name];
  7067. if (!namedListeners) {
  7068. this.$$listeners[name] = namedListeners = [];
  7069. }
  7070. namedListeners.push(listener);
  7071. return function() {
  7072. arrayRemove(namedListeners, listener);
  7073. };
  7074. },
  7075. /**
  7076. * @ngdoc function
  7077. * @name ng.$rootScope.Scope#$emit
  7078. * @methodOf ng.$rootScope.Scope
  7079. * @function
  7080. *
  7081. * @description
  7082. * Dispatches an event `name` upwards through the scope hierarchy notifying the
  7083. * registered {@link ng.$rootScope.Scope#$on} listeners.
  7084. *
  7085. * The event life cycle starts at the scope on which `$emit` was called. All
  7086. * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
  7087. * Afterwards, the event traverses upwards toward the root scope and calls all registered
  7088. * listeners along the way. The event will stop propagating if one of the listeners cancels it.
  7089. *
  7090. * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
  7091. * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
  7092. *
  7093. * @param {string} name Event name to emit.
  7094. * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
  7095. * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
  7096. */
  7097. $emit: function(name, args) {
  7098. var empty = [],
  7099. namedListeners,
  7100. scope = this,
  7101. stopPropagation = false,
  7102. event = {
  7103. name: name,
  7104. targetScope: scope,
  7105. stopPropagation: function() {stopPropagation = true;},
  7106. preventDefault: function() {
  7107. event.defaultPrevented = true;
  7108. },
  7109. defaultPrevented: false
  7110. },
  7111. listenerArgs = concat([event], arguments, 1),
  7112. i, length;
  7113. do {
  7114. namedListeners = scope.$$listeners[name] || empty;
  7115. event.currentScope = scope;
  7116. for (i=0, length=namedListeners.length; i<length; i++) {
  7117. try {
  7118. namedListeners[i].apply(null, listenerArgs);
  7119. if (stopPropagation) return event;
  7120. } catch (e) {
  7121. $exceptionHandler(e);
  7122. }
  7123. }
  7124. //traverse upwards
  7125. scope = scope.$parent;
  7126. } while (scope);
  7127. return event;
  7128. },
  7129. /**
  7130. * @ngdoc function
  7131. * @name ng.$rootScope.Scope#$broadcast
  7132. * @methodOf ng.$rootScope.Scope
  7133. * @function
  7134. *
  7135. * @description
  7136. * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
  7137. * registered {@link ng.$rootScope.Scope#$on} listeners.
  7138. *
  7139. * The event life cycle starts at the scope on which `$broadcast` was called. All
  7140. * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
  7141. * Afterwards, the event propagates to all direct and indirect scopes of the current scope and
  7142. * calls all registered listeners along the way. The event cannot be canceled.
  7143. *
  7144. * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
  7145. * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
  7146. *
  7147. * @param {string} name Event name to emit.
  7148. * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
  7149. * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
  7150. */
  7151. $broadcast: function(name, args) {
  7152. var target = this,
  7153. current = target,
  7154. next = target,
  7155. event = {
  7156. name: name,
  7157. targetScope: target,
  7158. preventDefault: function() {
  7159. event.defaultPrevented = true;
  7160. },
  7161. defaultPrevented: false
  7162. },
  7163. listenerArgs = concat([event], arguments, 1);
  7164. //down while you can, then up and next sibling or up and next sibling until back at root
  7165. do {
  7166. current = next;
  7167. event.currentScope = current;
  7168. forEach(current.$$listeners[name], function(listener) {
  7169. try {
  7170. listener.apply(null, listenerArgs);
  7171. } catch(e) {
  7172. $exceptionHandler(e);
  7173. }
  7174. });
  7175. // Insanity Warning: scope depth-first traversal
  7176. // yes, this code is a bit crazy, but it works and we have tests to prove it!
  7177. // this piece should be kept in sync with the traversal in $digest
  7178. if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
  7179. while(current !== target && !(next = current.$$nextSibling)) {
  7180. current = current.$parent;
  7181. }
  7182. }
  7183. } while ((current = next));
  7184. return event;
  7185. }
  7186. };
  7187. var $rootScope = new Scope();
  7188. return $rootScope;
  7189. function beginPhase(phase) {
  7190. if ($rootScope.$$phase) {
  7191. throw Error($rootScope.$$phase + ' already in progress');
  7192. }
  7193. $rootScope.$$phase = phase;
  7194. }
  7195. function clearPhase() {
  7196. $rootScope.$$phase = null;
  7197. }
  7198. function compileToFn(exp, name) {
  7199. var fn = $parse(exp);
  7200. assertArgFn(fn, name);
  7201. return fn;
  7202. }
  7203. /**
  7204. * function used as an initial value for watchers.
  7205. * because it's uniqueue we can easily tell it apart from other values
  7206. */
  7207. function initWatchVal() {}
  7208. }];
  7209. }
  7210. /**
  7211. * !!! This is an undocumented "private" service !!!
  7212. *
  7213. * @name ng.$sniffer
  7214. * @requires $window
  7215. *
  7216. * @property {boolean} history Does the browser support html5 history api ?
  7217. * @property {boolean} hashchange Does the browser support hashchange event ?
  7218. *
  7219. * @description
  7220. * This is very simple implementation of testing browser's features.
  7221. */
  7222. function $SnifferProvider() {
  7223. this.$get = ['$window', function($window) {
  7224. var eventSupport = {},
  7225. android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]);
  7226. return {
  7227. // Android has history.pushState, but it does not update location correctly
  7228. // so let's not use the history API at all.
  7229. // http://code.google.com/p/android/issues/detail?id=17471
  7230. // https://github.com/angular/angular.js/issues/904
  7231. history: !!($window.history && $window.history.pushState && !(android < 4)),
  7232. hashchange: 'onhashchange' in $window &&
  7233. // IE8 compatible mode lies
  7234. (!$window.document.documentMode || $window.document.documentMode > 7),
  7235. hasEvent: function(event) {
  7236. // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
  7237. // it. In particular the event is not fired when backspace or delete key are pressed or
  7238. // when cut operation is performed.
  7239. if (event == 'input' && msie == 9) return false;
  7240. if (isUndefined(eventSupport[event])) {
  7241. var divElm = $window.document.createElement('div');
  7242. eventSupport[event] = 'on' + event in divElm;
  7243. }
  7244. return eventSupport[event];
  7245. },
  7246. // TODO(i): currently there is no way to feature detect CSP without triggering alerts
  7247. csp: false
  7248. };
  7249. }];
  7250. }
  7251. /**
  7252. * @ngdoc object
  7253. * @name ng.$window
  7254. *
  7255. * @description
  7256. * A reference to the browser's `window` object. While `window`
  7257. * is globally available in JavaScript, it causes testability problems, because
  7258. * it is a global variable. In angular we always refer to it through the
  7259. * `$window` service, so it may be overriden, removed or mocked for testing.
  7260. *
  7261. * All expressions are evaluated with respect to current scope so they don't
  7262. * suffer from window globality.
  7263. *
  7264. * @example
  7265. <doc:example>
  7266. <doc:source>
  7267. <input ng-init="$window = $service('$window'); greeting='Hello World!'" type="text" ng-model="greeting" />
  7268. <button ng-click="$window.alert(greeting)">ALERT</button>
  7269. </doc:source>
  7270. <doc:scenario>
  7271. </doc:scenario>
  7272. </doc:example>
  7273. */
  7274. function $WindowProvider(){
  7275. this.$get = valueFn(window);
  7276. }
  7277. /**
  7278. * Parse headers into key value object
  7279. *
  7280. * @param {string} headers Raw headers as a string
  7281. * @returns {Object} Parsed headers as key value object
  7282. */
  7283. function parseHeaders(headers) {
  7284. var parsed = {}, key, val, i;
  7285. if (!headers) return parsed;
  7286. forEach(headers.split('\n'), function(line) {
  7287. i = line.indexOf(':');
  7288. key = lowercase(trim(line.substr(0, i)));
  7289. val = trim(line.substr(i + 1));
  7290. if (key) {
  7291. if (parsed[key]) {
  7292. parsed[key] += ', ' + val;
  7293. } else {
  7294. parsed[key] = val;
  7295. }
  7296. }
  7297. });
  7298. return parsed;
  7299. }
  7300. /**
  7301. * Returns a function that provides access to parsed headers.
  7302. *
  7303. * Headers are lazy parsed when first requested.
  7304. * @see parseHeaders
  7305. *
  7306. * @param {(string|Object)} headers Headers to provide access to.
  7307. * @returns {function(string=)} Returns a getter function which if called with:
  7308. *
  7309. * - if called with single an argument returns a single header value or null
  7310. * - if called with no arguments returns an object containing all headers.
  7311. */
  7312. function headersGetter(headers) {
  7313. var headersObj = isObject(headers) ? headers : undefined;
  7314. return function(name) {
  7315. if (!headersObj) headersObj = parseHeaders(headers);
  7316. if (name) {
  7317. return headersObj[lowercase(name)] || null;
  7318. }
  7319. return headersObj;
  7320. };
  7321. }
  7322. /**
  7323. * Chain all given functions
  7324. *
  7325. * This function is used for both request and response transforming
  7326. *
  7327. * @param {*} data Data to transform.
  7328. * @param {function(string=)} headers Http headers getter fn.
  7329. * @param {(function|Array.<function>)} fns Function or an array of functions.
  7330. * @returns {*} Transformed data.
  7331. */
  7332. function transformData(data, headers, fns) {
  7333. if (isFunction(fns))
  7334. return fns(data, headers);
  7335. forEach(fns, function(fn) {
  7336. data = fn(data, headers);
  7337. });
  7338. return data;
  7339. }
  7340. function isSuccess(status) {
  7341. return 200 <= status && status < 300;
  7342. }
  7343. function $HttpProvider() {
  7344. var JSON_START = /^\s*(\[|\{[^\{])/,
  7345. JSON_END = /[\}\]]\s*$/,
  7346. PROTECTION_PREFIX = /^\)\]\}',?\n/;
  7347. var $config = this.defaults = {
  7348. // transform incoming response data
  7349. transformResponse: [function(data) {
  7350. if (isString(data)) {
  7351. // strip json vulnerability protection prefix
  7352. data = data.replace(PROTECTION_PREFIX, '');
  7353. if (JSON_START.test(data) && JSON_END.test(data))
  7354. data = fromJson(data, true);
  7355. }
  7356. return data;
  7357. }],
  7358. // transform outgoing request data
  7359. transformRequest: [function(d) {
  7360. return isObject(d) && !isFile(d) ? toJson(d) : d;
  7361. }],
  7362. // default headers
  7363. headers: {
  7364. common: {
  7365. 'Accept': 'application/json, text/plain, */*',
  7366. 'X-Requested-With': 'XMLHttpRequest'
  7367. },
  7368. post: {'Content-Type': 'application/json;charset=utf-8'},
  7369. put: {'Content-Type': 'application/json;charset=utf-8'}
  7370. }
  7371. };
  7372. var providerResponseInterceptors = this.responseInterceptors = [];
  7373. this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
  7374. function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
  7375. var defaultCache = $cacheFactory('$http'),
  7376. responseInterceptors = [];
  7377. forEach(providerResponseInterceptors, function(interceptor) {
  7378. responseInterceptors.push(
  7379. isString(interceptor)
  7380. ? $injector.get(interceptor)
  7381. : $injector.invoke(interceptor)
  7382. );
  7383. });
  7384. /**
  7385. * @ngdoc function
  7386. * @name ng.$http
  7387. * @requires $httpBacked
  7388. * @requires $browser
  7389. * @requires $cacheFactory
  7390. * @requires $rootScope
  7391. * @requires $q
  7392. * @requires $injector
  7393. *
  7394. * @description
  7395. * The `$http` service is a core Angular service that facilitates communication with the remote
  7396. * HTTP servers via browser's {@link https://developer.mozilla.org/en/xmlhttprequest
  7397. * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}.
  7398. *
  7399. * For unit testing applications that use `$http` service, see
  7400. * {@link ngMock.$httpBackend $httpBackend mock}.
  7401. *
  7402. * For a higher level of abstraction, please check out the {@link ngResource.$resource
  7403. * $resource} service.
  7404. *
  7405. * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
  7406. * the $q service. While for simple usage patters this doesn't matter much, for advanced usage,
  7407. * it is important to familiarize yourself with these apis and guarantees they provide.
  7408. *
  7409. *
  7410. * # General usage
  7411. * The `$http` service is a function which takes a single argument — a configuration object —
  7412. * that is used to generate an http request and returns a {@link ng.$q promise}
  7413. * with two $http specific methods: `success` and `error`.
  7414. *
  7415. * <pre>
  7416. * $http({method: 'GET', url: '/someUrl'}).
  7417. * success(function(data, status, headers, config) {
  7418. * // this callback will be called asynchronously
  7419. * // when the response is available
  7420. * }).
  7421. * error(function(data, status, headers, config) {
  7422. * // called asynchronously if an error occurs
  7423. * // or server returns response with status
  7424. * // code outside of the <200, 400) range
  7425. * });
  7426. * </pre>
  7427. *
  7428. * Since the returned value of calling the $http function is a Promise object, you can also use
  7429. * the `then` method to register callbacks, and these callbacks will receive a single argument –
  7430. * an object representing the response. See the api signature and type info below for more
  7431. * details.
  7432. *
  7433. *
  7434. * # Shortcut methods
  7435. *
  7436. * Since all invocation of the $http service require definition of the http method and url and
  7437. * POST and PUT requests require response body/data to be provided as well, shortcut methods
  7438. * were created to simplify using the api:
  7439. *
  7440. * <pre>
  7441. * $http.get('/someUrl').success(successCallback);
  7442. * $http.post('/someUrl', data).success(successCallback);
  7443. * </pre>
  7444. *
  7445. * Complete list of shortcut methods:
  7446. *
  7447. * - {@link ng.$http#get $http.get}
  7448. * - {@link ng.$http#head $http.head}
  7449. * - {@link ng.$http#post $http.post}
  7450. * - {@link ng.$http#put $http.put}
  7451. * - {@link ng.$http#delete $http.delete}
  7452. * - {@link ng.$http#jsonp $http.jsonp}
  7453. *
  7454. *
  7455. * # Setting HTTP Headers
  7456. *
  7457. * The $http service will automatically add certain http headers to all requests. These defaults
  7458. * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
  7459. * object, which currently contains this default configuration:
  7460. *
  7461. * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
  7462. * - `Accept: application/json, text/plain, * / *`
  7463. * - `X-Requested-With: XMLHttpRequest`
  7464. * - `$httpProvider.defaults.headers.post`: (header defaults for HTTP POST requests)
  7465. * - `Content-Type: application/json`
  7466. * - `$httpProvider.defaults.headers.put` (header defaults for HTTP PUT requests)
  7467. * - `Content-Type: application/json`
  7468. *
  7469. * To add or overwrite these defaults, simply add or remove a property from this configuration
  7470. * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
  7471. * with name equal to the lower-cased http method name, e.g.
  7472. * `$httpProvider.defaults.headers.get['My-Header']='value'`.
  7473. *
  7474. * Additionally, the defaults can be set at runtime via the `$http.defaults` object in a similar
  7475. * fassion as described above.
  7476. *
  7477. *
  7478. * # Transforming Requests and Responses
  7479. *
  7480. * Both requests and responses can be transformed using transform functions. By default, Angular
  7481. * applies these transformations:
  7482. *
  7483. * Request transformations:
  7484. *
  7485. * - if the `data` property of the request config object contains an object, serialize it into
  7486. * JSON format.
  7487. *
  7488. * Response transformations:
  7489. *
  7490. * - if XSRF prefix is detected, strip it (see Security Considerations section below)
  7491. * - if json response is detected, deserialize it using a JSON parser
  7492. *
  7493. * To override these transformation locally, specify transform functions as `transformRequest`
  7494. * and/or `transformResponse` properties of the config object. To globally override the default
  7495. * transforms, override the `$httpProvider.defaults.transformRequest` and
  7496. * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`.
  7497. *
  7498. *
  7499. * # Caching
  7500. *
  7501. * To enable caching set the configuration property `cache` to `true`. When the cache is
  7502. * enabled, `$http` stores the response from the server in local cache. Next time the
  7503. * response is served from the cache without sending a request to the server.
  7504. *
  7505. * Note that even if the response is served from cache, delivery of the data is asynchronous in
  7506. * the same way that real requests are.
  7507. *
  7508. * If there are multiple GET requests for the same url that should be cached using the same
  7509. * cache, but the cache is not populated yet, only one request to the server will be made and
  7510. * the remaining requests will be fulfilled using the response for the first request.
  7511. *
  7512. *
  7513. * # Response interceptors
  7514. *
  7515. * Before you start creating interceptors, be sure to understand the
  7516. * {@link ng.$q $q and deferred/promise APIs}.
  7517. *
  7518. * For purposes of global error handling, authentication or any kind of synchronous or
  7519. * asynchronous preprocessing of received responses, it is desirable to be able to intercept
  7520. * responses for http requests before they are handed over to the application code that
  7521. * initiated these requests. The response interceptors leverage the {@link ng.$q
  7522. * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing.
  7523. *
  7524. * The interceptors are service factories that are registered with the $httpProvider by
  7525. * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
  7526. * injected with dependencies (if specified) and returns the interceptor — a function that
  7527. * takes a {@link ng.$q promise} and returns the original or a new promise.
  7528. *
  7529. * <pre>
  7530. * // register the interceptor as a service
  7531. * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
  7532. * return function(promise) {
  7533. * return promise.then(function(response) {
  7534. * // do something on success
  7535. * }, function(response) {
  7536. * // do something on error
  7537. * if (canRecover(response)) {
  7538. * return responseOrNewPromise
  7539. * }
  7540. * return $q.reject(response);
  7541. * });
  7542. * }
  7543. * });
  7544. *
  7545. * $httpProvider.responseInterceptors.push('myHttpInterceptor');
  7546. *
  7547. *
  7548. * // register the interceptor via an anonymous factory
  7549. * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
  7550. * return function(promise) {
  7551. * // same as above
  7552. * }
  7553. * });
  7554. * </pre>
  7555. *
  7556. *
  7557. * # Security Considerations
  7558. *
  7559. * When designing web applications, consider security threats from:
  7560. *
  7561. * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
  7562. * JSON Vulnerability}
  7563. * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}
  7564. *
  7565. * Both server and the client must cooperate in order to eliminate these threats. Angular comes
  7566. * pre-configured with strategies that address these issues, but for this to work backend server
  7567. * cooperation is required.
  7568. *
  7569. * ## JSON Vulnerability Protection
  7570. *
  7571. * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
  7572. * JSON Vulnerability} allows third party web-site to turn your JSON resource URL into
  7573. * {@link http://en.wikipedia.org/wiki/JSON#JSONP JSONP} request under some conditions. To
  7574. * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
  7575. * Angular will automatically strip the prefix before processing it as JSON.
  7576. *
  7577. * For example if your server needs to return:
  7578. * <pre>
  7579. * ['one','two']
  7580. * </pre>
  7581. *
  7582. * which is vulnerable to attack, your server can return:
  7583. * <pre>
  7584. * )]}',
  7585. * ['one','two']
  7586. * </pre>
  7587. *
  7588. * Angular will strip the prefix, before processing the JSON.
  7589. *
  7590. *
  7591. * ## Cross Site Request Forgery (XSRF) Protection
  7592. *
  7593. * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
  7594. * an unauthorized site can gain your user's private data. Angular provides following mechanism
  7595. * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
  7596. * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
  7597. * runs on your domain could read the cookie, your server can be assured that the XHR came from
  7598. * JavaScript running on your domain.
  7599. *
  7600. * To take advantage of this, your server needs to set a token in a JavaScript readable session
  7601. * cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
  7602. * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
  7603. * that only JavaScript running on your domain could have read the token. The token must be
  7604. * unique for each user and must be verifiable by the server (to prevent the JavaScript making
  7605. * up its own tokens). We recommend that the token is a digest of your site's authentication
  7606. * cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
  7607. *
  7608. *
  7609. * @param {object} config Object describing the request to be made and how it should be
  7610. * processed. The object has following properties:
  7611. *
  7612. * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
  7613. * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
  7614. * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be turned to
  7615. * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
  7616. * - **data** – `{string|Object}` – Data to be sent as the request message data.
  7617. * - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
  7618. * - **transformRequest** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
  7619. * transform function or an array of such functions. The transform function takes the http
  7620. * request body and headers and returns its transformed (typically serialized) version.
  7621. * - **transformResponse** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
  7622. * transform function or an array of such functions. The transform function takes the http
  7623. * response body and headers and returns its transformed (typically deserialized) version.
  7624. * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
  7625. * GET request, otherwise if a cache instance built with
  7626. * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
  7627. * caching.
  7628. * - **timeout** – `{number}` – timeout in milliseconds.
  7629. * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
  7630. * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
  7631. * requests with credentials} for more information.
  7632. *
  7633. * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
  7634. * standard `then` method and two http specific methods: `success` and `error`. The `then`
  7635. * method takes two arguments a success and an error callback which will be called with a
  7636. * response object. The `success` and `error` methods take a single argument - a function that
  7637. * will be called when the request succeeds or fails respectively. The arguments passed into
  7638. * these functions are destructured representation of the response object passed into the
  7639. * `then` method. The response object has these properties:
  7640. *
  7641. * - **data** – `{string|Object}` – The response body transformed with the transform functions.
  7642. * - **status** – `{number}` – HTTP status code of the response.
  7643. * - **headers** – `{function([headerName])}` – Header getter function.
  7644. * - **config** – `{Object}` – The configuration object that was used to generate the request.
  7645. *
  7646. * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
  7647. * requests. This is primarily meant to be used for debugging purposes.
  7648. *
  7649. *
  7650. * @example
  7651. <example>
  7652. <file name="index.html">
  7653. <div ng-controller="FetchCtrl">
  7654. <select ng-model="method">
  7655. <option>GET</option>
  7656. <option>JSONP</option>
  7657. </select>
  7658. <input type="text" ng-model="url" size="80"/>
  7659. <button ng-click="fetch()">fetch</button><br>
  7660. <button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
  7661. <button ng-click="updateModel('JSONP', 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">Sample JSONP</button>
  7662. <button ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">Invalid JSONP</button>
  7663. <pre>http status code: {{status}}</pre>
  7664. <pre>http response data: {{data}}</pre>
  7665. </div>
  7666. </file>
  7667. <file name="script.js">
  7668. function FetchCtrl($scope, $http, $templateCache) {
  7669. $scope.method = 'GET';
  7670. $scope.url = 'http-hello.html';
  7671. $scope.fetch = function() {
  7672. $scope.code = null;
  7673. $scope.response = null;
  7674. $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
  7675. success(function(data, status) {
  7676. $scope.status = status;
  7677. $scope.data = data;
  7678. }).
  7679. error(function(data, status) {
  7680. $scope.data = data || "Request failed";
  7681. $scope.status = status;
  7682. });
  7683. };
  7684. $scope.updateModel = function(method, url) {
  7685. $scope.method = method;
  7686. $scope.url = url;
  7687. };
  7688. }
  7689. </file>
  7690. <file name="http-hello.html">
  7691. Hello, $http!
  7692. </file>
  7693. <file name="scenario.js">
  7694. it('should make an xhr GET request', function() {
  7695. element(':button:contains("Sample GET")').click();
  7696. element(':button:contains("fetch")').click();
  7697. expect(binding('status')).toBe('200');
  7698. expect(binding('data')).toMatch(/Hello, \$http!/);
  7699. });
  7700. it('should make a JSONP request to angularjs.org', function() {
  7701. element(':button:contains("Sample JSONP")').click();
  7702. element(':button:contains("fetch")').click();
  7703. expect(binding('status')).toBe('200');
  7704. expect(binding('data')).toMatch(/Super Hero!/);
  7705. });
  7706. it('should make JSONP request to invalid URL and invoke the error handler',
  7707. function() {
  7708. element(':button:contains("Invalid JSONP")').click();
  7709. element(':button:contains("fetch")').click();
  7710. expect(binding('status')).toBe('0');
  7711. expect(binding('data')).toBe('Request failed');
  7712. });
  7713. </file>
  7714. </example>
  7715. */
  7716. function $http(config) {
  7717. config.method = uppercase(config.method);
  7718. var reqTransformFn = config.transformRequest || $config.transformRequest,
  7719. respTransformFn = config.transformResponse || $config.transformResponse,
  7720. defHeaders = $config.headers,
  7721. reqHeaders = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
  7722. defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
  7723. reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
  7724. promise;
  7725. // strip content-type if data is undefined
  7726. if (isUndefined(config.data)) {
  7727. delete reqHeaders['Content-Type'];
  7728. }
  7729. // send request
  7730. promise = sendReq(config, reqData, reqHeaders);
  7731. // transform future response
  7732. promise = promise.then(transformResponse, transformResponse);
  7733. // apply interceptors
  7734. forEach(responseInterceptors, function(interceptor) {
  7735. promise = interceptor(promise);
  7736. });
  7737. promise.success = function(fn) {
  7738. promise.then(function(response) {
  7739. fn(response.data, response.status, response.headers, config);
  7740. });
  7741. return promise;
  7742. };
  7743. promise.error = function(fn) {
  7744. promise.then(null, function(response) {
  7745. fn(response.data, response.status, response.headers, config);
  7746. });
  7747. return promise;
  7748. };
  7749. return promise;
  7750. function transformResponse(response) {
  7751. // make a copy since the response must be cacheable
  7752. var resp = extend({}, response, {
  7753. data: transformData(response.data, response.headers, respTransformFn)
  7754. });
  7755. return (isSuccess(response.status))
  7756. ? resp
  7757. : $q.reject(resp);
  7758. }
  7759. }
  7760. $http.pendingRequests = [];
  7761. /**
  7762. * @ngdoc method
  7763. * @name ng.$http#get
  7764. * @methodOf ng.$http
  7765. *
  7766. * @description
  7767. * Shortcut method to perform `GET` request
  7768. *
  7769. * @param {string} url Relative or absolute URL specifying the destination of the request
  7770. * @param {Object=} config Optional configuration object
  7771. * @returns {HttpPromise} Future object
  7772. */
  7773. /**
  7774. * @ngdoc method
  7775. * @name ng.$http#delete
  7776. * @methodOf ng.$http
  7777. *
  7778. * @description
  7779. * Shortcut method to perform `DELETE` request
  7780. *
  7781. * @param {string} url Relative or absolute URL specifying the destination of the request
  7782. * @param {Object=} config Optional configuration object
  7783. * @returns {HttpPromise} Future object
  7784. */
  7785. /**
  7786. * @ngdoc method
  7787. * @name ng.$http#head
  7788. * @methodOf ng.$http
  7789. *
  7790. * @description
  7791. * Shortcut method to perform `HEAD` request
  7792. *
  7793. * @param {string} url Relative or absolute URL specifying the destination of the request
  7794. * @param {Object=} config Optional configuration object
  7795. * @returns {HttpPromise} Future object
  7796. */
  7797. /**
  7798. * @ngdoc method
  7799. * @name ng.$http#jsonp
  7800. * @methodOf ng.$http
  7801. *
  7802. * @description
  7803. * Shortcut method to perform `JSONP` request
  7804. *
  7805. * @param {string} url Relative or absolute URL specifying the destination of the request.
  7806. * Should contain `JSON_CALLBACK` string.
  7807. * @param {Object=} config Optional configuration object
  7808. * @returns {HttpPromise} Future object
  7809. */
  7810. createShortMethods('get', 'delete', 'head', 'jsonp');
  7811. /**
  7812. * @ngdoc method
  7813. * @name ng.$http#post
  7814. * @methodOf ng.$http
  7815. *
  7816. * @description
  7817. * Shortcut method to perform `POST` request
  7818. *
  7819. * @param {string} url Relative or absolute URL specifying the destination of the request
  7820. * @param {*} data Request content
  7821. * @param {Object=} config Optional configuration object
  7822. * @returns {HttpPromise} Future object
  7823. */
  7824. /**
  7825. * @ngdoc method
  7826. * @name ng.$http#put
  7827. * @methodOf ng.$http
  7828. *
  7829. * @description
  7830. * Shortcut method to perform `PUT` request
  7831. *
  7832. * @param {string} url Relative or absolute URL specifying the destination of the request
  7833. * @param {*} data Request content
  7834. * @param {Object=} config Optional configuration object
  7835. * @returns {HttpPromise} Future object
  7836. */
  7837. createShortMethodsWithData('post', 'put');
  7838. /**
  7839. * @ngdoc property
  7840. * @name ng.$http#defaults
  7841. * @propertyOf ng.$http
  7842. *
  7843. * @description
  7844. * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
  7845. * default headers as well as request and response transformations.
  7846. *
  7847. * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
  7848. */
  7849. $http.defaults = $config;
  7850. return $http;
  7851. function createShortMethods(names) {
  7852. forEach(arguments, function(name) {
  7853. $http[name] = function(url, config) {
  7854. return $http(extend(config || {}, {
  7855. method: name,
  7856. url: url
  7857. }));
  7858. };
  7859. });
  7860. }
  7861. function createShortMethodsWithData(name) {
  7862. forEach(arguments, function(name) {
  7863. $http[name] = function(url, data, config) {
  7864. return $http(extend(config || {}, {
  7865. method: name,
  7866. url: url,
  7867. data: data
  7868. }));
  7869. };
  7870. });
  7871. }
  7872. /**
  7873. * Makes the request
  7874. *
  7875. * !!! ACCESSES CLOSURE VARS:
  7876. * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
  7877. */
  7878. function sendReq(config, reqData, reqHeaders) {
  7879. var deferred = $q.defer(),
  7880. promise = deferred.promise,
  7881. cache,
  7882. cachedResp,
  7883. url = buildUrl(config.url, config.params);
  7884. $http.pendingRequests.push(config);
  7885. promise.then(removePendingReq, removePendingReq);
  7886. if (config.cache && config.method == 'GET') {
  7887. cache = isObject(config.cache) ? config.cache : defaultCache;
  7888. }
  7889. if (cache) {
  7890. cachedResp = cache.get(url);
  7891. if (cachedResp) {
  7892. if (cachedResp.then) {
  7893. // cached request has already been sent, but there is no response yet
  7894. cachedResp.then(removePendingReq, removePendingReq);
  7895. return cachedResp;
  7896. } else {
  7897. // serving from cache
  7898. if (isArray(cachedResp)) {
  7899. resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
  7900. } else {
  7901. resolvePromise(cachedResp, 200, {});
  7902. }
  7903. }
  7904. } else {
  7905. // put the promise for the non-transformed response into cache as a placeholder
  7906. cache.put(url, promise);
  7907. }
  7908. }
  7909. // if we won't have the response in cache, send the request to the backend
  7910. if (!cachedResp) {
  7911. $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
  7912. config.withCredentials);
  7913. }
  7914. return promise;
  7915. /**
  7916. * Callback registered to $httpBackend():
  7917. * - caches the response if desired
  7918. * - resolves the raw $http promise
  7919. * - calls $apply
  7920. */
  7921. function done(status, response, headersString) {
  7922. if (cache) {
  7923. if (isSuccess(status)) {
  7924. cache.put(url, [status, response, parseHeaders(headersString)]);
  7925. } else {
  7926. // remove promise from the cache
  7927. cache.remove(url);
  7928. }
  7929. }
  7930. resolvePromise(response, status, headersString);
  7931. $rootScope.$apply();
  7932. }
  7933. /**
  7934. * Resolves the raw $http promise.
  7935. */
  7936. function resolvePromise(response, status, headers) {
  7937. // normalize internal statuses to 0
  7938. status = Math.max(status, 0);
  7939. (isSuccess(status) ? deferred.resolve : deferred.reject)({
  7940. data: response,
  7941. status: status,
  7942. headers: headersGetter(headers),
  7943. config: config
  7944. });
  7945. }
  7946. function removePendingReq() {
  7947. var idx = indexOf($http.pendingRequests, config);
  7948. if (idx !== -1) $http.pendingRequests.splice(idx, 1);
  7949. }
  7950. }
  7951. function buildUrl(url, params) {
  7952. if (!params) return url;
  7953. var parts = [];
  7954. forEachSorted(params, function(value, key) {
  7955. if (value == null || value == undefined) return;
  7956. if (isObject(value)) {
  7957. value = toJson(value);
  7958. }
  7959. parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
  7960. });
  7961. return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
  7962. }
  7963. }];
  7964. }
  7965. var XHR = window.XMLHttpRequest || function() {
  7966. try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
  7967. try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
  7968. try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
  7969. throw new Error("This browser does not support XMLHttpRequest.");
  7970. };
  7971. /**
  7972. * @ngdoc object
  7973. * @name ng.$httpBackend
  7974. * @requires $browser
  7975. * @requires $window
  7976. * @requires $document
  7977. *
  7978. * @description
  7979. * HTTP backend used by the {@link ng.$http service} that delegates to
  7980. * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
  7981. *
  7982. * You should never need to use this service directly, instead use the higher-level abstractions:
  7983. * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
  7984. *
  7985. * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
  7986. * $httpBackend} which can be trained with responses.
  7987. */
  7988. function $HttpBackendProvider() {
  7989. this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
  7990. return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks,
  7991. $document[0], $window.location.protocol.replace(':', ''));
  7992. }];
  7993. }
  7994. function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
  7995. // TODO(vojta): fix the signature
  7996. return function(method, url, post, callback, headers, timeout, withCredentials) {
  7997. $browser.$$incOutstandingRequestCount();
  7998. url = url || $browser.url();
  7999. if (lowercase(method) == 'jsonp') {
  8000. var callbackId = '_' + (callbacks.counter++).toString(36);
  8001. callbacks[callbackId] = function(data) {
  8002. callbacks[callbackId].data = data;
  8003. };
  8004. jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
  8005. function() {
  8006. if (callbacks[callbackId].data) {
  8007. completeRequest(callback, 200, callbacks[callbackId].data);
  8008. } else {
  8009. completeRequest(callback, -2);
  8010. }
  8011. delete callbacks[callbackId];
  8012. });
  8013. } else {
  8014. var xhr = new XHR();
  8015. xhr.open(method, url, true);
  8016. forEach(headers, function(value, key) {
  8017. if (value) xhr.setRequestHeader(key, value);
  8018. });
  8019. var status;
  8020. // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
  8021. // response is in the cache. the promise api will ensure that to the app code the api is
  8022. // always async
  8023. xhr.onreadystatechange = function() {
  8024. if (xhr.readyState == 4) {
  8025. completeRequest(
  8026. callback, status || xhr.status, xhr.responseText, xhr.getAllResponseHeaders());
  8027. }
  8028. };
  8029. if (withCredentials) {
  8030. xhr.withCredentials = true;
  8031. }
  8032. xhr.send(post || '');
  8033. if (timeout > 0) {
  8034. $browserDefer(function() {
  8035. status = -1;
  8036. xhr.abort();
  8037. }, timeout);
  8038. }
  8039. }
  8040. function completeRequest(callback, status, response, headersString) {
  8041. // URL_MATCH is defined in src/service/location.js
  8042. var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1];
  8043. // fix status code for file protocol (it's always 0)
  8044. status = (protocol == 'file') ? (response ? 200 : 404) : status;
  8045. // normalize IE bug (http://bugs.jquery.com/ticket/1450)
  8046. status = status == 1223 ? 204 : status;
  8047. callback(status, response, headersString);
  8048. $browser.$$completeOutstandingRequest(noop);
  8049. }
  8050. };
  8051. function jsonpReq(url, done) {
  8052. // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
  8053. // - fetches local scripts via XHR and evals them
  8054. // - adds and immediately removes script elements from the document
  8055. var script = rawDocument.createElement('script'),
  8056. doneWrapper = function() {
  8057. rawDocument.body.removeChild(script);
  8058. if (done) done();
  8059. };
  8060. script.type = 'text/javascript';
  8061. script.src = url;
  8062. if (msie) {
  8063. script.onreadystatechange = function() {
  8064. if (/loaded|complete/.test(script.readyState)) doneWrapper();
  8065. };
  8066. } else {
  8067. script.onload = script.onerror = doneWrapper;
  8068. }
  8069. rawDocument.body.appendChild(script);
  8070. }
  8071. }
  8072. /**
  8073. * @ngdoc object
  8074. * @name ng.$locale
  8075. *
  8076. * @description
  8077. * $locale service provides localization rules for various Angular components. As of right now the
  8078. * only public api is:
  8079. *
  8080. * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
  8081. */
  8082. function $LocaleProvider(){
  8083. this.$get = function() {
  8084. return {
  8085. id: 'en-us',
  8086. NUMBER_FORMATS: {
  8087. DECIMAL_SEP: '.',
  8088. GROUP_SEP: ',',
  8089. PATTERNS: [
  8090. { // Decimal Pattern
  8091. minInt: 1,
  8092. minFrac: 0,
  8093. maxFrac: 3,
  8094. posPre: '',
  8095. posSuf: '',
  8096. negPre: '-',
  8097. negSuf: '',
  8098. gSize: 3,
  8099. lgSize: 3
  8100. },{ //Currency Pattern
  8101. minInt: 1,
  8102. minFrac: 2,
  8103. maxFrac: 2,
  8104. posPre: '\u00A4',
  8105. posSuf: '',
  8106. negPre: '(\u00A4',
  8107. negSuf: ')',
  8108. gSize: 3,
  8109. lgSize: 3
  8110. }
  8111. ],
  8112. CURRENCY_SYM: '$'
  8113. },
  8114. DATETIME_FORMATS: {
  8115. MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
  8116. .split(','),
  8117. SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
  8118. DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
  8119. SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
  8120. AMPMS: ['AM','PM'],
  8121. medium: 'MMM d, y h:mm:ss a',
  8122. short: 'M/d/yy h:mm a',
  8123. fullDate: 'EEEE, MMMM d, y',
  8124. longDate: 'MMMM d, y',
  8125. mediumDate: 'MMM d, y',
  8126. shortDate: 'M/d/yy',
  8127. mediumTime: 'h:mm:ss a',
  8128. shortTime: 'h:mm a'
  8129. },
  8130. pluralCat: function(num) {
  8131. if (num === 1) {
  8132. return 'one';
  8133. }
  8134. return 'other';
  8135. }
  8136. };
  8137. };
  8138. }
  8139. function $TimeoutProvider() {
  8140. this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
  8141. function($rootScope, $browser, $q, $exceptionHandler) {
  8142. var deferreds = {};
  8143. /**
  8144. * @ngdoc function
  8145. * @name ng.$timeout
  8146. * @requires $browser
  8147. *
  8148. * @description
  8149. * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
  8150. * block and delegates any exceptions to
  8151. * {@link ng.$exceptionHandler $exceptionHandler} service.
  8152. *
  8153. * The return value of registering a timeout function is a promise which will be resolved when
  8154. * the timeout is reached and the timeout function is executed.
  8155. *
  8156. * To cancel a the timeout request, call `$timeout.cancel(promise)`.
  8157. *
  8158. * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
  8159. * synchronously flush the queue of deferred functions.
  8160. *
  8161. * @param {function()} fn A function, who's execution should be delayed.
  8162. * @param {number=} [delay=0] Delay in milliseconds.
  8163. * @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise
  8164. * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
  8165. * @returns {*} Promise that will be resolved when the timeout is reached. The value this
  8166. * promise will be resolved with is the return value of the `fn` function.
  8167. */
  8168. function timeout(fn, delay, invokeApply) {
  8169. var deferred = $q.defer(),
  8170. promise = deferred.promise,
  8171. skipApply = (isDefined(invokeApply) && !invokeApply),
  8172. timeoutId, cleanup;
  8173. timeoutId = $browser.defer(function() {
  8174. try {
  8175. deferred.resolve(fn());
  8176. } catch(e) {
  8177. deferred.reject(e);
  8178. $exceptionHandler(e);
  8179. }
  8180. if (!skipApply) $rootScope.$apply();
  8181. }, delay);
  8182. cleanup = function() {
  8183. delete deferreds[promise.$$timeoutId];
  8184. };
  8185. promise.$$timeoutId = timeoutId;
  8186. deferreds[timeoutId] = deferred;
  8187. promise.then(cleanup, cleanup);
  8188. return promise;
  8189. }
  8190. /**
  8191. * @ngdoc function
  8192. * @name ng.$timeout#cancel
  8193. * @methodOf ng.$timeout
  8194. *
  8195. * @description
  8196. * Cancels a task associated with the `promise`. As a result of this the promise will be
  8197. * resolved with a rejection.
  8198. *
  8199. * @param {Promise=} promise Promise returned by the `$timeout` function.
  8200. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
  8201. * canceled.
  8202. */
  8203. timeout.cancel = function(promise) {
  8204. if (promise && promise.$$timeoutId in deferreds) {
  8205. deferreds[promise.$$timeoutId].reject('canceled');
  8206. return $browser.defer.cancel(promise.$$timeoutId);
  8207. }
  8208. return false;
  8209. };
  8210. return timeout;
  8211. }];
  8212. }
  8213. /**
  8214. * @ngdoc object
  8215. * @name ng.$filterProvider
  8216. * @description
  8217. *
  8218. * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To
  8219. * achieve this a filter definition consists of a factory function which is annotated with dependencies and is
  8220. * responsible for creating a the filter function.
  8221. *
  8222. * <pre>
  8223. * // Filter registration
  8224. * function MyModule($provide, $filterProvider) {
  8225. * // create a service to demonstrate injection (not always needed)
  8226. * $provide.value('greet', function(name){
  8227. * return 'Hello ' + name + '!';
  8228. * });
  8229. *
  8230. * // register a filter factory which uses the
  8231. * // greet service to demonstrate DI.
  8232. * $filterProvider.register('greet', function(greet){
  8233. * // return the filter function which uses the greet service
  8234. * // to generate salutation
  8235. * return function(text) {
  8236. * // filters need to be forgiving so check input validity
  8237. * return text && greet(text) || text;
  8238. * };
  8239. * });
  8240. * }
  8241. * </pre>
  8242. *
  8243. * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`.
  8244. * <pre>
  8245. * it('should be the same instance', inject(
  8246. * function($filterProvider) {
  8247. * $filterProvider.register('reverse', function(){
  8248. * return ...;
  8249. * });
  8250. * },
  8251. * function($filter, reverseFilter) {
  8252. * expect($filter('reverse')).toBe(reverseFilter);
  8253. * });
  8254. * </pre>
  8255. *
  8256. *
  8257. * For more information about how angular filters work, and how to create your own filters, see
  8258. * {@link guide/dev_guide.views.filters Understanding Angular Filters} in the angular Developer
  8259. * Guide.
  8260. */
  8261. /**
  8262. * @ngdoc method
  8263. * @name ng.$filterProvider#register
  8264. * @methodOf ng.$filterProvider
  8265. * @description
  8266. * Register filter factory function.
  8267. *
  8268. * @param {String} name Name of the filter.
  8269. * @param {function} fn The filter factory function which is injectable.
  8270. */
  8271. /**
  8272. * @ngdoc function
  8273. * @name ng.$filter
  8274. * @function
  8275. * @description
  8276. * Filters are used for formatting data displayed to the user.
  8277. *
  8278. * The general syntax in templates is as follows:
  8279. *
  8280. * {{ expression | [ filter_name ] }}
  8281. *
  8282. * @param {String} name Name of the filter function to retrieve
  8283. * @return {Function} the filter function
  8284. */
  8285. $FilterProvider.$inject = ['$provide'];
  8286. function $FilterProvider($provide) {
  8287. var suffix = 'Filter';
  8288. function register(name, factory) {
  8289. return $provide.factory(name + suffix, factory);
  8290. }
  8291. this.register = register;
  8292. this.$get = ['$injector', function($injector) {
  8293. return function(name) {
  8294. return $injector.get(name + suffix);
  8295. }
  8296. }];
  8297. ////////////////////////////////////////
  8298. register('currency', currencyFilter);
  8299. register('date', dateFilter);
  8300. register('filter', filterFilter);
  8301. register('json', jsonFilter);
  8302. register('limitTo', limitToFilter);
  8303. register('lowercase', lowercaseFilter);
  8304. register('number', numberFilter);
  8305. register('orderBy', orderByFilter);
  8306. register('uppercase', uppercaseFilter);
  8307. }
  8308. /**
  8309. * @ngdoc filter
  8310. * @name ng.filter:filter
  8311. * @function
  8312. *
  8313. * @description
  8314. * Selects a subset of items from `array` and returns it as a new array.
  8315. *
  8316. * Note: This function is used to augment the `Array` type in Angular expressions. See
  8317. * {@link ng.$filter} for more information about Angular arrays.
  8318. *
  8319. * @param {Array} array The source array.
  8320. * @param {string|Object|function()} expression The predicate to be used for selecting items from
  8321. * `array`.
  8322. *
  8323. * Can be one of:
  8324. *
  8325. * - `string`: Predicate that results in a substring match using the value of `expression`
  8326. * string. All strings or objects with string properties in `array` that contain this string
  8327. * will be returned. The predicate can be negated by prefixing the string with `!`.
  8328. *
  8329. * - `Object`: A pattern object can be used to filter specific properties on objects contained
  8330. * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
  8331. * which have property `name` containing "M" and property `phone` containing "1". A special
  8332. * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
  8333. * property of the object. That's equivalent to the simple substring match with a `string`
  8334. * as described above.
  8335. *
  8336. * - `function`: A predicate function can be used to write arbitrary filters. The function is
  8337. * called for each element of `array`. The final result is an array of those elements that
  8338. * the predicate returned true for.
  8339. *
  8340. * @example
  8341. <doc:example>
  8342. <doc:source>
  8343. <div ng-init="friends = [{name:'John', phone:'555-1276'},
  8344. {name:'Mary', phone:'800-BIG-MARY'},
  8345. {name:'Mike', phone:'555-4321'},
  8346. {name:'Adam', phone:'555-5678'},
  8347. {name:'Julie', phone:'555-8765'}]"></div>
  8348. Search: <input ng-model="searchText">
  8349. <table id="searchTextResults">
  8350. <tr><th>Name</th><th>Phone</th><tr>
  8351. <tr ng-repeat="friend in friends | filter:searchText">
  8352. <td>{{friend.name}}</td>
  8353. <td>{{friend.phone}}</td>
  8354. <tr>
  8355. </table>
  8356. <hr>
  8357. Any: <input ng-model="search.$"> <br>
  8358. Name only <input ng-model="search.name"><br>
  8359. Phone only <input ng-model="search.phone"å><br>
  8360. <table id="searchObjResults">
  8361. <tr><th>Name</th><th>Phone</th><tr>
  8362. <tr ng-repeat="friend in friends | filter:search">
  8363. <td>{{friend.name}}</td>
  8364. <td>{{friend.phone}}</td>
  8365. <tr>
  8366. </table>
  8367. </doc:source>
  8368. <doc:scenario>
  8369. it('should search across all fields when filtering with a string', function() {
  8370. input('searchText').enter('m');
  8371. expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
  8372. toEqual(['Mary', 'Mike', 'Adam']);
  8373. input('searchText').enter('76');
  8374. expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
  8375. toEqual(['John', 'Julie']);
  8376. });
  8377. it('should search in specific fields when filtering with a predicate object', function() {
  8378. input('search.$').enter('i');
  8379. expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
  8380. toEqual(['Mary', 'Mike', 'Julie']);
  8381. });
  8382. </doc:scenario>
  8383. </doc:example>
  8384. */
  8385. function filterFilter() {
  8386. return function(array, expression) {
  8387. if (!(array instanceof Array)) return array;
  8388. var predicates = [];
  8389. predicates.check = function(value) {
  8390. for (var j = 0; j < predicates.length; j++) {
  8391. if(!predicates[j](value)) {
  8392. return false;
  8393. }
  8394. }
  8395. return true;
  8396. };
  8397. var search = function(obj, text){
  8398. if (text.charAt(0) === '!') {
  8399. return !search(obj, text.substr(1));
  8400. }
  8401. switch (typeof obj) {
  8402. case "boolean":
  8403. case "number":
  8404. case "string":
  8405. return ('' + obj).toLowerCase().indexOf(text) > -1;
  8406. case "object":
  8407. for ( var objKey in obj) {
  8408. if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
  8409. return true;
  8410. }
  8411. }
  8412. return false;
  8413. case "array":
  8414. for ( var i = 0; i < obj.length; i++) {
  8415. if (search(obj[i], text)) {
  8416. return true;
  8417. }
  8418. }
  8419. return false;
  8420. default:
  8421. return false;
  8422. }
  8423. };
  8424. switch (typeof expression) {
  8425. case "boolean":
  8426. case "number":
  8427. case "string":
  8428. expression = {$:expression};
  8429. case "object":
  8430. for (var key in expression) {
  8431. if (key == '$') {
  8432. (function() {
  8433. var text = (''+expression[key]).toLowerCase();
  8434. if (!text) return;
  8435. predicates.push(function(value) {
  8436. return search(value, text);
  8437. });
  8438. })();
  8439. } else {
  8440. (function() {
  8441. var path = key;
  8442. var text = (''+expression[key]).toLowerCase();
  8443. if (!text) return;
  8444. predicates.push(function(value) {
  8445. return search(getter(value, path), text);
  8446. });
  8447. })();
  8448. }
  8449. }
  8450. break;
  8451. case 'function':
  8452. predicates.push(expression);
  8453. break;
  8454. default:
  8455. return array;
  8456. }
  8457. var filtered = [];
  8458. for ( var j = 0; j < array.length; j++) {
  8459. var value = array[j];
  8460. if (predicates.check(value)) {
  8461. filtered.push(value);
  8462. }
  8463. }
  8464. return filtered;
  8465. }
  8466. }
  8467. /**
  8468. * @ngdoc filter
  8469. * @name ng.filter:currency
  8470. * @function
  8471. *
  8472. * @description
  8473. * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
  8474. * symbol for current locale is used.
  8475. *
  8476. * @param {number} amount Input to filter.
  8477. * @param {string=} symbol Currency symbol or identifier to be displayed.
  8478. * @returns {string} Formatted number.
  8479. *
  8480. *
  8481. * @example
  8482. <doc:example>
  8483. <doc:source>
  8484. <script>
  8485. function Ctrl($scope) {
  8486. $scope.amount = 1234.56;
  8487. }
  8488. </script>
  8489. <div ng-controller="Ctrl">
  8490. <input type="number" ng-model="amount"> <br>
  8491. default currency symbol ($): {{amount | currency}}<br>
  8492. custom currency identifier (USD$): {{amount | currency:"USD$"}}
  8493. </div>
  8494. </doc:source>
  8495. <doc:scenario>
  8496. it('should init with 1234.56', function() {
  8497. expect(binding('amount | currency')).toBe('$1,234.56');
  8498. expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
  8499. });
  8500. it('should update', function() {
  8501. input('amount').enter('-1234');
  8502. expect(binding('amount | currency')).toBe('($1,234.00)');
  8503. expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
  8504. });
  8505. </doc:scenario>
  8506. </doc:example>
  8507. */
  8508. currencyFilter.$inject = ['$locale'];
  8509. function currencyFilter($locale) {
  8510. var formats = $locale.NUMBER_FORMATS;
  8511. return function(amount, currencySymbol){
  8512. if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
  8513. return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
  8514. replace(/\u00A4/g, currencySymbol);
  8515. };
  8516. }
  8517. /**
  8518. * @ngdoc filter
  8519. * @name ng.filter:number
  8520. * @function
  8521. *
  8522. * @description
  8523. * Formats a number as text.
  8524. *
  8525. * If the input is not a number an empty string is returned.
  8526. *
  8527. * @param {number|string} number Number to format.
  8528. * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
  8529. * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
  8530. *
  8531. * @example
  8532. <doc:example>
  8533. <doc:source>
  8534. <script>
  8535. function Ctrl($scope) {
  8536. $scope.val = 1234.56789;
  8537. }
  8538. </script>
  8539. <div ng-controller="Ctrl">
  8540. Enter number: <input ng-model='val'><br>
  8541. Default formatting: {{val | number}}<br>
  8542. No fractions: {{val | number:0}}<br>
  8543. Negative number: {{-val | number:4}}
  8544. </div>
  8545. </doc:source>
  8546. <doc:scenario>
  8547. it('should format numbers', function() {
  8548. expect(binding('val | number')).toBe('1,234.568');
  8549. expect(binding('val | number:0')).toBe('1,235');
  8550. expect(binding('-val | number:4')).toBe('-1,234.5679');
  8551. });
  8552. it('should update', function() {
  8553. input('val').enter('3374.333');
  8554. expect(binding('val | number')).toBe('3,374.333');
  8555. expect(binding('val | number:0')).toBe('3,374');
  8556. expect(binding('-val | number:4')).toBe('-3,374.3330');
  8557. });
  8558. </doc:scenario>
  8559. </doc:example>
  8560. */
  8561. numberFilter.$inject = ['$locale'];
  8562. function numberFilter($locale) {
  8563. var formats = $locale.NUMBER_FORMATS;
  8564. return function(number, fractionSize) {
  8565. return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
  8566. fractionSize);
  8567. };
  8568. }
  8569. var DECIMAL_SEP = '.';
  8570. function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
  8571. if (isNaN(number) || !isFinite(number)) return '';
  8572. var isNegative = number < 0;
  8573. number = Math.abs(number);
  8574. var numStr = number + '',
  8575. formatedText = '',
  8576. parts = [];
  8577. if (numStr.indexOf('e') !== -1) {
  8578. formatedText = numStr;
  8579. } else {
  8580. var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
  8581. // determine fractionSize if it is not specified
  8582. if (isUndefined(fractionSize)) {
  8583. fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
  8584. }
  8585. var pow = Math.pow(10, fractionSize);
  8586. number = Math.round(number * pow) / pow;
  8587. var fraction = ('' + number).split(DECIMAL_SEP);
  8588. var whole = fraction[0];
  8589. fraction = fraction[1] || '';
  8590. var pos = 0,
  8591. lgroup = pattern.lgSize,
  8592. group = pattern.gSize;
  8593. if (whole.length >= (lgroup + group)) {
  8594. pos = whole.length - lgroup;
  8595. for (var i = 0; i < pos; i++) {
  8596. if ((pos - i)%group === 0 && i !== 0) {
  8597. formatedText += groupSep;
  8598. }
  8599. formatedText += whole.charAt(i);
  8600. }
  8601. }
  8602. for (i = pos; i < whole.length; i++) {
  8603. if ((whole.length - i)%lgroup === 0 && i !== 0) {
  8604. formatedText += groupSep;
  8605. }
  8606. formatedText += whole.charAt(i);
  8607. }
  8608. // format fraction part.
  8609. while(fraction.length < fractionSize) {
  8610. fraction += '0';
  8611. }
  8612. if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize);
  8613. }
  8614. parts.push(isNegative ? pattern.negPre : pattern.posPre);
  8615. parts.push(formatedText);
  8616. parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
  8617. return parts.join('');
  8618. }
  8619. function padNumber(num, digits, trim) {
  8620. var neg = '';
  8621. if (num < 0) {
  8622. neg = '-';
  8623. num = -num;
  8624. }
  8625. num = '' + num;
  8626. while(num.length < digits) num = '0' + num;
  8627. if (trim)
  8628. num = num.substr(num.length - digits);
  8629. return neg + num;
  8630. }
  8631. function dateGetter(name, size, offset, trim) {
  8632. return function(date) {
  8633. var value = date['get' + name]();
  8634. if (offset > 0 || value > -offset)
  8635. value += offset;
  8636. if (value === 0 && offset == -12 ) value = 12;
  8637. return padNumber(value, size, trim);
  8638. };
  8639. }
  8640. function dateStrGetter(name, shortForm) {
  8641. return function(date, formats) {
  8642. var value = date['get' + name]();
  8643. var get = uppercase(shortForm ? ('SHORT' + name) : name);
  8644. return formats[get][value];
  8645. };
  8646. }
  8647. function timeZoneGetter(date) {
  8648. var offset = date.getTimezoneOffset();
  8649. return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
  8650. }
  8651. function ampmGetter(date, formats) {
  8652. return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
  8653. }
  8654. var DATE_FORMATS = {
  8655. yyyy: dateGetter('FullYear', 4),
  8656. yy: dateGetter('FullYear', 2, 0, true),
  8657. y: dateGetter('FullYear', 1),
  8658. MMMM: dateStrGetter('Month'),
  8659. MMM: dateStrGetter('Month', true),
  8660. MM: dateGetter('Month', 2, 1),
  8661. M: dateGetter('Month', 1, 1),
  8662. dd: dateGetter('Date', 2),
  8663. d: dateGetter('Date', 1),
  8664. HH: dateGetter('Hours', 2),
  8665. H: dateGetter('Hours', 1),
  8666. hh: dateGetter('Hours', 2, -12),
  8667. h: dateGetter('Hours', 1, -12),
  8668. mm: dateGetter('Minutes', 2),
  8669. m: dateGetter('Minutes', 1),
  8670. ss: dateGetter('Seconds', 2),
  8671. s: dateGetter('Seconds', 1),
  8672. EEEE: dateStrGetter('Day'),
  8673. EEE: dateStrGetter('Day', true),
  8674. a: ampmGetter,
  8675. Z: timeZoneGetter
  8676. };
  8677. var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
  8678. NUMBER_STRING = /^\d+$/;
  8679. /**
  8680. * @ngdoc filter
  8681. * @name ng.filter:date
  8682. * @function
  8683. *
  8684. * @description
  8685. * Formats `date` to a string based on the requested `format`.
  8686. *
  8687. * `format` string can be composed of the following elements:
  8688. *
  8689. * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
  8690. * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
  8691. * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
  8692. * * `'MMMM'`: Month in year (January-December)
  8693. * * `'MMM'`: Month in year (Jan-Dec)
  8694. * * `'MM'`: Month in year, padded (01-12)
  8695. * * `'M'`: Month in year (1-12)
  8696. * * `'dd'`: Day in month, padded (01-31)
  8697. * * `'d'`: Day in month (1-31)
  8698. * * `'EEEE'`: Day in Week,(Sunday-Saturday)
  8699. * * `'EEE'`: Day in Week, (Sun-Sat)
  8700. * * `'HH'`: Hour in day, padded (00-23)
  8701. * * `'H'`: Hour in day (0-23)
  8702. * * `'hh'`: Hour in am/pm, padded (01-12)
  8703. * * `'h'`: Hour in am/pm, (1-12)
  8704. * * `'mm'`: Minute in hour, padded (00-59)
  8705. * * `'m'`: Minute in hour (0-59)
  8706. * * `'ss'`: Second in minute, padded (00-59)
  8707. * * `'s'`: Second in minute (0-59)
  8708. * * `'a'`: am/pm marker
  8709. * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
  8710. *
  8711. * `format` string can also be one of the following predefined
  8712. * {@link guide/i18n localizable formats}:
  8713. *
  8714. * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
  8715. * (e.g. Sep 3, 2010 12:05:08 pm)
  8716. * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
  8717. * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
  8718. * (e.g. Friday, September 3, 2010)
  8719. * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010
  8720. * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
  8721. * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
  8722. * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
  8723. * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
  8724. *
  8725. * `format` string can contain literal values. These need to be quoted with single quotes (e.g.
  8726. * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
  8727. * (e.g. `"h o''clock"`).
  8728. *
  8729. * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
  8730. * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
  8731. * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
  8732. * @param {string=} format Formatting rules (see Description). If not specified,
  8733. * `mediumDate` is used.
  8734. * @returns {string} Formatted string or the input if input is not recognized as date/millis.
  8735. *
  8736. * @example
  8737. <doc:example>
  8738. <doc:source>
  8739. <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
  8740. {{1288323623006 | date:'medium'}}<br>
  8741. <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
  8742. {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
  8743. <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
  8744. {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
  8745. </doc:source>
  8746. <doc:scenario>
  8747. it('should format date', function() {
  8748. expect(binding("1288323623006 | date:'medium'")).
  8749. toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
  8750. expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
  8751. toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
  8752. expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
  8753. toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
  8754. });
  8755. </doc:scenario>
  8756. </doc:example>
  8757. */
  8758. dateFilter.$inject = ['$locale'];
  8759. function dateFilter($locale) {
  8760. var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
  8761. function jsonStringToDate(string){
  8762. var match;
  8763. if (match = string.match(R_ISO8601_STR)) {
  8764. var date = new Date(0),
  8765. tzHour = 0,
  8766. tzMin = 0;
  8767. if (match[9]) {
  8768. tzHour = int(match[9] + match[10]);
  8769. tzMin = int(match[9] + match[11]);
  8770. }
  8771. date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
  8772. date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
  8773. return date;
  8774. }
  8775. return string;
  8776. }
  8777. return function(date, format) {
  8778. var text = '',
  8779. parts = [],
  8780. fn, match;
  8781. format = format || 'mediumDate';
  8782. format = $locale.DATETIME_FORMATS[format] || format;
  8783. if (isString(date)) {
  8784. if (NUMBER_STRING.test(date)) {
  8785. date = int(date);
  8786. } else {
  8787. date = jsonStringToDate(date);
  8788. }
  8789. }
  8790. if (isNumber(date)) {
  8791. date = new Date(date);
  8792. }
  8793. if (!isDate(date)) {
  8794. return date;
  8795. }
  8796. while(format) {
  8797. match = DATE_FORMATS_SPLIT.exec(format);
  8798. if (match) {
  8799. parts = concat(parts, match, 1);
  8800. format = parts.pop();
  8801. } else {
  8802. parts.push(format);
  8803. format = null;
  8804. }
  8805. }
  8806. forEach(parts, function(value){
  8807. fn = DATE_FORMATS[value];
  8808. text += fn ? fn(date, $locale.DATETIME_FORMATS)
  8809. : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
  8810. });
  8811. return text;
  8812. };
  8813. }
  8814. /**
  8815. * @ngdoc filter
  8816. * @name ng.filter:json
  8817. * @function
  8818. *
  8819. * @description
  8820. * Allows you to convert a JavaScript object into JSON string.
  8821. *
  8822. * This filter is mostly useful for debugging. When using the double curly {{value}} notation
  8823. * the binding is automatically converted to JSON.
  8824. *
  8825. * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
  8826. * @returns {string} JSON string.
  8827. *
  8828. *
  8829. * @example:
  8830. <doc:example>
  8831. <doc:source>
  8832. <pre>{{ {'name':'value'} | json }}</pre>
  8833. </doc:source>
  8834. <doc:scenario>
  8835. it('should jsonify filtered objects', function() {
  8836. expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
  8837. });
  8838. </doc:scenario>
  8839. </doc:example>
  8840. *
  8841. */
  8842. function jsonFilter() {
  8843. return function(object) {
  8844. return toJson(object, true);
  8845. };
  8846. }
  8847. /**
  8848. * @ngdoc filter
  8849. * @name ng.filter:lowercase
  8850. * @function
  8851. * @description
  8852. * Converts string to lowercase.
  8853. * @see angular.lowercase
  8854. */
  8855. var lowercaseFilter = valueFn(lowercase);
  8856. /**
  8857. * @ngdoc filter
  8858. * @name ng.filter:uppercase
  8859. * @function
  8860. * @description
  8861. * Converts string to uppercase.
  8862. * @see angular.uppercase
  8863. */
  8864. var uppercaseFilter = valueFn(uppercase);
  8865. /**
  8866. * @ngdoc function
  8867. * @name ng.filter:limitTo
  8868. * @function
  8869. *
  8870. * @description
  8871. * Creates a new array containing only a specified number of elements in an array. The elements
  8872. * are taken from either the beginning or the end of the source array, as specified by the
  8873. * value and sign (positive or negative) of `limit`.
  8874. *
  8875. * Note: This function is used to augment the `Array` type in Angular expressions. See
  8876. * {@link ng.$filter} for more information about Angular arrays.
  8877. *
  8878. * @param {Array} array Source array to be limited.
  8879. * @param {string|Number} limit The length of the returned array. If the `limit` number is
  8880. * positive, `limit` number of items from the beginning of the source array are copied.
  8881. * If the number is negative, `limit` number of items from the end of the source array are
  8882. * copied. The `limit` will be trimmed if it exceeds `array.length`
  8883. * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
  8884. * elements.
  8885. *
  8886. * @example
  8887. <doc:example>
  8888. <doc:source>
  8889. <script>
  8890. function Ctrl($scope) {
  8891. $scope.numbers = [1,2,3,4,5,6,7,8,9];
  8892. $scope.limit = 3;
  8893. }
  8894. </script>
  8895. <div ng-controller="Ctrl">
  8896. Limit {{numbers}} to: <input type="integer" ng-model="limit">
  8897. <p>Output: {{ numbers | limitTo:limit }}</p>
  8898. </div>
  8899. </doc:source>
  8900. <doc:scenario>
  8901. it('should limit the numer array to first three items', function() {
  8902. expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
  8903. expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
  8904. });
  8905. it('should update the output when -3 is entered', function() {
  8906. input('limit').enter(-3);
  8907. expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
  8908. });
  8909. it('should not exceed the maximum size of input array', function() {
  8910. input('limit').enter(100);
  8911. expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
  8912. });
  8913. </doc:scenario>
  8914. </doc:example>
  8915. */
  8916. function limitToFilter(){
  8917. return function(array, limit) {
  8918. if (!(array instanceof Array)) return array;
  8919. limit = int(limit);
  8920. var out = [],
  8921. i, n;
  8922. // check that array is iterable
  8923. if (!array || !(array instanceof Array))
  8924. return out;
  8925. // if abs(limit) exceeds maximum length, trim it
  8926. if (limit > array.length)
  8927. limit = array.length;
  8928. else if (limit < -array.length)
  8929. limit = -array.length;
  8930. if (limit > 0) {
  8931. i = 0;
  8932. n = limit;
  8933. } else {
  8934. i = array.length + limit;
  8935. n = array.length;
  8936. }
  8937. for (; i<n; i++) {
  8938. out.push(array[i]);
  8939. }
  8940. return out;
  8941. }
  8942. }
  8943. /**
  8944. * @ngdoc function
  8945. * @name ng.filter:orderBy
  8946. * @function
  8947. *
  8948. * @description
  8949. * Orders a specified `array` by the `expression` predicate.
  8950. *
  8951. * Note: this function is used to augment the `Array` type in Angular expressions. See
  8952. * {@link ng.$filter} for more informaton about Angular arrays.
  8953. *
  8954. * @param {Array} array The array to sort.
  8955. * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
  8956. * used by the comparator to determine the order of elements.
  8957. *
  8958. * Can be one of:
  8959. *
  8960. * - `function`: Getter function. The result of this function will be sorted using the
  8961. * `<`, `=`, `>` operator.
  8962. * - `string`: An Angular expression which evaluates to an object to order by, such as 'name'
  8963. * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control
  8964. * ascending or descending sort order (for example, +name or -name).
  8965. * - `Array`: An array of function or string predicates. The first predicate in the array
  8966. * is used for sorting, but when two items are equivalent, the next predicate is used.
  8967. *
  8968. * @param {boolean=} reverse Reverse the order the array.
  8969. * @returns {Array} Sorted copy of the source array.
  8970. *
  8971. * @example
  8972. <doc:example>
  8973. <doc:source>
  8974. <script>
  8975. function Ctrl($scope) {
  8976. $scope.friends =
  8977. [{name:'John', phone:'555-1212', age:10},
  8978. {name:'Mary', phone:'555-9876', age:19},
  8979. {name:'Mike', phone:'555-4321', age:21},
  8980. {name:'Adam', phone:'555-5678', age:35},
  8981. {name:'Julie', phone:'555-8765', age:29}]
  8982. $scope.predicate = '-age';
  8983. }
  8984. </script>
  8985. <div ng-controller="Ctrl">
  8986. <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
  8987. <hr/>
  8988. [ <a href="" ng-click="predicate=''">unsorted</a> ]
  8989. <table class="friend">
  8990. <tr>
  8991. <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a>
  8992. (<a href ng-click="predicate = '-name'; reverse=false">^</a>)</th>
  8993. <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
  8994. <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th>
  8995. <tr>
  8996. <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
  8997. <td>{{friend.name}}</td>
  8998. <td>{{friend.phone}}</td>
  8999. <td>{{friend.age}}</td>
  9000. <tr>
  9001. </table>
  9002. </div>
  9003. </doc:source>
  9004. <doc:scenario>
  9005. it('should be reverse ordered by aged', function() {
  9006. expect(binding('predicate')).toBe('-age');
  9007. expect(repeater('table.friend', 'friend in friends').column('friend.age')).
  9008. toEqual(['35', '29', '21', '19', '10']);
  9009. expect(repeater('table.friend', 'friend in friends').column('friend.name')).
  9010. toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
  9011. });
  9012. it('should reorder the table when user selects different predicate', function() {
  9013. element('.doc-example-live a:contains("Name")').click();
  9014. expect(repeater('table.friend', 'friend in friends').column('friend.name')).
  9015. toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
  9016. expect(repeater('table.friend', 'friend in friends').column('friend.age')).
  9017. toEqual(['35', '10', '29', '19', '21']);
  9018. element('.doc-example-live a:contains("Phone")').click();
  9019. expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
  9020. toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
  9021. expect(repeater('table.friend', 'friend in friends').column('friend.name')).
  9022. toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
  9023. });
  9024. </doc:scenario>
  9025. </doc:example>
  9026. */
  9027. orderByFilter.$inject = ['$parse'];
  9028. function orderByFilter($parse){
  9029. return function(array, sortPredicate, reverseOrder) {
  9030. if (!(array instanceof Array)) return array;
  9031. if (!sortPredicate) return array;
  9032. sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
  9033. sortPredicate = map(sortPredicate, function(predicate){
  9034. var descending = false, get = predicate || identity;
  9035. if (isString(predicate)) {
  9036. if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
  9037. descending = predicate.charAt(0) == '-';
  9038. predicate = predicate.substring(1);
  9039. }
  9040. get = $parse(predicate);
  9041. }
  9042. return reverseComparator(function(a,b){
  9043. return compare(get(a),get(b));
  9044. }, descending);
  9045. });
  9046. var arrayCopy = [];
  9047. for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
  9048. return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
  9049. function comparator(o1, o2){
  9050. for ( var i = 0; i < sortPredicate.length; i++) {
  9051. var comp = sortPredicate[i](o1, o2);
  9052. if (comp !== 0) return comp;
  9053. }
  9054. return 0;
  9055. }
  9056. function reverseComparator(comp, descending) {
  9057. return toBoolean(descending)
  9058. ? function(a,b){return comp(b,a);}
  9059. : comp;
  9060. }
  9061. function compare(v1, v2){
  9062. var t1 = typeof v1;
  9063. var t2 = typeof v2;
  9064. if (t1 == t2) {
  9065. if (t1 == "string") v1 = v1.toLowerCase();
  9066. if (t1 == "string") v2 = v2.toLowerCase();
  9067. if (v1 === v2) return 0;
  9068. return v1 < v2 ? -1 : 1;
  9069. } else {
  9070. return t1 < t2 ? -1 : 1;
  9071. }
  9072. }
  9073. }
  9074. }
  9075. function ngDirective(directive) {
  9076. if (isFunction(directive)) {
  9077. directive = {
  9078. link: directive
  9079. }
  9080. }
  9081. directive.restrict = directive.restrict || 'AC';
  9082. return valueFn(directive);
  9083. }
  9084. /*
  9085. * Modifies the default behavior of html A tag, so that the default action is prevented when href
  9086. * attribute is empty.
  9087. *
  9088. * The reasoning for this change is to allow easy creation of action links with `ngClick` directive
  9089. * without changing the location or causing page reloads, e.g.:
  9090. * <a href="" ng-click="model.$save()">Save</a>
  9091. */
  9092. var htmlAnchorDirective = valueFn({
  9093. restrict: 'E',
  9094. compile: function(element, attr) {
  9095. // turn <a href ng-click="..">link</a> into a link in IE
  9096. // but only if it doesn't have name attribute, in which case it's an anchor
  9097. if (!attr.href) {
  9098. attr.$set('href', '');
  9099. }
  9100. return function(scope, element) {
  9101. element.bind('click', function(event){
  9102. // if we have no href url, then don't navigate anywhere.
  9103. if (!element.attr('href')) {
  9104. event.preventDefault();
  9105. }
  9106. });
  9107. }
  9108. }
  9109. });
  9110. /**
  9111. * @ngdoc directive
  9112. * @name ng.directive:ngHref
  9113. * @restrict A
  9114. *
  9115. * @description
  9116. * Using Angular markup like {{hash}} in an href attribute makes
  9117. * the page open to a wrong URL, if the user clicks that link before
  9118. * angular has a chance to replace the {{hash}} with actual URL, the
  9119. * link will be broken and will most likely return a 404 error.
  9120. * The `ngHref` directive solves this problem.
  9121. *
  9122. * The buggy way to write it:
  9123. * <pre>
  9124. * <a href="http://www.gravatar.com/avatar/{{hash}}"/>
  9125. * </pre>
  9126. *
  9127. * The correct way to write it:
  9128. * <pre>
  9129. * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
  9130. * </pre>
  9131. *
  9132. * @element A
  9133. * @param {template} ngHref any string which can contain `{{}}` markup.
  9134. *
  9135. * @example
  9136. * This example uses `link` variable inside `href` attribute:
  9137. <doc:example>
  9138. <doc:source>
  9139. <input ng-model="value" /><br />
  9140. <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
  9141. <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
  9142. <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
  9143. <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
  9144. <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
  9145. <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
  9146. </doc:source>
  9147. <doc:scenario>
  9148. it('should execute ng-click but not reload when href without value', function() {
  9149. element('#link-1').click();
  9150. expect(input('value').val()).toEqual('1');
  9151. expect(element('#link-1').attr('href')).toBe("");
  9152. });
  9153. it('should execute ng-click but not reload when href empty string', function() {
  9154. element('#link-2').click();
  9155. expect(input('value').val()).toEqual('2');
  9156. expect(element('#link-2').attr('href')).toBe("");
  9157. });
  9158. it('should execute ng-click and change url when ng-href specified', function() {
  9159. expect(element('#link-3').attr('href')).toBe("/123");
  9160. element('#link-3').click();
  9161. expect(browser().window().path()).toEqual('/123');
  9162. });
  9163. it('should execute ng-click but not reload when href empty string and name specified', function() {
  9164. element('#link-4').click();
  9165. expect(input('value').val()).toEqual('4');
  9166. expect(element('#link-4').attr('href')).toBe('');
  9167. });
  9168. it('should execute ng-click but not reload when no href but name specified', function() {
  9169. element('#link-5').click();
  9170. expect(input('value').val()).toEqual('5');
  9171. expect(element('#link-5').attr('href')).toBe('');
  9172. });
  9173. it('should only change url when only ng-href', function() {
  9174. input('value').enter('6');
  9175. expect(element('#link-6').attr('href')).toBe('6');
  9176. element('#link-6').click();
  9177. expect(browser().location().url()).toEqual('/6');
  9178. });
  9179. </doc:scenario>
  9180. </doc:example>
  9181. */
  9182. /**
  9183. * @ngdoc directive
  9184. * @name ng.directive:ngSrc
  9185. * @restrict A
  9186. *
  9187. * @description
  9188. * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
  9189. * work right: The browser will fetch from the URL with the literal
  9190. * text `{{hash}}` until Angular replaces the expression inside
  9191. * `{{hash}}`. The `ngSrc` directive solves this problem.
  9192. *
  9193. * The buggy way to write it:
  9194. * <pre>
  9195. * <img src="http://www.gravatar.com/avatar/{{hash}}"/>
  9196. * </pre>
  9197. *
  9198. * The correct way to write it:
  9199. * <pre>
  9200. * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
  9201. * </pre>
  9202. *
  9203. * @element IMG
  9204. * @param {template} ngSrc any string which can contain `{{}}` markup.
  9205. */
  9206. /**
  9207. * @ngdoc directive
  9208. * @name ng.directive:ngDisabled
  9209. * @restrict A
  9210. *
  9211. * @description
  9212. *
  9213. * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
  9214. * <pre>
  9215. * <div ng-init="scope = { isDisabled: false }">
  9216. * <button disabled="{{scope.isDisabled}}">Disabled</button>
  9217. * </div>
  9218. * </pre>
  9219. *
  9220. * The HTML specs do not require browsers to preserve the special attributes such as disabled.
  9221. * (The presence of them means true and absence means false)
  9222. * This prevents the angular compiler from correctly retrieving the binding expression.
  9223. * To solve this problem, we introduce the `ngDisabled` directive.
  9224. *
  9225. * @example
  9226. <doc:example>
  9227. <doc:source>
  9228. Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
  9229. <button ng-model="button" ng-disabled="checked">Button</button>
  9230. </doc:source>
  9231. <doc:scenario>
  9232. it('should toggle button', function() {
  9233. expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
  9234. input('checked').check();
  9235. expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
  9236. });
  9237. </doc:scenario>
  9238. </doc:example>
  9239. *
  9240. * @element INPUT
  9241. * @param {expression} ngDisabled Angular expression that will be evaluated.
  9242. */
  9243. /**
  9244. * @ngdoc directive
  9245. * @name ng.directive:ngChecked
  9246. * @restrict A
  9247. *
  9248. * @description
  9249. * The HTML specs do not require browsers to preserve the special attributes such as checked.
  9250. * (The presence of them means true and absence means false)
  9251. * This prevents the angular compiler from correctly retrieving the binding expression.
  9252. * To solve this problem, we introduce the `ngChecked` directive.
  9253. * @example
  9254. <doc:example>
  9255. <doc:source>
  9256. Check me to check both: <input type="checkbox" ng-model="master"><br/>
  9257. <input id="checkSlave" type="checkbox" ng-checked="master">
  9258. </doc:source>
  9259. <doc:scenario>
  9260. it('should check both checkBoxes', function() {
  9261. expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
  9262. input('master').check();
  9263. expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
  9264. });
  9265. </doc:scenario>
  9266. </doc:example>
  9267. *
  9268. * @element INPUT
  9269. * @param {expression} ngChecked Angular expression that will be evaluated.
  9270. */
  9271. /**
  9272. * @ngdoc directive
  9273. * @name ng.directive:ngMultiple
  9274. * @restrict A
  9275. *
  9276. * @description
  9277. * The HTML specs do not require browsers to preserve the special attributes such as multiple.
  9278. * (The presence of them means true and absence means false)
  9279. * This prevents the angular compiler from correctly retrieving the binding expression.
  9280. * To solve this problem, we introduce the `ngMultiple` directive.
  9281. *
  9282. * @example
  9283. <doc:example>
  9284. <doc:source>
  9285. Check me check multiple: <input type="checkbox" ng-model="checked"><br/>
  9286. <select id="select" ng-multiple="checked">
  9287. <option>Misko</option>
  9288. <option>Igor</option>
  9289. <option>Vojta</option>
  9290. <option>Di</option>
  9291. </select>
  9292. </doc:source>
  9293. <doc:scenario>
  9294. it('should toggle multiple', function() {
  9295. expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
  9296. input('checked').check();
  9297. expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
  9298. });
  9299. </doc:scenario>
  9300. </doc:example>
  9301. *
  9302. * @element SELECT
  9303. * @param {expression} ngMultiple Angular expression that will be evaluated.
  9304. */
  9305. /**
  9306. * @ngdoc directive
  9307. * @name ng.directive:ngReadonly
  9308. * @restrict A
  9309. *
  9310. * @description
  9311. * The HTML specs do not require browsers to preserve the special attributes such as readonly.
  9312. * (The presence of them means true and absence means false)
  9313. * This prevents the angular compiler from correctly retrieving the binding expression.
  9314. * To solve this problem, we introduce the `ngReadonly` directive.
  9315. * @example
  9316. <doc:example>
  9317. <doc:source>
  9318. Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
  9319. <input type="text" ng-readonly="checked" value="I'm Angular"/>
  9320. </doc:source>
  9321. <doc:scenario>
  9322. it('should toggle readonly attr', function() {
  9323. expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
  9324. input('checked').check();
  9325. expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
  9326. });
  9327. </doc:scenario>
  9328. </doc:example>
  9329. *
  9330. * @element INPUT
  9331. * @param {string} expression Angular expression that will be evaluated.
  9332. */
  9333. /**
  9334. * @ngdoc directive
  9335. * @name ng.directive:ngSelected
  9336. * @restrict A
  9337. *
  9338. * @description
  9339. * The HTML specs do not require browsers to preserve the special attributes such as selected.
  9340. * (The presence of them means true and absence means false)
  9341. * This prevents the angular compiler from correctly retrieving the binding expression.
  9342. * To solve this problem, we introduced the `ngSelected` directive.
  9343. * @example
  9344. <doc:example>
  9345. <doc:source>
  9346. Check me to select: <input type="checkbox" ng-model="selected"><br/>
  9347. <select>
  9348. <option>Hello!</option>
  9349. <option id="greet" ng-selected="selected">Greetings!</option>
  9350. </select>
  9351. </doc:source>
  9352. <doc:scenario>
  9353. it('should select Greetings!', function() {
  9354. expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
  9355. input('selected').check();
  9356. expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
  9357. });
  9358. </doc:scenario>
  9359. </doc:example>
  9360. *
  9361. * @element OPTION
  9362. * @param {string} expression Angular expression that will be evaluated.
  9363. */
  9364. var ngAttributeAliasDirectives = {};
  9365. // boolean attrs are evaluated
  9366. forEach(BOOLEAN_ATTR, function(propName, attrName) {
  9367. var normalized = directiveNormalize('ng-' + attrName);
  9368. ngAttributeAliasDirectives[normalized] = function() {
  9369. return {
  9370. priority: 100,
  9371. compile: function() {
  9372. return function(scope, element, attr) {
  9373. scope.$watch(attr[normalized], function(value) {
  9374. attr.$set(attrName, !!value);
  9375. });
  9376. };
  9377. }
  9378. };
  9379. };
  9380. });
  9381. // ng-src, ng-href are interpolated
  9382. forEach(['src', 'href'], function(attrName) {
  9383. var normalized = directiveNormalize('ng-' + attrName);
  9384. ngAttributeAliasDirectives[normalized] = function() {
  9385. return {
  9386. priority: 99, // it needs to run after the attributes are interpolated
  9387. link: function(scope, element, attr) {
  9388. attr.$observe(normalized, function(value) {
  9389. attr.$set(attrName, value);
  9390. // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
  9391. // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
  9392. // to set the property as well to achieve the desired effect
  9393. if (msie) element.prop(attrName, value);
  9394. });
  9395. }
  9396. };
  9397. };
  9398. });
  9399. var nullFormCtrl = {
  9400. $addControl: noop,
  9401. $removeControl: noop,
  9402. $setValidity: noop,
  9403. $setDirty: noop
  9404. };
  9405. /**
  9406. * @ngdoc object
  9407. * @name ng.directive:form.FormController
  9408. *
  9409. * @property {boolean} $pristine True if user has not interacted with the form yet.
  9410. * @property {boolean} $dirty True if user has already interacted with the form.
  9411. * @property {boolean} $valid True if all of the containg forms and controls are valid.
  9412. * @property {boolean} $invalid True if at least one containing control or form is invalid.
  9413. *
  9414. * @property {Object} $error Is an object hash, containing references to all invalid controls or
  9415. * forms, where:
  9416. *
  9417. * - keys are validation tokens (error names) — such as `REQUIRED`, `URL` or `EMAIL`),
  9418. * - values are arrays of controls or forms that are invalid with given error.
  9419. *
  9420. * @description
  9421. * `FormController` keeps track of all its controls and nested forms as well as state of them,
  9422. * such as being valid/invalid or dirty/pristine.
  9423. *
  9424. * Each {@link ng.directive:form form} directive creates an instance
  9425. * of `FormController`.
  9426. *
  9427. */
  9428. //asks for $scope to fool the BC controller module
  9429. FormController.$inject = ['$element', '$attrs', '$scope'];
  9430. function FormController(element, attrs) {
  9431. var form = this,
  9432. parentForm = element.parent().controller('form') || nullFormCtrl,
  9433. invalidCount = 0, // used to easily determine if we are valid
  9434. errors = form.$error = {};
  9435. // init state
  9436. form.$name = attrs.name;
  9437. form.$dirty = false;
  9438. form.$pristine = true;
  9439. form.$valid = true;
  9440. form.$invalid = false;
  9441. parentForm.$addControl(form);
  9442. // Setup initial state of the control
  9443. element.addClass(PRISTINE_CLASS);
  9444. toggleValidCss(true);
  9445. // convenience method for easy toggling of classes
  9446. function toggleValidCss(isValid, validationErrorKey) {
  9447. validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
  9448. element.
  9449. removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
  9450. addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
  9451. }
  9452. form.$addControl = function(control) {
  9453. if (control.$name && !form.hasOwnProperty(control.$name)) {
  9454. form[control.$name] = control;
  9455. }
  9456. };
  9457. form.$removeControl = function(control) {
  9458. if (control.$name && form[control.$name] === control) {
  9459. delete form[control.$name];
  9460. }
  9461. forEach(errors, function(queue, validationToken) {
  9462. form.$setValidity(validationToken, true, control);
  9463. });
  9464. };
  9465. form.$setValidity = function(validationToken, isValid, control) {
  9466. var queue = errors[validationToken];
  9467. if (isValid) {
  9468. if (queue) {
  9469. arrayRemove(queue, control);
  9470. if (!queue.length) {
  9471. invalidCount--;
  9472. if (!invalidCount) {
  9473. toggleValidCss(isValid);
  9474. form.$valid = true;
  9475. form.$invalid = false;
  9476. }
  9477. errors[validationToken] = false;
  9478. toggleValidCss(true, validationToken);
  9479. parentForm.$setValidity(validationToken, true, form);
  9480. }
  9481. }
  9482. } else {
  9483. if (!invalidCount) {
  9484. toggleValidCss(isValid);
  9485. }
  9486. if (queue) {
  9487. if (includes(queue, control)) return;
  9488. } else {
  9489. errors[validationToken] = queue = [];
  9490. invalidCount++;
  9491. toggleValidCss(false, validationToken);
  9492. parentForm.$setValidity(validationToken, false, form);
  9493. }
  9494. queue.push(control);
  9495. form.$valid = false;
  9496. form.$invalid = true;
  9497. }
  9498. };
  9499. form.$setDirty = function() {
  9500. element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
  9501. form.$dirty = true;
  9502. form.$pristine = false;
  9503. };
  9504. }
  9505. /**
  9506. * @ngdoc directive
  9507. * @name ng.directive:ngForm
  9508. * @restrict EAC
  9509. *
  9510. * @description
  9511. * Nestable alias of {@link ng.directive:form `form`} directive. HTML
  9512. * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
  9513. * sub-group of controls needs to be determined.
  9514. *
  9515. * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
  9516. * related scope, under this name.
  9517. *
  9518. */
  9519. /**
  9520. * @ngdoc directive
  9521. * @name ng.directive:form
  9522. * @restrict E
  9523. *
  9524. * @description
  9525. * Directive that instantiates
  9526. * {@link ng.directive:form.FormController FormController}.
  9527. *
  9528. * If `name` attribute is specified, the form controller is published onto the current scope under
  9529. * this name.
  9530. *
  9531. * # Alias: {@link ng.directive:ngForm `ngForm`}
  9532. *
  9533. * In angular forms can be nested. This means that the outer form is valid when all of the child
  9534. * forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this
  9535. * reason angular provides {@link ng.directive:ngForm `ngForm`} alias
  9536. * which behaves identical to `<form>` but allows form nesting.
  9537. *
  9538. *
  9539. * # CSS classes
  9540. * - `ng-valid` Is set if the form is valid.
  9541. * - `ng-invalid` Is set if the form is invalid.
  9542. * - `ng-pristine` Is set if the form is pristine.
  9543. * - `ng-dirty` Is set if the form is dirty.
  9544. *
  9545. *
  9546. * # Submitting a form and preventing default action
  9547. *
  9548. * Since the role of forms in client-side Angular applications is different than in classical
  9549. * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
  9550. * page reload that sends the data to the server. Instead some javascript logic should be triggered
  9551. * to handle the form submission in application specific way.
  9552. *
  9553. * For this reason, Angular prevents the default action (form submission to the server) unless the
  9554. * `<form>` element has an `action` attribute specified.
  9555. *
  9556. * You can use one of the following two ways to specify what javascript method should be called when
  9557. * a form is submitted:
  9558. *
  9559. * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
  9560. * - {@link ng.directive:ngClick ngClick} directive on the first
  9561. * button or input field of type submit (input[type=submit])
  9562. *
  9563. * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This
  9564. * is because of the following form submission rules coming from the html spec:
  9565. *
  9566. * - If a form has only one input field then hitting enter in this field triggers form submit
  9567. * (`ngSubmit`)
  9568. * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
  9569. * doesn't trigger submit
  9570. * - if a form has one or more input fields and one or more buttons or input[type=submit] then
  9571. * hitting enter in any of the input fields will trigger the click handler on the *first* button or
  9572. * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
  9573. *
  9574. * @param {string=} name Name of the form. If specified, the form controller will be published into
  9575. * related scope, under this name.
  9576. *
  9577. * @example
  9578. <doc:example>
  9579. <doc:source>
  9580. <script>
  9581. function Ctrl($scope) {
  9582. $scope.userType = 'guest';
  9583. }
  9584. </script>
  9585. <form name="myForm" ng-controller="Ctrl">
  9586. userType: <input name="input" ng-model="userType" required>
  9587. <span class="error" ng-show="myForm.input.$error.REQUIRED">Required!</span><br>
  9588. <tt>userType = {{userType}}</tt><br>
  9589. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
  9590. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
  9591. <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
  9592. <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br>
  9593. </form>
  9594. </doc:source>
  9595. <doc:scenario>
  9596. it('should initialize to model', function() {
  9597. expect(binding('userType')).toEqual('guest');
  9598. expect(binding('myForm.input.$valid')).toEqual('true');
  9599. });
  9600. it('should be invalid if empty', function() {
  9601. input('userType').enter('');
  9602. expect(binding('userType')).toEqual('');
  9603. expect(binding('myForm.input.$valid')).toEqual('false');
  9604. });
  9605. </doc:scenario>
  9606. </doc:example>
  9607. */
  9608. var formDirectiveDir = {
  9609. name: 'form',
  9610. restrict: 'E',
  9611. controller: FormController,
  9612. compile: function() {
  9613. return {
  9614. pre: function(scope, formElement, attr, controller) {
  9615. if (!attr.action) {
  9616. formElement.bind('submit', function(event) {
  9617. event.preventDefault();
  9618. });
  9619. }
  9620. var parentFormCtrl = formElement.parent().controller('form'),
  9621. alias = attr.name || attr.ngForm;
  9622. if (alias) {
  9623. scope[alias] = controller;
  9624. }
  9625. if (parentFormCtrl) {
  9626. formElement.bind('$destroy', function() {
  9627. parentFormCtrl.$removeControl(controller);
  9628. if (alias) {
  9629. scope[alias] = undefined;
  9630. }
  9631. extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
  9632. });
  9633. }
  9634. }
  9635. };
  9636. }
  9637. };
  9638. var formDirective = valueFn(formDirectiveDir);
  9639. var ngFormDirective = valueFn(extend(copy(formDirectiveDir), {restrict: 'EAC'}));
  9640. var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
  9641. var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
  9642. var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
  9643. var inputType = {
  9644. /**
  9645. * @ngdoc inputType
  9646. * @name ng.directive:input.text
  9647. *
  9648. * @description
  9649. * Standard HTML text input with angular data binding.
  9650. *
  9651. * @param {string} ngModel Assignable angular expression to data-bind to.
  9652. * @param {string=} name Property name of the form under which the control is published.
  9653. * @param {string=} required Sets `required` validation error key if the value is not entered.
  9654. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  9655. * minlength.
  9656. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  9657. * maxlength.
  9658. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  9659. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  9660. * patterns defined as scope expressions.
  9661. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  9662. * interaction with the input element.
  9663. *
  9664. * @example
  9665. <doc:example>
  9666. <doc:source>
  9667. <script>
  9668. function Ctrl($scope) {
  9669. $scope.text = 'guest';
  9670. $scope.word = /^\w*$/;
  9671. }
  9672. </script>
  9673. <form name="myForm" ng-controller="Ctrl">
  9674. Single word: <input type="text" name="input" ng-model="text"
  9675. ng-pattern="word" required>
  9676. <span class="error" ng-show="myForm.input.$error.required">
  9677. Required!</span>
  9678. <span class="error" ng-show="myForm.input.$error.pattern">
  9679. Single word only!</span>
  9680. <tt>text = {{text}}</tt><br/>
  9681. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
  9682. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
  9683. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  9684. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  9685. </form>
  9686. </doc:source>
  9687. <doc:scenario>
  9688. it('should initialize to model', function() {
  9689. expect(binding('text')).toEqual('guest');
  9690. expect(binding('myForm.input.$valid')).toEqual('true');
  9691. });
  9692. it('should be invalid if empty', function() {
  9693. input('text').enter('');
  9694. expect(binding('text')).toEqual('');
  9695. expect(binding('myForm.input.$valid')).toEqual('false');
  9696. });
  9697. it('should be invalid if multi word', function() {
  9698. input('text').enter('hello world');
  9699. expect(binding('myForm.input.$valid')).toEqual('false');
  9700. });
  9701. </doc:scenario>
  9702. </doc:example>
  9703. */
  9704. 'text': textInputType,
  9705. /**
  9706. * @ngdoc inputType
  9707. * @name ng.directive:input.number
  9708. *
  9709. * @description
  9710. * Text input with number validation and transformation. Sets the `number` validation
  9711. * error if not a valid number.
  9712. *
  9713. * @param {string} ngModel Assignable angular expression to data-bind to.
  9714. * @param {string=} name Property name of the form under which the control is published.
  9715. * @param {string=} min Sets the `min` validation error key if the value entered is less then `min`.
  9716. * @param {string=} max Sets the `max` validation error key if the value entered is greater then `min`.
  9717. * @param {string=} required Sets `required` validation error key if the value is not entered.
  9718. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  9719. * minlength.
  9720. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  9721. * maxlength.
  9722. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  9723. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  9724. * patterns defined as scope expressions.
  9725. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  9726. * interaction with the input element.
  9727. *
  9728. * @example
  9729. <doc:example>
  9730. <doc:source>
  9731. <script>
  9732. function Ctrl($scope) {
  9733. $scope.value = 12;
  9734. }
  9735. </script>
  9736. <form name="myForm" ng-controller="Ctrl">
  9737. Number: <input type="number" name="input" ng-model="value"
  9738. min="0" max="99" required>
  9739. <span class="error" ng-show="myForm.list.$error.required">
  9740. Required!</span>
  9741. <span class="error" ng-show="myForm.list.$error.number">
  9742. Not valid number!</span>
  9743. <tt>value = {{value}}</tt><br/>
  9744. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
  9745. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
  9746. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  9747. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  9748. </form>
  9749. </doc:source>
  9750. <doc:scenario>
  9751. it('should initialize to model', function() {
  9752. expect(binding('value')).toEqual('12');
  9753. expect(binding('myForm.input.$valid')).toEqual('true');
  9754. });
  9755. it('should be invalid if empty', function() {
  9756. input('value').enter('');
  9757. expect(binding('value')).toEqual('');
  9758. expect(binding('myForm.input.$valid')).toEqual('false');
  9759. });
  9760. it('should be invalid if over max', function() {
  9761. input('value').enter('123');
  9762. expect(binding('value')).toEqual('');
  9763. expect(binding('myForm.input.$valid')).toEqual('false');
  9764. });
  9765. </doc:scenario>
  9766. </doc:example>
  9767. */
  9768. 'number': numberInputType,
  9769. /**
  9770. * @ngdoc inputType
  9771. * @name ng.directive:input.url
  9772. *
  9773. * @description
  9774. * Text input with URL validation. Sets the `url` validation error key if the content is not a
  9775. * valid URL.
  9776. *
  9777. * @param {string} ngModel Assignable angular expression to data-bind to.
  9778. * @param {string=} name Property name of the form under which the control is published.
  9779. * @param {string=} required Sets `required` validation error key if the value is not entered.
  9780. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  9781. * minlength.
  9782. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  9783. * maxlength.
  9784. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  9785. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  9786. * patterns defined as scope expressions.
  9787. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  9788. * interaction with the input element.
  9789. *
  9790. * @example
  9791. <doc:example>
  9792. <doc:source>
  9793. <script>
  9794. function Ctrl($scope) {
  9795. $scope.text = 'http://google.com';
  9796. }
  9797. </script>
  9798. <form name="myForm" ng-controller="Ctrl">
  9799. URL: <input type="url" name="input" ng-model="text" required>
  9800. <span class="error" ng-show="myForm.input.$error.required">
  9801. Required!</span>
  9802. <span class="error" ng-show="myForm.input.$error.url">
  9803. Not valid url!</span>
  9804. <tt>text = {{text}}</tt><br/>
  9805. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
  9806. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
  9807. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  9808. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  9809. <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
  9810. </form>
  9811. </doc:source>
  9812. <doc:scenario>
  9813. it('should initialize to model', function() {
  9814. expect(binding('text')).toEqual('http://google.com');
  9815. expect(binding('myForm.input.$valid')).toEqual('true');
  9816. });
  9817. it('should be invalid if empty', function() {
  9818. input('text').enter('');
  9819. expect(binding('text')).toEqual('');
  9820. expect(binding('myForm.input.$valid')).toEqual('false');
  9821. });
  9822. it('should be invalid if not url', function() {
  9823. input('text').enter('xxx');
  9824. expect(binding('myForm.input.$valid')).toEqual('false');
  9825. });
  9826. </doc:scenario>
  9827. </doc:example>
  9828. */
  9829. 'url': urlInputType,
  9830. /**
  9831. * @ngdoc inputType
  9832. * @name ng.directive:input.email
  9833. *
  9834. * @description
  9835. * Text input with email validation. Sets the `email` validation error key if not a valid email
  9836. * address.
  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 Sets `required` validation error key if the value is not entered.
  9841. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  9842. * minlength.
  9843. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  9844. * maxlength.
  9845. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  9846. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  9847. * patterns defined as scope expressions.
  9848. *
  9849. * @example
  9850. <doc:example>
  9851. <doc:source>
  9852. <script>
  9853. function Ctrl($scope) {
  9854. $scope.text = 'me@example.com';
  9855. }
  9856. </script>
  9857. <form name="myForm" ng-controller="Ctrl">
  9858. Email: <input type="email" name="input" ng-model="text" required>
  9859. <span class="error" ng-show="myForm.input.$error.required">
  9860. Required!</span>
  9861. <span class="error" ng-show="myForm.input.$error.email">
  9862. Not valid email!</span>
  9863. <tt>text = {{text}}</tt><br/>
  9864. <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
  9865. <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
  9866. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  9867. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  9868. <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
  9869. </form>
  9870. </doc:source>
  9871. <doc:scenario>
  9872. it('should initialize to model', function() {
  9873. expect(binding('text')).toEqual('me@example.com');
  9874. expect(binding('myForm.input.$valid')).toEqual('true');
  9875. });
  9876. it('should be invalid if empty', function() {
  9877. input('text').enter('');
  9878. expect(binding('text')).toEqual('');
  9879. expect(binding('myForm.input.$valid')).toEqual('false');
  9880. });
  9881. it('should be invalid if not email', function() {
  9882. input('text').enter('xxx');
  9883. expect(binding('myForm.input.$valid')).toEqual('false');
  9884. });
  9885. </doc:scenario>
  9886. </doc:example>
  9887. */
  9888. 'email': emailInputType,
  9889. /**
  9890. * @ngdoc inputType
  9891. * @name ng.directive:input.radio
  9892. *
  9893. * @description
  9894. * HTML radio button.
  9895. *
  9896. * @param {string} ngModel Assignable angular expression to data-bind to.
  9897. * @param {string} value The value to which the expression should be set when selected.
  9898. * @param {string=} name Property name of the form under which the control is published.
  9899. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  9900. * interaction with the input element.
  9901. *
  9902. * @example
  9903. <doc:example>
  9904. <doc:source>
  9905. <script>
  9906. function Ctrl($scope) {
  9907. $scope.color = 'blue';
  9908. }
  9909. </script>
  9910. <form name="myForm" ng-controller="Ctrl">
  9911. <input type="radio" ng-model="color" value="red"> Red <br/>
  9912. <input type="radio" ng-model="color" value="green"> Green <br/>
  9913. <input type="radio" ng-model="color" value="blue"> Blue <br/>
  9914. <tt>color = {{color}}</tt><br/>
  9915. </form>
  9916. </doc:source>
  9917. <doc:scenario>
  9918. it('should change state', function() {
  9919. expect(binding('color')).toEqual('blue');
  9920. input('color').select('red');
  9921. expect(binding('color')).toEqual('red');
  9922. });
  9923. </doc:scenario>
  9924. </doc:example>
  9925. */
  9926. 'radio': radioInputType,
  9927. /**
  9928. * @ngdoc inputType
  9929. * @name ng.directive:input.checkbox
  9930. *
  9931. * @description
  9932. * HTML checkbox.
  9933. *
  9934. * @param {string} ngModel Assignable angular expression to data-bind to.
  9935. * @param {string=} name Property name of the form under which the control is published.
  9936. * @param {string=} ngTrueValue The value to which the expression should be set when selected.
  9937. * @param {string=} ngFalseValue The value to which the expression should be set when not selected.
  9938. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  9939. * interaction with the input element.
  9940. *
  9941. * @example
  9942. <doc:example>
  9943. <doc:source>
  9944. <script>
  9945. function Ctrl($scope) {
  9946. $scope.value1 = true;
  9947. $scope.value2 = 'YES'
  9948. }
  9949. </script>
  9950. <form name="myForm" ng-controller="Ctrl">
  9951. Value1: <input type="checkbox" ng-model="value1"> <br/>
  9952. Value2: <input type="checkbox" ng-model="value2"
  9953. ng-true-value="YES" ng-false-value="NO"> <br/>
  9954. <tt>value1 = {{value1}}</tt><br/>
  9955. <tt>value2 = {{value2}}</tt><br/>
  9956. </form>
  9957. </doc:source>
  9958. <doc:scenario>
  9959. it('should change state', function() {
  9960. expect(binding('value1')).toEqual('true');
  9961. expect(binding('value2')).toEqual('YES');
  9962. input('value1').check();
  9963. input('value2').check();
  9964. expect(binding('value1')).toEqual('false');
  9965. expect(binding('value2')).toEqual('NO');
  9966. });
  9967. </doc:scenario>
  9968. </doc:example>
  9969. */
  9970. 'checkbox': checkboxInputType,
  9971. 'hidden': noop,
  9972. 'button': noop,
  9973. 'submit': noop,
  9974. 'reset': noop
  9975. };
  9976. function isEmpty(value) {
  9977. return isUndefined(value) || value === '' || value === null || value !== value;
  9978. }
  9979. function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  9980. var listener = function() {
  9981. var value = trim(element.val());
  9982. if (ctrl.$viewValue !== value) {
  9983. scope.$apply(function() {
  9984. ctrl.$setViewValue(value);
  9985. });
  9986. }
  9987. };
  9988. // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
  9989. // input event on backspace, delete or cut
  9990. if ($sniffer.hasEvent('input')) {
  9991. element.bind('input', listener);
  9992. } else {
  9993. var timeout;
  9994. element.bind('keydown', function(event) {
  9995. var key = event.keyCode;
  9996. // ignore
  9997. // command modifiers arrows
  9998. if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
  9999. if (!timeout) {
  10000. timeout = $browser.defer(function() {
  10001. listener();
  10002. timeout = null;
  10003. });
  10004. }
  10005. });
  10006. // if user paste into input using mouse, we need "change" event to catch it
  10007. element.bind('change', listener);
  10008. }
  10009. ctrl.$render = function() {
  10010. element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
  10011. };
  10012. // pattern validator
  10013. var pattern = attr.ngPattern,
  10014. patternValidator;
  10015. var validate = function(regexp, value) {
  10016. if (isEmpty(value) || regexp.test(value)) {
  10017. ctrl.$setValidity('pattern', true);
  10018. return value;
  10019. } else {
  10020. ctrl.$setValidity('pattern', false);
  10021. return undefined;
  10022. }
  10023. };
  10024. if (pattern) {
  10025. if (pattern.match(/^\/(.*)\/$/)) {
  10026. pattern = new RegExp(pattern.substr(1, pattern.length - 2));
  10027. patternValidator = function(value) {
  10028. return validate(pattern, value)
  10029. };
  10030. } else {
  10031. patternValidator = function(value) {
  10032. var patternObj = scope.$eval(pattern);
  10033. if (!patternObj || !patternObj.test) {
  10034. throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj);
  10035. }
  10036. return validate(patternObj, value);
  10037. };
  10038. }
  10039. ctrl.$formatters.push(patternValidator);
  10040. ctrl.$parsers.push(patternValidator);
  10041. }
  10042. // min length validator
  10043. if (attr.ngMinlength) {
  10044. var minlength = int(attr.ngMinlength);
  10045. var minLengthValidator = function(value) {
  10046. if (!isEmpty(value) && value.length < minlength) {
  10047. ctrl.$setValidity('minlength', false);
  10048. return undefined;
  10049. } else {
  10050. ctrl.$setValidity('minlength', true);
  10051. return value;
  10052. }
  10053. };
  10054. ctrl.$parsers.push(minLengthValidator);
  10055. ctrl.$formatters.push(minLengthValidator);
  10056. }
  10057. // max length validator
  10058. if (attr.ngMaxlength) {
  10059. var maxlength = int(attr.ngMaxlength);
  10060. var maxLengthValidator = function(value) {
  10061. if (!isEmpty(value) && value.length > maxlength) {
  10062. ctrl.$setValidity('maxlength', false);
  10063. return undefined;
  10064. } else {
  10065. ctrl.$setValidity('maxlength', true);
  10066. return value;
  10067. }
  10068. };
  10069. ctrl.$parsers.push(maxLengthValidator);
  10070. ctrl.$formatters.push(maxLengthValidator);
  10071. }
  10072. }
  10073. function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  10074. textInputType(scope, element, attr, ctrl, $sniffer, $browser);
  10075. ctrl.$parsers.push(function(value) {
  10076. var empty = isEmpty(value);
  10077. if (empty || NUMBER_REGEXP.test(value)) {
  10078. ctrl.$setValidity('number', true);
  10079. return value === '' ? null : (empty ? value : parseFloat(value));
  10080. } else {
  10081. ctrl.$setValidity('number', false);
  10082. return undefined;
  10083. }
  10084. });
  10085. ctrl.$formatters.push(function(value) {
  10086. return isEmpty(value) ? '' : '' + value;
  10087. });
  10088. if (attr.min) {
  10089. var min = parseFloat(attr.min);
  10090. var minValidator = function(value) {
  10091. if (!isEmpty(value) && value < min) {
  10092. ctrl.$setValidity('min', false);
  10093. return undefined;
  10094. } else {
  10095. ctrl.$setValidity('min', true);
  10096. return value;
  10097. }
  10098. };
  10099. ctrl.$parsers.push(minValidator);
  10100. ctrl.$formatters.push(minValidator);
  10101. }
  10102. if (attr.max) {
  10103. var max = parseFloat(attr.max);
  10104. var maxValidator = function(value) {
  10105. if (!isEmpty(value) && value > max) {
  10106. ctrl.$setValidity('max', false);
  10107. return undefined;
  10108. } else {
  10109. ctrl.$setValidity('max', true);
  10110. return value;
  10111. }
  10112. };
  10113. ctrl.$parsers.push(maxValidator);
  10114. ctrl.$formatters.push(maxValidator);
  10115. }
  10116. ctrl.$formatters.push(function(value) {
  10117. if (isEmpty(value) || isNumber(value)) {
  10118. ctrl.$setValidity('number', true);
  10119. return value;
  10120. } else {
  10121. ctrl.$setValidity('number', false);
  10122. return undefined;
  10123. }
  10124. });
  10125. }
  10126. function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  10127. textInputType(scope, element, attr, ctrl, $sniffer, $browser);
  10128. var urlValidator = function(value) {
  10129. if (isEmpty(value) || URL_REGEXP.test(value)) {
  10130. ctrl.$setValidity('url', true);
  10131. return value;
  10132. } else {
  10133. ctrl.$setValidity('url', false);
  10134. return undefined;
  10135. }
  10136. };
  10137. ctrl.$formatters.push(urlValidator);
  10138. ctrl.$parsers.push(urlValidator);
  10139. }
  10140. function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  10141. textInputType(scope, element, attr, ctrl, $sniffer, $browser);
  10142. var emailValidator = function(value) {
  10143. if (isEmpty(value) || EMAIL_REGEXP.test(value)) {
  10144. ctrl.$setValidity('email', true);
  10145. return value;
  10146. } else {
  10147. ctrl.$setValidity('email', false);
  10148. return undefined;
  10149. }
  10150. };
  10151. ctrl.$formatters.push(emailValidator);
  10152. ctrl.$parsers.push(emailValidator);
  10153. }
  10154. function radioInputType(scope, element, attr, ctrl) {
  10155. // make the name unique, if not defined
  10156. if (isUndefined(attr.name)) {
  10157. element.attr('name', nextUid());
  10158. }
  10159. element.bind('click', function() {
  10160. if (element[0].checked) {
  10161. scope.$apply(function() {
  10162. ctrl.$setViewValue(attr.value);
  10163. });
  10164. }
  10165. });
  10166. ctrl.$render = function() {
  10167. var value = attr.value;
  10168. element[0].checked = (value == ctrl.$viewValue);
  10169. };
  10170. attr.$observe('value', ctrl.$render);
  10171. }
  10172. function checkboxInputType(scope, element, attr, ctrl) {
  10173. var trueValue = attr.ngTrueValue,
  10174. falseValue = attr.ngFalseValue;
  10175. if (!isString(trueValue)) trueValue = true;
  10176. if (!isString(falseValue)) falseValue = false;
  10177. element.bind('click', function() {
  10178. scope.$apply(function() {
  10179. ctrl.$setViewValue(element[0].checked);
  10180. });
  10181. });
  10182. ctrl.$render = function() {
  10183. element[0].checked = ctrl.$viewValue;
  10184. };
  10185. ctrl.$formatters.push(function(value) {
  10186. return value === trueValue;
  10187. });
  10188. ctrl.$parsers.push(function(value) {
  10189. return value ? trueValue : falseValue;
  10190. });
  10191. }
  10192. /**
  10193. * @ngdoc directive
  10194. * @name ng.directive:textarea
  10195. * @restrict E
  10196. *
  10197. * @description
  10198. * HTML textarea element control with angular data-binding. The data-binding and validation
  10199. * properties of this element are exactly the same as those of the
  10200. * {@link ng.directive:input input element}.
  10201. *
  10202. * @param {string} ngModel Assignable angular expression to data-bind to.
  10203. * @param {string=} name Property name of the form under which the control is published.
  10204. * @param {string=} required Sets `required` validation error key if the value is not entered.
  10205. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  10206. * minlength.
  10207. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  10208. * maxlength.
  10209. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  10210. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  10211. * patterns defined as scope expressions.
  10212. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  10213. * interaction with the input element.
  10214. */
  10215. /**
  10216. * @ngdoc directive
  10217. * @name ng.directive:input
  10218. * @restrict E
  10219. *
  10220. * @description
  10221. * HTML input element control with angular data-binding. Input control follows HTML5 input types
  10222. * and polyfills the HTML5 validation behavior for older browsers.
  10223. *
  10224. * @param {string} ngModel Assignable angular expression to data-bind to.
  10225. * @param {string=} name Property name of the form under which the control is published.
  10226. * @param {string=} required Sets `required` validation error key if the value is not entered.
  10227. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
  10228. * minlength.
  10229. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
  10230. * maxlength.
  10231. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
  10232. * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
  10233. * patterns defined as scope expressions.
  10234. * @param {string=} ngChange Angular expression to be executed when input changes due to user
  10235. * interaction with the input element.
  10236. *
  10237. * @example
  10238. <doc:example>
  10239. <doc:source>
  10240. <script>
  10241. function Ctrl($scope) {
  10242. $scope.user = {name: 'guest', last: 'visitor'};
  10243. }
  10244. </script>
  10245. <div ng-controller="Ctrl">
  10246. <form name="myForm">
  10247. User name: <input type="text" name="userName" ng-model="user.name" required>
  10248. <span class="error" ng-show="myForm.userName.$error.required">
  10249. Required!</span><br>
  10250. Last name: <input type="text" name="lastName" ng-model="user.last"
  10251. ng-minlength="3" ng-maxlength="10">
  10252. <span class="error" ng-show="myForm.lastName.$error.minlength">
  10253. Too short!</span>
  10254. <span class="error" ng-show="myForm.lastName.$error.maxlength">
  10255. Too long!</span><br>
  10256. </form>
  10257. <hr>
  10258. <tt>user = {{user}}</tt><br/>
  10259. <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br>
  10260. <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br>
  10261. <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br>
  10262. <tt>myForm.userName.$error = {{myForm.lastName.$error}}</tt><br>
  10263. <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
  10264. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
  10265. <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br>
  10266. <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
  10267. </div>
  10268. </doc:source>
  10269. <doc:scenario>
  10270. it('should initialize to model', function() {
  10271. expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
  10272. expect(binding('myForm.userName.$valid')).toEqual('true');
  10273. expect(binding('myForm.$valid')).toEqual('true');
  10274. });
  10275. it('should be invalid if empty when required', function() {
  10276. input('user.name').enter('');
  10277. expect(binding('user')).toEqual('{"last":"visitor"}');
  10278. expect(binding('myForm.userName.$valid')).toEqual('false');
  10279. expect(binding('myForm.$valid')).toEqual('false');
  10280. });
  10281. it('should be valid if empty when min length is set', function() {
  10282. input('user.last').enter('');
  10283. expect(binding('user')).toEqual('{"name":"guest","last":""}');
  10284. expect(binding('myForm.lastName.$valid')).toEqual('true');
  10285. expect(binding('myForm.$valid')).toEqual('true');
  10286. });
  10287. it('should be invalid if less than required min length', function() {
  10288. input('user.last').enter('xx');
  10289. expect(binding('user')).toEqual('{"name":"guest"}');
  10290. expect(binding('myForm.lastName.$valid')).toEqual('false');
  10291. expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
  10292. expect(binding('myForm.$valid')).toEqual('false');
  10293. });
  10294. it('should be invalid if longer than max length', function() {
  10295. input('user.last').enter('some ridiculously long name');
  10296. expect(binding('user'))
  10297. .toEqual('{"name":"guest"}');
  10298. expect(binding('myForm.lastName.$valid')).toEqual('false');
  10299. expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
  10300. expect(binding('myForm.$valid')).toEqual('false');
  10301. });
  10302. </doc:scenario>
  10303. </doc:example>
  10304. */
  10305. var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
  10306. return {
  10307. restrict: 'E',
  10308. require: '?ngModel',
  10309. link: function(scope, element, attr, ctrl) {
  10310. if (ctrl) {
  10311. (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,
  10312. $browser);
  10313. }
  10314. }
  10315. };
  10316. }];
  10317. var VALID_CLASS = 'ng-valid',
  10318. INVALID_CLASS = 'ng-invalid',
  10319. PRISTINE_CLASS = 'ng-pristine',
  10320. DIRTY_CLASS = 'ng-dirty';
  10321. /**
  10322. * @ngdoc object
  10323. * @name ng.directive:ngModel.NgModelController
  10324. *
  10325. * @property {string} $viewValue Actual string value in the view.
  10326. * @property {*} $modelValue The value in the model, that the control is bound to.
  10327. * @property {Array.<Function>} $parsers Whenever the control reads value from the DOM, it executes
  10328. * all of these functions to sanitize / convert the value as well as validate.
  10329. *
  10330. * @property {Array.<Function>} $formatters Whenever the model value changes, it executes all of
  10331. * these functions to convert the value as well as validate.
  10332. *
  10333. * @property {Object} $error An bject hash with all errors as keys.
  10334. *
  10335. * @property {boolean} $pristine True if user has not interacted with the control yet.
  10336. * @property {boolean} $dirty True if user has already interacted with the control.
  10337. * @property {boolean} $valid True if there is no error.
  10338. * @property {boolean} $invalid True if at least one error on the control.
  10339. *
  10340. * @description
  10341. *
  10342. * `NgModelController` provides API for the `ng-model` directive. The controller contains
  10343. * services for data-binding, validation, CSS update, value formatting and parsing. It
  10344. * specifically does not contain any logic which deals with DOM rendering or listening to
  10345. * DOM events. The `NgModelController` is meant to be extended by other directives where, the
  10346. * directive provides DOM manipulation and the `NgModelController` provides the data-binding.
  10347. *
  10348. * This example shows how to use `NgModelController` with a custom control to achieve
  10349. * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
  10350. * collaborate together to achieve the desired result.
  10351. *
  10352. * <example module="customControl">
  10353. <file name="style.css">
  10354. [contenteditable] {
  10355. border: 1px solid black;
  10356. background-color: white;
  10357. min-height: 20px;
  10358. }
  10359. .ng-invalid {
  10360. border: 1px solid red;
  10361. }
  10362. </file>
  10363. <file name="script.js">
  10364. angular.module('customControl', []).
  10365. directive('contenteditable', function() {
  10366. return {
  10367. restrict: 'A', // only activate on element attribute
  10368. require: '?ngModel', // get a hold of NgModelController
  10369. link: function(scope, element, attrs, ngModel) {
  10370. if(!ngModel) return; // do nothing if no ng-model
  10371. // Specify how UI should be updated
  10372. ngModel.$render = function() {
  10373. element.html(ngModel.$viewValue || '');
  10374. };
  10375. // Listen for change events to enable binding
  10376. element.bind('blur keyup change', function() {
  10377. scope.$apply(read);
  10378. });
  10379. read(); // initialize
  10380. // Write data to the model
  10381. function read() {
  10382. ngModel.$setViewValue(element.html());
  10383. }
  10384. }
  10385. };
  10386. });
  10387. </file>
  10388. <file name="index.html">
  10389. <form name="myForm">
  10390. <div contenteditable
  10391. name="myWidget" ng-model="userContent"
  10392. required>Change me!</div>
  10393. <span ng-show="myForm.myWidget.$error.required">Required!</span>
  10394. <hr>
  10395. <textarea ng-model="userContent"></textarea>
  10396. </form>
  10397. </file>
  10398. <file name="scenario.js">
  10399. it('should data-bind and become invalid', function() {
  10400. var contentEditable = element('[contenteditable]');
  10401. expect(contentEditable.text()).toEqual('Change me!');
  10402. input('userContent').enter('');
  10403. expect(contentEditable.text()).toEqual('');
  10404. expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
  10405. });
  10406. </file>
  10407. * </example>
  10408. *
  10409. */
  10410. var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
  10411. function($scope, $exceptionHandler, $attr, $element, $parse) {
  10412. this.$viewValue = Number.NaN;
  10413. this.$modelValue = Number.NaN;
  10414. this.$parsers = [];
  10415. this.$formatters = [];
  10416. this.$viewChangeListeners = [];
  10417. this.$pristine = true;
  10418. this.$dirty = false;
  10419. this.$valid = true;
  10420. this.$invalid = false;
  10421. this.$name = $attr.name;
  10422. var ngModelGet = $parse($attr.ngModel),
  10423. ngModelSet = ngModelGet.assign;
  10424. if (!ngModelSet) {
  10425. throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel +
  10426. ' (' + startingTag($element) + ')');
  10427. }
  10428. /**
  10429. * @ngdoc function
  10430. * @name ng.directive:ngModel.NgModelController#$render
  10431. * @methodOf ng.directive:ngModel.NgModelController
  10432. *
  10433. * @description
  10434. * Called when the view needs to be updated. It is expected that the user of the ng-model
  10435. * directive will implement this method.
  10436. */
  10437. this.$render = noop;
  10438. var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
  10439. invalidCount = 0, // used to easily determine if we are valid
  10440. $error = this.$error = {}; // keep invalid keys here
  10441. // Setup initial state of the control
  10442. $element.addClass(PRISTINE_CLASS);
  10443. toggleValidCss(true);
  10444. // convenience method for easy toggling of classes
  10445. function toggleValidCss(isValid, validationErrorKey) {
  10446. validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
  10447. $element.
  10448. removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
  10449. addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
  10450. }
  10451. /**
  10452. * @ngdoc function
  10453. * @name ng.directive:ngModel.NgModelController#$setValidity
  10454. * @methodOf ng.directive:ngModel.NgModelController
  10455. *
  10456. * @description
  10457. * Change the validity state, and notifies the form when the control changes validity. (i.e. it
  10458. * does not notify form if given validator is already marked as invalid).
  10459. *
  10460. * This method should be called by validators - i.e. the parser or formatter functions.
  10461. *
  10462. * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
  10463. * to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
  10464. * The `validationErrorKey` should be in camelCase and will get converted into dash-case
  10465. * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
  10466. * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
  10467. * @param {boolean} isValid Whether the current state is valid (true) or invalid (false).
  10468. */
  10469. this.$setValidity = function(validationErrorKey, isValid) {
  10470. if ($error[validationErrorKey] === !isValid) return;
  10471. if (isValid) {
  10472. if ($error[validationErrorKey]) invalidCount--;
  10473. if (!invalidCount) {
  10474. toggleValidCss(true);
  10475. this.$valid = true;
  10476. this.$invalid = false;
  10477. }
  10478. } else {
  10479. toggleValidCss(false);
  10480. this.$invalid = true;
  10481. this.$valid = false;
  10482. invalidCount++;
  10483. }
  10484. $error[validationErrorKey] = !isValid;
  10485. toggleValidCss(isValid, validationErrorKey);
  10486. parentForm.$setValidity(validationErrorKey, isValid, this);
  10487. };
  10488. /**
  10489. * @ngdoc function
  10490. * @name ng.directive:ngModel.NgModelController#$setViewValue
  10491. * @methodOf ng.directive:ngModel.NgModelController
  10492. *
  10493. * @description
  10494. * Read a value from view.
  10495. *
  10496. * This method should be called from within a DOM event handler.
  10497. * For example {@link ng.directive:input input} or
  10498. * {@link ng.directive:select select} directives call it.
  10499. *
  10500. * It internally calls all `formatters` and if resulted value is valid, updates the model and
  10501. * calls all registered change listeners.
  10502. *
  10503. * @param {string} value Value from the view.
  10504. */
  10505. this.$setViewValue = function(value) {
  10506. this.$viewValue = value;
  10507. // change to dirty
  10508. if (this.$pristine) {
  10509. this.$dirty = true;
  10510. this.$pristine = false;
  10511. $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
  10512. parentForm.$setDirty();
  10513. }
  10514. forEach(this.$parsers, function(fn) {
  10515. value = fn(value);
  10516. });
  10517. if (this.$modelValue !== value) {
  10518. this.$modelValue = value;
  10519. ngModelSet($scope, value);
  10520. forEach(this.$viewChangeListeners, function(listener) {
  10521. try {
  10522. listener();
  10523. } catch(e) {
  10524. $exceptionHandler(e);
  10525. }
  10526. })
  10527. }
  10528. };
  10529. // model -> value
  10530. var ctrl = this;
  10531. $scope.$watch(ngModelGet, function(value) {
  10532. // ignore change from view
  10533. if (ctrl.$modelValue === value) return;
  10534. var formatters = ctrl.$formatters,
  10535. idx = formatters.length;
  10536. ctrl.$modelValue = value;
  10537. while(idx--) {
  10538. value = formatters[idx](value);
  10539. }
  10540. if (ctrl.$viewValue !== value) {
  10541. ctrl.$viewValue = value;
  10542. ctrl.$render();
  10543. }
  10544. });
  10545. }];
  10546. /**
  10547. * @ngdoc directive
  10548. * @name ng.directive:ngModel
  10549. *
  10550. * @element input
  10551. *
  10552. * @description
  10553. * Is directive that tells Angular to do two-way data binding. It works together with `input`,
  10554. * `select`, `textarea`. You can easily write your own directives to use `ngModel` as well.
  10555. *
  10556. * `ngModel` is responsible for:
  10557. *
  10558. * - binding the view into the model, which other directives such as `input`, `textarea` or `select`
  10559. * require,
  10560. * - providing validation behavior (i.e. required, number, email, url),
  10561. * - keeping state of the control (valid/invalid, dirty/pristine, validation errors),
  10562. * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`),
  10563. * - register the control with parent {@link ng.directive:form form}.
  10564. *
  10565. * For basic examples, how to use `ngModel`, see:
  10566. *
  10567. * - {@link ng.directive:input input}
  10568. * - {@link ng.directive:input.text text}
  10569. * - {@link ng.directive:input.checkbox checkbox}
  10570. * - {@link ng.directive:input.radio radio}
  10571. * - {@link ng.directive:input.number number}
  10572. * - {@link ng.directive:input.email email}
  10573. * - {@link ng.directive:input.url url}
  10574. * - {@link ng.directive:select select}
  10575. * - {@link ng.directive:textarea textarea}
  10576. *
  10577. */
  10578. var ngModelDirective = function() {
  10579. return {
  10580. require: ['ngModel', '^?form'],
  10581. controller: NgModelController,
  10582. link: function(scope, element, attr, ctrls) {
  10583. // notify others, especially parent forms
  10584. var modelCtrl = ctrls[0],
  10585. formCtrl = ctrls[1] || nullFormCtrl;
  10586. formCtrl.$addControl(modelCtrl);
  10587. element.bind('$destroy', function() {
  10588. formCtrl.$removeControl(modelCtrl);
  10589. });
  10590. }
  10591. };
  10592. };
  10593. /**
  10594. * @ngdoc directive
  10595. * @name ng.directive:ngChange
  10596. * @restrict E
  10597. *
  10598. * @description
  10599. * Evaluate given expression when user changes the input.
  10600. * The expression is not evaluated when the value change is coming from the model.
  10601. *
  10602. * Note, this directive requires `ngModel` to be present.
  10603. *
  10604. * @element input
  10605. *
  10606. * @example
  10607. * <doc:example>
  10608. * <doc:source>
  10609. * <script>
  10610. * function Controller($scope) {
  10611. * $scope.counter = 0;
  10612. * $scope.change = function() {
  10613. * $scope.counter++;
  10614. * };
  10615. * }
  10616. * </script>
  10617. * <div ng-controller="Controller">
  10618. * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
  10619. * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
  10620. * <label for="ng-change-example2">Confirmed</label><br />
  10621. * debug = {{confirmed}}<br />
  10622. * counter = {{counter}}
  10623. * </div>
  10624. * </doc:source>
  10625. * <doc:scenario>
  10626. * it('should evaluate the expression if changing from view', function() {
  10627. * expect(binding('counter')).toEqual('0');
  10628. * element('#ng-change-example1').click();
  10629. * expect(binding('counter')).toEqual('1');
  10630. * expect(binding('confirmed')).toEqual('true');
  10631. * });
  10632. *
  10633. * it('should not evaluate the expression if changing from model', function() {
  10634. * element('#ng-change-example2').click();
  10635. * expect(binding('counter')).toEqual('0');
  10636. * expect(binding('confirmed')).toEqual('true');
  10637. * });
  10638. * </doc:scenario>
  10639. * </doc:example>
  10640. */
  10641. var ngChangeDirective = valueFn({
  10642. require: 'ngModel',
  10643. link: function(scope, element, attr, ctrl) {
  10644. ctrl.$viewChangeListeners.push(function() {
  10645. scope.$eval(attr.ngChange);
  10646. });
  10647. }
  10648. });
  10649. var requiredDirective = function() {
  10650. return {
  10651. require: '?ngModel',
  10652. link: function(scope, elm, attr, ctrl) {
  10653. if (!ctrl) return;
  10654. attr.required = true; // force truthy in case we are on non input element
  10655. var validator = function(value) {
  10656. if (attr.required && (isEmpty(value) || value === false)) {
  10657. ctrl.$setValidity('required', false);
  10658. return;
  10659. } else {
  10660. ctrl.$setValidity('required', true);
  10661. return value;
  10662. }
  10663. };
  10664. ctrl.$formatters.push(validator);
  10665. ctrl.$parsers.unshift(validator);
  10666. attr.$observe('required', function() {
  10667. validator(ctrl.$viewValue);
  10668. });
  10669. }
  10670. };
  10671. };
  10672. /**
  10673. * @ngdoc directive
  10674. * @name ng.directive:ngList
  10675. *
  10676. * @description
  10677. * Text input that converts between comma-seperated string into an array of strings.
  10678. *
  10679. * @element input
  10680. * @param {string=} ngList optional delimiter that should be used to split the value. If
  10681. * specified in form `/something/` then the value will be converted into a regular expression.
  10682. *
  10683. * @example
  10684. <doc:example>
  10685. <doc:source>
  10686. <script>
  10687. function Ctrl($scope) {
  10688. $scope.names = ['igor', 'misko', 'vojta'];
  10689. }
  10690. </script>
  10691. <form name="myForm" ng-controller="Ctrl">
  10692. List: <input name="namesInput" ng-model="names" ng-list required>
  10693. <span class="error" ng-show="myForm.list.$error.required">
  10694. Required!</span>
  10695. <tt>names = {{names}}</tt><br/>
  10696. <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
  10697. <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
  10698. <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
  10699. <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
  10700. </form>
  10701. </doc:source>
  10702. <doc:scenario>
  10703. it('should initialize to model', function() {
  10704. expect(binding('names')).toEqual('["igor","misko","vojta"]');
  10705. expect(binding('myForm.namesInput.$valid')).toEqual('true');
  10706. });
  10707. it('should be invalid if empty', function() {
  10708. input('names').enter('');
  10709. expect(binding('names')).toEqual('[]');
  10710. expect(binding('myForm.namesInput.$valid')).toEqual('false');
  10711. });
  10712. </doc:scenario>
  10713. </doc:example>
  10714. */
  10715. var ngListDirective = function() {
  10716. return {
  10717. require: 'ngModel',
  10718. link: function(scope, element, attr, ctrl) {
  10719. var match = /\/(.*)\//.exec(attr.ngList),
  10720. separator = match && new RegExp(match[1]) || attr.ngList || ',';
  10721. var parse = function(viewValue) {
  10722. var list = [];
  10723. if (viewValue) {
  10724. forEach(viewValue.split(separator), function(value) {
  10725. if (value) list.push(trim(value));
  10726. });
  10727. }
  10728. return list;
  10729. };
  10730. ctrl.$parsers.push(parse);
  10731. ctrl.$formatters.push(function(value) {
  10732. if (isArray(value) && !equals(parse(ctrl.$viewValue), value)) {
  10733. return value.join(', ');
  10734. }
  10735. return undefined;
  10736. });
  10737. }
  10738. };
  10739. };
  10740. var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
  10741. var ngValueDirective = function() {
  10742. return {
  10743. priority: 100,
  10744. compile: function(tpl, tplAttr) {
  10745. if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
  10746. return function(scope, elm, attr) {
  10747. attr.$set('value', scope.$eval(attr.ngValue));
  10748. };
  10749. } else {
  10750. return function(scope, elm, attr) {
  10751. scope.$watch(attr.ngValue, function(value) {
  10752. attr.$set('value', value, false);
  10753. });
  10754. };
  10755. }
  10756. }
  10757. };
  10758. };
  10759. /**
  10760. * @ngdoc directive
  10761. * @name ng.directive:ngBind
  10762. *
  10763. * @description
  10764. * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
  10765. * with the value of a given expression, and to update the text content when the value of that
  10766. * expression changes.
  10767. *
  10768. * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
  10769. * `{{ expression }}` which is similar but less verbose.
  10770. *
  10771. * Once scenario in which the use of `ngBind` is prefered over `{{ expression }}` binding is when
  10772. * it's desirable to put bindings into template that is momentarily displayed by the browser in its
  10773. * raw state before Angular compiles it. Since `ngBind` is an element attribute, it makes the
  10774. * bindings invisible to the user while the page is loading.
  10775. *
  10776. * An alternative solution to this problem would be using the
  10777. * {@link ng.directive:ngCloak ngCloak} directive.
  10778. *
  10779. *
  10780. * @element ANY
  10781. * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
  10782. *
  10783. * @example
  10784. * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
  10785. <doc:example>
  10786. <doc:source>
  10787. <script>
  10788. function Ctrl($scope) {
  10789. $scope.name = 'Whirled';
  10790. }
  10791. </script>
  10792. <div ng-controller="Ctrl">
  10793. Enter name: <input type="text" ng-model="name"><br>
  10794. Hello <span ng-bind="name"></span>!
  10795. </div>
  10796. </doc:source>
  10797. <doc:scenario>
  10798. it('should check ng-bind', function() {
  10799. expect(using('.doc-example-live').binding('name')).toBe('Whirled');
  10800. using('.doc-example-live').input('name').enter('world');
  10801. expect(using('.doc-example-live').binding('name')).toBe('world');
  10802. });
  10803. </doc:scenario>
  10804. </doc:example>
  10805. */
  10806. var ngBindDirective = ngDirective(function(scope, element, attr) {
  10807. element.addClass('ng-binding').data('$binding', attr.ngBind);
  10808. scope.$watch(attr.ngBind, function(value) {
  10809. element.text(value == undefined ? '' : value);
  10810. });
  10811. });
  10812. /**
  10813. * @ngdoc directive
  10814. * @name ng.directive:ngBindTemplate
  10815. *
  10816. * @description
  10817. * The `ngBindTemplate` directive specifies that the element
  10818. * text should be replaced with the template in ngBindTemplate.
  10819. * Unlike ngBind the ngBindTemplate can contain multiple `{{` `}}`
  10820. * expressions. (This is required since some HTML elements
  10821. * can not have SPAN elements such as TITLE, or OPTION to name a few.)
  10822. *
  10823. * @element ANY
  10824. * @param {string} ngBindTemplate template of form
  10825. * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
  10826. *
  10827. * @example
  10828. * Try it here: enter text in text box and watch the greeting change.
  10829. <doc:example>
  10830. <doc:source>
  10831. <script>
  10832. function Ctrl($scope) {
  10833. $scope.salutation = 'Hello';
  10834. $scope.name = 'World';
  10835. }
  10836. </script>
  10837. <div ng-controller="Ctrl">
  10838. Salutation: <input type="text" ng-model="salutation"><br>
  10839. Name: <input type="text" ng-model="name"><br>
  10840. <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
  10841. </div>
  10842. </doc:source>
  10843. <doc:scenario>
  10844. it('should check ng-bind', function() {
  10845. expect(using('.doc-example-live').binding('salutation')).
  10846. toBe('Hello');
  10847. expect(using('.doc-example-live').binding('name')).
  10848. toBe('World');
  10849. using('.doc-example-live').input('salutation').enter('Greetings');
  10850. using('.doc-example-live').input('name').enter('user');
  10851. expect(using('.doc-example-live').binding('salutation')).
  10852. toBe('Greetings');
  10853. expect(using('.doc-example-live').binding('name')).
  10854. toBe('user');
  10855. });
  10856. </doc:scenario>
  10857. </doc:example>
  10858. */
  10859. var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
  10860. return function(scope, element, attr) {
  10861. // TODO: move this to scenario runner
  10862. var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
  10863. element.addClass('ng-binding').data('$binding', interpolateFn);
  10864. attr.$observe('ngBindTemplate', function(value) {
  10865. element.text(value);
  10866. });
  10867. }
  10868. }];
  10869. /**
  10870. * @ngdoc directive
  10871. * @name ng.directive:ngBindHtmlUnsafe
  10872. *
  10873. * @description
  10874. * Creates a binding that will innerHTML the result of evaluating the `expression` into the current
  10875. * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if
  10876. * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too
  10877. * restrictive and when you absolutely trust the source of the content you are binding to.
  10878. *
  10879. * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
  10880. *
  10881. * @element ANY
  10882. * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate.
  10883. */
  10884. var ngBindHtmlUnsafeDirective = [function() {
  10885. return function(scope, element, attr) {
  10886. element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe);
  10887. scope.$watch(attr.ngBindHtmlUnsafe, function(value) {
  10888. element.html(value || '');
  10889. });
  10890. };
  10891. }];
  10892. function classDirective(name, selector) {
  10893. name = 'ngClass' + name;
  10894. return ngDirective(function(scope, element, attr) {
  10895. scope.$watch(attr[name], function(newVal, oldVal) {
  10896. if (selector === true || scope.$index % 2 === selector) {
  10897. if (oldVal && (newVal !== oldVal)) {
  10898. if (isObject(oldVal) && !isArray(oldVal))
  10899. oldVal = map(oldVal, function(v, k) { if (v) return k });
  10900. element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal);
  10901. }
  10902. if (isObject(newVal) && !isArray(newVal))
  10903. newVal = map(newVal, function(v, k) { if (v) return k });
  10904. if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); }
  10905. }, true);
  10906. });
  10907. }
  10908. /**
  10909. * @ngdoc directive
  10910. * @name ng.directive:ngClass
  10911. *
  10912. * @description
  10913. * The `ngClass` allows you to set CSS class on HTML element dynamically by databinding an
  10914. * expression that represents all classes to be added.
  10915. *
  10916. * The directive won't add duplicate classes if a particular class was already set.
  10917. *
  10918. * When the expression changes, the previously added classes are removed and only then the classes
  10919. * new classes are added.
  10920. *
  10921. * @element ANY
  10922. * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
  10923. * of the evaluation can be a string representing space delimited class
  10924. * names, an array, or a map of class names to boolean values.
  10925. *
  10926. * @example
  10927. <example>
  10928. <file name="index.html">
  10929. <input type="button" value="set" ng-click="myVar='my-class'">
  10930. <input type="button" value="clear" ng-click="myVar=''">
  10931. <br>
  10932. <span ng-class="myVar">Sample Text</span>
  10933. </file>
  10934. <file name="style.css">
  10935. .my-class {
  10936. color: red;
  10937. }
  10938. </file>
  10939. <file name="scenario.js">
  10940. it('should check ng-class', function() {
  10941. expect(element('.doc-example-live span').prop('className')).not().
  10942. toMatch(/my-class/);
  10943. using('.doc-example-live').element(':button:first').click();
  10944. expect(element('.doc-example-live span').prop('className')).
  10945. toMatch(/my-class/);
  10946. using('.doc-example-live').element(':button:last').click();
  10947. expect(element('.doc-example-live span').prop('className')).not().
  10948. toMatch(/my-class/);
  10949. });
  10950. </file>
  10951. </example>
  10952. */
  10953. var ngClassDirective = classDirective('', true);
  10954. /**
  10955. * @ngdoc directive
  10956. * @name ng.directive:ngClassOdd
  10957. *
  10958. * @description
  10959. * The `ngClassOdd` and `ngClassEven` directives work exactly as
  10960. * {@link ng.directive:ngClass ngClass}, except it works in
  10961. * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
  10962. *
  10963. * This directive can be applied only within a scope of an
  10964. * {@link ng.directive:ngRepeat ngRepeat}.
  10965. *
  10966. * @element ANY
  10967. * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
  10968. * of the evaluation can be a string representing space delimited class names or an array.
  10969. *
  10970. * @example
  10971. <example>
  10972. <file name="index.html">
  10973. <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
  10974. <li ng-repeat="name in names">
  10975. <span ng-class-odd="'odd'" ng-class-even="'even'">
  10976. {{name}}
  10977. </span>
  10978. </li>
  10979. </ol>
  10980. </file>
  10981. <file name="style.css">
  10982. .odd {
  10983. color: red;
  10984. }
  10985. .even {
  10986. color: blue;
  10987. }
  10988. </file>
  10989. <file name="scenario.js">
  10990. it('should check ng-class-odd and ng-class-even', function() {
  10991. expect(element('.doc-example-live li:first span').prop('className')).
  10992. toMatch(/odd/);
  10993. expect(element('.doc-example-live li:last span').prop('className')).
  10994. toMatch(/even/);
  10995. });
  10996. </file>
  10997. </example>
  10998. */
  10999. var ngClassOddDirective = classDirective('Odd', 0);
  11000. /**
  11001. * @ngdoc directive
  11002. * @name ng.directive:ngClassEven
  11003. *
  11004. * @description
  11005. * The `ngClassOdd` and `ngClassEven` works exactly as
  11006. * {@link ng.directive:ngClass ngClass}, except it works in
  11007. * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
  11008. *
  11009. * This directive can be applied only within a scope of an
  11010. * {@link ng.directive:ngRepeat ngRepeat}.
  11011. *
  11012. * @element ANY
  11013. * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
  11014. * result of the evaluation can be a string representing space delimited class names or an array.
  11015. *
  11016. * @example
  11017. <example>
  11018. <file name="index.html">
  11019. <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
  11020. <li ng-repeat="name in names">
  11021. <span ng-class-odd="'odd'" ng-class-even="'even'">
  11022. {{name}} &nbsp; &nbsp; &nbsp;
  11023. </span>
  11024. </li>
  11025. </ol>
  11026. </file>
  11027. <file name="style.css">
  11028. .odd {
  11029. color: red;
  11030. }
  11031. .even {
  11032. color: blue;
  11033. }
  11034. </file>
  11035. <file name="scenario.js">
  11036. it('should check ng-class-odd and ng-class-even', function() {
  11037. expect(element('.doc-example-live li:first span').prop('className')).
  11038. toMatch(/odd/);
  11039. expect(element('.doc-example-live li:last span').prop('className')).
  11040. toMatch(/even/);
  11041. });
  11042. </file>
  11043. </example>
  11044. */
  11045. var ngClassEvenDirective = classDirective('Even', 1);
  11046. /**
  11047. * @ngdoc directive
  11048. * @name ng.directive:ngCloak
  11049. *
  11050. * @description
  11051. * The `ngCloak` directive is used to prevent the Angular html template from being briefly
  11052. * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
  11053. * directive to avoid the undesirable flicker effect caused by the html template display.
  11054. *
  11055. * The directive can be applied to the `<body>` element, but typically a fine-grained application is
  11056. * prefered in order to benefit from progressive rendering of the browser view.
  11057. *
  11058. * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and
  11059. * `angular.min.js` files. Following is the css rule:
  11060. *
  11061. * <pre>
  11062. * [ng\:cloak], [ng-cloak], .ng-cloak {
  11063. * display: none;
  11064. * }
  11065. * </pre>
  11066. *
  11067. * When this css rule is loaded by the browser, all html elements (including their children) that
  11068. * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive
  11069. * during the compilation of the template it deletes the `ngCloak` element attribute, which
  11070. * makes the compiled element visible.
  11071. *
  11072. * For the best result, `angular.js` script must be loaded in the head section of the html file;
  11073. * alternatively, the css rule (above) must be included in the external stylesheet of the
  11074. * application.
  11075. *
  11076. * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
  11077. * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
  11078. * class `ngCloak` in addition to `ngCloak` directive as shown in the example below.
  11079. *
  11080. * @element ANY
  11081. *
  11082. * @example
  11083. <doc:example>
  11084. <doc:source>
  11085. <div id="template1" ng-cloak>{{ 'hello' }}</div>
  11086. <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
  11087. </doc:source>
  11088. <doc:scenario>
  11089. it('should remove the template directive and css class', function() {
  11090. expect(element('.doc-example-live #template1').attr('ng-cloak')).
  11091. not().toBeDefined();
  11092. expect(element('.doc-example-live #template2').attr('ng-cloak')).
  11093. not().toBeDefined();
  11094. });
  11095. </doc:scenario>
  11096. </doc:example>
  11097. *
  11098. */
  11099. var ngCloakDirective = ngDirective({
  11100. compile: function(element, attr) {
  11101. attr.$set('ngCloak', undefined);
  11102. element.removeClass('ng-cloak');
  11103. }
  11104. });
  11105. /**
  11106. * @ngdoc directive
  11107. * @name ng.directive:ngController
  11108. *
  11109. * @description
  11110. * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular
  11111. * supports the principles behind the Model-View-Controller design pattern.
  11112. *
  11113. * MVC components in angular:
  11114. *
  11115. * * Model — The Model is data in scope properties; scopes are attached to the DOM.
  11116. * * View — The template (HTML with data bindings) is rendered into the View.
  11117. * * Controller — The `ngController` directive specifies a Controller class; the class has
  11118. * methods that typically express the business logic behind the application.
  11119. *
  11120. * Note that an alternative way to define controllers is via the `{@link ng.$route}`
  11121. * service.
  11122. *
  11123. * @element ANY
  11124. * @scope
  11125. * @param {expression} ngController Name of a globally accessible constructor function or an
  11126. * {@link guide/expression expression} that on the current scope evaluates to a
  11127. * constructor function.
  11128. *
  11129. * @example
  11130. * Here is a simple form for editing user contact information. Adding, removing, clearing, and
  11131. * greeting are methods declared on the controller (see source tab). These methods can
  11132. * easily be called from the angular markup. Notice that the scope becomes the `this` for the
  11133. * controller's instance. This allows for easy access to the view data from the controller. Also
  11134. * notice that any changes to the data are automatically reflected in the View without the need
  11135. * for a manual update.
  11136. <doc:example>
  11137. <doc:source>
  11138. <script>
  11139. function SettingsController($scope) {
  11140. $scope.name = "John Smith";
  11141. $scope.contacts = [
  11142. {type:'phone', value:'408 555 1212'},
  11143. {type:'email', value:'john.smith@example.org'} ];
  11144. $scope.greet = function() {
  11145. alert(this.name);
  11146. };
  11147. $scope.addContact = function() {
  11148. this.contacts.push({type:'email', value:'yourname@example.org'});
  11149. };
  11150. $scope.removeContact = function(contactToRemove) {
  11151. var index = this.contacts.indexOf(contactToRemove);
  11152. this.contacts.splice(index, 1);
  11153. };
  11154. $scope.clearContact = function(contact) {
  11155. contact.type = 'phone';
  11156. contact.value = '';
  11157. };
  11158. }
  11159. </script>
  11160. <div ng-controller="SettingsController">
  11161. Name: <input type="text" ng-model="name"/>
  11162. [ <a href="" ng-click="greet()">greet</a> ]<br/>
  11163. Contact:
  11164. <ul>
  11165. <li ng-repeat="contact in contacts">
  11166. <select ng-model="contact.type">
  11167. <option>phone</option>
  11168. <option>email</option>
  11169. </select>
  11170. <input type="text" ng-model="contact.value"/>
  11171. [ <a href="" ng-click="clearContact(contact)">clear</a>
  11172. | <a href="" ng-click="removeContact(contact)">X</a> ]
  11173. </li>
  11174. <li>[ <a href="" ng-click="addContact()">add</a> ]</li>
  11175. </ul>
  11176. </div>
  11177. </doc:source>
  11178. <doc:scenario>
  11179. it('should check controller', function() {
  11180. expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
  11181. expect(element('.doc-example-live li:nth-child(1) input').val())
  11182. .toBe('408 555 1212');
  11183. expect(element('.doc-example-live li:nth-child(2) input').val())
  11184. .toBe('john.smith@example.org');
  11185. element('.doc-example-live li:first a:contains("clear")').click();
  11186. expect(element('.doc-example-live li:first input').val()).toBe('');
  11187. element('.doc-example-live li:last a:contains("add")').click();
  11188. expect(element('.doc-example-live li:nth-child(3) input').val())
  11189. .toBe('yourname@example.org');
  11190. });
  11191. </doc:scenario>
  11192. </doc:example>
  11193. */
  11194. var ngControllerDirective = [function() {
  11195. return {
  11196. scope: true,
  11197. controller: '@'
  11198. };
  11199. }];
  11200. /**
  11201. * @ngdoc directive
  11202. * @name ng.directive:ngCsp
  11203. * @priority 1000
  11204. *
  11205. * @description
  11206. * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
  11207. * This directive should be used on the root element of the application (typically the `<html>`
  11208. * element or other element with the {@link ng.directive:ngApp ngApp}
  11209. * directive).
  11210. *
  11211. * If enabled the performance of template expression evaluator will suffer slightly, so don't enable
  11212. * this mode unless you need it.
  11213. *
  11214. * @element html
  11215. */
  11216. var ngCspDirective = ['$sniffer', function($sniffer) {
  11217. return {
  11218. priority: 1000,
  11219. compile: function() {
  11220. $sniffer.csp = true;
  11221. }
  11222. };
  11223. }];
  11224. /**
  11225. * @ngdoc directive
  11226. * @name ng.directive:ngClick
  11227. *
  11228. * @description
  11229. * The ngClick allows you to specify custom behavior when
  11230. * element is clicked.
  11231. *
  11232. * @element ANY
  11233. * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
  11234. * click. (Event object is available as `$event`)
  11235. *
  11236. * @example
  11237. <doc:example>
  11238. <doc:source>
  11239. <button ng-click="count = count + 1" ng-init="count=0">
  11240. Increment
  11241. </button>
  11242. count: {{count}}
  11243. </doc:source>
  11244. <doc:scenario>
  11245. it('should check ng-click', function() {
  11246. expect(binding('count')).toBe('0');
  11247. element('.doc-example-live :button').click();
  11248. expect(binding('count')).toBe('1');
  11249. });
  11250. </doc:scenario>
  11251. </doc:example>
  11252. */
  11253. /*
  11254. * A directive that allows creation of custom onclick handlers that are defined as angular
  11255. * expressions and are compiled and executed within the current scope.
  11256. *
  11257. * Events that are handled via these handler are always configured not to propagate further.
  11258. */
  11259. var ngEventDirectives = {};
  11260. forEach(
  11261. 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '),
  11262. function(name) {
  11263. var directiveName = directiveNormalize('ng-' + name);
  11264. ngEventDirectives[directiveName] = ['$parse', function($parse) {
  11265. return function(scope, element, attr) {
  11266. var fn = $parse(attr[directiveName]);
  11267. element.bind(lowercase(name), function(event) {
  11268. scope.$apply(function() {
  11269. fn(scope, {$event:event});
  11270. });
  11271. });
  11272. };
  11273. }];
  11274. }
  11275. );
  11276. /**
  11277. * @ngdoc directive
  11278. * @name ng.directive:ngDblclick
  11279. *
  11280. * @description
  11281. * The `ngDblclick` directive allows you to specify custom behavior on dblclick event.
  11282. *
  11283. * @element ANY
  11284. * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
  11285. * dblclick. (Event object is available as `$event`)
  11286. *
  11287. * @example
  11288. * See {@link ng.directive:ngClick ngClick}
  11289. */
  11290. /**
  11291. * @ngdoc directive
  11292. * @name ng.directive:ngMousedown
  11293. *
  11294. * @description
  11295. * The ngMousedown directive allows you to specify custom behavior on mousedown event.
  11296. *
  11297. * @element ANY
  11298. * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
  11299. * mousedown. (Event object is available as `$event`)
  11300. *
  11301. * @example
  11302. * See {@link ng.directive:ngClick ngClick}
  11303. */
  11304. /**
  11305. * @ngdoc directive
  11306. * @name ng.directive:ngMouseup
  11307. *
  11308. * @description
  11309. * Specify custom behavior on mouseup event.
  11310. *
  11311. * @element ANY
  11312. * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
  11313. * mouseup. (Event object is available as `$event`)
  11314. *
  11315. * @example
  11316. * See {@link ng.directive:ngClick ngClick}
  11317. */
  11318. /**
  11319. * @ngdoc directive
  11320. * @name ng.directive:ngMouseover
  11321. *
  11322. * @description
  11323. * Specify custom behavior on mouseover event.
  11324. *
  11325. * @element ANY
  11326. * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
  11327. * mouseover. (Event object is available as `$event`)
  11328. *
  11329. * @example
  11330. * See {@link ng.directive:ngClick ngClick}
  11331. */
  11332. /**
  11333. * @ngdoc directive
  11334. * @name ng.directive:ngMouseenter
  11335. *
  11336. * @description
  11337. * Specify custom behavior on mouseenter event.
  11338. *
  11339. * @element ANY
  11340. * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
  11341. * mouseenter. (Event object is available as `$event`)
  11342. *
  11343. * @example
  11344. * See {@link ng.directive:ngClick ngClick}
  11345. */
  11346. /**
  11347. * @ngdoc directive
  11348. * @name ng.directive:ngMouseleave
  11349. *
  11350. * @description
  11351. * Specify custom behavior on mouseleave event.
  11352. *
  11353. * @element ANY
  11354. * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
  11355. * mouseleave. (Event object is available as `$event`)
  11356. *
  11357. * @example
  11358. * See {@link ng.directive:ngClick ngClick}
  11359. */
  11360. /**
  11361. * @ngdoc directive
  11362. * @name ng.directive:ngMousemove
  11363. *
  11364. * @description
  11365. * Specify custom behavior on mousemove event.
  11366. *
  11367. * @element ANY
  11368. * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
  11369. * mousemove. (Event object is available as `$event`)
  11370. *
  11371. * @example
  11372. * See {@link ng.directive:ngClick ngClick}
  11373. */
  11374. /**
  11375. * @ngdoc directive
  11376. * @name ng.directive:ngSubmit
  11377. *
  11378. * @description
  11379. * Enables binding angular expressions to onsubmit events.
  11380. *
  11381. * Additionally it prevents the default action (which for form means sending the request to the
  11382. * server and reloading the current page).
  11383. *
  11384. * @element form
  11385. * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
  11386. *
  11387. * @example
  11388. <doc:example>
  11389. <doc:source>
  11390. <script>
  11391. function Ctrl($scope) {
  11392. $scope.list = [];
  11393. $scope.text = 'hello';
  11394. $scope.submit = function() {
  11395. if (this.text) {
  11396. this.list.push(this.text);
  11397. this.text = '';
  11398. }
  11399. };
  11400. }
  11401. </script>
  11402. <form ng-submit="submit()" ng-controller="Ctrl">
  11403. Enter text and hit enter:
  11404. <input type="text" ng-model="text" name="text" />
  11405. <input type="submit" id="submit" value="Submit" />
  11406. <pre>list={{list}}</pre>
  11407. </form>
  11408. </doc:source>
  11409. <doc:scenario>
  11410. it('should check ng-submit', function() {
  11411. expect(binding('list')).toBe('[]');
  11412. element('.doc-example-live #submit').click();
  11413. expect(binding('list')).toBe('["hello"]');
  11414. expect(input('text').val()).toBe('');
  11415. });
  11416. it('should ignore empty strings', function() {
  11417. expect(binding('list')).toBe('[]');
  11418. element('.doc-example-live #submit').click();
  11419. element('.doc-example-live #submit').click();
  11420. expect(binding('list')).toBe('["hello"]');
  11421. });
  11422. </doc:scenario>
  11423. </doc:example>
  11424. */
  11425. var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
  11426. element.bind('submit', function() {
  11427. scope.$apply(attrs.ngSubmit);
  11428. });
  11429. });
  11430. /**
  11431. * @ngdoc directive
  11432. * @name ng.directive:ngInclude
  11433. * @restrict ECA
  11434. *
  11435. * @description
  11436. * Fetches, compiles and includes an external HTML fragment.
  11437. *
  11438. * Keep in mind that Same Origin Policy applies to included resources
  11439. * (e.g. ngInclude won't work for cross-domain requests on all browsers and for
  11440. * file:// access on some browsers).
  11441. *
  11442. * @scope
  11443. *
  11444. * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
  11445. * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`.
  11446. * @param {string=} onload Expression to evaluate when a new partial is loaded.
  11447. *
  11448. * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
  11449. * $anchorScroll} to scroll the viewport after the content is loaded.
  11450. *
  11451. * - If the attribute is not set, disable scrolling.
  11452. * - If the attribute is set without value, enable scrolling.
  11453. * - Otherwise enable scrolling only if the expression evaluates to truthy value.
  11454. *
  11455. * @example
  11456. <example>
  11457. <file name="index.html">
  11458. <div ng-controller="Ctrl">
  11459. <select ng-model="template" ng-options="t.name for t in templates">
  11460. <option value="">(blank)</option>
  11461. </select>
  11462. url of the template: <tt>{{template.url}}</tt>
  11463. <hr/>
  11464. <div ng-include src="template.url"></div>
  11465. </div>
  11466. </file>
  11467. <file name="script.js">
  11468. function Ctrl($scope) {
  11469. $scope.templates =
  11470. [ { name: 'template1.html', url: 'template1.html'}
  11471. , { name: 'template2.html', url: 'template2.html'} ];
  11472. $scope.template = $scope.templates[0];
  11473. }
  11474. </file>
  11475. <file name="template1.html">
  11476. Content of template1.html
  11477. </file>
  11478. <file name="template2.html">
  11479. Content of template2.html
  11480. </file>
  11481. <file name="scenario.js">
  11482. it('should load template1.html', function() {
  11483. expect(element('.doc-example-live [ng-include]').text()).
  11484. toMatch(/Content of template1.html/);
  11485. });
  11486. it('should load template2.html', function() {
  11487. select('template').option('1');
  11488. expect(element('.doc-example-live [ng-include]').text()).
  11489. toMatch(/Content of template2.html/);
  11490. });
  11491. it('should change to blank', function() {
  11492. select('template').option('');
  11493. expect(element('.doc-example-live [ng-include]').text()).toEqual('');
  11494. });
  11495. </file>
  11496. </example>
  11497. */
  11498. /**
  11499. * @ngdoc event
  11500. * @name ng.directive:ngInclude#$includeContentLoaded
  11501. * @eventOf ng.directive:ngInclude
  11502. * @eventType emit on the current ngInclude scope
  11503. * @description
  11504. * Emitted every time the ngInclude content is reloaded.
  11505. */
  11506. var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
  11507. function($http, $templateCache, $anchorScroll, $compile) {
  11508. return {
  11509. restrict: 'ECA',
  11510. terminal: true,
  11511. compile: function(element, attr) {
  11512. var srcExp = attr.ngInclude || attr.src,
  11513. onloadExp = attr.onload || '',
  11514. autoScrollExp = attr.autoscroll;
  11515. return function(scope, element) {
  11516. var changeCounter = 0,
  11517. childScope;
  11518. var clearContent = function() {
  11519. if (childScope) {
  11520. childScope.$destroy();
  11521. childScope = null;
  11522. }
  11523. element.html('');
  11524. };
  11525. scope.$watch(srcExp, function(src) {
  11526. var thisChangeId = ++changeCounter;
  11527. if (src) {
  11528. $http.get(src, {cache: $templateCache}).success(function(response) {
  11529. if (thisChangeId !== changeCounter) return;
  11530. if (childScope) childScope.$destroy();
  11531. childScope = scope.$new();
  11532. element.html(response);
  11533. $compile(element.contents())(childScope);
  11534. if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
  11535. $anchorScroll();
  11536. }
  11537. childScope.$emit('$includeContentLoaded');
  11538. scope.$eval(onloadExp);
  11539. }).error(function() {
  11540. if (thisChangeId === changeCounter) clearContent();
  11541. });
  11542. } else clearContent();
  11543. });
  11544. };
  11545. }
  11546. };
  11547. }];
  11548. /**
  11549. * @ngdoc directive
  11550. * @name ng.directive:ngInit
  11551. *
  11552. * @description
  11553. * The `ngInit` directive specifies initialization tasks to be executed
  11554. * before the template enters execution mode during bootstrap.
  11555. *
  11556. * @element ANY
  11557. * @param {expression} ngInit {@link guide/expression Expression} to eval.
  11558. *
  11559. * @example
  11560. <doc:example>
  11561. <doc:source>
  11562. <div ng-init="greeting='Hello'; person='World'">
  11563. {{greeting}} {{person}}!
  11564. </div>
  11565. </doc:source>
  11566. <doc:scenario>
  11567. it('should check greeting', function() {
  11568. expect(binding('greeting')).toBe('Hello');
  11569. expect(binding('person')).toBe('World');
  11570. });
  11571. </doc:scenario>
  11572. </doc:example>
  11573. */
  11574. var ngInitDirective = ngDirective({
  11575. compile: function() {
  11576. return {
  11577. pre: function(scope, element, attrs) {
  11578. scope.$eval(attrs.ngInit);
  11579. }
  11580. }
  11581. }
  11582. });
  11583. /**
  11584. * @ngdoc directive
  11585. * @name ng.directive:ngNonBindable
  11586. * @priority 1000
  11587. *
  11588. * @description
  11589. * Sometimes it is necessary to write code which looks like bindings but which should be left alone
  11590. * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML.
  11591. *
  11592. * @element ANY
  11593. *
  11594. * @example
  11595. * In this example there are two location where a simple binding (`{{}}`) is present, but the one
  11596. * wrapped in `ngNonBindable` is left alone.
  11597. *
  11598. * @example
  11599. <doc:example>
  11600. <doc:source>
  11601. <div>Normal: {{1 + 2}}</div>
  11602. <div ng-non-bindable>Ignored: {{1 + 2}}</div>
  11603. </doc:source>
  11604. <doc:scenario>
  11605. it('should check ng-non-bindable', function() {
  11606. expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
  11607. expect(using('.doc-example-live').element('div:last').text()).
  11608. toMatch(/1 \+ 2/);
  11609. });
  11610. </doc:scenario>
  11611. </doc:example>
  11612. */
  11613. var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
  11614. /**
  11615. * @ngdoc directive
  11616. * @name ng.directive:ngPluralize
  11617. * @restrict EA
  11618. *
  11619. * @description
  11620. * # Overview
  11621. * `ngPluralize` is a directive that displays messages according to en-US localization rules.
  11622. * These rules are bundled with angular.js and the rules can be overridden
  11623. * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
  11624. * by specifying the mappings between
  11625. * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
  11626. * plural categories} and the strings to be displayed.
  11627. *
  11628. * # Plural categories and explicit number rules
  11629. * There are two
  11630. * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
  11631. * plural categories} in Angular's default en-US locale: "one" and "other".
  11632. *
  11633. * While a pural category may match many numbers (for example, in en-US locale, "other" can match
  11634. * any number that is not 1), an explicit number rule can only match one number. For example, the
  11635. * explicit number rule for "3" matches the number 3. You will see the use of plural categories
  11636. * and explicit number rules throughout later parts of this documentation.
  11637. *
  11638. * # Configuring ngPluralize
  11639. * You configure ngPluralize by providing 2 attributes: `count` and `when`.
  11640. * You can also provide an optional attribute, `offset`.
  11641. *
  11642. * The value of the `count` attribute can be either a string or an {@link guide/expression
  11643. * Angular expression}; these are evaluated on the current scope for its bound value.
  11644. *
  11645. * The `when` attribute specifies the mappings between plural categories and the actual
  11646. * string to be displayed. The value of the attribute should be a JSON object so that Angular
  11647. * can interpret it correctly.
  11648. *
  11649. * The following example shows how to configure ngPluralize:
  11650. *
  11651. * <pre>
  11652. * <ng-pluralize count="personCount"
  11653. when="{'0': 'Nobody is viewing.',
  11654. * 'one': '1 person is viewing.',
  11655. * 'other': '{} people are viewing.'}">
  11656. * </ng-pluralize>
  11657. *</pre>
  11658. *
  11659. * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
  11660. * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
  11661. * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
  11662. * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
  11663. * show "a dozen people are viewing".
  11664. *
  11665. * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
  11666. * into pluralized strings. In the previous example, Angular will replace `{}` with
  11667. * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
  11668. * for <span ng-non-bindable>{{numberExpression}}</span>.
  11669. *
  11670. * # Configuring ngPluralize with offset
  11671. * The `offset` attribute allows further customization of pluralized text, which can result in
  11672. * a better user experience. For example, instead of the message "4 people are viewing this document",
  11673. * you might display "John, Kate and 2 others are viewing this document".
  11674. * The offset attribute allows you to offset a number by any desired value.
  11675. * Let's take a look at an example:
  11676. *
  11677. * <pre>
  11678. * <ng-pluralize count="personCount" offset=2
  11679. * when="{'0': 'Nobody is viewing.',
  11680. * '1': '{{person1}} is viewing.',
  11681. * '2': '{{person1}} and {{person2}} are viewing.',
  11682. * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
  11683. * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
  11684. * </ng-pluralize>
  11685. * </pre>
  11686. *
  11687. * Notice that we are still using two plural categories(one, other), but we added
  11688. * three explicit number rules 0, 1 and 2.
  11689. * When one person, perhaps John, view the document, "John is viewing" will be shown.
  11690. * When three people view the document, no explicit number rule is found, so
  11691. * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
  11692. * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
  11693. * is shown.
  11694. *
  11695. * Note that when you specify offsets, you must provide explicit number rules for
  11696. * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
  11697. * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
  11698. * plural categories "one" and "other".
  11699. *
  11700. * @param {string|expression} count The variable to be bounded to.
  11701. * @param {string} when The mapping between plural category to its correspoding strings.
  11702. * @param {number=} offset Offset to deduct from the total number.
  11703. *
  11704. * @example
  11705. <doc:example>
  11706. <doc:source>
  11707. <script>
  11708. function Ctrl($scope) {
  11709. $scope.person1 = 'Igor';
  11710. $scope.person2 = 'Misko';
  11711. $scope.personCount = 1;
  11712. }
  11713. </script>
  11714. <div ng-controller="Ctrl">
  11715. Person 1:<input type="text" ng-model="person1" value="Igor" /><br/>
  11716. Person 2:<input type="text" ng-model="person2" value="Misko" /><br/>
  11717. Number of People:<input type="text" ng-model="personCount" value="1" /><br/>
  11718. <!--- Example with simple pluralization rules for en locale --->
  11719. Without Offset:
  11720. <ng-pluralize count="personCount"
  11721. when="{'0': 'Nobody is viewing.',
  11722. 'one': '1 person is viewing.',
  11723. 'other': '{} people are viewing.'}">
  11724. </ng-pluralize><br>
  11725. <!--- Example with offset --->
  11726. With Offset(2):
  11727. <ng-pluralize count="personCount" offset=2
  11728. when="{'0': 'Nobody is viewing.',
  11729. '1': '{{person1}} is viewing.',
  11730. '2': '{{person1}} and {{person2}} are viewing.',
  11731. 'one': '{{person1}}, {{person2}} and one other person are viewing.',
  11732. 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
  11733. </ng-pluralize>
  11734. </div>
  11735. </doc:source>
  11736. <doc:scenario>
  11737. it('should show correct pluralized string', function() {
  11738. expect(element('.doc-example-live ng-pluralize:first').text()).
  11739. toBe('1 person is viewing.');
  11740. expect(element('.doc-example-live ng-pluralize:last').text()).
  11741. toBe('Igor is viewing.');
  11742. using('.doc-example-live').input('personCount').enter('0');
  11743. expect(element('.doc-example-live ng-pluralize:first').text()).
  11744. toBe('Nobody is viewing.');
  11745. expect(element('.doc-example-live ng-pluralize:last').text()).
  11746. toBe('Nobody is viewing.');
  11747. using('.doc-example-live').input('personCount').enter('2');
  11748. expect(element('.doc-example-live ng-pluralize:first').text()).
  11749. toBe('2 people are viewing.');
  11750. expect(element('.doc-example-live ng-pluralize:last').text()).
  11751. toBe('Igor and Misko are viewing.');
  11752. using('.doc-example-live').input('personCount').enter('3');
  11753. expect(element('.doc-example-live ng-pluralize:first').text()).
  11754. toBe('3 people are viewing.');
  11755. expect(element('.doc-example-live ng-pluralize:last').text()).
  11756. toBe('Igor, Misko and one other person are viewing.');
  11757. using('.doc-example-live').input('personCount').enter('4');
  11758. expect(element('.doc-example-live ng-pluralize:first').text()).
  11759. toBe('4 people are viewing.');
  11760. expect(element('.doc-example-live ng-pluralize:last').text()).
  11761. toBe('Igor, Misko and 2 other people are viewing.');
  11762. });
  11763. it('should show data-binded names', function() {
  11764. using('.doc-example-live').input('personCount').enter('4');
  11765. expect(element('.doc-example-live ng-pluralize:last').text()).
  11766. toBe('Igor, Misko and 2 other people are viewing.');
  11767. using('.doc-example-live').input('person1').enter('Di');
  11768. using('.doc-example-live').input('person2').enter('Vojta');
  11769. expect(element('.doc-example-live ng-pluralize:last').text()).
  11770. toBe('Di, Vojta and 2 other people are viewing.');
  11771. });
  11772. </doc:scenario>
  11773. </doc:example>
  11774. */
  11775. var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
  11776. var BRACE = /{}/g;
  11777. return {
  11778. restrict: 'EA',
  11779. link: function(scope, element, attr) {
  11780. var numberExp = attr.count,
  11781. whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs
  11782. offset = attr.offset || 0,
  11783. whens = scope.$eval(whenExp),
  11784. whensExpFns = {};
  11785. forEach(whens, function(expression, key) {
  11786. whensExpFns[key] =
  11787. $interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}'));
  11788. });
  11789. scope.$watch(function() {
  11790. var value = parseFloat(scope.$eval(numberExp));
  11791. if (!isNaN(value)) {
  11792. //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
  11793. //check it against pluralization rules in $locale service
  11794. if (!whens[value]) value = $locale.pluralCat(value - offset);
  11795. return whensExpFns[value](scope, element, true);
  11796. } else {
  11797. return '';
  11798. }
  11799. }, function(newVal) {
  11800. element.text(newVal);
  11801. });
  11802. }
  11803. };
  11804. }];
  11805. /**
  11806. * @ngdoc directive
  11807. * @name ng.directive:ngRepeat
  11808. *
  11809. * @description
  11810. * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
  11811. * instance gets its own scope, where the given loop variable is set to the current collection item,
  11812. * and `$index` is set to the item index or key.
  11813. *
  11814. * Special properties are exposed on the local scope of each template instance, including:
  11815. *
  11816. * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
  11817. * * `$first` – `{boolean}` – true if the repeated element is first in the iterator.
  11818. * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
  11819. * * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
  11820. *
  11821. *
  11822. * @element ANY
  11823. * @scope
  11824. * @priority 1000
  11825. * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two
  11826. * formats are currently supported:
  11827. *
  11828. * * `variable in expression` – where variable is the user defined loop variable and `expression`
  11829. * is a scope expression giving the collection to enumerate.
  11830. *
  11831. * For example: `track in cd.tracks`.
  11832. *
  11833. * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
  11834. * and `expression` is the scope expression giving the collection to enumerate.
  11835. *
  11836. * For example: `(name, age) in {'adam':10, 'amalie':12}`.
  11837. *
  11838. * @example
  11839. * This example initializes the scope to a list of names and
  11840. * then uses `ngRepeat` to display every person:
  11841. <doc:example>
  11842. <doc:source>
  11843. <div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
  11844. I have {{friends.length}} friends. They are:
  11845. <ul>
  11846. <li ng-repeat="friend in friends">
  11847. [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
  11848. </li>
  11849. </ul>
  11850. </div>
  11851. </doc:source>
  11852. <doc:scenario>
  11853. it('should check ng-repeat', function() {
  11854. var r = using('.doc-example-live').repeater('ul li');
  11855. expect(r.count()).toBe(2);
  11856. expect(r.row(0)).toEqual(["1","John","25"]);
  11857. expect(r.row(1)).toEqual(["2","Mary","28"]);
  11858. });
  11859. </doc:scenario>
  11860. </doc:example>
  11861. */
  11862. var ngRepeatDirective = ngDirective({
  11863. transclude: 'element',
  11864. priority: 1000,
  11865. terminal: true,
  11866. compile: function(element, attr, linker) {
  11867. return function(scope, iterStartElement, attr){
  11868. var expression = attr.ngRepeat;
  11869. var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
  11870. lhs, rhs, valueIdent, keyIdent;
  11871. if (! match) {
  11872. throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" +
  11873. expression + "'.");
  11874. }
  11875. lhs = match[1];
  11876. rhs = match[2];
  11877. match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
  11878. if (!match) {
  11879. throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
  11880. lhs + "'.");
  11881. }
  11882. valueIdent = match[3] || match[1];
  11883. keyIdent = match[2];
  11884. // Store a list of elements from previous run. This is a hash where key is the item from the
  11885. // iterator, and the value is an array of objects with following properties.
  11886. // - scope: bound scope
  11887. // - element: previous element.
  11888. // - index: position
  11889. // We need an array of these objects since the same object can be returned from the iterator.
  11890. // We expect this to be a rare case.
  11891. var lastOrder = new HashQueueMap();
  11892. scope.$watch(function(scope){
  11893. var index, length,
  11894. collection = scope.$eval(rhs),
  11895. collectionLength = size(collection, true),
  11896. childScope,
  11897. // Same as lastOrder but it has the current state. It will become the
  11898. // lastOrder on the next iteration.
  11899. nextOrder = new HashQueueMap(),
  11900. key, value, // key/value of iteration
  11901. array, last, // last object information {scope, element, index}
  11902. cursor = iterStartElement; // current position of the node
  11903. if (!isArray(collection)) {
  11904. // if object, extract keys, sort them and use to determine order of iteration over obj props
  11905. array = [];
  11906. for(key in collection) {
  11907. if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
  11908. array.push(key);
  11909. }
  11910. }
  11911. array.sort();
  11912. } else {
  11913. array = collection || [];
  11914. }
  11915. // we are not using forEach for perf reasons (trying to avoid #call)
  11916. for (index = 0, length = array.length; index < length; index++) {
  11917. key = (collection === array) ? index : array[index];
  11918. value = collection[key];
  11919. last = lastOrder.shift(value);
  11920. if (last) {
  11921. // if we have already seen this object, then we need to reuse the
  11922. // associated scope/element
  11923. childScope = last.scope;
  11924. nextOrder.push(value, last);
  11925. if (index === last.index) {
  11926. // do nothing
  11927. cursor = last.element;
  11928. } else {
  11929. // existing item which got moved
  11930. last.index = index;
  11931. // This may be a noop, if the element is next, but I don't know of a good way to
  11932. // figure this out, since it would require extra DOM access, so let's just hope that
  11933. // the browsers realizes that it is noop, and treats it as such.
  11934. cursor.after(last.element);
  11935. cursor = last.element;
  11936. }
  11937. } else {
  11938. // new item which we don't know about
  11939. childScope = scope.$new();
  11940. }
  11941. childScope[valueIdent] = value;
  11942. if (keyIdent) childScope[keyIdent] = key;
  11943. childScope.$index = index;
  11944. childScope.$first = (index === 0);
  11945. childScope.$last = (index === (collectionLength - 1));
  11946. childScope.$middle = !(childScope.$first || childScope.$last);
  11947. if (!last) {
  11948. linker(childScope, function(clone){
  11949. cursor.after(clone);
  11950. last = {
  11951. scope: childScope,
  11952. element: (cursor = clone),
  11953. index: index
  11954. };
  11955. nextOrder.push(value, last);
  11956. });
  11957. }
  11958. }
  11959. //shrink children
  11960. for (key in lastOrder) {
  11961. if (lastOrder.hasOwnProperty(key)) {
  11962. array = lastOrder[key];
  11963. while(array.length) {
  11964. value = array.pop();
  11965. value.element.remove();
  11966. value.scope.$destroy();
  11967. }
  11968. }
  11969. }
  11970. lastOrder = nextOrder;
  11971. });
  11972. };
  11973. }
  11974. });
  11975. /**
  11976. * @ngdoc directive
  11977. * @name ng.directive:ngShow
  11978. *
  11979. * @description
  11980. * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
  11981. * conditionally.
  11982. *
  11983. * @element ANY
  11984. * @param {expression} ngShow If the {@link guide/expression expression} is truthy
  11985. * then the element is shown or hidden respectively.
  11986. *
  11987. * @example
  11988. <doc:example>
  11989. <doc:source>
  11990. Click me: <input type="checkbox" ng-model="checked"><br/>
  11991. Show: <span ng-show="checked">I show up when your checkbox is checked.</span> <br/>
  11992. Hide: <span ng-hide="checked">I hide when your checkbox is checked.</span>
  11993. </doc:source>
  11994. <doc:scenario>
  11995. it('should check ng-show / ng-hide', function() {
  11996. expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
  11997. expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
  11998. input('checked').check();
  11999. expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
  12000. expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
  12001. });
  12002. </doc:scenario>
  12003. </doc:example>
  12004. */
  12005. //TODO(misko): refactor to remove element from the DOM
  12006. var ngShowDirective = ngDirective(function(scope, element, attr){
  12007. scope.$watch(attr.ngShow, function(value){
  12008. element.css('display', toBoolean(value) ? '' : 'none');
  12009. });
  12010. });
  12011. /**
  12012. * @ngdoc directive
  12013. * @name ng.directive:ngHide
  12014. *
  12015. * @description
  12016. * The `ngHide` and `ngShow` directives hide or show a portion
  12017. * of the HTML conditionally.
  12018. *
  12019. * @element ANY
  12020. * @param {expression} ngHide If the {@link guide/expression expression} truthy then
  12021. * the element is shown or hidden respectively.
  12022. *
  12023. * @example
  12024. <doc:example>
  12025. <doc:source>
  12026. Click me: <input type="checkbox" ng-model="checked"><br/>
  12027. Show: <span ng-show="checked">I show up when you checkbox is checked?</span> <br/>
  12028. Hide: <span ng-hide="checked">I hide when you checkbox is checked?</span>
  12029. </doc:source>
  12030. <doc:scenario>
  12031. it('should check ng-show / ng-hide', function() {
  12032. expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
  12033. expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
  12034. input('checked').check();
  12035. expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
  12036. expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
  12037. });
  12038. </doc:scenario>
  12039. </doc:example>
  12040. */
  12041. //TODO(misko): refactor to remove element from the DOM
  12042. var ngHideDirective = ngDirective(function(scope, element, attr){
  12043. scope.$watch(attr.ngHide, function(value){
  12044. element.css('display', toBoolean(value) ? 'none' : '');
  12045. });
  12046. });
  12047. /**
  12048. * @ngdoc directive
  12049. * @name ng.directive:ngStyle
  12050. *
  12051. * @description
  12052. * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
  12053. *
  12054. * @element ANY
  12055. * @param {expression} ngStyle {@link guide/expression Expression} which evals to an
  12056. * object whose keys are CSS style names and values are corresponding values for those CSS
  12057. * keys.
  12058. *
  12059. * @example
  12060. <example>
  12061. <file name="index.html">
  12062. <input type="button" value="set" ng-click="myStyle={color:'red'}">
  12063. <input type="button" value="clear" ng-click="myStyle={}">
  12064. <br/>
  12065. <span ng-style="myStyle">Sample Text</span>
  12066. <pre>myStyle={{myStyle}}</pre>
  12067. </file>
  12068. <file name="style.css">
  12069. span {
  12070. color: black;
  12071. }
  12072. </file>
  12073. <file name="scenario.js">
  12074. it('should check ng-style', function() {
  12075. expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
  12076. element('.doc-example-live :button[value=set]').click();
  12077. expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
  12078. element('.doc-example-live :button[value=clear]').click();
  12079. expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
  12080. });
  12081. </file>
  12082. </example>
  12083. */
  12084. var ngStyleDirective = ngDirective(function(scope, element, attr) {
  12085. scope.$watch(attr.ngStyle, function(newStyles, oldStyles) {
  12086. if (oldStyles && (newStyles !== oldStyles)) {
  12087. forEach(oldStyles, function(val, style) { element.css(style, '');});
  12088. }
  12089. if (newStyles) element.css(newStyles);
  12090. }, true);
  12091. });
  12092. /**
  12093. * @ngdoc directive
  12094. * @name ng.directive:ngSwitch
  12095. * @restrict EA
  12096. *
  12097. * @description
  12098. * Conditionally change the DOM structure.
  12099. *
  12100. * @usageContent
  12101. * <ANY ng-switch-when="matchValue1">...</ANY>
  12102. * <ANY ng-switch-when="matchValue2">...</ANY>
  12103. * ...
  12104. * <ANY ng-switch-default>...</ANY>
  12105. *
  12106. * @scope
  12107. * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
  12108. * @paramDescription
  12109. * On child elments add:
  12110. *
  12111. * * `ngSwitchWhen`: the case statement to match against. If match then this
  12112. * case will be displayed.
  12113. * * `ngSwitchDefault`: the default case when no other casses match.
  12114. *
  12115. * @example
  12116. <doc:example>
  12117. <doc:source>
  12118. <script>
  12119. function Ctrl($scope) {
  12120. $scope.items = ['settings', 'home', 'other'];
  12121. $scope.selection = $scope.items[0];
  12122. }
  12123. </script>
  12124. <div ng-controller="Ctrl">
  12125. <select ng-model="selection" ng-options="item for item in items">
  12126. </select>
  12127. <tt>selection={{selection}}</tt>
  12128. <hr/>
  12129. <div ng-switch on="selection" >
  12130. <div ng-switch-when="settings">Settings Div</div>
  12131. <span ng-switch-when="home">Home Span</span>
  12132. <span ng-switch-default>default</span>
  12133. </div>
  12134. </div>
  12135. </doc:source>
  12136. <doc:scenario>
  12137. it('should start in settings', function() {
  12138. expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
  12139. });
  12140. it('should change to home', function() {
  12141. select('selection').option('home');
  12142. expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
  12143. });
  12144. it('should select deafault', function() {
  12145. select('selection').option('other');
  12146. expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
  12147. });
  12148. </doc:scenario>
  12149. </doc:example>
  12150. */
  12151. var NG_SWITCH = 'ng-switch';
  12152. var ngSwitchDirective = valueFn({
  12153. restrict: 'EA',
  12154. compile: function(element, attr) {
  12155. var watchExpr = attr.ngSwitch || attr.on,
  12156. cases = {};
  12157. element.data(NG_SWITCH, cases);
  12158. return function(scope, element){
  12159. var selectedTransclude,
  12160. selectedElement,
  12161. selectedScope;
  12162. scope.$watch(watchExpr, function(value) {
  12163. if (selectedElement) {
  12164. selectedScope.$destroy();
  12165. selectedElement.remove();
  12166. selectedElement = selectedScope = null;
  12167. }
  12168. if ((selectedTransclude = cases['!' + value] || cases['?'])) {
  12169. scope.$eval(attr.change);
  12170. selectedScope = scope.$new();
  12171. selectedTransclude(selectedScope, function(caseElement) {
  12172. selectedElement = caseElement;
  12173. element.append(caseElement);
  12174. });
  12175. }
  12176. });
  12177. };
  12178. }
  12179. });
  12180. var ngSwitchWhenDirective = ngDirective({
  12181. transclude: 'element',
  12182. priority: 500,
  12183. compile: function(element, attrs, transclude) {
  12184. var cases = element.inheritedData(NG_SWITCH);
  12185. assertArg(cases);
  12186. cases['!' + attrs.ngSwitchWhen] = transclude;
  12187. }
  12188. });
  12189. var ngSwitchDefaultDirective = ngDirective({
  12190. transclude: 'element',
  12191. priority: 500,
  12192. compile: function(element, attrs, transclude) {
  12193. var cases = element.inheritedData(NG_SWITCH);
  12194. assertArg(cases);
  12195. cases['?'] = transclude;
  12196. }
  12197. });
  12198. /**
  12199. * @ngdoc directive
  12200. * @name ng.directive:ngTransclude
  12201. *
  12202. * @description
  12203. * Insert the transcluded DOM here.
  12204. *
  12205. * @element ANY
  12206. *
  12207. * @example
  12208. <doc:example module="transclude">
  12209. <doc:source>
  12210. <script>
  12211. function Ctrl($scope) {
  12212. $scope.title = 'Lorem Ipsum';
  12213. $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
  12214. }
  12215. angular.module('transclude', [])
  12216. .directive('pane', function(){
  12217. return {
  12218. restrict: 'E',
  12219. transclude: true,
  12220. scope: 'isolate',
  12221. locals: { title:'bind' },
  12222. template: '<div style="border: 1px solid black;">' +
  12223. '<div style="background-color: gray">{{title}}</div>' +
  12224. '<div ng-transclude></div>' +
  12225. '</div>'
  12226. };
  12227. });
  12228. </script>
  12229. <div ng-controller="Ctrl">
  12230. <input ng-model="title"><br>
  12231. <textarea ng-model="text"></textarea> <br/>
  12232. <pane title="{{title}}">{{text}}</pane>
  12233. </div>
  12234. </doc:source>
  12235. <doc:scenario>
  12236. it('should have transcluded', function() {
  12237. input('title').enter('TITLE');
  12238. input('text').enter('TEXT');
  12239. expect(binding('title')).toEqual('TITLE');
  12240. expect(binding('text')).toEqual('TEXT');
  12241. });
  12242. </doc:scenario>
  12243. </doc:example>
  12244. *
  12245. */
  12246. var ngTranscludeDirective = ngDirective({
  12247. controller: ['$transclude', '$element', function($transclude, $element) {
  12248. $transclude(function(clone) {
  12249. $element.append(clone);
  12250. });
  12251. }]
  12252. });
  12253. /**
  12254. * @ngdoc directive
  12255. * @name ng.directive:ngView
  12256. * @restrict ECA
  12257. *
  12258. * @description
  12259. * # Overview
  12260. * `ngView` is a directive that complements the {@link ng.$route $route} service by
  12261. * including the rendered template of the current route into the main layout (`index.html`) file.
  12262. * Every time the current route changes, the included view changes with it according to the
  12263. * configuration of the `$route` service.
  12264. *
  12265. * @scope
  12266. * @example
  12267. <example module="ngView">
  12268. <file name="index.html">
  12269. <div ng-controller="MainCntl">
  12270. Choose:
  12271. <a href="Book/Moby">Moby</a> |
  12272. <a href="Book/Moby/ch/1">Moby: Ch1</a> |
  12273. <a href="Book/Gatsby">Gatsby</a> |
  12274. <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
  12275. <a href="Book/Scarlet">Scarlet Letter</a><br/>
  12276. <div ng-view></div>
  12277. <hr />
  12278. <pre>$location.path() = {{$location.path()}}</pre>
  12279. <pre>$route.current.template = {{$route.current.template}}</pre>
  12280. <pre>$route.current.params = {{$route.current.params}}</pre>
  12281. <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
  12282. <pre>$routeParams = {{$routeParams}}</pre>
  12283. </div>
  12284. </file>
  12285. <file name="book.html">
  12286. controller: {{name}}<br />
  12287. Book Id: {{params.bookId}}<br />
  12288. </file>
  12289. <file name="chapter.html">
  12290. controller: {{name}}<br />
  12291. Book Id: {{params.bookId}}<br />
  12292. Chapter Id: {{params.chapterId}}
  12293. </file>
  12294. <file name="script.js">
  12295. angular.module('ngView', [], function($routeProvider, $locationProvider) {
  12296. $routeProvider.when('/Book/:bookId', {
  12297. templateUrl: 'book.html',
  12298. controller: BookCntl
  12299. });
  12300. $routeProvider.when('/Book/:bookId/ch/:chapterId', {
  12301. templateUrl: 'chapter.html',
  12302. controller: ChapterCntl
  12303. });
  12304. // configure html5 to get links working on jsfiddle
  12305. $locationProvider.html5Mode(true);
  12306. });
  12307. function MainCntl($scope, $route, $routeParams, $location) {
  12308. $scope.$route = $route;
  12309. $scope.$location = $location;
  12310. $scope.$routeParams = $routeParams;
  12311. }
  12312. function BookCntl($scope, $routeParams) {
  12313. $scope.name = "BookCntl";
  12314. $scope.params = $routeParams;
  12315. }
  12316. function ChapterCntl($scope, $routeParams) {
  12317. $scope.name = "ChapterCntl";
  12318. $scope.params = $routeParams;
  12319. }
  12320. </file>
  12321. <file name="scenario.js">
  12322. it('should load and compile correct template', function() {
  12323. element('a:contains("Moby: Ch1")').click();
  12324. var content = element('.doc-example-live [ng-view]').text();
  12325. expect(content).toMatch(/controller\: ChapterCntl/);
  12326. expect(content).toMatch(/Book Id\: Moby/);
  12327. expect(content).toMatch(/Chapter Id\: 1/);
  12328. element('a:contains("Scarlet")').click();
  12329. content = element('.doc-example-live [ng-view]').text();
  12330. expect(content).toMatch(/controller\: BookCntl/);
  12331. expect(content).toMatch(/Book Id\: Scarlet/);
  12332. });
  12333. </file>
  12334. </example>
  12335. */
  12336. /**
  12337. * @ngdoc event
  12338. * @name ng.directive:ngView#$viewContentLoaded
  12339. * @eventOf ng.directive:ngView
  12340. * @eventType emit on the current ngView scope
  12341. * @description
  12342. * Emitted every time the ngView content is reloaded.
  12343. */
  12344. var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
  12345. '$controller',
  12346. function($http, $templateCache, $route, $anchorScroll, $compile,
  12347. $controller) {
  12348. return {
  12349. restrict: 'ECA',
  12350. terminal: true,
  12351. link: function(scope, element, attr) {
  12352. var lastScope,
  12353. onloadExp = attr.onload || '';
  12354. scope.$on('$routeChangeSuccess', update);
  12355. update();
  12356. function destroyLastScope() {
  12357. if (lastScope) {
  12358. lastScope.$destroy();
  12359. lastScope = null;
  12360. }
  12361. }
  12362. function clearContent() {
  12363. element.html('');
  12364. destroyLastScope();
  12365. }
  12366. function update() {
  12367. var locals = $route.current && $route.current.locals,
  12368. template = locals && locals.$template;
  12369. if (template) {
  12370. element.html(template);
  12371. destroyLastScope();
  12372. var link = $compile(element.contents()),
  12373. current = $route.current,
  12374. controller;
  12375. lastScope = current.scope = scope.$new();
  12376. if (current.controller) {
  12377. locals.$scope = lastScope;
  12378. controller = $controller(current.controller, locals);
  12379. element.contents().data('$ngControllerController', controller);
  12380. }
  12381. link(lastScope);
  12382. lastScope.$emit('$viewContentLoaded');
  12383. lastScope.$eval(onloadExp);
  12384. // $anchorScroll might listen on event...
  12385. $anchorScroll();
  12386. } else {
  12387. clearContent();
  12388. }
  12389. }
  12390. }
  12391. };
  12392. }];
  12393. /**
  12394. * @ngdoc directive
  12395. * @name ng.directive:script
  12396. *
  12397. * @description
  12398. * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
  12399. * template can be used by `ngInclude`, `ngView` or directive templates.
  12400. *
  12401. * @restrict E
  12402. * @param {'text/ng-template'} type must be set to `'text/ng-template'`
  12403. *
  12404. * @example
  12405. <doc:example>
  12406. <doc:source>
  12407. <script type="text/ng-template" id="/tpl.html">
  12408. Content of the template.
  12409. </script>
  12410. <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
  12411. <div id="tpl-content" ng-include src="currentTpl"></div>
  12412. </doc:source>
  12413. <doc:scenario>
  12414. it('should load template defined inside script tag', function() {
  12415. element('#tpl-link').click();
  12416. expect(element('#tpl-content').text()).toMatch(/Content of the template/);
  12417. });
  12418. </doc:scenario>
  12419. </doc:example>
  12420. */
  12421. var scriptDirective = ['$templateCache', function($templateCache) {
  12422. return {
  12423. restrict: 'E',
  12424. terminal: true,
  12425. compile: function(element, attr) {
  12426. if (attr.type == 'text/ng-template') {
  12427. var templateUrl = attr.id,
  12428. // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
  12429. text = element[0].text;
  12430. $templateCache.put(templateUrl, text);
  12431. }
  12432. }
  12433. };
  12434. }];
  12435. /**
  12436. * @ngdoc directive
  12437. * @name ng.directive:select
  12438. * @restrict E
  12439. *
  12440. * @description
  12441. * HTML `SELECT` element with angular data-binding.
  12442. *
  12443. * # `ngOptions`
  12444. *
  12445. * Optionally `ngOptions` attribute can be used to dynamically generate a list of `<option>`
  12446. * elements for a `<select>` element using an array or an object obtained by evaluating the
  12447. * `ngOptions` expression.
  12448. *˝˝
  12449. * When an item in the select menu is select, the value of array element or object property
  12450. * represented by the selected option will be bound to the model identified by the `ngModel`
  12451. * directive of the parent select element.
  12452. *
  12453. * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
  12454. * be nested into the `<select>` element. This element will then represent `null` or "not selected"
  12455. * option. See example below for demonstration.
  12456. *
  12457. * Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead
  12458. * of {@link ng.directive:ngRepeat ngRepeat} when you want the
  12459. * `select` model to be bound to a non-string value. This is because an option element can currently
  12460. * be bound to string values only.
  12461. *
  12462. * @param {string} name assignable expression to data-bind to.
  12463. * @param {string=} required The control is considered valid only if value is entered.
  12464. * @param {comprehension_expression=} ngOptions in one of the following forms:
  12465. *
  12466. * * for array data sources:
  12467. * * `label` **`for`** `value` **`in`** `array`
  12468. * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
  12469. * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
  12470. * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array`
  12471. * * for object data sources:
  12472. * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
  12473. * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
  12474. * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
  12475. * * `select` **`as`** `label` **`group by`** `group`
  12476. * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
  12477. *
  12478. * Where:
  12479. *
  12480. * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
  12481. * * `value`: local variable which will refer to each item in the `array` or each property value
  12482. * of `object` during iteration.
  12483. * * `key`: local variable which will refer to a property name in `object` during iteration.
  12484. * * `label`: The result of this expression will be the label for `<option>` element. The
  12485. * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
  12486. * * `select`: The result of this expression will be bound to the model of the parent `<select>`
  12487. * element. If not specified, `select` expression will default to `value`.
  12488. * * `group`: The result of this expression will be used to group options using the `<optgroup>`
  12489. * DOM element.
  12490. *
  12491. * @example
  12492. <doc:example>
  12493. <doc:source>
  12494. <script>
  12495. function MyCntrl($scope) {
  12496. $scope.colors = [
  12497. {name:'black', shade:'dark'},
  12498. {name:'white', shade:'light'},
  12499. {name:'red', shade:'dark'},
  12500. {name:'blue', shade:'dark'},
  12501. {name:'yellow', shade:'light'}
  12502. ];
  12503. $scope.color = $scope.colors[2]; // red
  12504. }
  12505. </script>
  12506. <div ng-controller="MyCntrl">
  12507. <ul>
  12508. <li ng-repeat="color in colors">
  12509. Name: <input ng-model="color.name">
  12510. [<a href ng-click="colors.splice($index, 1)">X</a>]
  12511. </li>
  12512. <li>
  12513. [<a href ng-click="colors.push({})">add</a>]
  12514. </li>
  12515. </ul>
  12516. <hr/>
  12517. Color (null not allowed):
  12518. <select ng-model="color" ng-options="c.name for c in colors"></select><br>
  12519. Color (null allowed):
  12520. <span class="nullable">
  12521. <select ng-model="color" ng-options="c.name for c in colors">
  12522. <option value="">-- chose color --</option>
  12523. </select>
  12524. </span><br/>
  12525. Color grouped by shade:
  12526. <select ng-model="color" ng-options="c.name group by c.shade for c in colors">
  12527. </select><br/>
  12528. Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br>
  12529. <hr/>
  12530. Currently selected: {{ {selected_color:color} }}
  12531. <div style="border:solid 1px black; height:20px"
  12532. ng-style="{'background-color':color.name}">
  12533. </div>
  12534. </div>
  12535. </doc:source>
  12536. <doc:scenario>
  12537. it('should check ng-options', function() {
  12538. expect(binding('{selected_color:color}')).toMatch('red');
  12539. select('color').option('0');
  12540. expect(binding('{selected_color:color}')).toMatch('black');
  12541. using('.nullable').select('color').option('');
  12542. expect(binding('{selected_color:color}')).toMatch('null');
  12543. });
  12544. </doc:scenario>
  12545. </doc:example>
  12546. */
  12547. var ngOptionsDirective = valueFn({ terminal: true });
  12548. var selectDirective = ['$compile', '$parse', function($compile, $parse) {
  12549. //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
  12550. 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+(.*)$/,
  12551. nullModelCtrl = {$setViewValue: noop};
  12552. return {
  12553. restrict: 'E',
  12554. require: ['select', '?ngModel'],
  12555. controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
  12556. var self = this,
  12557. optionsMap = {},
  12558. ngModelCtrl = nullModelCtrl,
  12559. nullOption,
  12560. unknownOption;
  12561. self.databound = $attrs.ngModel;
  12562. self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
  12563. ngModelCtrl = ngModelCtrl_;
  12564. nullOption = nullOption_;
  12565. unknownOption = unknownOption_;
  12566. }
  12567. self.addOption = function(value) {
  12568. optionsMap[value] = true;
  12569. if (ngModelCtrl.$viewValue == value) {
  12570. $element.val(value);
  12571. if (unknownOption.parent()) unknownOption.remove();
  12572. }
  12573. };
  12574. self.removeOption = function(value) {
  12575. if (this.hasOption(value)) {
  12576. delete optionsMap[value];
  12577. if (ngModelCtrl.$viewValue == value) {
  12578. this.renderUnknownOption(value);
  12579. }
  12580. }
  12581. };
  12582. self.renderUnknownOption = function(val) {
  12583. var unknownVal = '? ' + hashKey(val) + ' ?';
  12584. unknownOption.val(unknownVal);
  12585. $element.prepend(unknownOption);
  12586. $element.val(unknownVal);
  12587. unknownOption.prop('selected', true); // needed for IE
  12588. }
  12589. self.hasOption = function(value) {
  12590. return optionsMap.hasOwnProperty(value);
  12591. }
  12592. $scope.$on('$destroy', function() {
  12593. // disable unknown option so that we don't do work when the whole select is being destroyed
  12594. self.renderUnknownOption = noop;
  12595. });
  12596. }],
  12597. link: function(scope, element, attr, ctrls) {
  12598. // if ngModel is not defined, we don't need to do anything
  12599. if (!ctrls[1]) return;
  12600. var selectCtrl = ctrls[0],
  12601. ngModelCtrl = ctrls[1],
  12602. multiple = attr.multiple,
  12603. optionsExp = attr.ngOptions,
  12604. nullOption = false, // if false, user will not be able to select it (used by ngOptions)
  12605. emptyOption,
  12606. // we can't just jqLite('<option>') since jqLite is not smart enough
  12607. // to create it in <select> and IE barfs otherwise.
  12608. optionTemplate = jqLite(document.createElement('option')),
  12609. optGroupTemplate =jqLite(document.createElement('optgroup')),
  12610. unknownOption = optionTemplate.clone();
  12611. // find "null" option
  12612. for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
  12613. if (children[i].value == '') {
  12614. emptyOption = nullOption = children.eq(i);
  12615. break;
  12616. }
  12617. }
  12618. selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
  12619. // required validator
  12620. if (multiple && (attr.required || attr.ngRequired)) {
  12621. var requiredValidator = function(value) {
  12622. ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
  12623. return value;
  12624. };
  12625. ngModelCtrl.$parsers.push(requiredValidator);
  12626. ngModelCtrl.$formatters.unshift(requiredValidator);
  12627. attr.$observe('required', function() {
  12628. requiredValidator(ngModelCtrl.$viewValue);
  12629. });
  12630. }
  12631. if (optionsExp) Options(scope, element, ngModelCtrl);
  12632. else if (multiple) Multiple(scope, element, ngModelCtrl);
  12633. else Single(scope, element, ngModelCtrl, selectCtrl);
  12634. ////////////////////////////
  12635. function Single(scope, selectElement, ngModelCtrl, selectCtrl) {
  12636. ngModelCtrl.$render = function() {
  12637. var viewValue = ngModelCtrl.$viewValue;
  12638. if (selectCtrl.hasOption(viewValue)) {
  12639. if (unknownOption.parent()) unknownOption.remove();
  12640. selectElement.val(viewValue);
  12641. if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
  12642. } else {
  12643. if (isUndefined(viewValue) && emptyOption) {
  12644. selectElement.val('');
  12645. } else {
  12646. selectCtrl.renderUnknownOption(viewValue);
  12647. }
  12648. }
  12649. };
  12650. selectElement.bind('change', function() {
  12651. scope.$apply(function() {
  12652. if (unknownOption.parent()) unknownOption.remove();
  12653. ngModelCtrl.$setViewValue(selectElement.val());
  12654. });
  12655. });
  12656. }
  12657. function Multiple(scope, selectElement, ctrl) {
  12658. var lastView;
  12659. ctrl.$render = function() {
  12660. var items = new HashMap(ctrl.$viewValue);
  12661. forEach(selectElement.children(), function(option) {
  12662. option.selected = isDefined(items.get(option.value));
  12663. });
  12664. };
  12665. // we have to do it on each watch since ngModel watches reference, but
  12666. // we need to work of an array, so we need to see if anything was inserted/removed
  12667. scope.$watch(function() {
  12668. if (!equals(lastView, ctrl.$viewValue)) {
  12669. lastView = copy(ctrl.$viewValue);
  12670. ctrl.$render();
  12671. }
  12672. });
  12673. selectElement.bind('change', function() {
  12674. scope.$apply(function() {
  12675. var array = [];
  12676. forEach(selectElement.children(), function(option) {
  12677. if (option.selected) {
  12678. array.push(option.value);
  12679. }
  12680. });
  12681. ctrl.$setViewValue(array);
  12682. });
  12683. });
  12684. }
  12685. function Options(scope, selectElement, ctrl) {
  12686. var match;
  12687. if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
  12688. throw Error(
  12689. "Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
  12690. " but got '" + optionsExp + "'.");
  12691. }
  12692. var displayFn = $parse(match[2] || match[1]),
  12693. valueName = match[4] || match[6],
  12694. keyName = match[5],
  12695. groupByFn = $parse(match[3] || ''),
  12696. valueFn = $parse(match[2] ? match[1] : valueName),
  12697. valuesFn = $parse(match[7]),
  12698. // This is an array of array of existing option groups in DOM. We try to reuse these if possible
  12699. // optionGroupsCache[0] is the options with no option group
  12700. // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
  12701. optionGroupsCache = [[{element: selectElement, label:''}]];
  12702. if (nullOption) {
  12703. // compile the element since there might be bindings in it
  12704. $compile(nullOption)(scope);
  12705. // remove the class, which is added automatically because we recompile the element and it
  12706. // becomes the compilation root
  12707. nullOption.removeClass('ng-scope');
  12708. // we need to remove it before calling selectElement.html('') because otherwise IE will
  12709. // remove the label from the element. wtf?
  12710. nullOption.remove();
  12711. }
  12712. // clear contents, we'll add what's needed based on the model
  12713. selectElement.html('');
  12714. selectElement.bind('change', function() {
  12715. scope.$apply(function() {
  12716. var optionGroup,
  12717. collection = valuesFn(scope) || [],
  12718. locals = {},
  12719. key, value, optionElement, index, groupIndex, length, groupLength;
  12720. if (multiple) {
  12721. value = [];
  12722. for (groupIndex = 0, groupLength = optionGroupsCache.length;
  12723. groupIndex < groupLength;
  12724. groupIndex++) {
  12725. // list of options for that group. (first item has the parent)
  12726. optionGroup = optionGroupsCache[groupIndex];
  12727. for(index = 1, length = optionGroup.length; index < length; index++) {
  12728. if ((optionElement = optionGroup[index].element)[0].selected) {
  12729. key = optionElement.val();
  12730. if (keyName) locals[keyName] = key;
  12731. locals[valueName] = collection[key];
  12732. value.push(valueFn(scope, locals));
  12733. }
  12734. }
  12735. }
  12736. } else {
  12737. key = selectElement.val();
  12738. if (key == '?') {
  12739. value = undefined;
  12740. } else if (key == ''){
  12741. value = null;
  12742. } else {
  12743. locals[valueName] = collection[key];
  12744. if (keyName) locals[keyName] = key;
  12745. value = valueFn(scope, locals);
  12746. }
  12747. }
  12748. ctrl.$setViewValue(value);
  12749. });
  12750. });
  12751. ctrl.$render = render;
  12752. // TODO(vojta): can't we optimize this ?
  12753. scope.$watch(render);
  12754. function render() {
  12755. var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
  12756. optionGroupNames = [''],
  12757. optionGroupName,
  12758. optionGroup,
  12759. option,
  12760. existingParent, existingOptions, existingOption,
  12761. modelValue = ctrl.$modelValue,
  12762. values = valuesFn(scope) || [],
  12763. keys = keyName ? sortedKeys(values) : values,
  12764. groupLength, length,
  12765. groupIndex, index,
  12766. locals = {},
  12767. selected,
  12768. selectedSet = false, // nothing is selected yet
  12769. lastElement,
  12770. element;
  12771. if (multiple) {
  12772. selectedSet = new HashMap(modelValue);
  12773. } else if (modelValue === null || nullOption) {
  12774. // if we are not multiselect, and we are null then we have to add the nullOption
  12775. optionGroups[''].push({selected:modelValue === null, id:'', label:''});
  12776. selectedSet = true;
  12777. }
  12778. // We now build up the list of options we need (we merge later)
  12779. for (index = 0; length = keys.length, index < length; index++) {
  12780. locals[valueName] = values[keyName ? locals[keyName]=keys[index]:index];
  12781. optionGroupName = groupByFn(scope, locals) || '';
  12782. if (!(optionGroup = optionGroups[optionGroupName])) {
  12783. optionGroup = optionGroups[optionGroupName] = [];
  12784. optionGroupNames.push(optionGroupName);
  12785. }
  12786. if (multiple) {
  12787. selected = selectedSet.remove(valueFn(scope, locals)) != undefined;
  12788. } else {
  12789. selected = modelValue === valueFn(scope, locals);
  12790. selectedSet = selectedSet || selected; // see if at least one item is selected
  12791. }
  12792. optionGroup.push({
  12793. id: keyName ? keys[index] : index, // either the index into array or key from object
  12794. label: displayFn(scope, locals) || '', // what will be seen by the user
  12795. selected: selected // determine if we should be selected
  12796. });
  12797. }
  12798. if (!multiple && !selectedSet) {
  12799. // nothing was selected, we have to insert the undefined item
  12800. optionGroups[''].unshift({id:'?', label:'', selected:true});
  12801. }
  12802. // Now we need to update the list of DOM nodes to match the optionGroups we computed above
  12803. for (groupIndex = 0, groupLength = optionGroupNames.length;
  12804. groupIndex < groupLength;
  12805. groupIndex++) {
  12806. // current option group name or '' if no group
  12807. optionGroupName = optionGroupNames[groupIndex];
  12808. // list of options for that group. (first item has the parent)
  12809. optionGroup = optionGroups[optionGroupName];
  12810. if (optionGroupsCache.length <= groupIndex) {
  12811. // we need to grow the optionGroups
  12812. existingParent = {
  12813. element: optGroupTemplate.clone().attr('label', optionGroupName),
  12814. label: optionGroup.label
  12815. };
  12816. existingOptions = [existingParent];
  12817. optionGroupsCache.push(existingOptions);
  12818. selectElement.append(existingParent.element);
  12819. } else {
  12820. existingOptions = optionGroupsCache[groupIndex];
  12821. existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
  12822. // update the OPTGROUP label if not the same.
  12823. if (existingParent.label != optionGroupName) {
  12824. existingParent.element.attr('label', existingParent.label = optionGroupName);
  12825. }
  12826. }
  12827. lastElement = null; // start at the beginning
  12828. for(index = 0, length = optionGroup.length; index < length; index++) {
  12829. option = optionGroup[index];
  12830. if ((existingOption = existingOptions[index+1])) {
  12831. // reuse elements
  12832. lastElement = existingOption.element;
  12833. if (existingOption.label !== option.label) {
  12834. lastElement.text(existingOption.label = option.label);
  12835. }
  12836. if (existingOption.id !== option.id) {
  12837. lastElement.val(existingOption.id = option.id);
  12838. }
  12839. if (existingOption.element.selected !== option.selected) {
  12840. lastElement.prop('selected', (existingOption.selected = option.selected));
  12841. }
  12842. } else {
  12843. // grow elements
  12844. // if it's a null option
  12845. if (option.id === '' && nullOption) {
  12846. // put back the pre-compiled element
  12847. element = nullOption;
  12848. } else {
  12849. // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
  12850. // in this version of jQuery on some browser the .text() returns a string
  12851. // rather then the element.
  12852. (element = optionTemplate.clone())
  12853. .val(option.id)
  12854. .attr('selected', option.selected)
  12855. .text(option.label);
  12856. }
  12857. existingOptions.push(existingOption = {
  12858. element: element,
  12859. label: option.label,
  12860. id: option.id,
  12861. selected: option.selected
  12862. });
  12863. if (lastElement) {
  12864. lastElement.after(element);
  12865. } else {
  12866. existingParent.element.append(element);
  12867. }
  12868. lastElement = element;
  12869. }
  12870. }
  12871. // remove any excessive OPTIONs in a group
  12872. index++; // increment since the existingOptions[0] is parent element not OPTION
  12873. while(existingOptions.length > index) {
  12874. existingOptions.pop().element.remove();
  12875. }
  12876. }
  12877. // remove any excessive OPTGROUPs from select
  12878. while(optionGroupsCache.length > groupIndex) {
  12879. optionGroupsCache.pop()[0].element.remove();
  12880. }
  12881. }
  12882. }
  12883. }
  12884. }
  12885. }];
  12886. var optionDirective = ['$interpolate', function($interpolate) {
  12887. var nullSelectCtrl = {
  12888. addOption: noop,
  12889. removeOption: noop
  12890. };
  12891. return {
  12892. restrict: 'E',
  12893. priority: 100,
  12894. require: '^select',
  12895. compile: function(element, attr) {
  12896. if (isUndefined(attr.value)) {
  12897. var interpolateFn = $interpolate(element.text(), true);
  12898. if (!interpolateFn) {
  12899. attr.$set('value', element.text());
  12900. }
  12901. }
  12902. return function (scope, element, attr, selectCtrl) {
  12903. if (selectCtrl.databound) {
  12904. // For some reason Opera defaults to true and if not overridden this messes up the repeater.
  12905. // We don't want the view to drive the initialization of the model anyway.
  12906. element.prop('selected', false);
  12907. } else {
  12908. selectCtrl = nullSelectCtrl;
  12909. }
  12910. if (interpolateFn) {
  12911. scope.$watch(interpolateFn, function(newVal, oldVal) {
  12912. attr.$set('value', newVal);
  12913. if (newVal !== oldVal) selectCtrl.removeOption(oldVal);
  12914. selectCtrl.addOption(newVal);
  12915. });
  12916. } else {
  12917. selectCtrl.addOption(attr.value);
  12918. }
  12919. element.bind('$destroy', function() {
  12920. selectCtrl.removeOption(attr.value);
  12921. });
  12922. };
  12923. }
  12924. }
  12925. }];
  12926. var styleDirective = valueFn({
  12927. restrict: 'E',
  12928. terminal: true
  12929. });
  12930. //try to bind to jquery now so that one can write angular.element().read()
  12931. //but we will rebind on bootstrap again.
  12932. bindJQuery();
  12933. publishExternalAPI(angular);
  12934. jqLite(document).ready(function() {
  12935. angularInit(document, bootstrap);
  12936. });
  12937. })(window, document);
  12938. 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>');