887bdc4778090e0a8ab3680abde704ea306ab87a
[odoo/odoo.git] / addons / web_calendar / static / src / js / calendar.js
1 /*---------------------------------------------------------
2  * OpenERP web_calendar
3  *---------------------------------------------------------*/
4
5 openerp.web_calendar = function(openerp) {
6 var _t = openerp.web._t;
7 var QWeb = openerp.web.qweb;
8 openerp.web.views.add('calendar', 'openerp.web_calendar.CalendarView');
9 openerp.web_calendar.CalendarView = openerp.web.View.extend({
10 // Dhtmlx scheduler ?
11     init: function(parent, dataset, view_id, options) {
12         this._super(parent);
13         this.set_default_options(options);
14         this.dataset = dataset;
15         this.model = dataset.model;
16         this.fields_view = {};
17         this.view_id = view_id;
18         this.has_been_loaded = $.Deferred();
19         this.creating_event_id = null;
20         this.dataset_events = [];
21         this.form_dialog = new openerp.web_calendar.CalendarFormDialog(this, {}, this.options.action_views_ids.form, dataset);
22         this.form_dialog.start();
23         this.COLOR_PALETTE = ['#f57900', '#cc0000', '#d400a8', '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400',
24              '#fcaf3e', '#ef2929', '#ff00c9', '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f',
25              '#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
26              '#905000', '#9b0000', '#840067', '#510090', '#0000c9', '#009b00', '#9abe00', '#ffc900' ];
27         this.color_map = {};
28         this.last_search = [];
29     },
30     start: function() {
31         this._super();
32         return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type":"calendar", 'toolbar': true}, this.on_loaded);
33     },
34     stop: function() {
35         scheduler.clearAll();
36         this._super();
37     },
38     on_loaded: function(data) {
39         this.fields_view = data;
40         this.calendar_fields = {};
41         this.ids = this.dataset.ids;
42         this.color_values = [];
43         this.info_fields = [];
44
45         this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
46         this.view_id = this.fields_view.view_id;
47
48         // mode, one of month, week or day
49         this.mode = this.fields_view.arch.attrs.mode;
50
51         // date_start is mandatory, date_delay and date_stop are optional
52         this.date_start = this.fields_view.arch.attrs.date_start;
53         this.date_delay = this.fields_view.arch.attrs.date_delay;
54         this.date_stop = this.fields_view.arch.attrs.date_stop;
55
56         this.colors = this.fields_view.arch.attrs.colors;
57         this.day_length = this.fields_view.arch.attrs.day_length || 8;
58         this.color_field = this.fields_view.arch.attrs.color;
59         this.fields =  this.fields_view.fields;
60         
61         if (!this.date_start) {
62             throw new Error("Calendar view has not defined 'date_start' attribute.");
63         }
64
65         //* Calendar Fields *
66         this.calendar_fields.date_start = {'name': this.date_start, 'kind': this.fields[this.date_start].type};
67
68         if (this.date_delay) {
69             if (this.fields[this.date_delay].type != 'float') {
70                 throw new Error("Calendar view has a 'date_delay' type != float");
71             }
72             this.calendar_fields.date_delay = {'name': this.date_delay, 'kind': this.fields[this.date_delay].type};
73         }
74         if (this.date_stop) {
75             this.calendar_fields.date_stop = {'name': this.date_stop, 'kind': this.fields[this.date_stop].type};
76         }
77
78         for (var fld = 0; fld < this.fields_view.arch.children.length; fld++) {
79             this.info_fields.push(this.fields_view.arch.children[fld].attrs.name);
80         }
81         this.$element.html(QWeb.render("CalendarView", {"fields_view": this.fields_view}));
82
83         if (this.options.sidebar && this.options.sidebar_id) {
84             this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
85             this.sidebar.start();
86             this.sidebar.navigator = new openerp.web_calendar.SidebarNavigator(this.sidebar, this);
87             this.sidebar.responsible = new openerp.web_calendar.SidebarResponsible(this.sidebar, this);
88             this.sidebar.add_toolbar(this.fields_view.toolbar);
89             this.set_common_sidebar_sections(this.sidebar);
90             this.sidebar.do_unfold();
91             this.sidebar.do_fold.add_last(this.refresh_scheduler);
92             this.sidebar.do_unfold.add_last(this.refresh_scheduler);
93             this.sidebar.do_toggle.add_last(this.refresh_scheduler);
94         }
95
96         this.init_scheduler();
97         this.has_been_loaded.resolve();
98     },
99     init_scheduler: function() {
100         var self = this;
101         scheduler.clearAll();
102         if (this.fields[this.date_start]['type'] == 'time') {
103             scheduler.config.xml_date = "%H:%M:%S";
104         } else {
105             scheduler.config.xml_date = "%Y-%m-%d %H:%i";
106         }
107         scheduler.config.api_date = "%Y-%m-%d %H:%i";
108         scheduler.config.multi_day = true; //Multi day events are not rendered in daily and weekly views
109         scheduler.config.start_on_monday = true;
110         scheduler.config.time_step = 30;
111         scheduler.config.scroll_hour = 8;
112         scheduler.config.drag_resize = true;
113         scheduler.config.drag_create = true;
114         scheduler.config.mark_now = true;
115         scheduler.config.day_date = '%l %j';
116
117         scheduler.init('openerp_scheduler', null, this.mode || 'month');
118
119         // Remove hard coded style attributes from dhtmlx scheduler
120         this.$element.find(".dhx_cal_navline div").removeAttr('style');
121
122         scheduler.detachAllEvents();
123         scheduler.attachEvent('onEventAdded', this.do_create_event);
124         scheduler.attachEvent('onEventDeleted', this.do_delete_event);
125         scheduler.attachEvent('onEventChanged', this.do_save_event);
126         scheduler.attachEvent('onClick', this.do_edit_event);
127         scheduler.attachEvent('onLightbox', this.do_edit_event);
128
129         scheduler.attachEvent('onViewChange', function(mode, date) {
130             self.$element.removeClass('oe_cal_day oe_cal_week oe_cal_month').addClass('oe_cal_' + mode);
131         });
132
133         if (this.options.sidebar) {
134             this.mini_calendar = scheduler.renderCalendar({
135                 container: this.sidebar.navigator.element_id,
136                 navigation: true,
137                 date: scheduler._date,
138                 handler: function(date, calendar) {
139                     scheduler.setCurrentView(date, 'day');
140                 }
141             });
142         }
143     },
144     refresh_scheduler: function() {
145         scheduler.setCurrentView(scheduler._date);
146     },
147     refresh_minical: function() {
148         if (this.options.sidebar) {
149             scheduler.updateCalendar(this.mini_calendar);
150         }
151     },
152     reload_event: function(id) {
153         this.dataset.read_ids([id], _.keys(this.fields), this.on_events_loaded);
154     },
155     get_color: function(key) {
156         if (this.color_map[key]) {
157             return this.color_map[key];
158         }
159         var index = _.keys(this.color_map).length % this.COLOR_PALETTE.length;
160         var color = this.COLOR_PALETTE[index];
161         this.color_map[key] = color;
162         return color;
163     },
164     on_events_loaded: function(events, fn_filter, no_filter_reload) {
165         var self = this;
166
167         //To parse Events we have to convert date Format
168         var res_events = [],
169             sidebar_items = {};
170         for (var e = 0; e < events.length; e++) {
171             var evt = events[e];
172             if (!evt[this.date_start]) {
173                 break;
174             }
175
176             if (this.color_field) {
177                 var filter = evt[this.color_field];
178                 if (filter) {
179                     var filter_value = (typeof filter === 'object') ? filter[0] : filter;
180                     if (typeof(fn_filter) === 'function' && !fn_filter(filter_value)) {
181                         continue;
182                     }
183                     var filter_item = {
184                         value: filter_value,
185                         label: (typeof filter === 'object') ? filter[1] : filter,
186                         color: this.get_color(filter_value)
187                     };
188                     if (!sidebar_items[filter_value]) {
189                         sidebar_items[filter_value] = filter_item;
190                     }
191                     evt.color = filter_item.color;
192                     evt.textColor = '#ffffff';
193                 }
194             }
195
196             if (this.fields[this.date_start]['type'] == 'date') {
197                 evt[this.date_start] = openerp.web.auto_str_to_date(evt[this.date_start]).set({hour: 9}).toString('yyyy-MM-dd HH:mm:ss');
198             }
199             if (this.date_stop && evt[this.date_stop] && this.fields[this.date_stop]['type'] == 'date') {
200                 evt[this.date_stop] = openerp.web.auto_str_to_date(evt[this.date_stop]).set({hour: 17}).toString('yyyy-MM-dd HH:mm:ss');
201             }
202             res_events.push(this.convert_event(evt));
203         }
204         scheduler.parse(res_events, 'json');
205         this.refresh_scheduler();
206         this.refresh_minical();
207         if (!no_filter_reload && this.options.sidebar) {
208             this.sidebar.responsible.on_events_loaded(sidebar_items);
209         }
210     },
211     convert_event: function(evt) {
212         var date_start = openerp.web.str_to_datetime(evt[this.date_start]),
213             date_stop = this.date_stop ? openerp.web.str_to_datetime(evt[this.date_stop]) : null,
214             date_delay = evt[this.date_delay] || 1.0,
215             res_text = '',
216             res_description = [];
217
218         if (this.info_fields) {
219             var fld = evt[this.info_fields[0]];
220             res_text = (typeof fld == 'object') ? fld[fld.length -1] : res_text = fld;
221
222             var sliced_info_fields = this.info_fields.slice(1);
223             for (var sl_fld in sliced_info_fields) {
224                 var slc_fld = evt[sliced_info_fields[sl_fld]];
225                 if (typeof slc_fld == 'object') {
226                     res_description.push(slc_fld[slc_fld.length - 1]);
227                 } else if (slc_fld) {
228                     res_description.push(slc_fld);
229                 }
230             }
231         }
232         if (!date_stop && date_delay) {
233             date_stop = date_start.clone().addHours(date_delay);
234         }
235         var r = {
236             'start_date': date_start.toString('yyyy-MM-dd HH:mm:ss'),
237             'end_date': date_stop.toString('yyyy-MM-dd HH:mm:ss'),
238             'text': res_text,
239             'id': evt.id,
240             'title': res_description.join()
241         };
242         if (evt.color) {
243             r.color = evt.color;
244         }
245         if (evt.textColor) {
246             r.textColor = evt.textColor;
247         }
248         return r;
249     },
250     do_create_event: function(event_id, event_obj) {
251         var self = this,
252             data = this.get_event_data(event_obj);
253         this.dataset.create(data, function(r) {
254             var id = r.result;
255             self.dataset.ids.push(id);
256             scheduler.changeEventId(event_id, id);
257             self.refresh_minical();
258         }, function(r, event) {
259             event.preventDefault();
260             self.do_create_event_with_formdialog(event_id, event_obj);
261         });
262     },
263     do_create_event_with_formdialog: function(event_id, event_obj) {
264         if (!event_obj) {
265             event_obj = scheduler.getEvent(event_id);
266         }
267         var self = this,
268             data = this.get_event_data(event_obj),
269             form = self.form_dialog.form,
270             fields_to_fetch = _(form.fields_view.fields).keys();
271         this.dataset.index = null;
272         self.creating_event_id = event_id;
273         this.form_dialog.form.do_show().then(function() {
274             form.show_invalid = false;
275             _.each(['date_start', 'date_stop', 'date_delay'], function(field) {
276                 var field_name = self[field];
277                 if (field_name) {
278                     field = form.fields[field_name];
279                     field.set_value(data[field_name]);
280                     field.dirty = true;
281                     form.do_onchange(field);
282                 }
283             });
284             form.show_invalid = true;
285             self.form_dialog.open();
286         });
287     },
288     do_save_event: function(event_id, event_obj) {
289         var self = this,
290             data = this.get_event_data(event_obj),
291             index = this.dataset.get_id_index(event_id);
292         if (index != null) {
293             event_id = this.dataset.ids[index];
294             this.dataset.write(event_id, data, {}, function() {
295                 self.refresh_minical();
296             });
297         }
298     },
299     do_delete_event: function(event_id, event_obj) {
300         // dhtmlx sends this event even when it does not exist in openerp.
301         // Eg: use cancel in dhtmlx new event dialog
302         var self = this,
303             index = this.dataset.get_id_index(event_id);
304         if (index !== null) {
305             this.dataset.unlink(event_id, function() {
306                 self.refresh_minical();
307             });
308         }
309     },
310     do_edit_event: function(event_id) {
311         var self = this;
312         var index = this.dataset.get_id_index(event_id);
313         if (index !== null) {
314             this.dataset.index = index;
315             this.do_switch_view('page');
316         } else if (scheduler.getState().mode === 'month') {
317             var event_obj = scheduler.getEvent(event_id);
318             if (event_obj._length === 1) {
319                 event_obj['start_date'].addHours(8);
320                 event_obj['end_date'] = new Date(event_obj['start_date']);
321                 event_obj['end_date'].addHours(1);
322             }
323             this.do_create_event_with_formdialog(event_id, event_obj);
324             // return false;
325             // Theorically, returning false should prevent the lightbox to open.
326             // It works, but then the scheduler is in a buggy state where drag'n drop
327             // related internal Event won't be fired anymore.
328             // I tried scheduler.editStop(event_id); but doesn't work either
329             // After losing one hour on this, here's a quick and very dirty fix :
330             $(".dhx_cancel_btn").click();
331         }
332     },
333     get_event_data: function(event_obj) {
334         var data = {
335             name: event_obj.text
336         };
337         data[this.date_start] = openerp.web.datetime_to_str(event_obj.start_date);
338         if (this.date_stop) {
339             data[this.date_stop] = openerp.web.datetime_to_str(event_obj.end_date);
340         }
341         if (this.date_delay) {
342             var diff_seconds = Math.round((event_obj.end_date.getTime() - event_obj.start_date.getTime()) / 1000);
343             data[this.date_delay] = diff_seconds / 3600;
344         }
345         return data;
346     },
347     do_search: function(domain, context, group_by) {
348         var self = this
349         if (!domain) {
350             this.do_search.apply(this, this.last_search);
351         } else {
352             this.last_search = [domain, context, group_by];
353         }
354         scheduler.clearAll();
355         $.when(this.has_been_loaded).then(function() {
356             self.dataset.read_slice(_.keys(self.fields), {
357                 offset: 0,
358                 limit: self.limit
359             }, function(events) {
360                 self.dataset_events = events;
361                 self.on_events_loaded(events);
362             });
363         });
364     },
365     do_show: function () {
366         var self = this;
367         $.when(this.has_been_loaded).then(function() {
368             self.$element.show();
369             if (self.sidebar) {
370                 self.sidebar.$element.show();
371             }
372         });
373     },
374     do_hide: function () {
375         this.$element.hide();
376         if (this.sidebar) {
377             this.sidebar.$element.hide();
378         }
379     },
380     get_selected_ids: function() {
381         // no way to select a record anyway
382         return [];
383     }
384 });
385
386 openerp.web_calendar.CalendarFormDialog = openerp.web.Dialog.extend({
387     init: function(view, options, view_id, dataset) {
388         this._super(view, options);
389         this.dataset = dataset;
390         this.view_id = view_id;
391         this.view = view;
392     },
393     start: function() {
394         this._super();
395         this.form = new openerp.web.FormView(this, this.dataset, this.view_id, {
396             sidebar: false,
397             pager: false
398         });
399         this.form.appendTo(this.$element);
400         this.form.on_created.add_last(this.on_form_dialog_saved);
401         this.form.on_saved.add_last(this.on_form_dialog_saved);
402     },
403     on_form_dialog_saved: function() {
404         var id = this.dataset.ids[this.dataset.index];
405         if (this.view.creating_event_id) {
406             scheduler.changeEventId(this.view.creating_event_id, id);
407             this.view.creating_event_id = null;
408         }
409         this.view.reload_event(id);
410         this.close();
411     },
412     on_close: function() {
413         if (this.view.creating_event_id) {
414             scheduler.deleteEvent(this.view.creating_event_id);
415             this.view.creating_event_id = null;
416         }
417     }
418 });
419
420 openerp.web_calendar.SidebarResponsible = openerp.web.Widget.extend({
421     init: function(parent, view) {
422         var $section = parent.add_section(_t('Responsible'), 'responsible');
423         this.$div = $('<div></div>');
424         $section.append(this.$div);
425         this._super(parent, $section.attr('id'));
426         this.view = view;
427         this.$element.delegate('input:checkbox', 'change', this.on_filter_click);
428     },
429     on_events_loaded: function(filters) {
430         this.$div.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters }));
431     },
432     on_filter_click: function(e) {
433         var responsibles = [],
434             $e = $(e.target);
435         this.$element.find('div.oe_calendar_responsible input:checked').each(function() {
436             responsibles.push($(this).val());
437         });
438         scheduler.clearAll();
439         if (responsibles.length) {
440             this.view.on_events_loaded(this.view.dataset_events, function(filter_value) {
441                 return _.indexOf(responsibles, filter_value.toString()) > -1;
442             }, true);
443         } else {
444             this.view.on_events_loaded(this.view.dataset_events, false, true);
445         }
446     }
447 });
448
449 openerp.web_calendar.SidebarNavigator = openerp.web.Widget.extend({
450     init: function(parent, view) {
451         var $section = parent.add_section(_t('Navigator'), 'navigator');
452         this._super(parent, $section.attr('id'));
453         this.view = view;
454     },
455     on_events_loaded: function(events) {
456     }
457 });
458 };
459
460 // DEBUG_RPC:rpc.request:('execute', 'addons-dsh-l10n_us', 1, '*', ('ir.filters', 'get_filters', u'res.partner'))
461 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: