UNPKG

m-persian-datepicker

Version:

jQuery datepicker plugin work with Iranian calendar. (Jalali)

633 lines (571 loc) 21.5 kB
let Template = require('./template'); let Helper = require('./helper'); let Mustache = require('mustache'); /** * As its name suggests, all rendering works do in this object */ class View { /** * * @param {Datepicker} model * @return {View} */ constructor (model) { /** * @type {number} */ this.yearsViewCount = 12; /** * * @type {Datepicker} */ this.model = model; /** * * @type {null} */ this.rendered = null; /** * * @type {null} */ this.$container = null; /** * * @type {string} */ this.id = `persianDateInstance-${parseInt(Math.random(100) * 1000)}`; let that = this; if (this.model.state.ui.isInline) { this.$container = $('<div id="' + this.id + '" class="datepicker-container-inline"></div>').appendTo(that.model.inputElement); } else { this.$container = $('<div id="' + this.id + '" class="datepicker-container"></div>').appendTo('body'); this.hide(); this.setPickerBoxPosition(); this.addCompatibilityClass(); } return this; } /** * @desc add css class to handle compatibility ui things */ addCompatibilityClass () { if (Helper.isMobile && this.model.options.responsive) { this.$container.addClass('pwt-mobile-view'); } } /** * @desc remove datepicker container element from dom */ destroy () { this.$container.remove(); } /** * @desc set datepicker container element based on <input/> element position */ setPickerBoxPosition () { let inputPosition = this.model.input.getInputPosition(), inputSize = this.model.input.getInputSize(); if (Helper.isMobile && this.model.options.responsive) { return false; } if (this.model.options.position === 'auto') { this.$container.css({ left: (inputPosition.left) + 'px', top: (inputSize.height + inputPosition.top) + 'px' }); } else { this.$container.css({ left: (this.model.options.position[1] + inputPosition.left) + 'px', top: (this.model.options.position[0] + inputPosition.top) + 'px' }); } } /** * @desc show datepicker container element */ show () { this.$container.removeClass('pwt-hide'); this.setPickerBoxPosition(); } /** * @desc hide datepicker container element */ hide () { this.$container.addClass('pwt-hide'); } /** * @desc toggle datepicker container element */ toggle () { this.$container.toggleClass('pwt-hide'); } /** * @desc return navigator switch text * @param {String} data - accept day, month, year * @private * @return {String} */ _getNavSwitchText (data) { let output; if (this.model.state.viewMode == 'day') { output = this.model.options.dayPicker.titleFormatter.call(this, data.year, data.month); } else if (this.model.state.viewMode == 'month') { output = this.model.options.monthPicker.titleFormatter.call(this, data.dateObject.valueOf()); } else if (this.model.state.viewMode == 'year') { output = this.model.options.yearPicker.titleFormatter.call(this, data.year); } return output; } /** * @desc check year is accessible * @param {Number} year - year number * @return {Boolean} */ checkYearAccess (year) { let output = true; if (this.model.state.filetredDate) { let startYear = this.model.state.filterDate.start.year, endYear = this.model.state.filterDate.end.year; if (startYear && year < startYear) { return false; } else if (endYear && year > endYear) { return false; } } if (output) { return this.model.options.checkYear(year); } } /** * @private * @param viewState * @return {{enabled: boolean, viewMode: boolean, list: Array}} */ _getYearViewModel (viewState) { let isEnabled = this.model.options.yearPicker.enabled; // Make performance better if (!isEnabled) { return { enabled: false }; } /** * @description Generate years list based on viewState year * @return ['1380',n+12,'1392'] */ let list = [...Array(this.yearsViewCount).keys()].map(value => value + parseInt(viewState.year / this.yearsViewCount) * this.yearsViewCount); /* * @description Generate years object based on list */ let yearsModel = [], yearStr = this.model.PersianDate.date(); for (let i of list) { yearStr.year([i]); yearsModel.push({ title: yearStr.format('YYYY'), enabled: this.checkYearAccess(i), dataYear: i, selected: this.model.state.selected.year == i }); } return { enabled: isEnabled, viewMode: this.model.state.viewMode == 'year', list: yearsModel }; } /** * @desc check month is accessible * @param {Number} month - month number * @return {Boolean} */ checkMonthAccess (month) { month = month + 1; let output = true, y = this.model.state.view.year; if (this.model.state.filetredDate) { let startMonth = this.model.state.filterDate.start.month, endMonth = this.model.state.filterDate.end.month, startYear = this.model.state.filterDate.start.year, endYear = this.model.state.filterDate.end.year; if (startMonth && endMonth && ((y == endYear && month > endMonth) || y > endYear) || ((y == startYear && month < startMonth) || y < startYear)) { return false; } else if (endMonth && ((y == endYear && month > endMonth) || y > endYear)) { return false; } else if (startMonth && ((y == startYear && month < startMonth) || y < startYear)) { return false; } } if (output) { return this.model.options.checkMonth(month, y); } } /** * @private * @return {{enabled: boolean, viewMode: boolean, list: Array}} */ _getMonthViewModel () { let isEnaled = this.model.options.monthPicker.enabled; // Make performance better if (!isEnaled) { return { enabled: false }; } let monthModel = [], that = this; for (let [index, month] of that.model.PersianDate.date().rangeName().months.entries()) { monthModel.push({ title: month, enabled: this.checkMonthAccess(index), year: this.model.state.view.year, dataMonth: index + 1, selected: (this.model.state.selected.year == this.model.state.view.year && this.model.state.selected.month == (index + 1)) }); } return { enabled: isEnaled, viewMode: this.model.state.viewMode == 'month', list: monthModel }; } /** * @desc check day is accessible * @param {Number} thisUnix - month number * @return {Boolean} */ checkDayAccess (unixtimespan) { let self = this, output = true; self.minDate = this.model.options.minDate; self.maxDate = this.model.options.maxDate; if (self.model.state.filetredDate) { if (self.minDate && self.maxDate) { self.minDate = self.model.PersianDate.date(self.minDate).startOf('day').valueOf(); self.maxDate = self.model.PersianDate.date(self.maxDate).endOf('day').valueOf(); if (!(unixtimespan >= self.minDate && unixtimespan <= self.maxDate)) { return false; } } else if (self.minDate) { self.minDate = self.model.PersianDate.date(self.minDate).startOf('day').valueOf(); if (unixtimespan <= self.minDate) { return false; } } else if (self.maxDate) { self.maxDate = self.model.PersianDate.date(self.maxDate).endOf('day').valueOf(); if (unixtimespan >= self.maxDate) { return false; } } } if (output) { return self.model.options.checkDate(unixtimespan); } } /** * @private * @return {object} */ _getDayViewModel () { if (this.model.state.viewMode != 'day') { return []; } let isEnabled = this.model.options.dayPicker.enabled; // Make performance better if (!isEnabled) { return { enabled: false }; } let viewMonth, viewYear; //log('if you see this many time your code has performance issue'); viewMonth = this.model.state.view.month; viewYear = this.model.state.view.year; let pdateInstance = this.model.PersianDate.date(), daysCount = pdateInstance.daysInMonth(viewYear, viewMonth), firstWeekDayOfMonth = pdateInstance.getFirstWeekDayOfMonth(viewYear, viewMonth) - 1, outputList = [], daysListindex = 0, nextMonthListIndex = 0, daysMatrix = [ ['null', 'null', 'null', 'null', 'null', 'null', 'null'], ['null', 'null', 'null', 'null', 'null', 'null', 'null'], ['null', 'null', 'null', 'null', 'null', 'null', 'null'], ['null', 'null', 'null', 'null', 'null', 'null', 'null'], ['null', 'null', 'null', 'null', 'null', 'null', 'null'], ['null', 'null', 'null', 'null', 'null', 'null', 'null'] ]; const anotherCalendar = this._getAnotherCalendar(); for (let [rowIndex, daysRow] of daysMatrix.entries()) { outputList[rowIndex] = []; for (let [dayIndex] of daysRow.entries()) { let calcedDate, otherMonth; // Set hour 12 prevent issues with DST times if (rowIndex === 0 && dayIndex < firstWeekDayOfMonth) { calcedDate = this.model.state.view.dateObject.startOf('month').hour(12).subtract('days', ((firstWeekDayOfMonth) - dayIndex )); otherMonth = true; } else if ((rowIndex === 0 && dayIndex >= firstWeekDayOfMonth) || (rowIndex <= 5 && daysListindex < daysCount)) { daysListindex += 1; calcedDate = new persianDate([this.model.state.view.year, this.model.state.view.month, daysListindex]); otherMonth = false; } else { nextMonthListIndex += 1; calcedDate = this.model.state.view.dateObject.endOf('month').hour(12).add('days', nextMonthListIndex); otherMonth = true; } outputList[rowIndex].push({ title: calcedDate.format('D'), alterCalTitle: new persianDate(calcedDate.valueOf()).toCalendar(anotherCalendar[0]).toLocale(anotherCalendar[1]).format('D'), dataDate: [calcedDate.year(), calcedDate.month(), calcedDate.date()].join(','), dataUnix: calcedDate.hour(12).minute(0).second(0).millisecond(0).valueOf(), otherMonth: otherMonth, selected: this.isInSelectedDays(calcedDate.hour(12).valueOf()), // TODO: make configurable enabled: this.checkDayAccess(calcedDate.valueOf()) }); } } return { enabled: isEnabled, viewMode: this.model.state.viewMode == 'day', list: outputList }; } markSelectedDay (unixDate = -1) { if (this.model.options.multiSelect) { if (unixDate !== -1){ let td = this.$container.find('td[data-unix^="'+unixDate+'"]'); if (this.isInSelectedDays(unixDate)) { $(td[0]).removeClass('selected'); } else { $(td[0]).addClass('selected'); } } } else { const selected = this.model.state.selected; this.$container.find('.table-days td').each(function () { if ($(this).data('date') == [selected.year, selected.month, selected.date].join(',')) { $(this).addClass('selected'); } else { $(this).removeClass('selected'); } }); } } isInSelectedDays (value) { const selected = this.model.state.selectedInMultiSelectMode; let found = false; selected.forEach(function (obj) { if (found) return true; if (obj.unixDate === value) { found = true; } }); return found; } isSelected (value) { const selected = this.model.state.selected; if (selected.unixDate === value) return true; return false; } markToday () { const today = new persianDate(); this.$container.find('.table-days td').each(function () { if ($(this).data('date') == [today.year(), today.month(), today.date()].join(',')) { $(this).addClass('today'); } else { $(this).removeClass('today'); } }); } /** * @private * @return {{enabled: boolean, hour: {title, enabled: boolean}, minute: {title, enabled: boolean}, second: {title, enabled: boolean}, meridian: {title: (meridian|{title, enabled}|ClassDatepicker.ClassConfig.timePicker.meridian|{enabled}|string|string), enabled: boolean}}} */ _getTimeViewModel () { let isEnabled = this.model.options.timePicker.enabled; // Make performance better if (!isEnabled) { return { enabled: false }; } let hourTitle; if (this.model.options.timePicker.meridian.enabled) { hourTitle = this.model.state.view.dateObject.format('hh'); } else { hourTitle = this.model.state.view.dateObject.format('HH'); } return { enabled: isEnabled, hour: { title: hourTitle, enabled: this.model.options.timePicker.hour.enabled }, minute: { title: this.model.state.view.dateObject.format('mm'), enabled: this.model.options.timePicker.minute.enabled }, second: { title: this.model.state.view.dateObject.format('ss'), enabled: this.model.options.timePicker.second.enabled }, meridian: { title: this.model.state.view.dateObject.format('a'), enabled: this.model.options.timePicker.meridian.enabled } }; } /** * * @return {{enabled: boolean, list: (*|Array)}} * @private */ _getWeekViewModel () { let weekdaysList = []; if (this.model.options !== null && this.model.options.customWeekdays !== undefined){ let weekdays = this.model.options.customWeekdays; switch (weekdays.mode) { case 'custom': weekdaysList = weekdays.list; break; case 'extended': weekdaysList = this.model.PersianDate.date().rangeName().weekdays; break; case 'min': default: weekdaysList = this.model.PersianDate.date().rangeName().weekdaysMin; } }else{ weekdaysList = this.model.PersianDate.date().rangeName().weekdaysMin; } return { enabled: true, list: weekdaysList }; } /** * * @return {string} */ getCssClass () { return [ this.model.state.ui.isInline ? 'datepicker-plot-area-inline-view' : '', !this.model.options.timePicker.meridian.enabled ? 'datepicker-state-no-meridian' : '', this.model.options.onlyTimePicker ? 'datepicker-state-only-time' : '', !this.model.options.timePicker.second.enabled ? 'datepicker-state-no-second' : '', (this.model.options.calendar_ == 'gregorian') ? 'datepicker-gregorian' : 'datepicker-persian' ].join(' '); } /** * @param data * @return {*} */ getViewModel (data) { const anotherCalendar = this._getAnotherCalendar(); return { plotId: 'plotId' + this.model.view.id, Id: this.model.view.id, navigator: { enabled: this.model.options.navigator.enabled, switch: { enabled: true, text: this._getNavSwitchText(data) }, text: this.model.options.navigator.text }, selected: this.model.state.selected, time: this._getTimeViewModel(data), days: this._getDayViewModel(data), weekdays: this._getWeekViewModel(data), month: this._getMonthViewModel(data), year: this._getYearViewModel(data), toolbox: this.model.options.toolbox, cssClass: this.getCssClass(), theme: { enabled: (this.model.options.theme !== undefined), values: this.model.options.theme }, multiSelect: ((this.model.options.multiSelect !== undefined) && (this.model.options.multiSelect === true)), onlyTimePicker: this.model.options.onlyTimePicker, altCalendarShowHint: this.model.options.calendar[anotherCalendar[0]].showHint, calendarSwitchText: this.model.state.view.dateObject.toCalendar(anotherCalendar[0]).toLocale(anotherCalendar[1]).format(this.model.options.toolbox.calendarSwitch.format), todayButtonText: this._getButtonText().todayButtontext, submitButtonText: this._getButtonText().submitButtonText }; } _getButtonText () { let output = {}; if (this.model.options.locale_ == 'fa') { output.todayButtontext = this.model.options.toolbox.todayButton.text.fa; output.submitButtonText = this.model.options.toolbox.submitButton.text.fa; } else if (this.model.options.locale_ == 'en') { output.todayButtontext = this.model.options.toolbox.todayButton.text.en; output.submitButtonText = this.model.options.toolbox.submitButton.text.en; } return output; } _getAnotherCalendar () { let that = this, cal, loc; if (that.model.options.calendar_ == 'persian') { cal = 'gregorian'; loc = that.model.options.calendar.gregorian.locale; } else { cal = 'persian'; loc = that.model.options.calendar.persian.locale; } return [cal, loc]; } /** * @desc render times area, prevent performance issue with scroll and time section */ renderTimePartial () { const timeViewModel = this._getTimeViewModel(this.model.state.view); this.$container.find('[data-time-key="hour"] input').val(timeViewModel.hour.title); this.$container.find('[data-time-key="minute"] input').val(timeViewModel.minute.title); this.$container.find('[data-time-key="second"] input').val(timeViewModel.second.title); this.$container.find('[data-time-key="meridian"] input').val(timeViewModel.meridian.title); } /** * @render datepicker view element * @param data */ render (data) { if (!data) { data = this.model.state.view; } Helper.debug(this, 'render'); Mustache.parse(Template); this.rendered = $(Mustache.render(this.model.options.template, this.getViewModel(data))); this.$container.empty().append(this.rendered); this.markSelectedDay(); this.markToday(); this.afterRender(); } reRender () { let data = this.model.state.view; this.render(data); } /** * @desc do after render work like attache events */ afterRender () { if (this.model.navigator) { this.model.navigator.liveAttach(); } if (this.model.options.theme && this.model.options.theme.dayCell && this.model.options.theme.dayCell.shape === 'circle') this.$container.find('.datepicker-plot-area .datepicker-day-view .table-days td span').css('border-radius', '50%'); } } module.exports = View;