angular-moment.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /* angular-moment.js / v0.9.0 / (c) 2013, 2014, 2015 Uri Shaked / MIT Licence */
  2. 'format global';
  3. /* global define */
  4. 'deps angular';
  5. 'deps moment';
  6. (function () {
  7. 'use strict';
  8. function angularMoment(angular, moment) {
  9. /**
  10. * @ngdoc overview
  11. * @name angularMoment
  12. *
  13. * @description
  14. * angularMoment module provides moment.js functionality for angular.js apps.
  15. */
  16. return angular.module('angularMoment', [])
  17. /**
  18. * @ngdoc object
  19. * @name angularMoment.config:angularMomentConfig
  20. *
  21. * @description
  22. * Common configuration of the angularMoment module
  23. */
  24. .constant('angularMomentConfig', {
  25. /**
  26. * @ngdoc property
  27. * @name angularMoment.config.angularMomentConfig#preprocess
  28. * @propertyOf angularMoment.config:angularMomentConfig
  29. * @returns {string} The default preprocessor to apply
  30. *
  31. * @description
  32. * Defines a default preprocessor to apply (e.g. 'unix', 'etc', ...). The default value is null,
  33. * i.e. no preprocessor will be applied.
  34. */
  35. preprocess: null, // e.g. 'unix', 'utc', ...
  36. /**
  37. * @ngdoc property
  38. * @name angularMoment.config.angularMomentConfig#timezone
  39. * @propertyOf angularMoment.config:angularMomentConfig
  40. * @returns {string} The default timezone
  41. *
  42. * @description
  43. * The default timezone (e.g. 'Europe/London'). Empty string by default (does not apply
  44. * any timezone shift).
  45. */
  46. timezone: '',
  47. /**
  48. * @ngdoc property
  49. * @name angularMoment.config.angularMomentConfig#format
  50. * @propertyOf angularMoment.config:angularMomentConfig
  51. * @returns {string} The pre-conversion format of the date
  52. *
  53. * @description
  54. * Specify the format of the input date. Essentially it's a
  55. * default and saves you from specifying a format in every
  56. * element. Overridden by element attr. Null by default.
  57. */
  58. format: null,
  59. /**
  60. * @ngdoc property
  61. * @name angularMoment.config.angularMomentConfig#statefulFilters
  62. * @propertyOf angularMoment.config:angularMomentConfig
  63. * @returns {boolean} Whether angular-moment filters should be stateless (or not)
  64. *
  65. * @description
  66. * Specifies whether the filters included with angular-moment are stateful.
  67. * Stateful filters will automatically re-evaluate whenever you change the timezone
  68. * or language settings, but may negatively impact performance. true by default.
  69. */
  70. statefulFilters: true
  71. })
  72. /**
  73. * @ngdoc object
  74. * @name angularMoment.object:moment
  75. *
  76. * @description
  77. * moment global (as provided by the moment.js library)
  78. */
  79. .constant('moment', moment)
  80. /**
  81. * @ngdoc object
  82. * @name angularMoment.config:amTimeAgoConfig
  83. * @module angularMoment
  84. *
  85. * @description
  86. * configuration specific to the amTimeAgo directive
  87. */
  88. .constant('amTimeAgoConfig', {
  89. /**
  90. * @ngdoc property
  91. * @name angularMoment.config.amTimeAgoConfig#withoutSuffix
  92. * @propertyOf angularMoment.config:amTimeAgoConfig
  93. * @returns {boolean} Whether to include a suffix in am-time-ago directive
  94. *
  95. * @description
  96. * Defaults to false.
  97. */
  98. withoutSuffix: false,
  99. /**
  100. * @ngdoc property
  101. * @name angularMoment.config.amTimeAgoConfig#serverTime
  102. * @propertyOf angularMoment.config:amTimeAgoConfig
  103. * @returns {number} Server time in milliseconds since the epoch
  104. *
  105. * @description
  106. * If set, time ago will be calculated relative to the given value.
  107. * If null, local time will be used. Defaults to null.
  108. */
  109. serverTime: null,
  110. /**
  111. * @ngdoc property
  112. * @name angularMoment.config.amTimeAgoConfig#format
  113. * @propertyOf angularMoment.config:amTimeAgoConfig
  114. * @returns {string} The format of the date to be displayed in the title of the element. If null,
  115. * the directive set the title of the element.
  116. *
  117. * @description
  118. * Specify the format of the date when displayed. null by default.
  119. */
  120. titleFormat: null
  121. })
  122. /**
  123. * @ngdoc directive
  124. * @name angularMoment.directive:amTimeAgo
  125. * @module angularMoment
  126. *
  127. * @restrict A
  128. */
  129. .directive('amTimeAgo', ['$window', 'moment', 'amMoment', 'amTimeAgoConfig', 'angularMomentConfig', function ($window, moment, amMoment, amTimeAgoConfig, angularMomentConfig) {
  130. return function (scope, element, attr) {
  131. var activeTimeout = null;
  132. var currentValue;
  133. var currentFormat = angularMomentConfig.format;
  134. var withoutSuffix = amTimeAgoConfig.withoutSuffix;
  135. var titleFormat = amTimeAgoConfig.titleFormat;
  136. var localDate = new Date().getTime();
  137. var preprocess = angularMomentConfig.preprocess;
  138. var modelName = attr.amTimeAgo.replace(/^::/, '');
  139. var isBindOnce = (attr.amTimeAgo.indexOf('::') === 0);
  140. var isTimeElement = ('TIME' === element[0].nodeName.toUpperCase());
  141. var unwatchChanges;
  142. function getNow() {
  143. var now;
  144. if (amTimeAgoConfig.serverTime) {
  145. var localNow = new Date().getTime();
  146. var nowMillis = localNow - localDate + amTimeAgoConfig.serverTime;
  147. now = moment(nowMillis);
  148. }
  149. else {
  150. now = moment();
  151. }
  152. return now;
  153. }
  154. function cancelTimer() {
  155. if (activeTimeout) {
  156. $window.clearTimeout(activeTimeout);
  157. activeTimeout = null;
  158. }
  159. }
  160. function updateTime(momentInstance) {
  161. element.text(momentInstance.from(getNow(), withoutSuffix));
  162. if (titleFormat && !element.attr('title')) {
  163. element.attr('title', momentInstance.local().format(titleFormat));
  164. }
  165. if (!isBindOnce) {
  166. var howOld = Math.abs(getNow().diff(momentInstance, 'minute'));
  167. var secondsUntilUpdate = 3600;
  168. if (howOld < 1) {
  169. secondsUntilUpdate = 1;
  170. } else if (howOld < 60) {
  171. secondsUntilUpdate = 30;
  172. } else if (howOld < 180) {
  173. secondsUntilUpdate = 300;
  174. }
  175. activeTimeout = $window.setTimeout(function () {
  176. updateTime(momentInstance);
  177. }, secondsUntilUpdate * 1000);
  178. }
  179. }
  180. function updateDateTimeAttr(value) {
  181. if (isTimeElement) {
  182. element.attr('datetime', value);
  183. }
  184. }
  185. function updateMoment() {
  186. cancelTimer();
  187. if (currentValue) {
  188. var momentValue = amMoment.preprocessDate(currentValue, preprocess, currentFormat);
  189. updateTime(momentValue);
  190. updateDateTimeAttr(momentValue.toISOString());
  191. }
  192. }
  193. unwatchChanges = scope.$watch(modelName, function (value) {
  194. if ((typeof value === 'undefined') || (value === null) || (value === '')) {
  195. cancelTimer();
  196. if (currentValue) {
  197. element.text('');
  198. updateDateTimeAttr('');
  199. currentValue = null;
  200. }
  201. return;
  202. }
  203. currentValue = value;
  204. updateMoment();
  205. if (value !== undefined && isBindOnce) {
  206. unwatchChanges();
  207. }
  208. });
  209. if (angular.isDefined(attr.amWithoutSuffix)) {
  210. scope.$watch(attr.amWithoutSuffix, function (value) {
  211. if (typeof value === 'boolean') {
  212. withoutSuffix = value;
  213. updateMoment();
  214. } else {
  215. withoutSuffix = amTimeAgoConfig.withoutSuffix;
  216. }
  217. });
  218. }
  219. attr.$observe('amFormat', function (format) {
  220. if (typeof format !== 'undefined') {
  221. currentFormat = format;
  222. updateMoment();
  223. }
  224. });
  225. attr.$observe('amPreprocess', function (newValue) {
  226. preprocess = newValue;
  227. updateMoment();
  228. });
  229. scope.$on('$destroy', function () {
  230. cancelTimer();
  231. });
  232. scope.$on('amMoment:localeChanged', function () {
  233. updateMoment();
  234. });
  235. };
  236. }])
  237. /**
  238. * @ngdoc service
  239. * @name angularMoment.service.amMoment
  240. * @module angularMoment
  241. */
  242. .service('amMoment', ['moment', '$rootScope', '$log', 'angularMomentConfig', function (moment, $rootScope, $log, angularMomentConfig) {
  243. /**
  244. * @ngdoc property
  245. * @name angularMoment:amMoment#preprocessors
  246. * @module angularMoment
  247. *
  248. * @description
  249. * Defines the preprocessors for the preprocessDate method. By default, the following preprocessors
  250. * are defined: utc, unix.
  251. */
  252. this.preprocessors = {
  253. utc: moment.utc,
  254. unix: moment.unix
  255. };
  256. /**
  257. * @ngdoc function
  258. * @name angularMoment.service.amMoment#changeLocale
  259. * @methodOf angularMoment.service.amMoment
  260. *
  261. * @description
  262. * Changes the locale for moment.js and updates all the am-time-ago directive instances
  263. * with the new locale. Also broadcasts a `amMoment:localeChanged` event on $rootScope.
  264. *
  265. * @param {string} locale Locale code (e.g. en, es, ru, pt-br, etc.)
  266. */
  267. this.changeLocale = function (locale) {
  268. var result = moment.locale(locale);
  269. if (angular.isDefined(locale)) {
  270. $rootScope.$broadcast('amMoment:localeChanged');
  271. }
  272. return result;
  273. };
  274. /**
  275. * @ngdoc function
  276. * @name angularMoment.service.amMoment#preprocessDate
  277. * @methodOf angularMoment.service.amMoment
  278. *
  279. * @description
  280. * Preprocess a given value and convert it into a Moment instance appropriate for use in the
  281. * am-time-ago directive and the filters.
  282. *
  283. * @param {*} value The value to be preprocessed
  284. * @param {string} preprocess The name of the preprocessor the apply (e.g. utc, unix)
  285. * @param {string=} format Specifies how to parse the value (see {@link http://momentjs.com/docs/#/parsing/string-format/})
  286. * @return {Moment} A value that can be parsed by the moment library
  287. */
  288. this.preprocessDate = function (value, preprocess, format) {
  289. if (angular.isUndefined(preprocess)) {
  290. preprocess = angularMomentConfig.preprocess;
  291. }
  292. if (this.preprocessors[preprocess]) {
  293. return this.preprocessors[preprocess](value, format);
  294. }
  295. if (preprocess) {
  296. $log.warn('angular-moment: Ignoring unsupported value for preprocess: ' + preprocess);
  297. }
  298. if (!isNaN(parseFloat(value)) && isFinite(value)) {
  299. // Milliseconds since the epoch
  300. return moment(parseInt(value, 10));
  301. }
  302. // else just returns the value as-is.
  303. return moment(value, format);
  304. };
  305. /**
  306. * @ngdoc function
  307. * @name angularMoment.service.amMoment#applyTimezone
  308. * @methodOf angularMoment.service.amMoment
  309. *
  310. * @description
  311. * Apply a timezone onto a given moment object - if moment-timezone.js is included
  312. * Otherwise, it'll not apply any timezone shift.
  313. *
  314. * @param {Moment} aMoment a moment() instance to apply the timezone shift to
  315. * @returns {Moment} The given moment with the timezone shift applied
  316. */
  317. this.applyTimezone = function (aMoment) {
  318. var timezone = angularMomentConfig.timezone;
  319. if (aMoment && timezone) {
  320. if (aMoment.tz) {
  321. aMoment = aMoment.tz(timezone);
  322. } else {
  323. $log.warn('angular-moment: timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?');
  324. }
  325. }
  326. return aMoment;
  327. };
  328. }])
  329. /**
  330. * @ngdoc filter
  331. * @name angularMoment.filter:amCalendar
  332. * @module angularMoment
  333. */
  334. .filter('amCalendar', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) {
  335. function amCalendarFilter(value, preprocess) {
  336. if (typeof value === 'undefined' || value === null) {
  337. return '';
  338. }
  339. value = amMoment.preprocessDate(value, preprocess);
  340. var date = moment(value);
  341. if (!date.isValid()) {
  342. return '';
  343. }
  344. return amMoment.applyTimezone(date).calendar();
  345. }
  346. // Since AngularJS 1.3, filters have to explicitly define being stateful
  347. // (this is no longer the default).
  348. amCalendarFilter.$stateful = angularMomentConfig.statefulFilters;
  349. return amCalendarFilter;
  350. }])
  351. /**
  352. * @ngdoc filter
  353. * @name angularMoment.filter:amDateFormat
  354. * @module angularMoment
  355. * @function
  356. */
  357. .filter('amDateFormat', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) {
  358. function amDateFormatFilter(value, format, preprocess) {
  359. if (typeof value === 'undefined' || value === null) {
  360. return '';
  361. }
  362. value = amMoment.preprocessDate(value, preprocess);
  363. var date = moment(value);
  364. if (!date.isValid()) {
  365. return '';
  366. }
  367. return amMoment.applyTimezone(date).format(format);
  368. }
  369. amDateFormatFilter.$stateful = angularMomentConfig.statefulFilters;
  370. return amDateFormatFilter;
  371. }])
  372. /**
  373. * @ngdoc filter
  374. * @name angularMoment.filter:amDurationFormat
  375. * @module angularMoment
  376. * @function
  377. */
  378. .filter('amDurationFormat', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) {
  379. function amDurationFormatFilter(value, format, suffix) {
  380. if (typeof value === 'undefined' || value === null) {
  381. return '';
  382. }
  383. return moment.duration(value, format).humanize(suffix);
  384. }
  385. amDurationFormatFilter.$stateful = angularMomentConfig.statefulFilters;
  386. return amDurationFormatFilter;
  387. }])
  388. /**
  389. * @ngdoc filter
  390. * @name angularMoment.filter:amTimeAgo
  391. * @module angularMoment
  392. * @function
  393. */
  394. .filter('amTimeAgo', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) {
  395. function amTimeAgoFilter(value, preprocess, suffix) {
  396. if (typeof value === 'undefined' || value === null) {
  397. return '';
  398. }
  399. value = amMoment.preprocessDate(value, preprocess);
  400. var date = moment(value);
  401. if (!date.isValid()) {
  402. return '';
  403. }
  404. return amMoment.applyTimezone(date).fromNow(suffix);
  405. }
  406. amTimeAgoFilter.$stateful = angularMomentConfig.statefulFilters;
  407. return amTimeAgoFilter;
  408. }]);
  409. }
  410. if (typeof define === 'function' && define.amd) {
  411. define('angular-moment', ['angular', 'moment'], angularMoment);
  412. } else if (typeof module !== 'undefined' && module && module.exports) {
  413. angularMoment(angular, require('moment'));
  414. } else {
  415. angularMoment(angular, window.moment);
  416. }
  417. })();