new periodPicker form widget
[tine20] / tine20 / Tinebase / js / ux / form / PeriodPicker.js
1 /*
2  * Tine 2.0
3  *
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Cornelius Weiss <c.weiss@metaways.de>
6  * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
7  *
8  */
9
10 /*global Ext*/
11
12 Ext.ns('Ext.ux', 'Ext.ux.form');
13
14 /**
15  * A combination range and paging control
16  *
17  * @namespace   Ext.ux.form
18  * @class       Ext.ux.form.PeriodPicker
19  * @extends     Ext.form.Field
20  */
21 Ext.ux.form.PeriodPicker = Ext.extend(Ext.form.Field, {
22
23     /**
24      * @cfg {String} availableRanges
25      * ranges available in range picker
26      * _('Day'), _('Week'), _('Month'), _('Year')
27      */
28     availableRanges: 'day,week,month',
29
30     /**
31      * @cfg {String} range
32      * initial range
33      */
34     range: 'month',
35
36     /**
37      * @cfg {Date} startDate
38      * defaults to toDay, will be constraint to period, gets overwritten by value
39      */
40     startDate: null,
41
42     defaultAutoCreate: {
43         tag: 'table',
44         cls: 'ux-pp-field',
45         cn: [{
46             tag: 'tr',
47             cn: [{
48                 tag: 'td',
49                 cls: 'ux-pp-range'
50             }, {
51                 tag: 'td',
52                 cls: 'ux-pp-prev'
53             }, {
54                 tag: 'td',
55                 cls: 'ux-pp-period'
56             }, {
57                 tag: 'td',
58                 cls: 'ux-pp-next'
59             }]
60         }]
61     },
62
63     initComponent: function() {
64         Ext.ux.form.PeriodPicker.superclass.initComponent.call(this);
65
66         this.value = Ext.ux.form.PeriodPicker.getPeriod(this.value ? this.value.from : this.startDate || new Date(), this.range);
67         this.startDate = this.value.from;
68     },
69
70     /**
71      * @return {Object} {from: Date, until: Date}
72      */
73     getValue: function() {
74         return this.value;
75     },
76
77     /**
78      * @param {Object} value {from: Date, until: Date}
79      */
80     setValue: function(value) {
81         this.range = Ext.ux.form.PeriodPicker.getRange(value);
82         this.value = Ext.ux.form.PeriodPicker.getPeriod(value.from, this.range);
83         this.startDate = this.value.from;
84
85         this.getRangeCombo().setValue(this.range);
86         var dateString;
87
88         switch(this.range) {
89             case 'day':
90                 dateString = Tine.Tinebase.common.dateRenderer(this.startDate);
91                 break;
92             case 'week':
93                 // NOTE: '+1' is to ensure we display the ISO8601 based week where weeks always start on monday!
94                 var wkStart = this.startDate.add(Date.DAY, this.startDate.getDay() < 1 ? 1 : 0);
95
96                 dateString = wkStart.getWeekOfYear() + ' ' + this.startDate.format('Y');
97                 break;
98             case 'month':
99                 dateString = Ext.DatePicker.prototype.monthNames[this.startDate.getMonth()] + ' ' + this.startDate.format('Y');
100                 break;
101             case 'year':
102                 dateString = this.startDate.format('Y');
103                 break;
104         }
105         this.setPeriodText(dateString);
106
107     },
108
109     setStartDate: function(startDate) {
110         var value = Ext.ux.form.PeriodPicker.getPeriod(startDate, this.range);
111         this.setValue(value);
112     },
113
114     setPeriodText: function(text) {
115         this.el.child('.ux-pp-period').update(Ext.util.Format.htmlEncode(text));
116     },
117
118     // private
119     onRangeComboChange: function() {
120         this.setValue(Ext.ux.form.PeriodPicker.getPeriod(this.startDate, this.getRangeCombo().getValue()));
121         this.fireEvent('change', this, this.value, this.startValue);
122     },
123
124     // private
125     onClick: function(e) {
126         var prev = e.getTarget('.ux-pp-prev'),
127             next = e.getTarget('.ux-pp-next'),
128             period = e.getTarget('.ux-pp-period');
129
130         if (next) {
131             this.setStartDate(this.value.until.add(Date.DAY, 1));
132             this.fireEvent('change', this, this.value, this.startValue);
133         } else if (prev) {
134             this.setStartDate(this.value.from.add(Date.DAY, -1));
135             this.fireEvent('change', this, this.value, this.startValue);
136         } else if (period) {
137             this.getDatePickerMenu().show(period);
138         }
139     },
140
141     // private
142     onRender : function(ct, position){
143         this.doc = Ext.isIE ? Ext.getBody() : Ext.getDoc();
144         Ext.ux.form.PeriodPicker.superclass.onRender.call(this, ct, position);
145
146         var rangeCombo = this.getRangeCombo();
147         rangeCombo.render(this.el.child('.ux-pp-range'));
148         rangeCombo.setWidth(this.getEl().getWidth() * 0.4);
149
150         this.setValue(this.value);
151         this.mon(this.getEl(), 'click', this.onClick, this);
152     },
153
154     // private
155     onResize: function (w, h) {
156         Ext.ux.form.PeriodPicker.superclass.onResize.apply(this, arguments);
157
158         this.getRangeCombo().setWidth(this.getEl().getWidth() * 0.4);
159     },
160
161     getRangeCombo: function() {
162         if (! this.rangeCombo) {
163             var fieldDef = [];
164             Ext.each(this.availableRanges.split(','), function(range) {
165                 fieldDef.push([range, i18n._hidden(Ext.util.Format.capitalize(range))]);
166             });
167
168             this.rangeCombo = new Ext.form.ComboBox({
169                 typeAhead: true,
170                 triggerAction: 'all',
171                 mode: 'local',
172                 forceSelection: true,
173                 allowEmpty: false,
174                 editable: false,
175                 store: fieldDef,
176                 // value: this.range,
177                 listeners: {
178                     scope: this,
179                     change: this.onRangeComboChange,
180                     select: this.onRangeComboChange
181                 }
182             });
183         }
184
185         return this.rangeCombo;
186     },
187
188     /**
189      * returns a new datepickerMenu
190      *
191      * @returns {Ext.menu.DateMenu}
192      */
193     getDatePickerMenu: function() {
194             var me = this;
195
196             return new Ext.menu.DateMenu({
197                 value: this.startDate,
198                 hideOnClick: true,
199                 focusOnSelect: true,
200                 plugins: [new Ext.ux.DatePickerWeekPlugin({
201                     weekHeaderString: Tine.Tinebase.appMgr.get('Calendar').i18n._hidden('WK'),
202                     inspectMonthPickerClick: function(btn, e) {
203                         if (e.getTarget('button')) {
204                             me.getRangeCombo().setValue('month');
205                             me.range = 'month';
206                             me.setStartDate(this.activeDate);
207                             this.destroy();
208                             me.fireEvent('change', me, me.value, me.startValue);
209                             return false;
210                         }
211                     }
212                 })],
213                 listeners: {
214                     scope: this,
215                     select: function(picker, value, weekNumber) {
216                         this.getRangeCombo().setValue(weekNumber ? 'week' : 'day');
217                         this.range = weekNumber ? 'week' : 'day';
218                         this.setStartDate(value);
219                         this.fireEvent('change', this, this.value, this.startValue);
220                     }
221                 }
222             });
223     }
224 });
225
226 /**
227  * gets period
228  *
229  * @static
230  * @param {Date} startDate date within period
231  * @param {String} range day|week|month|year
232  * @return {Object} {from: Date, until: Date}
233  */
234 Ext.ux.form.PeriodPicker.getPeriod = function(startDate, range) {
235     var from, until;
236     switch(range) {
237         case 'day':
238             from = startDate.clearTime(true);
239             until = from.add(Date.DAY, 1);
240             break;
241         case 'week':
242             from = startDate.clearTime(true).add(Date.DAY, -1 * startDate.getDay())
243                 .add(Date.DAY, Ext.DatePicker.prototype.startDay - (startDate.getDay() == 0 ? 7 : 0));
244             until = from.add(Date.DAY, 7);
245             break;
246         case 'month':
247             from = startDate.clearTime(true).getFirstDateOfMonth();
248             until = from.getLastDateOfMonth().add(Date.DAY, 1);
249             break;
250         case 'year':
251             var year = this.startDate.format('y');
252             from = Date.parse(year + '-0-0 00:00:00', 'Y-m-d H:i:s');
253             until = Date.parse(++year + '-0-0 00:00:00', 'Y-m-d H:i:s');
254             break;
255     }
256
257     return {from: from, until: until};
258 };
259
260 /**
261  * gets period
262  *
263  * @static
264  * @param {Object} {from: Date, until: Date}
265  * @return {String} range day|week|month|year
266  */
267 Ext.ux.form.PeriodPicker.getRange = function(period) {
268     var ms = period.from.getElapsed(period.until),
269         msDay = 86400000;
270
271     if (ms > msDay * 300) {
272         return 'year';
273     } else if (ms > msDay * 20) {
274         return 'month';
275     } else if (ms > msDay * 5) {
276         return 'week';
277     } else {
278         return 'day'
279     }
280
281 }
282 Ext.reg('ux-period-picker', Ext.ux.form.PeriodPicker)