/* angular-moment.js / v1.0.0-beta.6 / (c) 2013, 2014, 2015, 2016 Uri Shaked / MIT Licence */ 'format amd'; /* global define */ (function () { 'use strict'; function isUndefinedOrNull(val) { return angular.isUndefined(val) || val === null; } function requireMoment() { try { return require('moment'); // Using nw.js or browserify? } catch (e) { throw new Error('Please install moment via npm. Please reference to: https://github.com/urish/angular-moment'); // Add wiki/troubleshooting section? } } function angularMoment(angular, moment) { if(typeof moment === 'undefined') { if(typeof require === 'function') { moment = requireMoment(); }else{ throw new Error('Moment cannot be found by angular-moment! Please reference to: https://github.com/urish/angular-moment'); // Add wiki/troubleshooting section? } } /** * @ngdoc overview * @name angularMoment * * @description * angularMoment module provides moment.js functionality for angular.js apps. */ return angular.module('angularMoment', []) /** * @ngdoc object * @name angularMoment.config:angularMomentConfig * * @description * Common configuration of the angularMoment module */ .constant('angularMomentConfig', { /** * @ngdoc property * @name angularMoment.config.angularMomentConfig#preprocess * @propertyOf angularMoment.config:angularMomentConfig * @returns {function} A preprocessor function that will be applied on all incoming dates * * @description * Defines a preprocessor function to apply on all input dates (e.g. the input of `am-time-ago`, * `amCalendar`, etc.). The function must return a `moment` object. * * @example * // Causes angular-moment to always treat the input values as unix timestamps * angularMomentConfig.preprocess = function(value) { * return moment.unix(value); * } */ preprocess: null, /** * @ngdoc property * @name angularMoment.config.angularMomentConfig#timezone * @propertyOf angularMoment.config:angularMomentConfig * @returns {string} The default timezone * * @description * The default timezone (e.g. 'Europe/London'). Empty string by default (does not apply * any timezone shift). * * NOTE: This option requires moment-timezone >= 0.3.0. */ timezone: null, /** * @ngdoc property * @name angularMoment.config.angularMomentConfig#format * @propertyOf angularMoment.config:angularMomentConfig * @returns {string} The pre-conversion format of the date * * @description * Specify the format of the input date. Essentially it's a * default and saves you from specifying a format in every * element. Overridden by element attr. Null by default. */ format: null, /** * @ngdoc property * @name angularMoment.config.angularMomentConfig#statefulFilters * @propertyOf angularMoment.config:angularMomentConfig * @returns {boolean} Whether angular-moment filters should be stateless (or not) * * @description * Specifies whether the filters included with angular-moment are stateful. * Stateful filters will automatically re-evaluate whenever you change the timezone * or locale settings, but may negatively impact performance. true by default. */ statefulFilters: true }) /** * @ngdoc object * @name angularMoment.object:moment * * @description * moment global (as provided by the moment.js library) */ .constant('moment', moment) /** * @ngdoc object * @name angularMoment.config:amTimeAgoConfig * @module angularMoment * * @description * configuration specific to the amTimeAgo directive */ .constant('amTimeAgoConfig', { /** * @ngdoc property * @name angularMoment.config.amTimeAgoConfig#withoutSuffix * @propertyOf angularMoment.config:amTimeAgoConfig * @returns {boolean} Whether to include a suffix in am-time-ago directive * * @description * Defaults to false. */ withoutSuffix: false, /** * @ngdoc property * @name angularMoment.config.amTimeAgoConfig#serverTime * @propertyOf angularMoment.config:amTimeAgoConfig * @returns {number} Server time in milliseconds since the epoch * * @description * If set, time ago will be calculated relative to the given value. * If null, local time will be used. Defaults to null. */ serverTime: null, /** * @ngdoc property * @name angularMoment.config.amTimeAgoConfig#titleFormat * @propertyOf angularMoment.config:amTimeAgoConfig * @returns {string} The format of the date to be displayed in the title of the element. If null, * the directive set the title of the element. * * @description * The format of the date used for the title of the element. null by default. */ titleFormat: null, /** * @ngdoc property * @name angularMoment.config.amTimeAgoConfig#fullDateThreshold * @propertyOf angularMoment.config:amTimeAgoConfig * @returns {number} The minimum number of days for showing a full date instead of relative time * * @description * The threshold for displaying a full date. The default is null, which means the date will always * be relative, and full date will never be displayed. */ fullDateThreshold: null, /** * @ngdoc property * @name angularMoment.config.amTimeAgoConfig#fullDateFormat * @propertyOf angularMoment.config:amTimeAgoConfig * @returns {string} The format to use when displaying a full date. * * @description * Specify the format of the date when displayed as full date. null by default. */ fullDateFormat: null, fullDateThresholdUnit: 'day' }) /** * @ngdoc directive * @name angularMoment.directive:amTimeAgo * @module angularMoment * * @restrict A */ .directive('amTimeAgo', ['$window', 'moment', 'amMoment', 'amTimeAgoConfig', function ($window, moment, amMoment, amTimeAgoConfig) { return function (scope, element, attr) { var activeTimeout = null; var currentValue; var withoutSuffix = amTimeAgoConfig.withoutSuffix; var titleFormat = amTimeAgoConfig.titleFormat; var fullDateThreshold = amTimeAgoConfig.fullDateThreshold; var fullDateFormat = amTimeAgoConfig.fullDateFormat; var fullDateThresholdUnit = amTimeAgoConfig.fullDateThresholdUnit; var localDate = new Date().getTime(); var modelName = attr.amTimeAgo; var currentFrom; var isTimeElement = ('TIME' === element[0].nodeName.toUpperCase()); var setTitleTime = !element.attr('title'); function getNow() { var now; if (currentFrom) { now = currentFrom; } else if (amTimeAgoConfig.serverTime) { var localNow = new Date().getTime(); var nowMillis = localNow - localDate + amTimeAgoConfig.serverTime; now = moment(nowMillis); } else { now = moment(); } return now; } function cancelTimer() { if (activeTimeout) { $window.clearTimeout(activeTimeout); activeTimeout = null; } } function updateTime(momentInstance) { var timeAgo = getNow().diff(momentInstance, fullDateThresholdUnit); var showFullDate = fullDateThreshold && timeAgo >= fullDateThreshold; if (showFullDate) { element.text(momentInstance.format(fullDateFormat)); } else { element.text(momentInstance.from(getNow(), withoutSuffix)); } if (titleFormat && setTitleTime) { element.attr('title', momentInstance.format(titleFormat)); } if (!showFullDate) { var howOld = Math.abs(getNow().diff(momentInstance, 'minute')); var secondsUntilUpdate = 3600; if (howOld < 1) { secondsUntilUpdate = 1; } else if (howOld < 60) { secondsUntilUpdate = 30; } else if (howOld < 180) { secondsUntilUpdate = 300; } activeTimeout = $window.setTimeout(function () { updateTime(momentInstance); }, secondsUntilUpdate * 1000); } } function updateDateTimeAttr(value) { if (isTimeElement) { element.attr('datetime', value); } } function updateMoment() { cancelTimer(); if (currentValue) { var momentValue = amMoment.preprocessDate(currentValue); updateTime(momentValue); updateDateTimeAttr(momentValue.toISOString()); } } scope.$watch(modelName, function (value) { if (isUndefinedOrNull(value) || (value === '')) { cancelTimer(); if (currentValue) { element.text(''); updateDateTimeAttr(''); currentValue = null; } return; } currentValue = value; updateMoment(); }); if (angular.isDefined(attr.amFrom)) { scope.$watch(attr.amFrom, function (value) { if (isUndefinedOrNull(value) || (value === '')) { currentFrom = null; } else { currentFrom = moment(value); } updateMoment(); }); } if (angular.isDefined(attr.amWithoutSuffix)) { scope.$watch(attr.amWithoutSuffix, function (value) { if (typeof value === 'boolean') { withoutSuffix = value; updateMoment(); } else { withoutSuffix = amTimeAgoConfig.withoutSuffix; } }); } attr.$observe('amFullDateThreshold', function (newValue) { fullDateThreshold = newValue; updateMoment(); }); attr.$observe('amFullDateFormat', function (newValue) { fullDateFormat = newValue; updateMoment(); }); attr.$observe('amFullDateThresholdUnit', function (newValue) { fullDateThresholdUnit = newValue; updateMoment(); }); scope.$on('$destroy', function () { cancelTimer(); }); scope.$on('amMoment:localeChanged', function () { updateMoment(); }); }; }]) /** * @ngdoc service * @name angularMoment.service.amMoment * @module angularMoment */ .service('amMoment', ['moment', '$rootScope', '$log', 'angularMomentConfig', function (moment, $rootScope, $log, angularMomentConfig) { var defaultTimezone = null; /** * @ngdoc function * @name angularMoment.service.amMoment#changeLocale * @methodOf angularMoment.service.amMoment * * @description * Changes the locale for moment.js and updates all the am-time-ago directive instances * with the new locale. Also broadcasts an `amMoment:localeChanged` event on $rootScope. * * @param {string} locale Locale code (e.g. en, es, ru, pt-br, etc.) * @param {object} customization object of locale strings to override */ this.changeLocale = function (locale, customization) { var result = moment.locale(locale, customization); if (angular.isDefined(locale)) { $rootScope.$broadcast('amMoment:localeChanged'); } return result; }; /** * @ngdoc function * @name angularMoment.service.amMoment#changeTimezone * @methodOf angularMoment.service.amMoment * * @description * Changes the default timezone for amCalendar, amDateFormat and amTimeAgo. Also broadcasts an * `amMoment:timezoneChanged` event on $rootScope. * * Note: this method works only if moment-timezone > 0.3.0 is loaded * * @param {string} timezone Timezone name (e.g. UTC) */ this.changeTimezone = function (timezone) { if (moment.tz && moment.tz.setDefault) { moment.tz.setDefault(timezone); $rootScope.$broadcast('amMoment:timezoneChanged'); } else { $log.warn('angular-moment: changeTimezone() works only with moment-timezone.js v0.3.0 or greater.'); } angularMomentConfig.timezone = timezone; defaultTimezone = timezone; }; /** * @ngdoc function * @name angularMoment.service.amMoment#preprocessDate * @methodOf angularMoment.service.amMoment * * @description * Preprocess a given value and convert it into a Moment instance appropriate for use in the * am-time-ago directive and the filters. The behavior of this function can be overriden by * setting `angularMomentConfig.preprocess`. * * @param {*} value The value to be preprocessed * @return {Moment} A `moment` object */ this.preprocessDate = function (value) { // Configure the default timezone if needed if (defaultTimezone !== angularMomentConfig.timezone) { this.changeTimezone(angularMomentConfig.timezone); } if (angularMomentConfig.preprocess) { return angularMomentConfig.preprocess(value); } if (!isNaN(parseFloat(value)) && isFinite(value)) { // Milliseconds since the epoch return moment(parseInt(value, 10)); } // else just returns the value as-is. return moment(value); }; }]) /** * @ngdoc filter * @name angularMoment.filter:amParse * @module angularMoment */ .filter('amParse', ['moment', function (moment) { return function (value, format) { return moment(value, format); }; }]) /** * @ngdoc filter * @name angularMoment.filter:amFromUnix * @module angularMoment */ .filter('amFromUnix', ['moment', function (moment) { return function (value) { return moment.unix(value); }; }]) /** * @ngdoc filter * @name angularMoment.filter:amUtc * @module angularMoment */ .filter('amUtc', ['moment', function (moment) { return function (value) { return moment.utc(value); }; }]) /** * @ngdoc filter * @name angularMoment.filter:amUtcOffset * @module angularMoment * * @description * Adds a UTC offset to the given timezone object. The offset can be a number of minutes, or a string such as * '+0300', '-0300' or 'Z'. */ .filter('amUtcOffset', ['amMoment', function (amMoment) { function amUtcOffset(value, offset) { return amMoment.preprocessDate(value).utcOffset(offset); } return amUtcOffset; }]) /** * @ngdoc filter * @name angularMoment.filter:amLocal * @module angularMoment */ .filter('amLocal', ['moment', function (moment) { return function (value) { return moment.isMoment(value) ? value.local() : null; }; }]) /** * @ngdoc filter * @name angularMoment.filter:amTimezone * @module angularMoment * * @description * Apply a timezone onto a given moment object, e.g. 'America/Phoenix'). * * You need to include moment-timezone.js for timezone support. */ .filter('amTimezone', ['amMoment', 'angularMomentConfig', '$log', function (amMoment, angularMomentConfig, $log) { function amTimezone(value, timezone) { var aMoment = amMoment.preprocessDate(value); if (!timezone) { return aMoment; } if (aMoment.tz) { return aMoment.tz(timezone); } else { $log.warn('angular-moment: named timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js ?'); return aMoment; } } return amTimezone; }]) /** * @ngdoc filter * @name angularMoment.filter:amCalendar * @module angularMoment */ .filter('amCalendar', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) { function amCalendarFilter(value, referenceTime, formats) { if (isUndefinedOrNull(value)) { return ''; } var date = amMoment.preprocessDate(value); return date.isValid() ? date.calendar(referenceTime, formats) : ''; } // Since AngularJS 1.3, filters have to explicitly define being stateful // (this is no longer the default). amCalendarFilter.$stateful = angularMomentConfig.statefulFilters; return amCalendarFilter; }]) /** * @ngdoc filter * @name angularMoment.filter:amDifference * @module angularMoment */ .filter('amDifference', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) { function amDifferenceFilter(value, otherValue, unit, usePrecision) { if (isUndefinedOrNull(value)) { return ''; } var date = amMoment.preprocessDate(value); var date2 = !isUndefinedOrNull(otherValue) ? amMoment.preprocessDate(otherValue) : moment(); if (!date.isValid() || !date2.isValid()) { return ''; } return date.diff(date2, unit, usePrecision); } amDifferenceFilter.$stateful = angularMomentConfig.statefulFilters; return amDifferenceFilter; }]) /** * @ngdoc filter * @name angularMoment.filter:amDateFormat * @module angularMoment * @function */ .filter('amDateFormat', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) { function amDateFormatFilter(value, format) { if (isUndefinedOrNull(value)) { return ''; } var date = amMoment.preprocessDate(value); if (!date.isValid()) { return ''; } return date.format(format); } amDateFormatFilter.$stateful = angularMomentConfig.statefulFilters; return amDateFormatFilter; }]) /** * @ngdoc filter * @name angularMoment.filter:amDurationFormat * @module angularMoment * @function */ .filter('amDurationFormat', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) { function amDurationFormatFilter(value, format, suffix) { if (isUndefinedOrNull(value)) { return ''; } return moment.duration(value, format).humanize(suffix); } amDurationFormatFilter.$stateful = angularMomentConfig.statefulFilters; return amDurationFormatFilter; }]) /** * @ngdoc filter * @name angularMoment.filter:amTimeAgo * @module angularMoment * @function */ .filter('amTimeAgo', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) { function amTimeAgoFilter(value, suffix, from) { var date, dateFrom; if (isUndefinedOrNull(value)) { return ''; } value = amMoment.preprocessDate(value); date = moment(value); if (!date.isValid()) { return ''; } dateFrom = moment(from); if (!isUndefinedOrNull(from) && dateFrom.isValid()) { return date.from(dateFrom, suffix); } return date.fromNow(suffix); } amTimeAgoFilter.$stateful = angularMomentConfig.statefulFilters; return amTimeAgoFilter; }]) /** * @ngdoc filter * @name angularMoment.filter:amSubtract * @module angularMoment * @function */ .filter('amSubtract', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) { function amSubtractFilter(value, amount, type) { if (isUndefinedOrNull(value)) { return ''; } return moment(value).subtract(parseInt(amount, 10), type); } amSubtractFilter.$stateful = angularMomentConfig.statefulFilters; return amSubtractFilter; }]) /** * @ngdoc filter * @name angularMoment.filter:amAdd * @module angularMoment * @function */ .filter('amAdd', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) { function amAddFilter(value, amount, type) { if (isUndefinedOrNull(value)) { return ''; } return moment(value).add(parseInt(amount, 10), type); } amAddFilter.$stateful = angularMomentConfig.statefulFilters; return amAddFilter; }]) /** * @ngdoc filter * @name angularMoment.filter:amStartOf * @module angularMoment * @function */ .filter('amStartOf', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) { function amStartOfFilter(value, type) { if (isUndefinedOrNull(value)) { return ''; } return moment(value).startOf(type); } amStartOfFilter.$stateful = angularMomentConfig.statefulFilters; return amStartOfFilter; }]) /** * @ngdoc filter * @name angularMoment.filter:amEndOf * @module angularMoment * @function */ .filter('amEndOf', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) { function amEndOfFilter(value, type) { if (isUndefinedOrNull(value)) { return ''; } return moment(value).endOf(type); } amEndOfFilter.$stateful = angularMomentConfig.statefulFilters; return amEndOfFilter; }]); } if (typeof define === 'function' && define.amd) { define(['angular', 'moment'], angularMoment); } else if (typeof module !== 'undefined' && module && module.exports) { angularMoment(require('angular'), require('moment')); module.exports = 'angularMoment'; } else { angularMoment(angular, (typeof global !== 'undefined' ? global : window).moment); } })();