'use strict';
/**
* @name keta.directives.TimeRangeSelector
* @author Marco Lehmann <marco.lehmann@kiwigrid.com>
* @copyright Kiwigrid GmbH 2014-2016
* @module keta.directives.TimeRangeSelector
* @description
* <p>
* A simple time range selector component to select a time range.
* </p>
* @example
* <div data-keta-time-range-selector
* data-ng-model="model"
* data-css-classes="cssClasses"
* data-current-locale="currentLocale"
* data-display-mode="displayMode"
* data-display-value="displayValue"
* data-element-identifier="elementIdentifier"
* data-enable-display-mode-switch="enableDisplayModeSwitch"
* data-enable-week-selection="enableWeekSelection"
* data-first-click="firstClick"
* data-first-day-of-week="firstDayOfWeek"
* data-labels="labels"
* data-maximum="maximum"
* data-max-range-length="maxRangeLength"
* data-minimum="minimum"
* data-min-range-length="minRangeLength"
* data-show-pager="showPager"
* data-show-jump-to-selection-button="showJumpToSelectionButton"
* data-show-jump-to-today-button="showJumpToTodayButton"
* data-show-select-button="showSelectButton"
* data-show-week-numbers="showWeekNumbers"
* data-years-after="yearsAfter"
* data-years-before="yearsBefore"></div>
* @example
* angular.module('exampleApp', ['keta.directives.TimeRangeSelector'])
* .controller('ExampleController', function(
* $scope, ketaTimeRangeSelectorConstants, ketaTimeRangeSelectorMessageKeys
* ) {
*
* // current range to use
* $scope.model = new Date(2016, 3, 20, 9, 0, 0, 0);
*
* // css classes to use
* $scope.cssClasses = ketaTimeRangeSelectorConstants.CSS_CLASSES;
*
* // current locale to use
* $scope.currentLocale = 'de_DE';
*
* // display mode to use (@see ketaTimeRangeSelectorConstants.DISPLAY_MODE)
* $scope.displayMode = ketaTimeRangeSelectorConstants.DISPLAY_MODE.DAY;
*
* // display value to use
* $scope.displayValue = angular.copy($scope.model);
*
* // element identifier
* $scope.elementIdentifier = 'myTimeRangeSelector';
*
* // enable display mode switch
* $scope.enableDisplayModeSwitch = true;
*
* // enable week selection
* $scope.enableWeekSelection = true;
*
* // define first click flag
* $scope.firstClick = true;
*
* // define first day of week
* $scope.firstDayOfWeek = ketaTimeRangeSelectorConstants.DAY.SUNDAY;
*
* // define labels to use
* $scope.labels = TimeRangeSelectorMessageKeys;
*
* // define an optional maximum boundary
* $scope.maximum = moment($scope.now).add(30, 'days').toDate();
*
* // define an optional maximum range length in days or 0 to disable it
* $scope.maxRangeLength = 3;
*
* // define an optional minimum boundary
* $scope.minimum = moment($scope.now).subtract(30, 'days').toDate();
*
* // define an optional minimum range length in days or 0 to disable it
* $scope.minRangeLength = 3;
*
* // show display mode switcher
* $scope.showDisplayModeSwitcher = true;
*
* // show pager above
* $scope.showPager = true;
*
* // show jump to selection button under calendar sheet
* $scope.showJumpToSelectionButton = true;
*
* // show jump to today button under calendar sheet
* $scope.showJumpToTodayButton = true;
*
* // show select button under calendar sheet
* $scope.showSelectButton = true;
*
* // show week number row
* $scope.showWeekNumbers = true;
*
* // define number of years to show after current in year list
* $scope.yearsAfter = 4;
*
* // define number of years to show before current in year list
* $scope.yearsBefore = 4;
*
* });
*/
angular.module('keta.directives.TimeRangeSelector', [
'moment'
])
.constant('ketaTimeRangeSelectorConstants', {
CSS_CLASSES: {
CURRENT_DATE: 'current-date',
OUT_OF_BOUNDS: 'out-of-bounds',
OUT_OF_BOUNDS_AFTER: 'out-of-bounds-after',
OUT_OF_BOUNDS_BEFORE: 'out-of-bounds-before',
OUT_OF_MONTH: 'out-of-month',
SELECTED_DATE: 'selected-date',
SELECTED_DATE_FROM: 'selected-date-from',
SELECTED_DATE_TO: 'selected-date-to'
},
DAY: {
SUNDAY: 0,
MONDAY: 1,
TUESDAY: 2,
WEDNESDAY: 3,
THURSDAY: 4,
FRIDAY: 5,
SATURDAY: 6
},
DISPLAY_MODE: {
DAY: 'day',
MONTH: 'month',
YEAR: 'year'
},
EVENT: {
SELECT: 'keta.directives.TimeRangeSelector.Event.Selected'
}
})
// message keys with default values
.constant('ketaTimeRangeSelectorMessageKeys', {
'en_GB': {
'__keta.directives.TimeRangeSelector_display_mode_days': 'Days',
'__keta.directives.TimeRangeSelector_display_mode_months': 'Months',
'__keta.directives.TimeRangeSelector_display_mode_years': 'Years',
'__keta.directives.TimeRangeSelector_select': 'Select',
'__keta.directives.TimeRangeSelector_selection': 'Selection',
'__keta.directives.TimeRangeSelector_today': 'Today',
'__keta.directives.TimeRangeSelector_week': 'Week',
'__keta.directives.TimeRangeSelector_weekday_sunday': 'Sunday',
'__keta.directives.TimeRangeSelector_weekday_sunday_short': 'Sun',
'__keta.directives.TimeRangeSelector_weekday_monday': 'Monday',
'__keta.directives.TimeRangeSelector_weekday_monday_short': 'Mon',
'__keta.directives.TimeRangeSelector_weekday_tuesday': 'Tuesday',
'__keta.directives.TimeRangeSelector_weekday_tuesday_short': 'Tue',
'__keta.directives.TimeRangeSelector_weekday_wednesday': 'Wednesday',
'__keta.directives.TimeRangeSelector_weekday_wednesday_short': 'Wed',
'__keta.directives.TimeRangeSelector_weekday_thursday': 'Thursday',
'__keta.directives.TimeRangeSelector_weekday_thursday_short': 'Thu',
'__keta.directives.TimeRangeSelector_weekday_friday': 'Friday',
'__keta.directives.TimeRangeSelector_weekday_friday_short': 'Fri',
'__keta.directives.TimeRangeSelector_weekday_saturday': 'Saturday',
'__keta.directives.TimeRangeSelector_weekday_saturday_short': 'Sat',
'__keta.directives.TimeRangeSelector_week_number': 'Week Number',
'__keta.directives.TimeRangeSelector_week_number_short': 'Wn'
},
'de_DE': {
'__keta.directives.TimeRangeSelector_display_mode_days': 'Tage',
'__keta.directives.TimeRangeSelector_display_mode_months': 'Monate',
'__keta.directives.TimeRangeSelector_display_mode_years': 'Jahre',
'__keta.directives.TimeRangeSelector_select': 'Auswählen',
'__keta.directives.TimeRangeSelector_selection': 'Auswahl',
'__keta.directives.TimeRangeSelector_today': 'Heute',
'__keta.directives.TimeRangeSelector_week': 'Woche',
'__keta.directives.TimeRangeSelector_weekday_sunday': 'Sonntag',
'__keta.directives.TimeRangeSelector_weekday_sunday_short': 'So',
'__keta.directives.TimeRangeSelector_weekday_monday': 'Montag',
'__keta.directives.TimeRangeSelector_weekday_monday_short': 'Mo',
'__keta.directives.TimeRangeSelector_weekday_tuesday': 'Dienstag',
'__keta.directives.TimeRangeSelector_weekday_tuesday_short': 'Di',
'__keta.directives.TimeRangeSelector_weekday_wednesday': 'Mittwoch',
'__keta.directives.TimeRangeSelector_weekday_wednesday_short': 'Mi',
'__keta.directives.TimeRangeSelector_weekday_thursday': 'Donnerstag',
'__keta.directives.TimeRangeSelector_weekday_thursday_short': 'Do',
'__keta.directives.TimeRangeSelector_weekday_friday': 'Freitag',
'__keta.directives.TimeRangeSelector_weekday_friday_short': 'Fr',
'__keta.directives.TimeRangeSelector_weekday_saturday': 'Samstag',
'__keta.directives.TimeRangeSelector_weekday_saturday_short': 'Sa',
'__keta.directives.TimeRangeSelector_week_number': 'Kalenderwoche',
'__keta.directives.TimeRangeSelector_week_number_short': 'KW'
},
'fr_FR': {
'__keta.directives.TimeRangeSelector_display_mode_days': 'Journées',
'__keta.directives.TimeRangeSelector_display_mode_months': 'Mois',
'__keta.directives.TimeRangeSelector_display_mode_years': 'Années',
'__keta.directives.TimeRangeSelector_select': 'Choisir',
'__keta.directives.TimeRangeSelector_selection': 'Sélection',
'__keta.directives.TimeRangeSelector_today': 'Aujourd’hui',
'__keta.directives.TimeRangeSelector_week': 'Semaine',
'__keta.directives.TimeRangeSelector_weekday_sunday': 'Dimanche',
'__keta.directives.TimeRangeSelector_weekday_sunday_short': 'Dim',
'__keta.directives.TimeRangeSelector_weekday_monday': 'Lundi',
'__keta.directives.TimeRangeSelector_weekday_monday_short': 'Lun',
'__keta.directives.TimeRangeSelector_weekday_tuesday': 'Mardi',
'__keta.directives.TimeRangeSelector_weekday_tuesday_short': 'Mar',
'__keta.directives.TimeRangeSelector_weekday_wednesday': 'Mercredi',
'__keta.directives.TimeRangeSelector_weekday_wednesday_short': 'Mer',
'__keta.directives.TimeRangeSelector_weekday_thursday': 'Jeudi',
'__keta.directives.TimeRangeSelector_weekday_thursday_short': 'Jeu',
'__keta.directives.TimeRangeSelector_weekday_friday': 'Vendredi',
'__keta.directives.TimeRangeSelector_weekday_friday_short': 'Ven',
'__keta.directives.TimeRangeSelector_weekday_saturday': 'Samedi',
'__keta.directives.TimeRangeSelector_weekday_saturday_short': 'Sam',
'__keta.directives.TimeRangeSelector_week_number': 'Numéro de la semaine',
'__keta.directives.TimeRangeSelector_week_number_short': 'Sem.'
},
'nl_NL': {
'__keta.directives.TimeRangeSelector_display_mode_days': 'Dagen',
'__keta.directives.TimeRangeSelector_display_mode_months': 'Maanden',
'__keta.directives.TimeRangeSelector_display_mode_years': 'Jaren',
'__keta.directives.TimeRangeSelector_select': 'Kiezen',
'__keta.directives.TimeRangeSelector_selection': 'Selectie',
'__keta.directives.TimeRangeSelector_today': 'Vandaag',
'__keta.directives.TimeRangeSelector_week': 'Week',
'__keta.directives.TimeRangeSelector_weekday_sunday': 'Zondag',
'__keta.directives.TimeRangeSelector_weekday_sunday_short': 'Zo',
'__keta.directives.TimeRangeSelector_weekday_monday': 'Maandag',
'__keta.directives.TimeRangeSelector_weekday_monday_short': 'Ma',
'__keta.directives.TimeRangeSelector_weekday_tuesday': 'Dinsdag',
'__keta.directives.TimeRangeSelector_weekday_tuesday_short': 'Di',
'__keta.directives.TimeRangeSelector_weekday_wednesday': 'Woensdag',
'__keta.directives.TimeRangeSelector_weekday_wednesday_short': 'Wo',
'__keta.directives.TimeRangeSelector_weekday_thursday': 'Donderdag',
'__keta.directives.TimeRangeSelector_weekday_thursday_short': 'Do',
'__keta.directives.TimeRangeSelector_weekday_friday': 'Vrijdag',
'__keta.directives.TimeRangeSelector_weekday_friday_short': 'Vr',
'__keta.directives.TimeRangeSelector_weekday_saturday': 'Zaterdag',
'__keta.directives.TimeRangeSelector_weekday_saturday_short': 'Za',
'__keta.directives.TimeRangeSelector_week_number': 'Weeknummer',
'__keta.directives.TimeRangeSelector_week_number_short': 'Wn'
},
'it_IT': {
'__keta.directives.TimeRangeSelector_display_mode_days': 'Giorni',
'__keta.directives.TimeRangeSelector_display_mode_months': 'Mesi',
'__keta.directives.TimeRangeSelector_display_mode_years': 'Anni',
'__keta.directives.TimeRangeSelector_select': 'Scegliere',
'__keta.directives.TimeRangeSelector_selection': 'Selezione',
'__keta.directives.TimeRangeSelector_today': 'Oggi',
'__keta.directives.TimeRangeSelector_week': 'Settimana',
'__keta.directives.TimeRangeSelector_weekday_sunday': 'Domenica',
'__keta.directives.TimeRangeSelector_weekday_sunday_short': 'Dom.',
'__keta.directives.TimeRangeSelector_weekday_monday': 'Lunedì',
'__keta.directives.TimeRangeSelector_weekday_monday_short': 'Lun.',
'__keta.directives.TimeRangeSelector_weekday_tuesday': 'Martedì',
'__keta.directives.TimeRangeSelector_weekday_tuesday_short': 'Mar.',
'__keta.directives.TimeRangeSelector_weekday_wednesday': 'Mercoledì',
'__keta.directives.TimeRangeSelector_weekday_wednesday_short': 'Mer.',
'__keta.directives.TimeRangeSelector_weekday_thursday': 'Giovedì',
'__keta.directives.TimeRangeSelector_weekday_thursday_short': 'Gio.',
'__keta.directives.TimeRangeSelector_weekday_friday': 'Venerdì',
'__keta.directives.TimeRangeSelector_weekday_friday_short': 'Ven.',
'__keta.directives.TimeRangeSelector_weekday_saturday': 'Sabato',
'__keta.directives.TimeRangeSelector_weekday_saturday_short': 'Sab.',
'__keta.directives.TimeRangeSelector_week_number': 'Numero della settimana',
'__keta.directives.TimeRangeSelector_week_number_short': 'Set.'
}
})
.directive('ketaTimeRangeSelector', function TimeRangeSelectorDirective(
$filter,
ketaTimeRangeSelectorConstants, ketaTimeRangeSelectorMessageKeys, moment
) {
return {
restrict: 'EA',
replace: true,
require: 'ngModel',
scope: {
// css classes to use
cssClasses: '=?',
// current locale
currentLocale: '=?',
// display mode (@see ketaTimeRangeSelectorConstants.DISPLAY)
displayMode: '=?',
// display value (date)
displayValue: '=?',
// element identifier
elementIdentifier: '=?',
// enable display mode switch
enableDisplayModeSwitch: '=?',
// enable week selection
enableWeekSelection: '=?',
// first click flag
firstClick: '=?',
// first day of week (0...Sun, 1...Mon, 2...Tue, 3...Wed, 4...Thu, 5...Fri, 6...Sat)
firstDayOfWeek: '=?',
// object of labels to use in template
labels: '=?',
// maximum date selectable (date is included)
maximum: '=?',
// maximum range length in days or 0 to disable it
maxRangeLength: '=?',
// minimum date selectable (date is included)
minimum: '=?',
// minimum range length in days or 0 to disable it
minRangeLength: '=?',
// model value (range)
model: '=ngModel',
// show display mode switcher
showDisplayModeSwitcher: '=?',
// show pager above
showPager: '=?',
// show jump to selection button
showJumpToSelectionButton: '=?',
// show jump to today button
showJumpToTodayButton: '=?',
// show select button
showSelectButton: '=?',
// show week numbers
showWeekNumbers: '=?',
// number of years to show before current in display mode year
// must be a multiple of 3 added by 1 (e.g. 1, 4, 7, 11, ...)
yearsAfter: '=?',
// number of years to show after current in display mode year
// must be a multiple of 3 added by 1 (e.g. 1, 4, 7, 11, ...)
yearsBefore: '=?'
},
templateUrl: '/components/directives/time-range-selector.html',
link: function(scope) {
// CONSTANTS
// ---------
scope.DISPLAY_MODE_DAY = ketaTimeRangeSelectorConstants.DISPLAY_MODE.DAY;
scope.DISPLAY_MODE_MONTH = ketaTimeRangeSelectorConstants.DISPLAY_MODE.MONTH;
scope.DISPLAY_MODE_YEAR = ketaTimeRangeSelectorConstants.DISPLAY_MODE.YEAR;
var DAYS_PER_WEEK = 7;
var ISO_DATE_LENGTH_DAY = 10;
var ISO_DATE_LENGTH_MONTH = 7;
var ISO_DATE_LENGTH_YEAR = 4;
var ISO_PADDING = 2;
var LOCALE_SHORT_LENGTH = 5;
var MONTHS_PER_ROW = 3;
var MONTHS_PER_YEAR = 12;
var YEARS_AFTER = 4;
var YEARS_BEFORE = 7;
var YEARS_PER_ROW = 3;
// CONFIGURATION
// -------------
var today = new Date();
today.setHours(0, 0, 0, 0);
var defaultModel = {from: null, to: null};
scope.firstClick = true;
scope.model =
angular.isDefined(scope.model.from) && angular.isDate(scope.model.from) &&
angular.isDefined(scope.model.to) && angular.isDate(scope.model.to) ?
scope.model : defaultModel;
if (scope.model.from !== null) {
scope.model.from.setHours(0, 0, 0, 0);
scope.firstClick = false;
}
if (scope.model.to !== null) {
scope.model.to.setHours(0, 0, 0, 0);
scope.firstClick = true;
}
scope.cssClasses =
angular.isObject(scope.cssClasses) ?
angular.extend(ketaTimeRangeSelectorConstants.CSS_CLASSES, scope.cssClasses) :
ketaTimeRangeSelectorConstants.CSS_CLASSES;
scope.currentLocale = angular.isString(scope.currentLocale) ? scope.currentLocale : 'en_GB';
scope.displayMode = scope.displayMode || scope.DISPLAY_MODE_DAY;
scope.displayValue =
angular.isDate(scope.displayValue) ?
new Date(scope.displayValue.setHours(0, 0, 0, 0)) : angular.copy(scope.model.from);
if (scope.displayValue === null) {
scope.displayValue = today;
}
scope.elementIdentifier =
angular.isDefined(scope.elementIdentifier) ?
scope.elementIdentifier : null;
scope.enableDisplayModeSwitch =
angular.isDefined(scope.enableDisplayModeSwitch) ?
scope.enableDisplayModeSwitch : false;
scope.enableWeekSelection =
angular.isDefined(scope.enableWeekSelection) ?
scope.enableWeekSelection : true;
scope.firstDayOfWeek = scope.firstDayOfWeek || ketaTimeRangeSelectorConstants.DAY.SUNDAY;
// object of labels
scope.MESSAGE_KEY_PREFIX = '__keta.directives.TimeRangeSelector';
scope.labels =
angular.isObject(scope.labels) ?
angular.extend(ketaTimeRangeSelectorMessageKeys, scope.labels) :
ketaTimeRangeSelectorMessageKeys;
scope.currentLabels =
angular.isDefined(
ketaTimeRangeSelectorMessageKeys[scope.currentLocale.substr(0, LOCALE_SHORT_LENGTH)]
) ?
ketaTimeRangeSelectorMessageKeys[scope.currentLocale.substr(0, LOCALE_SHORT_LENGTH)] :
ketaTimeRangeSelectorMessageKeys.en_GB;
scope.maximum =
angular.isDate(scope.maximum) ?
new Date(scope.maximum.setHours(0, 0, 0, 0)) : null;
scope.maxRangeLength =
angular.isNumber(scope.maxRangeLength) ?
Math.max(0, Math.round(scope.maxRangeLength)) : 0;
scope.minimum =
angular.isDate(scope.minimum) ?
new Date(scope.minimum.setHours(0, 0, 0, 0)) : null;
scope.minRangeLength =
angular.isNumber(scope.minRangeLength) ?
Math.max(0, Math.round(scope.minRangeLength)) : 0;
scope.showDisplayModeSwitcher =
angular.isDefined(scope.showDisplayModeSwitcher) ?
scope.showDisplayModeSwitcher : true;
scope.showPager =
angular.isDefined(scope.showPager) ?
scope.showPager : true;
scope.showJumpToSelectionButton =
angular.isDefined(scope.showJumpToSelectionButton) ?
scope.showJumpToSelectionButton : false;
scope.showJumpToTodayButton =
angular.isDefined(scope.showJumpToTodayButton) ?
scope.showJumpToTodayButton : false;
scope.showSelectButton =
angular.isDefined(scope.showSelectButton) ?
scope.showSelectButton : false;
scope.showWeekNumbers =
angular.isDefined(scope.showWeekNumbers) ?
scope.showWeekNumbers : true;
scope.yearsBefore = scope.yearsBefore || YEARS_BEFORE;
scope.yearsAfter = scope.yearsAfter || YEARS_AFTER;
// DATA
// ----
scope.weekDays = [];
scope.days = [];
scope.months = [];
scope.years = [];
// HELPER
// ------
/**
* get week days for currently set first day of week
* @returns {void} nothing
*/
var getWeekDays = function getWeekDays() {
var weekDays = [];
var weekDayLabels = [
scope.currentLabels[scope.MESSAGE_KEY_PREFIX + '_weekday_sunday_short'],
scope.currentLabels[scope.MESSAGE_KEY_PREFIX + '_weekday_monday_short'],
scope.currentLabels[scope.MESSAGE_KEY_PREFIX + '_weekday_tuesday_short'],
scope.currentLabels[scope.MESSAGE_KEY_PREFIX + '_weekday_wednesday_short'],
scope.currentLabels[scope.MESSAGE_KEY_PREFIX + '_weekday_thursday_short'],
scope.currentLabels[scope.MESSAGE_KEY_PREFIX + '_weekday_friday_short'],
scope.currentLabels[scope.MESSAGE_KEY_PREFIX + '_weekday_saturday_short']
];
var start = scope.firstDayOfWeek;
while (weekDays.length < DAYS_PER_WEEK) {
weekDays.push(weekDayLabels[start]);
start++;
start %= DAYS_PER_WEEK;
}
scope.weekDays = weekDays;
};
/**
* get days for month in timeRange.view
* @returns {void} nothing
*/
var getDays = function getDays() {
// get first of current and next month
var firstOfCurrentMonth =
moment(scope.displayValue.getTime()).startOf('month')
.hours(0).minutes(0).seconds(0).milliseconds(0);
var firstOfCurrentMonthWeekDay = firstOfCurrentMonth.day();
var lastOfCurrentMonth = moment(firstOfCurrentMonth).add(1, 'months').subtract(1, 'days');
var lastOfCurrentMonthWeekDay = lastOfCurrentMonth.day();
var days = [];
var i = 0;
// PREPEND DAYS ===
// days to prepend to first of week
if (firstOfCurrentMonthWeekDay < scope.firstDayOfWeek) {
firstOfCurrentMonthWeekDay += DAYS_PER_WEEK;
}
var daysToPrepend = firstOfCurrentMonthWeekDay - scope.firstDayOfWeek;
// prepend days
for (i = daysToPrepend; i > 0; i--) {
var prependedDay = moment(firstOfCurrentMonth).subtract(i, 'days');
days.push(prependedDay.toDate());
}
// MONTH DAYS ===
var monthDays = moment(lastOfCurrentMonth).date();
for (i = 0; i < monthDays; i++) {
var monthDay = moment(firstOfCurrentMonth).add(i, 'days');
days.push(monthDay.toDate());
}
// APPEND DAYS ===
// days to append to last of week
if (lastOfCurrentMonthWeekDay < scope.firstDayOfWeek) {
lastOfCurrentMonthWeekDay += DAYS_PER_WEEK;
}
var daysToAppend = DAYS_PER_WEEK - (lastOfCurrentMonthWeekDay - scope.firstDayOfWeek) - 1;
// append days
for (i = 1; i <= daysToAppend; i++) {
var appendedDay = moment(lastOfCurrentMonth).add(i, 'days');
days.push(appendedDay.toDate());
}
// CHUNK DAYS ===
// chunk days in weeks
var chunks = [];
var chunk = [];
angular.forEach(days, function(day, index) {
chunk.push(day);
// add week
if ((index + 1) % DAYS_PER_WEEK === 0) {
chunks.push(chunk);
chunk = [];
}
});
scope.days = chunks;
};
/**
* get months
* @returns {void} nothing
*/
var getMonths = function getMonths() {
var months = [];
var monthsChunked = [];
var now =
moment(scope.displayValue)
.date(1).hour(0).minute(0).second(0).millisecond(0)
.toDate();
for (var i = 0; i < MONTHS_PER_YEAR; i++) {
now.setMonth(i);
months.push(angular.copy(now));
}
// chunk months in blocks of four
var chunk = [];
angular.forEach(months, function(month, index) {
chunk.push(month);
// add row
if ((index + 1) % MONTHS_PER_ROW === 0) {
monthsChunked.push(chunk);
chunk = [];
}
});
scope.months = monthsChunked;
};
/**
* get years around year in timeRange.view
* @returns {void} nothing
*/
var getYears = function getYears() {
var years = [];
var yearsChunked = [];
var now =
moment(new Date())
.month(0).date(1).hour(0).minute(0).second(0).millisecond(0)
.toDate();
var currentYear = parseInt($filter('date')(now, 'yyyy'), 10);
var yearStart = currentYear - scope.yearsBefore;
var yearEnd = currentYear + scope.yearsAfter;
var viewYear = parseInt($filter('date')(scope.displayValue, 'yyyy'), 10);
var range = scope.yearsBefore + scope.yearsAfter + 1;
// shift to the past until view year is within range
if (viewYear < yearStart) {
while (viewYear < yearStart) {
yearStart -= range;
yearEnd -= range;
}
}
// shift to the future until view year is within range
if (viewYear > yearEnd) {
while (viewYear > yearEnd) {
yearStart += range;
yearEnd += range;
}
}
for (var i = yearStart; i <= yearEnd; i++) {
now.setFullYear(i);
years.push(angular.copy(now));
}
// chunk years in blocks of four
var chunk = [];
angular.forEach(years, function(year, index) {
chunk.push(year);
// add row
if ((index + 1) % YEARS_PER_ROW === 0) {
yearsChunked.push(chunk);
chunk = [];
}
});
scope.years = yearsChunked;
};
/**
* get locale iso date for given date and length
* @param {date} date date to extract iso format from
* @param {number} length length of iso date
* @returns {string} iso date
*/
var getLocaleISO = function getLocaleISO(date, length) {
var iso =
date.getFullYear() + '-' +
('00' + (date.getMonth() + 1)).slice(-ISO_PADDING) + '-' +
('00' + date.getDate()).slice(-ISO_PADDING);
return iso.substr(0, length);
};
/**
* checks if given date is out of bounds
* @param {date} date date to get classes for
* @returns {boolean} true if out of bounds
*/
scope.isOutOfBounds = function isOutOfBounds(date) {
var outOfBounds = false;
var dateISO = getLocaleISO(date, ISO_DATE_LENGTH_DAY);
if (scope.minimum !== null &&
dateISO < getLocaleISO(scope.minimum, ISO_DATE_LENGTH_DAY)) {
outOfBounds = true;
}
if (scope.maximum !== null &&
dateISO > getLocaleISO(scope.maximum, ISO_DATE_LENGTH_DAY)) {
outOfBounds = true;
}
return outOfBounds;
};
/**
* get date classes
* @param {Date} date date to get classes for
* @returns {string} classes as space-separated strings
*/
scope.getDateClasses = function getDateClasses(date) {
var classes = [];
var td = new Date(new Date().setHours(0, 0, 0, 0));
var current = scope.displayValue;
var isoDateLength = ISO_DATE_LENGTH_DAY;
var outOfBoundDates = [
getLocaleISO(date, ISO_DATE_LENGTH_DAY),
getLocaleISO(date, ISO_DATE_LENGTH_DAY)
];
if (scope.displayMode === scope.DISPLAY_MODE_MONTH) {
isoDateLength = ISO_DATE_LENGTH_MONTH;
outOfBoundDates = [
getLocaleISO(moment(date).clone().startOf('month').toDate(), ISO_DATE_LENGTH_DAY),
getLocaleISO(moment(date).clone().endOf('month').toDate(), ISO_DATE_LENGTH_DAY)
];
}
if (scope.displayMode === scope.DISPLAY_MODE_YEAR) {
isoDateLength = ISO_DATE_LENGTH_YEAR;
outOfBoundDates = [
getLocaleISO(moment(date).clone().startOf('year').toDate(), ISO_DATE_LENGTH_DAY),
getLocaleISO(moment(date).clone().endOf('year').toDate(), ISO_DATE_LENGTH_DAY)
];
}
var selectedDateFromISO = scope.model.from !== null ?
getLocaleISO(scope.model.from, isoDateLength) : null;
var selectedDateToISO = scope.model.to !== null ?
getLocaleISO(scope.model.to, isoDateLength) : null;
var dateISO = getLocaleISO(date, isoDateLength);
var todayISO = getLocaleISO(td, isoDateLength);
if (scope.displayMode === scope.DISPLAY_MODE_DAY &&
date.getMonth() !== current.getMonth()) {
classes.push(scope.cssClasses.OUT_OF_MONTH);
}
if (scope.minimum !== null) {
var minimumISO = getLocaleISO(scope.minimum, ISO_DATE_LENGTH_DAY);
if (outOfBoundDates[0] < minimumISO && outOfBoundDates[1] < minimumISO) {
classes.push(scope.cssClasses.OUT_OF_BOUNDS);
classes.push(scope.cssClasses.OUT_OF_BOUNDS_BEFORE);
}
}
if (scope.maximum !== null) {
var maximumISO = getLocaleISO(scope.maximum, ISO_DATE_LENGTH_DAY);
if (outOfBoundDates[0] > maximumISO && outOfBoundDates[1] > maximumISO) {
classes.push(scope.cssClasses.OUT_OF_BOUNDS);
classes.push(scope.cssClasses.OUT_OF_BOUNDS_AFTER);
}
}
if (dateISO === todayISO) {
classes.push(scope.cssClasses.CURRENT_DATE);
}
if (selectedDateFromISO !== null && selectedDateToISO !== null &&
dateISO >= selectedDateFromISO && dateISO <= selectedDateToISO) {
classes.push(scope.cssClasses.SELECTED_DATE);
}
if (selectedDateFromISO !== null && dateISO === selectedDateFromISO) {
classes.push(scope.cssClasses.SELECTED_DATE_FROM);
}
if (selectedDateToISO !== null && dateISO === selectedDateToISO) {
classes.push(scope.cssClasses.SELECTED_DATE_TO);
}
return classes.join(' ');
};
/**
* get year range around timeRange.view
* @returns {string} year range
*/
scope.getYearRange = function getYearRange() {
var year = parseInt($filter('date')(scope.displayValue, 'yyyy'), 10);
var yearStart = year - scope.yearsBefore;
var yearEnd = year + scope.yearsAfter;
return yearStart + ' – ' + yearEnd;
};
// INIT
// ----
/**
* update component data
* @returns {void} nothing
*/
var update = function update() {
// update current labels
scope.currentLabels =
angular.isDefined(
ketaTimeRangeSelectorMessageKeys[scope.currentLocale.substr(0, LOCALE_SHORT_LENGTH)]
) ?
ketaTimeRangeSelectorMessageKeys[scope.currentLocale.substr(0, LOCALE_SHORT_LENGTH)] :
ketaTimeRangeSelectorMessageKeys.en_GB;
// init week days
getWeekDays();
// init calendar sheet data
if (scope.displayMode === scope.DISPLAY_MODE_MONTH) {
getMonths();
} else if (scope.displayMode === scope.DISPLAY_MODE_YEAR) {
getYears();
} else {
getDays();
}
};
update();
// ACTIONS
// -------
/**
* move to direction based on current display
* @param {number} direction move offset
* @returns {void} nothing
*/
var move = function move(direction) {
if (scope.displayMode === scope.DISPLAY_MODE_MONTH) {
scope.displayValue = moment(scope.displayValue).add(direction, 'years').toDate();
} else if (scope.displayMode === scope.DISPLAY_MODE_YEAR) {
scope.displayValue = moment(scope.displayValue).add(
direction * (scope.yearsBefore + scope.yearsAfter + 1),
'years'
).toDate();
} else {
scope.displayValue = moment(scope.displayValue).add(direction, 'months').toDate();
}
update();
};
/**
* move to previous based on current display
* @returns {void} nothing
*/
scope.prev = function prev() {
move(-1);
};
/**
* move to next based on current display
* @returns {void} nothing
*/
scope.next = function next() {
move(1);
};
/**
* get display mode dependent date
* @param {Date} date date to convert to a display mode dependent value
* @param {string} mode display mode
* @param {boolean} from from or to value
* @returns {Date} converted date
*/
var getDisplayModeDate = function getDisplayModeDate(date, mode, from) {
var displayModeDate = null;
if (mode === ketaTimeRangeSelectorConstants.DISPLAY_MODE.MONTH) {
displayModeDate = from ?
date : moment(date).endOf('month').hour(0).minute(0).second(0).millisecond(0).toDate();
} else if (mode === ketaTimeRangeSelectorConstants.DISPLAY_MODE.YEAR) {
displayModeDate = from ?
date : moment(date).endOf('year').hour(0).minute(0).second(0).millisecond(0).toDate();
} else {
displayModeDate = angular.copy(date);
}
return displayModeDate;
};
/**
* apply boundaries constraint (if enabled)
* @param {Date} from from date
* @param {Date} to to date
* @param {number} min minimum as timestamp or null
* @param {number} max maximum as timestamp or null
* @returns {{from: *, to: *}} range
*/
var applyBoundaries = function applyBoundaries(from, to, min, max) {
var range = {from: from, to: to};
if (min !== null && range.from < min) {
range.from = min;
}
if (max !== null && range.to > max) {
range.to = max;
}
return range;
};
/**
* apply range length constraint (if enabled)
* @param {Date} from from date
* @param {Date} to to date
* @param {number} min minimum range length in days or null
* @param {number} max maximum range length in days or null
* @returns {{from: *, to: *}} range
*/
var applyRangeLength = function applyRangeLength(from, to, min, max) {
var range = {from: from, to: to};
// get duration in days
var days = moment(to).diff(moment(from), 'days');
// selection is longer than the valid max value (!== 0)
if (max !== 0 && days >= max) {
range.to = moment(from).add(max - 1, 'days').toDate();
}
// selection is shorter than the valid min value (!== 0)
if (min !== 0 && days <= min) {
range.to = moment(from).add(min - 1, 'days').toDate();
}
return range;
};
/**
* set display mode by display mode switcher or sheet title
* @param {string} mode to set
* @returns {void} nothing
*/
scope.setDisplayMode = function(mode) {
if (mode !== scope.displayMode) {
// reset selection if display mode changes
scope.model = {from: null, to: null};
scope.firstClick = true;
scope.displayMode = mode;
update();
}
};
/**
* select week for given date
* @param {Date} date date selected
* @returns {void} nothing
*/
scope.selectWeek = function selectWeek(date) {
var firstOfWeek = moment(date).hour(0).minute(0).second(0).millisecond(0).toDate();
var lastOfWeek =
moment(firstOfWeek).add(DAYS_PER_WEEK - 1, 'days')
.hour(0).minute(0).second(0).millisecond(0).toDate();
if (!scope.isOutOfBounds(firstOfWeek) &&
!scope.isOutOfBounds(lastOfWeek) &&
(scope.minRangeLength === 0 || scope.minRangeLength <= DAYS_PER_WEEK) &&
(scope.maxRangeLength === 0 || scope.maxRangeLength >= DAYS_PER_WEEK) &&
scope.enableWeekSelection) {
scope.model.from = getDisplayModeDate(firstOfWeek, scope.displayMode, true);
scope.model.to = getDisplayModeDate(lastOfWeek, scope.displayMode, false);
scope.firstClick = true;
}
};
/**
* select given date as from or to depending on first click flag
* @param {date} date date selected
* @returns {void} nothing
*/
scope.select = function select(date) {
// check out of bounds
if (!scope.isOutOfBounds(date)) {
if (scope.firstClick) {
scope.model.from = getDisplayModeDate(date, scope.displayMode, true);
scope.model.to = null;
scope.firstClick = false;
} else {
var toBeforeFrom = date.getTime() < scope.model.from.getTime();
if (toBeforeFrom) {
scope.model.from = getDisplayModeDate(date, scope.displayMode, true);
scope.model.to = null;
scope.firstClick = false;
} else {
scope.model.to = getDisplayModeDate(date, scope.displayMode, false);
scope.firstClick = true;
}
}
// apply minRangeLength and maxRangeLength
scope.model = applyRangeLength(
scope.model.from, scope.model.to,
scope.minRangeLength, scope.maxRangeLength
);
// apply minimum and maximum
scope.model = applyBoundaries(
scope.model.from, scope.model.to,
scope.minimum, scope.maximum
);
// update display value
scope.displayValue =
scope.firstClick ?
angular.copy(scope.model.to) : angular.copy(scope.model.from);
}
};
/**
* jump to the view with the current selection in it
* @returns {void} nothing
*/
scope.goToSelection = function goToSelection() {
if (scope.model.from !== null) {
scope.displayValue = angular.copy(scope.model.from);
}
};
/**
* jump to the view with the current day in it
* @returns {void} nothing
*/
scope.goToToday = function goToToday() {
var td = new Date();
td.setHours(0, 0, 0, 0);
scope.displayValue = td;
};
/**
* submit
* @returns {void} nothing
*/
scope.submit = function submit() {
// if only from was selected and submitted afterwards automatically set to
if (scope.model.from !== null && scope.model.to === null) {
scope.model.to = getDisplayModeDate(scope.model.from, scope.displayMode, false);
}
// emit event
scope.$emit(
ketaTimeRangeSelectorConstants.EVENT.SELECT,
{
id: scope.elementIdentifier,
model: scope.model
}
);
};
// WATCHER
// -------
// watcher for current locale
scope.$watch('currentLocale', function(newValue, oldValue) {
if (newValue !== oldValue && angular.isString(newValue)) {
update();
}
});
// watcher for display mode
scope.$watch('displayMode', function(newValue, oldValue) {
if (newValue !== oldValue) {
update();
}
});
// watcher for display value
scope.$watch('displayValue', function(newValue, oldValue) {
if (newValue !== oldValue && angular.isDate(newValue)) {
update();
}
});
// watcher for first day of week
scope.$watch('firstDayOfWeek', function(newValue, oldValue) {
if (newValue !== oldValue) {
update();
}
});
// watcher for maximum
scope.$watch('maximum', function(newValue, oldValue) {
if (newValue !== oldValue) {
scope.model = applyBoundaries(
scope.model.from, scope.model.to,
scope.mininum, newValue
);
}
});
// watcher for mininum
scope.$watch('minimum', function(newValue, oldValue) {
if (newValue !== oldValue) {
scope.model = applyBoundaries(
scope.model.from, scope.model.to,
newValue, scope.maximum
);
}
});
// watcher for max range length
scope.$watch('maxRangeLength', function(newValue, oldValue) {
if (newValue !== oldValue) {
scope.model = applyRangeLength(
scope.model.from, scope.model.to,
scope.minRangeLength, newValue
);
}
});
// watcher for min range length
scope.$watch('minRangeLength', function(newValue, oldValue) {
if (newValue !== oldValue) {
scope.model = applyRangeLength(
scope.model.from, scope.model.to,
newValue, scope.maxRangeLength
);
}
});
// watcher for years after
scope.$watch('yearsAfter', function(newValue, oldValue) {
if (newValue !== oldValue) {
update();
}
});
// watcher for years before
scope.$watch('yearsBefore', function(newValue, oldValue) {
if (newValue !== oldValue) {
update();
}
});
}
};
});