/* * Tine 2.0 * * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3 * @author Cornelius Weiss * @copyright Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de) */ Ext.ns('Tine.Calendar'); Tine.Calendar.PagingToolbar = Ext.extend(Ext.Toolbar, { /** * @cfg {Date} dtstart */ dtStart: null, /** * @cfg {String} view */ view: 'day', /** * @private periodPicker */ periodPicker: null, /** * @cfg {Boolean} showReloadBtn */ showReloadBtn: true, /** * @cfg {Boolean} showTodayBtn */ showTodayBtn: true, /** * shows if the periodpicker is active * @type boolean */ periodPickerActive: null, /** * @private */ initComponent: function() { this.addEvents( /** * @event change * Fired whenever a viewstate changes * @param {Tine.Calendar.PagingToolbar} this * @param {String} activeView * @param {Array} period */ 'change', /** * @event refresh * Fired when user request view freshresh * @param {Tine.Calendar.PagingToolbar} this * @param {String} activeView * @param {Array} period */ 'refresh' ); if (! Ext.isDate(this.dtStart)) { this.dtStart = new Date().clearTime(); } this.periodPicker = new Tine.Calendar.PagingToolbar[Ext.util.Format.capitalize(this.view) + 'PeriodPicker']({ tb: this, listeners: { scope: this, change: function(picker, view, period) { this.dtStart = period.from.clearTime(true); this.fireEvent('change', this, view, period); }, menushow: function(){this.periodPickerActive = true; }, menuhide: function(){this.periodPickerActive = false;} } }); Tine.Calendar.PagingToolbar.superclass.initComponent.call(this); this.bind(this.store); }, /** * @private */ onRender: function(ct, position) { Tine.Calendar.PagingToolbar.superclass.onRender.call(this, ct, position); this.prevBtn = this.addButton({ tooltip: Ext.PagingToolbar.prototype.prevText, iconCls: "x-tbar-page-prev", handler: this.onClick.createDelegate(this, ["prev"]) }); this.addSeparator(); this.periodPicker.render(); this.addSeparator(); this.nextBtn = this.addButton({ tooltip: Ext.PagingToolbar.prototype.nextText, iconCls: "x-tbar-page-next", handler: this.onClick.createDelegate(this, ["next"]) }); if(this.showTodayBtn || this.showReloadBtn) this.addSeparator(); if(this.showTodayBtn) { this.todayBtn = this.addButton({ text: Ext.DatePicker.prototype.todayText, iconCls: 'cal-today-action', handler: this.onClick.createDelegate(this, ["today"]) }); } if(this.showReloadBtn) { this.loading = this.addButton({ tooltip: Ext.PagingToolbar.prototype.refreshText, iconCls: "x-tbar-loading", handler: this.onClick.createDelegate(this, ["refresh"]) }); } this.addFill(); if (this.additionalItems) { this.addButton(this.additionalItems); } if(this.isLoading){ this.loading.disable(); } }, /** * @private * @param {String} which */ onClick: function(which) { switch(which) { case 'today': case 'next': case 'prev': this.periodPicker[which](); this.fireEvent('change', this, this.activeView, this.periodPicker.getPeriod()); break; case 'refresh': this.fireEvent('refresh', this, this.activeView, this.periodPicker.getPeriod()); break; } }, /** * returns requested period * @return {Array} */ getPeriod: function() { return this.periodPicker.getPeriod(); }, // private beforeLoad : function(){ this.isLoading = true; if(this.rendered && this.loading) { this.loading.disable(); } }, // private onLoad : function(store, r, o){ this.isLoading = false; if(this.rendered && this.loading) { this.loading.enable(); } }, /** * Unbinds the paging toolbar from the specified {@link Ext.data.Store} * @param {Ext.data.Store} store The data store to unbind */ unbind : function(store){ store = Ext.StoreMgr.lookup(store); store.un("beforeload", this.beforeLoad, this); store.un("load", this.onLoad, this); //store.un("loadexception", this.onLoadError, this); this.store = undefined; }, /** * Binds the paging toolbar to the specified {@link Ext.data.Store} * @param {Ext.data.Store} store The data store to bind */ bind : function(store){ store = Ext.StoreMgr.lookup(store); store.on("beforeload", this.beforeLoad, this); store.on("load", this.onLoad, this); //store.on("loadexception", this.onLoadError, this); this.store = store; }, /** * just needed when inserted in an eventpickercombobox */ bindStore: function() {}, // private onDestroy : function(){ if(this.store){ this.unbind(this.store); } Tine.Calendar.PagingToolbar.superclass.onDestroy.call(this); } }); /** * @class Tine.Calendar.PagingToolbar.AbstractPeriodPicker * @extends Ext.util.Observable * @constructor * @param {Object} config */ Tine.Calendar.PagingToolbar.AbstractPeriodPicker = function(config) { Ext.apply(this, config); this.addEvents( /** * @event change * Fired whenever a period changes * @param {Tine.Calendar.PagingToolbar.AbstractPeriodPicker} this * @param {String} corresponding view * @param {Array} period */ 'change' ); Tine.Calendar.PagingToolbar.AbstractPeriodPicker.superclass.constructor.call(this); this.update(this.tb.dtStart); this.init(); }; Ext.extend(Tine.Calendar.PagingToolbar.AbstractPeriodPicker, Ext.util.Observable, { /** * period NOTE: might not fit to current range on view changes! */ period: null, init: function() {}, hide: function() {this.button.hide();}, show: function() {this.button.show();}, update: function(period) {}, render: function() {}, prev: function() {}, next: function() {}, today: function() {this.update(new Date().clearTime());}, getPeriod: function() {} }); /** * @class Tine.Calendar.PagingToolbar.DayPeriodPicker * @extends Tine.Calendar.PagingToolbar.AbstractPeriodPicker * @constructor */ Tine.Calendar.PagingToolbar.DayPeriodPicker = Ext.extend(Tine.Calendar.PagingToolbar.AbstractPeriodPicker, { init: function() { this.button = new Ext.Button({ text: this.tb.dtStart.format(Ext.DatePicker.prototype.format), //hidden: this.tb.activeView != 'day', menu: new Ext.menu.DateMenu({ listeners: { scope: this, select: function(field) { if (typeof(field.getValue) == 'function') { this.update(field.getValue()); this.fireEvent('change', this, 'day', this.getPeriod()); } } } }) }); }, update: function(period) { this.dtStart = _.get(period, 'from', period).clearTime(true); if (this.button && this.button.rendered) { this.button.setText(this.dtStart.format(Ext.DatePicker.prototype.format)); } }, render: function() { this.button = this.tb.addButton(this.button); }, next: function() { this.dtStart = this.dtStart.add(Date.DAY, 1); this.update(this.dtStart); }, prev: function() { this.dtStart = this.dtStart.add(Date.DAY, -1); this.update(this.dtStart); }, getPeriod: function() { var from = Date.parseDate(this.dtStart.format('Y-m-d') + ' 00:00:00', Date.patterns.ISO8601Long); return { from: from, until: from.add(Date.DAY, 1)/*.add(Date.SECOND, -1)*/ }; } }); /** * @class Tine.Calendar.PagingToolbar.WeekPeriodPicker * @extends Tine.Calendar.PagingToolbar.AbstractPeriodPicker * @constructor */ Tine.Calendar.PagingToolbar.WeekPeriodPicker = Ext.extend(Tine.Calendar.PagingToolbar.AbstractPeriodPicker, { datepickerMenu: null, datepickerButton: null, wkField: null, init: function() { this.label = new Ext.form.Label({ text: Tine.Tinebase.appMgr.get('Calendar').i18n._('Week'), style: 'padding-right: 3px' }); this.wkField = new Ext.form.TextField({ value: this.tb.dtStart.getWeekOfYear(), cls: "x-tbar-page-number", listeners: { scope: this, specialkey: this.onSelect, blur: this.onSelect } }); this.yearField = new Ext.form.Label({ text: this.tb.dtStart.format('o'), style: 'padding-left: 3px' }); this.datepickerMenu = new Ext.menu.DateMenu({ value: this.tb.dtStart, hideOnClick: true, focusOnSelect: true, plugins: [new Ext.ux.DatePickerWeekPlugin({ weekHeaderString: Tine.Tinebase.appMgr.get('Calendar').i18n._('WK') })], listeners: { scope: this, 'select': function (picker, date) { var oldPeriod = this.getPeriod(); this.update(date); if (this.getPeriod().from.getElapsed(oldPeriod.from)) { this.fireEvent('change', this, 'week', this.getPeriod()); } } } }); this.datepickerButton = new Ext.Button({ iconCls: 'cal-sheet-view-type' }); this.datepickerButton.on('click', function () { this.datepickerMenu.show(this.datepickerButton.el); }.createDelegate(this)); }, onSelect: function(field, e) { if (e && e.getKey() == e.ENTER) { return field.blur(); } var diff = field.getValue() - this.dtStart.getWeekOfYear() - parseInt(this.dtStart.getDay() < 1 ? 1 : 0, 10); if (diff !== 0) { this.update(this.dtStart.add(Date.DAY, diff * 7)); this.fireEvent('change', this, 'week', this.getPeriod()); } }, update: function(period) { let dtStart = _.get(period, 'from', period).clearTime(true); // const state = Ext.state.Manager.get('cal-pgtb-pp-wkbtn'); let wkStartDiff = 0; if (this.tb.periodBtn) { const state = this.tb.periodBtn.getState(); wkStartDiff = state.startIdx - Tine.Calendar.PagingToolbar.WeekPeriodPicker.Button.prototype.beforeDays; } const startDay = Ext.DatePicker.prototype.startDay; // recalculate dtstart according to WeekPeriodPicker.Button this.dtStart = dtStart.add(Date.DAY, -1 * (7+dtStart.getDay() - startDay - wkStartDiff)%7); if (this.getPeriod().until < dtStart) { this.dtStart = this.dtStart.add(Date.DAY, 7); } if (this.wkField && this.wkField.rendered) { // NOTE: '+1' is to ensure we display the ISO8601 based week where weeks always start on monday! var wkStart = this.dtStart.add(Date.DAY, dtStart.getDay() < 1 ? 1 : 0) .add(Date.DAY, -1*wkStartDiff); this.wkField.setValue(parseInt(wkStart.getWeekOfYear(), 10)); this.yearField.setText(this.dtStart.format('o')); } }, render: function() { this.tb.addField(this.label); this.tb.addField(this.wkField); this.tb.addField(this.yearField); this.tb.addField(this.datepickerButton); }, hide: function() { this.label.hide(); this.wkField.hide(); this.yearField.hide(); this.datepickerButton.hide(); }, show: function() { this.label.show(); this.wkField.show(); this.yearField.show(); this.datepickerButton.show(); }, next: function() { this.dtStart = this.dtStart.add(Date.DAY, 7); this.update(this.dtStart); }, prev: function() { this.dtStart = this.dtStart.add(Date.DAY, -7); this.update(this.dtStart); }, getPeriod: function() { const state = this.tb.periodBtn ? this.tb.periodBtn.getState() : { endIdx: 7, startIdx: 1, }; // const state = Ext.state.Manager.get('cal-pgtb-pp-wkbtn'); return { from: this.dtStart.clone(), until: this.dtStart.add(Date.DAY, state.endIdx - state.startIdx+1) }; } }); Tine.Calendar.PagingToolbar.WeekPeriodPicker.Button = Ext.extend(Ext.SplitButton, { // override via prototype or config beforeDays: 1, numDays: 10, stateful: true, stateId: 'cal-pgtb-pp-wkbtn', initComponent: function() { this.app = Tine.Tinebase.appMgr.get('Calendar'); const state = Ext.state.Manager.get(this.stateId); const wkStartDay = _.get(state, 'wkStart', Ext.DatePicker.prototype.startDay); const selectionStartIdx = _.get(state, 'startIdx', this.beforeDays+wkStartDay-1); const selectionEndIdx = _.get(state, 'endIdx',selectionStartIdx + 6); this.startDay = (wkStartDay+7-this.beforeDays)%7; this.dayBtns = []; // @TODO: if week is not standard, have a marker in btn? for (let i=0,dayNum=this.startDay; i= selectionStartIdx && i <= selectionEndIdx, dayIdx: i, dayNum: dayNum, text: Date.dayNames[dayNum%7].substr(0,2), enableToggle: true, scope: this, handler: this.onDayBtnPress })); } this.menu = new Ext.menu.Menu({ items: [new Ext.Toolbar({ cls: 'cal-wkperiod-config-menu', items: [].concat(this.dayBtns, {xtype: 'tbtext', width: 40}, {text: i18n._hidden('OK'), variant: 'primary', handler: () => {this.menu.hide()}}) })]}); this.menu.on('hide', this.onMenuHide, this); this.supr().initComponent.call(this); }, onMenuHide: function() { const state = Ext.state.Manager.get(this.stateId); if (JSON.stringify(state) !== JSON.stringify(this.getState())) { this.saveState(); this.fireEvent('change', this, state); } }, onDayBtnPress: function(btn) { const selected = _.filter(this.dayBtns, {pressed: true}); if (! btn.pressed) { // deselect/reduce const bellow = _.filter(selected, (cmp) => {return cmp.dayIdx < btn.dayIdx}); const above = _.filter(selected, (cmp) => { return cmp.dayIdx > btn.dayIdx }); const toReduce = bellow.length < above.length ? bellow : above; _.each(toReduce, (btn) => { btn.toggle(false, false) }); if (bellow.length + above.length < 3) { btn.toggle(true, false); } } else { // select/expand let lastIdx = selected[0].dayIdx; _.each(selected, (btn) => { for(let idx=lastIdx+1; idx= 0) { state.startIdx = state.startIdx-7; state.endIdx = state.endIdx-7; } return state; }, onRender : function(){ Tine.Calendar.PagingToolbar.WeekPeriodPicker.Button.superclass.onRender.apply(this, arguments); // have less optical focus this.btnEl.setStyle({border: "none"}); }, }); /** * @class Tine.Calendar.PagingToolbar.MonthPeriodPicker * @extends Tine.Calendar.PagingToolbar.AbstractPeriodPicker * @constructor */ Tine.Calendar.PagingToolbar.MonthPeriodPicker = Ext.extend(Tine.Calendar.PagingToolbar.AbstractPeriodPicker, { init: function() { this.dateMenu = new Ext.menu.DateMenu({ hideMonthPicker: Ext.DatePicker.prototype.hideMonthPicker.createSequence(function() { if (this.monthPickerActive) { this.monthPickerActive = false; this.value = this.activeDate; this.fireEvent('select', this, this.value); } }), listeners: { scope: this, select: function(field) { if (typeof(field.getValue) == 'function') { this.update(field.getValue()); this.fireEvent('change', this, 'month', this.getPeriod()); } } } }); this.button = new Ext.Button({ minWidth: 130, text: Ext.DatePicker.prototype.monthNames[this.tb.dtStart.getMonth()] + this.tb.dtStart.format(' Y'), //hidden: this.tb.activeView != 'month', menu: this.dateMenu, listeners: { scope: this, menushow: function(btn, menu) { menu.picker.showMonthPicker(); menu.picker.monthPickerActive = true; this.fireEvent('menushow'); }, menuhide: function(btn, menu) { menu.picker.monthPickerActive = false; this.fireEvent('menuhide'); } } }); }, update: function(period) { this.dtStart = _.get(period, 'from', period).clearTime(true); if (this.button && this.button.rendered) { var monthName = Ext.DatePicker.prototype.monthNames[this.dtStart.getMonth()]; this.button.setText(monthName + this.dtStart.format(' Y')); this.dateMenu.picker.setValue(this.dtStart.clone()); } }, render: function() { this.button = this.tb.addButton(this.button); }, next: function() { this.dtStart = this.dtStart.add(Date.MONTH, 1); this.update(this.dtStart); }, prev: function() { this.dtStart = this.dtStart.add(Date.MONTH, -1); this.update(this.dtStart); }, getPeriod: function() { var from = Date.parseDate(this.dtStart.format('Y-m') + '-01 00:00:00', Date.patterns.ISO8601Long); return { from: from, until: from.add(Date.MONTH, 1)/*.add(Date.SECOND, -1)*/ }; } }); /** * @class Tine.Calendar.PagingToolbar.YearPeriodPicker * @extends Tine.Calendar.PagingToolbar.AbstractPeriodPicker * @constructor */ Tine.Calendar.PagingToolbar.YearPeriodPicker = Ext.extend(Tine.Calendar.PagingToolbar.AbstractPeriodPicker, { init: function() { this.label = new Ext.form.Label({ text: Tine.Tinebase.appMgr.get('Calendar').i18n._('Year'), style: 'padding-right: 3px' }); this.field = new Ext.form.TextField({ value: this.tb.dtStart.format('Y'), width: 40, cls: "x-tbar-page-number", listeners: { scope: this, specialkey: this.onSelect, blur: this.onSelect } }); }, onSelect: function(field, e) { if (e && e.getKey() == e.ENTER) { return field.blur(); } var diff = field.getValue() - this.dtStart.format('Y'); if (diff !== 0) { this.update(this.dtStart.add(Date.YEAR, diff )) this.fireEvent('change', this, 'year', this.getPeriod()); } }, update: function(period) { this.dtStart = _.get(period, 'from', period).clearTime(true); if (this.field && this.field.rendered) { this.field.setValue(this.dtStart.format('Y')); } }, render: function() { this.tb.addField(this.label); this.tb.addField(this.field); }, hide: function() { this.label.hide(); this.field.hide(); }, show: function() { this.label.show(); this.field.show(); }, next: function() { this.dtStart = this.dtStart.add(Date.YEAR, 1); this.update(this.dtStart); }, prev: function() { this.dtStart = this.dtStart.add(Date.YEAR, -1); this.update(this.dtStart); }, getPeriod: function() { var from = Date.parseDate(this.dtStart.format('Y') + '-01-01 00:00:00', Date.patterns.ISO8601Long); return { from: from, until: from.add(Date.YEAR, 1) }; } }); /** * @class Tine.Calendar.PagingToolbar.CustomPeriodPicker * @extends Tine.Calendar.PagingToolbar.AbstractPeriodPicker * @constructor */ Tine.Calendar.PagingToolbar.CustomPeriodPicker = Ext.extend(Tine.Calendar.PagingToolbar.AbstractPeriodPicker, { init: function() { this.tb.showTodayBtn = false; this.fromLabel = new Ext.form.Label({ text: Tine.Tinebase.appMgr.get('Calendar').i18n._('From'), style: 'padding-right: 3px' }); this.fromPicker = new Ext.ux.form.DateTimeField ({ style: 'margin-top: 2px', value: _.get(this, 'tb.period.from', this.tb.startDate) || new Date().clearTime(), listeners: { scope: this, specialkey: this.onSelect, blur: this.onSelect } }); this.untilLabel = new Ext.form.Label({ text: Tine.Tinebase.appMgr.get('Calendar').i18n._('until'), style: 'padding-left: 10px; padding-right: 3px' }); this.untilPicker = new Ext.ux.form.DateTimeField ({ style: 'margin-top: 2px', value: _.get(this, 'tb.period.until', this.tb.startDate) || new Date().clearTime().add(Date.DAY, 5), listeners: { scope: this, specialkey: this.onSelect, blur: this.onSelect } }); this.period = this.getPeriod(); }, onSelect: function(field, e) { if (e && e.getKey() === e.ENTER) { return field.blur(); } let currentPeriod = this.period; Tine.Tinebase.common.assertComparable(currentPeriod); let period = this.getPeriod(); Tine.Tinebase.common.assertComparable(period); if (String(currentPeriod) !== String(period)) { this.fireEvent('change', this, 'custom', period); } }, update: function(periodStart, period) { let boundaries = ['from', 'until']; _.each(boundaries, (boundary) => { if (_.get(this, `${boundary}Picker`) && _.isDate(_.get(period, boundary))) { let date = period[boundary].clone(); // constrain to other boundary let boundaryIdx = boundaries.indexOf(boundary); let otherBoundary = boundaries[(boundaryIdx+1)%2]; let otherDate = _.isDate(_.get(period, otherBoundary)) ? _.get(period, otherBoundary) : new Date().clearTime(); this[`${boundary}Picker`].setValue( new Date(Math[boundaryIdx ? 'max' : 'min'](date, otherDate)) ); // save state _.set(this, `period.${boundary}`, _.get(period, boundary).clone()); } }); }, render: function() { this.tb.addField(this.fromLabel); this.tb.addField(this.fromPicker); this.tb.addField(this.untilLabel); this.tb.addField(this.untilPicker); }, hide: function() { this.fromLabel.hide(); this.fromPicker.hide(); this.untilLabel.hide(); this.untilPicker.hide(); }, show: function() { this.fromLabel.show(); this.fromPicker.show(); this.untilLabel.show(); this.untilPicker.show(); }, next: function() { let period = this.getPeriod(); this.update({ from: period.until, until: period.until.add(Date.MILLI, period.until.getTime() - period.from.getTime()) .add(Date.SECOND, period.until.format('Z') - period.from.format('Z')) }); }, prev: function() { let period = this.getPeriod(); this.update({ from: period.until.add(Date.MILLI, period.from.getTime() - period.until.getTime()) .add(Date.SECOND, period.from.format('Z') - period.until.format('Z')), until: period.from }); }, getPeriod: function() { // let from = this.fromPicker.getValue() || new Date return { from: this.fromPicker.getValue().clone(), until: this.untilPicker.getValue().clone() }; } });