var Backbone = require('backbone'); var CoreView = require('backbone/core-view'); var _ = require('underscore'); var moment = require('moment'); var $ = require('jquery'); var Utils = require('builder/helpers/utils'); require('builder/components/form-components/index'); require('datepicker'); var datePickerTemplate = require('./date-picker-range-form.tpl'); var template = require('./date-picker-range.tpl'); var MAX_RANGE = 30; var FOUR_HOURS = { amount: 4, unit: 'hours' }; var ONE_DAY = { amount: 1, unit: 'day' }; var ONE_WEEK = { amount: 1, unit: 'week' }; /** * Custom picer for a dates range. */ module.exports = CoreView.extend({ className: 'DatePicker', options: { flat: true, date: ['2008-07-31', '2008-07-31'], current: '2008-07-31', calendars: 2, mode: 'range', starts: 1 }, events: { 'click .js-dates': '_toggleCalendar', 'click .js-fourHours': function () { this._setPreviousTime(FOUR_HOURS); }, 'click .js-oneDay': function () { this._setPreviousTime(ONE_DAY); }, 'click .js-oneWeek': function () { this._setPreviousTime(ONE_WEEK); } }, initialize: function (opts) { var isDisabled = (opts && opts.disabled) ? opts.disabled : false; this.model = new Backbone.Model({ fromDate: '', fromHour: 0, fromMin: 0, toDate: '', toHour: 23, toMin: 59, user_timezone: 0, // Explained as GMT+0 disabled: isDisabled }); this.template = opts.template || template; this._initBinds(); this._setDefaultDate(); }, render: function () { var self = this; this.clearSubViews(); this.$el.empty(); this.$el.append( this.template( _.extend( this.model.attributes, { max_days: MAX_RANGE, pad: Utils.pad } ) ) ); setTimeout(function () { self._initCalendar(); self._hideCalendar(); self._initTimers(); }, 100); return this; }, _initBinds: function () { this.model.bind('change', this._setValues, this); this.model.bind('change', this._onValuesChange, this); $(document).bind('click', this._onDocumentClick.bind(this)); }, _destroyBinds: function () { $(document).unbind('click', this._onDocumentClick.bind(this)); }, _formattedDate: function (which) { var text = 'components.datepicker.' + which; return _t(text) + ' ' + '' + this.model.get(which + 'Date') + ' ' + (Utils.pad(this.model.get(which + 'Hour'), 2) + ':' + Utils.pad(this.model.get(which + 'Min'), 2)) + ''; }, _setValues: function () { var text = _t('components.datepicker.dates-placeholder'); var data = this.model.attributes; if (data.fromDate && data.toDate) { var calendarIcon = ''; text = this._formattedDate('from') + ' ' + this._formattedDate('to') + calendarIcon; } this.$('.DatePicker-dates').html(text); }, _setDefaultDate: function () { var datesUTC = this.model.get('user_timezone'); var today = moment().utc(datesUTC); var previous = moment().utc(datesUTC).subtract((MAX_RANGE - 1), 'days'); this.options.date = [previous.format('YYYY-MM-DD'), today.format('YYYY-MM-DD')]; this.options.current = today.format('YYYY-MM-DD'); this._setModelFromPrevious(previous); }, _initCalendar: function () { var selector = '.DatePicker-calendar'; // Can't initialize calendar if not already present in document... avoid errors being thrown if (!document.body.contains(this.$(selector)[0])) return; this.calendar = this.$(selector).DatePicker( _.extend(this.options, { onChange: this._onDatesChange.bind(this), onRender: function (d) { // Disable future dates and dates < MAX_RANGE days ago var date = d.valueOf(); var now = new Date(); var thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(now.getDate() - MAX_RANGE); return (date < thirtyDaysAgo) || (date > now) ? { disabled: true } : ''; } }) ); }, _onDatesChange: function (formatted, dates) { // Check if selected dates have more than MAX_RANGE days var start = moment(formatted[0]); var end = moment(formatted[1]); if (Math.abs(start.diff(end, 'days')) > MAX_RANGE) { formatted[1] = moment(formatted[0]).add('days', MAX_RANGE).format('YYYY-MM-DD'); this.$('.DatePicker-calendar').DatePickerSetDate([formatted[0], formatted[1]]); } this.model.set({ fromDate: formatted[0], toDate: formatted[1] }); }, _hideCalendar: function (e) { if (e) this.killEvent(e); this.$('.DatePicker-dropdown').hide(); }, _toggleCalendar: function (ev) { if (ev) this.killEvent(ev); this.$('.DatePicker-dropdown').toggle(); }, _setPreviousTime: function (timeSpan) { var previous = moment().utc(0).subtract(timeSpan.amount, timeSpan.unit); this._setModelFromPrevious(previous); this._setDatepickerFromPrevious(previous); this.closeCalendar(); }, _setModelFromPrevious: function (previous) { var today = moment().utc(0); this.model.set({ fromDate: previous.format('YYYY-MM-DD'), fromHour: parseInt(previous.format('H'), 10), fromMin: parseInt(previous.format('m'), 10), toDate: today.format('YYYY-MM-DD'), toHour: parseInt(today.format('H'), 10), toMin: parseInt(today.format('m'), 10) }); }, _setDatepickerFromPrevious: function (previous) { var today = moment().utc(0); this.$('.DatePicker-calendar').DatePickerSetDate([ previous.format('YYYY-MM-DD'), today.format('YYYY-MM-DD') ]); }, _initTimers: function () { var generateNumberType = function (min, max) { var title = max === 23 ? _t('components.datepicker.hour') : _t('components.datepicker.min'); return { type: 'Number', title: title, validators: ['required', { type: 'interval', min: min, max: max }] }; }; this.model.schema = { fromHour: generateNumberType(0, 23), fromMin: generateNumberType(0, 59), toHour: generateNumberType(0, 23), toMin: generateNumberType(0, 59) }; this._datesForm = new Backbone.Form({ model: this.model, template: datePickerTemplate }); this._datesForm.bind('change', function () { this.commit(); }); this.$('.js-timers').append(this._datesForm.render().el); }, _onValuesChange: function () { this.trigger('changeDate', this.model.toJSON(), this); }, getDates: function () { return this.model.toJSON(); }, closeCalendar: function () { this.$('.DatePicker-dropdown').hide(); }, _onDocumentClick: function (e) { var $el = $(e.target); if ($el.closest('.DatePicker').length === 0) { this.closeCalendar(); } }, clean: function () { this._datesForm && this._datesForm.remove(); this._destroyBinds(); this.closeCalendar(); this.$('.DatePicker-calendar').DatePickerHide(); CoreView.prototype.clean.call(this); } });