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