1 /*---------------------------------------------------------
3 *---------------------------------------------------------*/
5 openerp.web_calendar = function(instance) {
6 var _t = instance.web._t,
7 _lt = instance.web._lt;
8 var QWeb = instance.web.qweb;
9 instance.web.views.add('calendar', 'instance.web_calendar.CalendarView');
10 instance.web_calendar.CalendarView = instance.web.View.extend({
11 template: "CalendarView",
12 display_name: _lt('Calendar'),
14 init: function(parent, dataset, view_id, options) {
16 this.ready = $.Deferred();
17 this.set_default_options(options);
18 this.dataset = dataset;
19 this.model = dataset.model;
20 this.fields_view = {};
21 this.view_id = view_id;
22 this.view_type = 'calendar';
23 this.has_been_loaded = $.Deferred();
24 this.creating_event_id = null;
25 this.dataset_events = [];
26 this.form_dialog = new instance.web_calendar.CalendarFormDialog(this, {
27 destroy_on_close: false,
30 }, this.options.action_views_ids.form, dataset);
31 this.form_dialog.start();
32 this.COLOR_PALETTE = ['#f57900', '#cc0000', '#d400a8', '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400',
33 '#fcaf3e', '#ef2929', '#ff00c9', '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f',
34 '#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
35 '#905000', '#9b0000', '#840067', '#510090', '#0000c9', '#009b00', '#9abe00', '#ffc900' ];
37 this.last_search = [];
38 this.range_start = null;
39 this.range_stop = null;
40 this.update_range_dates(Date.today());
41 this.selected_filters = [];
45 return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type":"calendar", 'toolbar': false}, this.on_loaded);
51 on_loaded: function(data) {
52 this.fields_view = data;
53 this.calendar_fields = {};
54 this.ids = this.dataset.ids;
55 this.color_values = [];
56 this.info_fields = [];
58 this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
59 this.view_id = this.fields_view.view_id;
61 // mode, one of month, week or day
62 this.mode = this.fields_view.arch.attrs.mode;
64 // date_start is mandatory, date_delay and date_stop are optional
65 this.date_start = this.fields_view.arch.attrs.date_start;
66 this.date_delay = this.fields_view.arch.attrs.date_delay;
67 this.date_stop = this.fields_view.arch.attrs.date_stop;
69 this.day_length = this.fields_view.arch.attrs.day_length || 8;
70 this.color_field = this.fields_view.arch.attrs.color;
71 this.color_string = this.fields_view.fields[this.color_field] ?
72 this.fields_view.fields[this.color_field].string : _t("Filter");
74 if (this.color_field && this.selected_filters.length === 0) {
76 if (default_filter = this.dataset.context['calendar_default_' + this.color_field]) {
77 this.selected_filters.push(default_filter + '');
80 this.fields = this.fields_view.fields;
82 if (!this.date_start) {
83 throw new Error("Calendar view has not defined 'date_start' attribute.");
87 this.calendar_fields.date_start = {'name': this.date_start, 'kind': this.fields[this.date_start].type};
89 if (this.date_delay) {
90 if (this.fields[this.date_delay].type != 'float') {
91 throw new Error("Calendar view has a 'date_delay' type != float");
93 this.calendar_fields.date_delay = {'name': this.date_delay, 'kind': this.fields[this.date_delay].type};
96 this.calendar_fields.date_stop = {'name': this.date_stop, 'kind': this.fields[this.date_stop].type};
99 for (var fld = 0; fld < this.fields_view.arch.children.length; fld++) {
100 this.info_fields.push(this.fields_view.arch.children[fld].attrs.name);
103 this.init_scheduler();
105 if (!this.sidebar && this.options.$sidebar) {
106 this.sidebar = new instance.web_calendar.Sidebar(this);
107 this.has_been_loaded.pipe(this.sidebar.appendTo(this.$element.find('.oe_calendar_sidebar_container')));
110 return this.has_been_loaded.resolve();
112 init_scheduler: function() {
114 scheduler.clearAll();
115 if (this.fields[this.date_start]['type'] == 'time') {
116 scheduler.config.xml_date = "%H:%M:%S";
118 scheduler.config.xml_date = "%Y-%m-%d %H:%i";
120 scheduler.config.api_date = "%Y-%m-%d %H:%i";
121 scheduler.config.multi_day = true; //Multi day events are not rendered in daily and weekly views
122 scheduler.config.start_on_monday = Date.CultureInfo.firstDayOfWeek !== 0; //Sunday = Sunday, Others = Monday
123 scheduler.config.time_step = 30;
124 scheduler.config.scroll_hour = 8;
125 scheduler.config.drag_resize = true;
126 scheduler.config.drag_create = true;
127 scheduler.config.mark_now = true;
128 scheduler.config.day_date = '%l %j';
132 month_full: Date.CultureInfo.monthNames,
133 month_short: Date.CultureInfo.abbreviatedMonthNames,
134 day_full: Date.CultureInfo.dayNames,
135 day_short: Date.CultureInfo.abbreviatedDayNames
138 dhx_cal_today_button: _t("Today"),
140 week_tab: _t("Week"),
141 month_tab: _t("Month"),
142 new_event: _t("New event"),
143 icon_save: _t("Save"),
144 icon_cancel: _t("Cancel"),
145 icon_details: _t("Details"),
146 icon_edit: _t("Edit"),
147 icon_delete: _t("Delete"),
148 confirm_closing: "",//Your changes will be lost, are your sure ?
149 confirm_deleting: _t("Event will be deleted permanently, are you sure?"),
150 section_description: _t("Description"),
151 section_time: _t("Time period"),
152 full_day: _t("Full day"),
155 confirm_recurring: _t("Do you want to edit the whole set of repeated events?"),
156 section_recurring: _t("Repeat event"),
157 button_recurring: _t("Disabled"),
158 button_recurring_open: _t("Enabled"),
160 /*agenda view extension*/
161 agenda_tab: _t("Agenda"),
163 description: _t("Description"),
165 /*year view extension*/
166 year_tab: _t("Year"),
168 /* week agenda extension */
169 week_agenda_tab: _t("Agenda")
173 scheduler.init(this.$element.find('.oe_calendar')[0], null, this.mode || 'month');
175 // Remove hard coded style attributes from dhtmlx scheduler
176 this.$element.find(".dhx_cal_navline div").removeAttr('style');
178 scheduler.detachAllEvents();
179 scheduler.attachEvent('onEventAdded', this.do_create_event);
180 scheduler.attachEvent('onEventDeleted', this.do_delete_event);
181 scheduler.attachEvent('onEventChanged', this.do_save_event);
182 scheduler.attachEvent('onClick', this.do_edit_event);
183 scheduler.attachEvent('onLightbox', this.do_edit_event);
185 scheduler.attachEvent('onViewChange', this.on_view_changed);
186 this.refresh_scheduler();
188 on_view_changed: function(mode, date) {
189 this.$element.find('.oe_calendar').removeClass('oe_cal_day oe_cal_week oe_cal_month').addClass('oe_cal_' + mode);
190 if (!date.between(this.range_start, this.range_stop)) {
191 this.update_range_dates(date);
192 this.do_ranged_search();
194 this.ready.resolve();
196 update_range_dates: function(date) {
197 this.range_start = date.clone().moveToFirstDayOfMonth();
198 this.range_stop = this.range_start.clone().addMonths(1).addSeconds(-1);
200 refresh_scheduler: function() {
201 scheduler.setCurrentView(scheduler._date);
203 refresh_minical: function() {
205 scheduler.updateCalendar(this.sidebar.mini_calendar);
208 reload_event: function(id) {
209 this.dataset.read_ids([id], _.keys(this.fields)).then(this.on_events_loaded);
211 get_color: function(key) {
212 if (this.color_map[key]) {
213 return this.color_map[key];
215 var index = _.keys(this.color_map).length % this.COLOR_PALETTE.length;
216 var color = this.COLOR_PALETTE[index];
217 this.color_map[key] = color;
220 on_events_loaded: function(events, fn_filter, no_filter_reload) {
223 //To parse Events we have to convert date Format
226 for (var e = 0; e < events.length; e++) {
228 if (!evt[this.date_start]) {
232 if (this.color_field) {
233 var filter = evt[this.color_field];
235 var filter_value = (typeof filter === 'object') ? filter[0] : filter;
236 if (typeof(fn_filter) === 'function' && !fn_filter(filter_value)) {
241 label: (typeof filter === 'object') ? filter[1] : filter,
242 color: this.get_color(filter_value)
244 if (!sidebar_items[filter_value]) {
245 sidebar_items[filter_value] = filter_item;
247 evt.color = filter_item.color;
248 evt.textColor = '#ffffff';
252 if (this.fields[this.date_start]['type'] == 'date') {
253 evt[this.date_start] = instance.web.auto_str_to_date(evt[this.date_start]).set({hour: 9}).toString('yyyy-MM-dd HH:mm:ss');
255 if (this.date_stop && evt[this.date_stop] && this.fields[this.date_stop]['type'] == 'date') {
256 evt[this.date_stop] = instance.web.auto_str_to_date(evt[this.date_stop]).set({hour: 17}).toString('yyyy-MM-dd HH:mm:ss');
258 res_events.push(this.convert_event(evt));
260 scheduler.parse(res_events, 'json');
261 this.refresh_scheduler();
262 this.refresh_minical();
263 if (!no_filter_reload && this.sidebar) {
264 this.sidebar.filter.on_events_loaded(sidebar_items);
267 convert_event: function(evt) {
268 var date_start = instance.web.str_to_datetime(evt[this.date_start]),
269 date_stop = this.date_stop ? instance.web.str_to_datetime(evt[this.date_stop]) : null,
270 date_delay = evt[this.date_delay] || 1.0,
273 if (this.info_fields) {
274 res_text = _.map(this.info_fields, function(fld) {
275 if(evt[fld] instanceof Array)
280 if (!date_stop && date_delay) {
281 date_stop = date_start.clone().addHours(date_delay);
284 'start_date': date_start.toString('yyyy-MM-dd HH:mm:ss'),
285 'end_date': date_stop.toString('yyyy-MM-dd HH:mm:ss'),
286 'text': res_text.join(', '),
293 r.textColor = evt.textColor;
297 do_create_event: function(event_id, event_obj) {
299 data = this.get_event_data(event_obj);
300 this.dataset.create(data, function(r) {
302 self.dataset.ids.push(id);
303 scheduler.changeEventId(event_id, id);
304 self.refresh_minical();
305 self.reload_event(id);
306 }, function(r, event) {
307 event.preventDefault();
308 self.do_create_event_with_formdialog(event_id, event_obj);
311 do_create_event_with_formdialog: function(event_id, event_obj) {
313 event_obj = scheduler.getEvent(event_id);
316 data = this.get_event_data(event_obj),
317 form = self.form_dialog.form,
318 fields_to_fetch = _(form.fields_view.fields).keys();
319 this.dataset.index = null;
320 self.creating_event_id = event_id;
321 this.form_dialog.form.do_show().then(function() {
322 _.each(['date_start', 'date_delay', 'date_stop'], function(field) {
323 var field_name = self[field];
324 if (field_name && form.fields[field_name]) {
325 var ffield = form.fields[field_name];
326 ffield._dirty_flag = false;
327 $.when(ffield.set_value(data[field_name])).then(function() {
328 ffield._dirty_flag = true;
329 form.do_onchange(ffield);
333 self.form_dialog.open();
336 do_save_event: function(event_id, event_obj) {
338 data = this.get_event_data(event_obj),
339 index = this.dataset.get_id_index(event_id);
341 event_id = this.dataset.ids[index];
342 this.dataset.write(event_id, data, {}, function() {
343 self.refresh_minical();
347 do_delete_event: function(event_id, event_obj) {
348 // dhtmlx sends this event even when it does not exist in openerp.
349 // Eg: use cancel in dhtmlx new event dialog
351 index = this.dataset.get_id_index(event_id);
352 if (index !== null) {
353 this.dataset.unlink(event_id, function() {
354 self.refresh_minical();
358 do_edit_event: function(event_id, evt) {
360 var index = this.dataset.get_id_index(event_id);
361 if (index !== null) {
362 this.dataset.index = index;
363 this.do_switch_view('form');
364 } else if (scheduler.getState().mode === 'month') {
365 var event_obj = scheduler.getEvent(event_id);
366 if (event_obj._length === 1) {
367 event_obj['start_date'].addHours(8);
368 event_obj['end_date'] = new Date(event_obj['start_date']);
369 event_obj['end_date'].addHours(1);
371 event_obj['start_date'].addHours(8);
372 event_obj['end_date'].addHours(-4);
374 this.do_create_event_with_formdialog(event_id, event_obj);
376 // Theorically, returning false should prevent the lightbox to open.
377 // It works, but then the scheduler is in a buggy state where drag'n drop
378 // related internal Event won't be fired anymore.
379 // I tried scheduler.editStop(event_id); but doesn't work either
380 // After losing one hour on this, here's a quick and very dirty fix :
381 $(".dhx_cancel_btn").click();
383 scheduler.editStop($(evt.target).hasClass('icon_save'));
386 get_event_data: function(event_obj) {
390 data[this.date_start] = instance.web.datetime_to_str(event_obj.start_date);
391 if (this.date_stop) {
392 data[this.date_stop] = instance.web.datetime_to_str(event_obj.end_date);
394 if (this.date_delay) {
395 var diff_seconds = Math.round((event_obj.end_date.getTime() - event_obj.start_date.getTime()) / 1000);
396 data[this.date_delay] = diff_seconds / 3600;
400 do_search: function(domain, context, group_by) {
401 this.last_search = arguments;
402 this.do_ranged_search();
404 do_ranged_search: function() {
406 scheduler.clearAll();
407 $.when(this.has_been_loaded, this.ready).then(function() {
408 self.dataset.read_slice(_.keys(self.fields), {
410 domain: self.get_range_domain(),
411 context: self.last_search[1]
412 }).then(function(events) {
413 self.dataset_events = events;
414 self.on_events_loaded(events);
418 get_range_domain: function() {
419 var format = instance.web.date_to_str,
420 domain = this.last_search[0].slice(0);
421 domain.unshift([this.date_start, '>=', format(this.range_start.clone().addDays(-6))]);
422 domain.unshift([this.date_start, '<=', format(this.range_stop.clone().addDays(6))]);
425 do_show: function () {
427 $.when(this.has_been_loaded).then(function() {
428 self.$element.show();
429 self.do_push_state({});
432 get_selected_ids: function() {
433 // no way to select a record anyway
438 instance.web_calendar.CalendarFormDialog = instance.web.Dialog.extend({
439 init: function(view, options, view_id, dataset) {
440 this._super(view, options);
441 this.dataset = dataset;
442 this.view_id = view_id;
448 this.form = new instance.web.FormView(this, this.dataset, this.view_id, {
451 this.form.appendTo(this.$element);
452 this.form.on_created.add_last(this.on_form_dialog_saved);
453 this.form.on_saved.add_last(this.on_form_dialog_saved);
454 this.form.on_button_cancel = function() {
458 on_form_dialog_saved: function() {
459 var id = this.dataset.ids[this.dataset.index];
460 if (this.view.creating_event_id) {
461 scheduler.changeEventId(this.view.creating_event_id, id);
462 this.view.creating_event_id = null;
464 this.view.reload_event(id);
467 on_close: function() {
468 if (this.view.creating_event_id) {
469 scheduler.deleteEvent(this.view.creating_event_id);
470 this.view.creating_event_id = null;
475 instance.web_calendar.Sidebar = instance.web.Widget.extend({
476 template: 'CalendarView.sidebar',
479 this.mini_calendar = scheduler.renderCalendar({
480 container: this.$element.find('.oe_calendar_mini')[0],
482 date: scheduler._date,
483 handler: function(date, calendar) {
484 scheduler.setCurrentView(date, 'day');
487 this.filter = new instance.web_calendar.SidebarFilter(this, this.getParent());
488 this.filter.appendTo(this.$element.find('.oe_calendar_filter'));
491 instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
492 init: function(parent, view) {
495 this.$element.delegate('input:checkbox', 'change', this.on_filter_click);
497 on_events_loaded: function(filters) {
498 var selected_filters = this.view.selected_filters.slice(0);
499 this.$element.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters }));
500 this.$element.find('div.oe_calendar_responsible input').each(function() {
501 if (_.indexOf(selected_filters, $(this).val()) > -1) {
506 on_filter_click: function(e) {
510 this.view.selected_filters = [];
511 this.$element.find('div.oe_calendar_responsible input:checked').each(function() {
512 responsibles.push($(this).val());
513 self.view.selected_filters.push($(this).val());
515 scheduler.clearAll();
516 if (responsibles.length) {
517 this.view.on_events_loaded(this.view.dataset_events, function(filter_value) {
518 return _.indexOf(responsibles, filter_value.toString()) > -1;
521 this.view.on_events_loaded(this.view.dataset_events, false, true);
528 // DEBUG_RPC:rpc.request:('execute', 'addons-dsh-l10n_us', 1, '*', ('ir.filters', 'get_filters', u'res.partner'))
529 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: