[MERGE] forward port of branch 8.0 up to 92c7874
[odoo/odoo.git] / addons / web_calendar / static / src / js / web_calendar.js
1 /*---------------------------------------------------------
2  * OpenERP web_calendar
3  *---------------------------------------------------------*/
4
5 _.str.toBoolElse = function (str, elseValues, trueValues, falseValues) {
6     var ret = _.str.toBool(str, trueValues, falseValues);
7     if (_.isUndefined(ret)) {
8         return elseValues;
9     }
10     return ret;
11 };
12
13 openerp.web_calendar = function(instance) {
14     var _t = instance.web._t,
15         _lt = instance.web._lt,
16         QWeb = instance.web.qweb;
17
18     function get_class(name) {
19         return new instance.web.Registry({'tmp' : name}).get_object("tmp");
20     }
21
22     function get_fc_defaultOptions() {
23         shortTimeformat = Date.CultureInfo.formatPatterns.shortTime;
24         var dateFormat = Date.normalizeFormat(instance.web.strip_raw_chars(_t.database.parameters.date_format));
25         return {
26             weekNumberTitle: _t("W"),
27             allDayText: _t("All day"),
28             buttonText : {
29                 today:    _t("Today"),
30                 month:    _t("Month"),
31                 week:     _t("Week"),
32                 day:      _t("Day")
33             },
34             monthNames: Date.CultureInfo.monthNames,
35             monthNamesShort: Date.CultureInfo.abbreviatedMonthNames,
36             dayNames: Date.CultureInfo.dayNames,
37             dayNamesShort: Date.CultureInfo.abbreviatedDayNames,
38             firstDay: Date.CultureInfo.firstDayOfWeek,
39             weekNumbers: true,
40             axisFormat : shortTimeformat.replace(/:mm/,'(:mm)'),
41             timeFormat : {
42                // for agendaWeek and agendaDay               
43                agenda: shortTimeformat + '{ - ' + shortTimeformat + '}', // 5:00 - 6:30
44                 // for all other views
45                 '': shortTimeformat.replace(/:mm/,'(:mm)')  // 7pm
46             },
47             titleFormat: {
48                 month: 'MMMM yyyy',
49                 week: dateFormat + "{ '—'"+ dateFormat,
50                 day: dateFormat,
51             },
52             columnFormat: {
53                 month: 'ddd',
54                 week: 'ddd ' + dateFormat,
55                 day: 'dddd ' + dateFormat,
56             },
57             weekMode : 'liquid',
58             aspectRatio: 1.8,
59             snapMinutes: 15,
60         };
61     }
62
63     function is_virtual_id(id) {
64         return typeof id === "string" && id.indexOf('-') >= 0;
65     }
66
67     function isNullOrUndef(value) {
68         return _.isUndefined(value) || _.isNull(value);
69     }
70
71     instance.web.views.add('calendar', 'instance.web_calendar.CalendarView');
72
73     instance.web_calendar.CalendarView = instance.web.View.extend({
74         template: "CalendarView",
75         display_name: _lt('Calendar'),
76         quick_create_instance: 'instance.web_calendar.QuickCreate',
77
78         init: function (parent, dataset, view_id, options) {
79             this._super(parent);
80             this.ready = $.Deferred();
81             this.set_default_options(options);
82             this.dataset = dataset;
83             this.model = dataset.model;
84             this.fields_view = {};
85             this.view_id = view_id;
86             this.view_type = 'calendar';
87             this.color_map = {};
88             this.range_start = null;
89             this.range_stop = null;
90             this.selected_filters = [];
91
92             this.shown = $.Deferred();
93         },
94
95         set_default_options: function(options) {
96             this._super(options);
97             _.defaults(this.options, {
98                 confirm_on_delete: true
99             });
100         },
101
102         destroy: function() {
103             this.$calendar.fullCalendar('destroy');
104             if (this.$small_calendar) {
105                 this.$small_calendar.datepicker('destroy');
106             }
107             this._super.apply(this, arguments);
108         },
109
110         view_loading: function (fv) {
111             /* xml view calendar options */
112             var attrs = fv.arch.attrs,
113                 self = this;
114             this.fields_view = fv;
115             this.$calendar = this.$el.find(".oe_calendar_widget");
116
117             this.info_fields = [];
118
119             /* buttons */
120             this.$buttons = $(QWeb.render("CalendarView.buttons", {'widget': this}));
121             if (this.options.$buttons) {
122                 this.$buttons.appendTo(this.options.$buttons);
123             } else {
124                 this.$el.find('.oe_calendar_buttons').replaceWith(this.$buttons);
125             }
126
127             this.$buttons.on('click', 'button.oe_calendar_button_new', function () {
128                 self.dataset.index = null;
129                 self.do_switch_view('form');
130             });
131
132             if (!attrs.date_start) {
133                 throw new Error(_t("Calendar view has not defined 'date_start' attribute."));
134             }
135
136             this.$el.addClass(attrs['class']);
137
138             this.name = fv.name || attrs.string;
139             this.view_id = fv.view_id;
140
141             this.mode = attrs.mode;                 // one of month, week or day
142             this.date_start = attrs.date_start;     // Field name of starting date field
143             this.date_delay = attrs.date_delay;     // duration
144             this.date_stop = attrs.date_stop;
145             this.all_day = attrs.all_day;
146             this.how_display_event = '';
147             this.attendee_people = attrs.attendee;
148
149             if (!isNullOrUndef(attrs.quick_create_instance)) {
150                 self.quick_create_instance = 'instance.' + attrs.quick_create_instance;
151             }
152
153             //if quick_add = False, we don't allow quick_add
154             //if quick_add = not specified in view, we use the default quick_create_instance
155             //if quick_add = is NOT False and IS specified in view, we this one for quick_create_instance'   
156
157             this.quick_add_pop = (isNullOrUndef(attrs.quick_add) || _.str.toBoolElse(attrs.quick_add, true));
158             if (this.quick_add_pop && !isNullOrUndef(attrs.quick_add)) {
159                 self.quick_create_instance = 'instance.' + attrs.quick_add;
160             }
161             // The display format which will be used to display the event where fields are between "[" and "]"
162             if (!isNullOrUndef(attrs.display)) {
163                 this.how_display_event = attrs.display; // String with [FIELD]
164             }
165
166             // If this field is set ot true, we don't open the event in form view, but in a popup with the view_id passed by this parameter
167             if (isNullOrUndef(attrs.event_open_popup) || !_.str.toBoolElse(attrs.event_open_popup, true)) {
168                 this.open_popup_action = false;
169             } else {
170                 this.open_popup_action = attrs.event_open_popup;
171             }
172             // If this field is set to true, we will use the calendar_friends model as filter and not the color field.
173             this.useContacts = (!isNullOrUndef(attrs.use_contacts) && _.str.toBool(attrs.use_contacts)) && (!isNullOrUndef(self.options.$sidebar));
174
175             // If this field is set ot true, we don't add itself as an attendee when we use attendee_people to add each attendee icon on an event
176             // The color is the color of the attendee, so don't need to show again that it will be present
177             this.colorIsAttendee = (!(isNullOrUndef(attrs.color_is_attendee) || !_.str.toBoolElse(attrs.color_is_attendee, true))) && (!isNullOrUndef(self.options.$sidebar));
178
179             // if we have not sidebar, (eg: Dashboard), we don't use the filter "coworkers"
180             if (isNullOrUndef(self.options.$sidebar)) {
181                 this.useContacts = false;
182                 this.colorIsAttendee = false;
183                 this.attendee_people = undefined;
184             }
185
186 /*
187             Will be more logic to do it in futur, but see below to stay Retro-compatible
188             
189             if (isNull(attrs.avatar_model)) {
190                 this.avatar_model = 'res.partner'; 
191             }
192             else {
193                 if (attrs.avatar_model == 'False') {
194                     this.avatar_model = null;
195                 }
196                 else {  
197                     this.avatar_model = attrs.avatar_model;
198                 }
199             }            
200 */
201             if (isNullOrUndef(attrs.avatar_model)) {
202                 this.avatar_model = null;
203             } else {
204                 this.avatar_model = attrs.avatar_model;
205             }
206
207             if (isNullOrUndef(attrs.avatar_title)) {
208                 this.avatar_title = this.avatar_model;
209             } else {
210                 this.avatar_title = attrs.avatar_title;
211             }
212
213             if (isNullOrUndef(attrs.avatar_filter)) {
214                 this.avatar_filter = this.avatar_model;
215             } else {
216                 this.avatar_filter = attrs.avatar_filter;
217             }
218
219             this.color_field = attrs.color;
220
221             if (this.color_field && this.selected_filters.length === 0) {
222                 var default_filter;
223                 if ((default_filter = this.dataset.context['calendar_default_' + this.color_field])) {
224                     this.selected_filters.push(default_filter + '');
225                 }
226             }
227
228             this.fields = fv.fields;
229
230             for (var fld = 0; fld < fv.arch.children.length; fld++) {
231                 this.info_fields.push(fv.arch.children[fld].attrs.name);
232             }
233
234             self.shown.done(this._do_show_init.bind(this));
235             var edit_check = new instance.web.Model(this.dataset.model)
236                 .call("check_access_rights", ["write", false])
237                 .then(function (write_right) {
238                     self.write_right = write_right;
239                 });
240             var init = new instance.web.Model(this.dataset.model)
241                 .call("check_access_rights", ["create", false])
242                 .then(function (create_right) {
243                     self.create_right = create_right;
244                     self.ready.resolve();
245                     self.trigger('calendar_view_loaded', fv);
246                 });
247             return $.when(edit_check, init);
248         },
249         _do_show_init: function () {
250             var self = this;
251             this.init_calendar().then(function() {
252                 $(window).trigger('resize');
253                 self.trigger('calendar_view_loaded', self.fields_view);
254             });
255         },
256         get_fc_init_options: function () {
257             //Documentation here : http://arshaw.com/fullcalendar/docs/
258             var self = this;
259             return  $.extend({}, get_fc_defaultOptions(), {
260                 
261                 defaultView: (this.mode == "month")?"month":
262                     (this.mode == "week"?"agendaWeek":
263                      (this.mode == "day"?"agendaDay":"month")),
264                 header: {
265                     left: 'prev,next today',
266                     center: 'title',
267                     right: 'month,agendaWeek,agendaDay'
268                 },
269                 selectable: !this.options.read_only_mode && this.create_right,
270                 selectHelper: true,
271                 editable: !this.options.read_only_mode,
272                 droppable: true,
273
274                 // callbacks
275
276                 eventDrop: function (event, _day_delta, _minute_delta, _all_day, _revertFunc) {
277                     var data = self.get_event_data(event);
278                     self.proxy('update_record')(event._id, data); // we don't revert the event, but update it.
279                 },
280                 eventResize: function (event, _day_delta, _minute_delta, _revertFunc) {
281                     var data = self.get_event_data(event);
282                     self.proxy('update_record')(event._id, data);
283                 },
284                 eventRender: function (event, element, view) {
285                     element.find('.fc-event-title').html(event.title);
286                 },
287                 eventAfterRender: function (event, element, view) {
288                     if ((view.name !== 'month') && (((event.end-event.start)/60000)<=30)) {
289                         //if duration is too small, we see the html code of img
290                         var current_title = $(element.find('.fc-event-time')).text();
291                         var new_title = current_title.substr(0,current_title.indexOf("<img")>0?current_title.indexOf("<img"):current_title.length);
292                         element.find('.fc-event-time').html(new_title);
293                     }
294                 },
295                 eventClick: function (event) { self.open_event(event._id,event.title); },
296                 select: function (start_date, end_date, all_day, _js_event, _view) {
297                     var data_template = self.get_event_data({
298                         start: start_date,
299                         end: end_date,
300                         allDay: all_day,
301                     });
302                     self.open_quick_create(data_template);
303
304                 },
305
306                 unselectAuto: false,
307
308
309             });
310         },
311
312         calendarMiniChanged: function (context) {
313             return function(datum,obj) {
314                 var curView = context.$calendar.fullCalendar( 'getView');
315                 var curDate = new Date(obj.currentYear , obj.currentMonth, obj.currentDay);
316
317                 if (curView.name == "agendaWeek") {
318                     if (curDate <= curView.end && curDate >= curView.start) {
319                         context.$calendar.fullCalendar('changeView','agendaDay');
320                     }
321                 }
322                 else if (curView.name != "agendaDay" || (curView.name == "agendaDay" && curDate.compareTo(curView.start)===0)) {
323                         context.$calendar.fullCalendar('changeView','agendaWeek');
324                 }
325                 context.$calendar.fullCalendar('gotoDate', obj.currentYear , obj.currentMonth, obj.currentDay);
326             };
327         },
328
329         init_calendar: function() {
330             var self = this;
331              
332             if (!this.sidebar && this.options.$sidebar) {
333                 translate = get_fc_defaultOptions();
334                 this.sidebar = new instance.web_calendar.Sidebar(this);
335                 this.sidebar.appendTo(this.$el.find('.oe_calendar_sidebar_container'));
336
337                 this.$small_calendar = self.$el.find(".oe_calendar_mini");
338                 this.$small_calendar.datepicker({ 
339                     onSelect: self.calendarMiniChanged(self),
340                     dayNamesMin : translate.dayNamesShort,
341                     monthNames: translate.monthNamesShort,
342                     firstDay: translate.firstDay,
343                 });
344
345                 this.extraSideBar();                
346             }
347             self.$calendar.fullCalendar(self.get_fc_init_options());
348             
349             return $.when();
350         },
351         extraSideBar: function() {
352         },
353
354         open_quick_create: function(data_template) {
355             if (!isNullOrUndef(this.quick)) {
356                 return this.quick.trigger('close');
357             }
358             var QuickCreate = get_class(this.quick_create_instance);
359             
360             this.options.disable_quick_create =  this.options.disable_quick_create || !this.quick_add_pop;
361             
362             this.quick = new QuickCreate(this, this.dataset, true, this.options, data_template);
363             this.quick.on('added', this, this.quick_created)
364                     .on('slowadded', this, this.slow_created)
365                     .on('close', this, function() {
366                         this.quick.destroy();
367                         delete this.quick;
368                         this.$calendar.fullCalendar('unselect');
369                     });
370             this.quick.replace(this.$el.find('.oe_calendar_qc_placeholder'));
371             this.quick.focus();
372             
373         },
374
375         /**
376          * Refresh one fullcalendar event identified by it's 'id' by reading OpenERP record state.
377          * If event was not existent in fullcalendar, it'll be created.
378          */
379         refresh_event: function(id) {
380             var self = this;
381             if (is_virtual_id(id)) {
382                 // Should avoid "refreshing" a virtual ID because it can't
383                 // really be modified so it should never be refreshed. As upon
384                 // edition, a NEW event with a non-virtual id will be created.
385                 console.warn("Unwise use of refresh_event on a virtual ID.");
386             }
387             this.dataset.read_ids([id], _.keys(this.fields)).done(function (incomplete_records) {
388                 self.perform_necessary_name_gets(incomplete_records).then(function(records) {
389                     // Event boundaries were already changed by fullcalendar, but we need to reload them:
390                     var new_event = self.event_data_transform(records[0]);
391                     // fetch event_obj
392                     var event_objs = self.$calendar.fullCalendar('clientEvents', id);
393                     if (event_objs.length == 1) { // Already existing obj to update
394                         var event_obj = event_objs[0];
395                         // update event_obj
396                         _(new_event).each(function (value, key) {
397                             event_obj[key] = value;
398                         });
399                         self.$calendar.fullCalendar('updateEvent', event_obj);
400                     } else { // New event object to create
401                         self.$calendar.fullCalendar('renderEvent', new_event);
402                         // By forcing attribution of this event to this source, we
403                         // make sure that the event will be removed when the source
404                         // will be removed (which occurs at each do_search)
405                         self.$calendar.fullCalendar('clientEvents', id)[0].source = self.event_source;
406                     }
407                 });
408             });
409         },
410
411         get_color: function(key) {
412             if (this.color_map[key]) {
413                 return this.color_map[key];
414             }
415             var index = (((_.keys(this.color_map).length + 1) * 5) % 24) + 1;
416             this.color_map[key] = index;
417             return index;
418         },
419         
420
421         /**
422          * In o2m case, records from dataset won't have names attached to their *2o values.
423          * We should make sure this is the case.
424          */
425         perform_necessary_name_gets: function(evts) {
426             var def = $.Deferred();
427             var self = this;
428             var to_get = {};
429             _(this.info_fields).each(function (fieldname) {
430                 if (!_(["many2one", "one2one"]).contains(
431                     self.fields[fieldname].type))
432                     return;
433                 to_get[fieldname] = [];
434                 _(evts).each(function (evt) {
435                     var value = evt[fieldname];
436                     if (value === false || (value instanceof Array)) {
437                         return;
438                     }
439                     to_get[fieldname].push(value);
440                 });
441                 if (to_get[fieldname].length === 0) {
442                     delete to_get[fieldname];
443                 }
444             });
445             var defs = _(to_get).map(function (ids, fieldname) {
446                 return (new instance.web.Model(self.fields[fieldname].relation))
447                     .call('name_get', ids).then(function (vals) {
448                         return [fieldname, vals];
449                     });
450             });
451
452             $.when.apply(this, defs).then(function() {
453                 var values = arguments;
454                 _(values).each(function(value) {
455                     var fieldname = value[0];
456                     var name_gets = value[1];
457                     _(name_gets).each(function(name_get) {
458                         _(evts).chain()
459                             .filter(function (e) {return e[fieldname] == name_get[0];})
460                             .each(function(evt) {
461                                 evt[fieldname] = name_get;
462                             });
463                     });
464                 });
465                 def.resolve(evts);
466             });
467             return def;
468         },
469         
470         /**
471          * Transform OpenERP event object to fullcalendar event object
472          */
473         event_data_transform: function(evt) {
474             var self = this;
475
476             var date_delay = evt[this.date_delay] || 1.0,
477                 all_day = this.all_day ? evt[this.all_day] : false,
478                 res_computed_text = '',
479                 the_title = '',
480                 attendees = [];
481
482             if (!all_day) {
483                 date_start = instance.web.auto_str_to_date(evt[this.date_start]);
484                 date_stop = this.date_stop ? instance.web.auto_str_to_date(evt[this.date_stop]) : null;
485             }
486             else {
487                 date_start = instance.web.auto_str_to_date(evt[this.date_start].split(' ')[0],'start');
488                 date_stop = this.date_stop ? instance.web.auto_str_to_date(evt[this.date_stop].split(' ')[0],'start') : null; //.addSeconds(-1) : null;
489             }
490
491             if (this.info_fields) {
492                 var temp_ret = {};
493                 res_computed_text = this.how_display_event;
494                 
495                 _.each(this.info_fields, function (fieldname) {
496                     var value = evt[fieldname];
497                     if (_.contains(["many2one", "one2one"], self.fields[fieldname].type)) {
498                         if (value === false) {
499                             temp_ret[fieldname] = null;
500                         }
501                         else if (value instanceof Array) {
502                             temp_ret[fieldname] = value[1]; // no name_get to make
503                         }
504                         else {
505                             throw new Error("Incomplete data received from dataset for record " + evt.id);
506                         }
507                     }
508                     else if (_.contains(["one2many","many2many"], self.fields[fieldname].type)) {
509                         if (value === false) {
510                             temp_ret[fieldname] = null;
511                         }
512                         else if (value instanceof Array)  {
513                             temp_ret[fieldname] = value; // if x2many, keep all id !
514                         }
515                         else {
516                             throw new Error("Incomplete data received from dataset for record " + evt.id);
517                         }
518                     }
519                     else {
520                         temp_ret[fieldname] = value;
521                     }
522                     res_computed_text = res_computed_text.replace("["+fieldname+"]",temp_ret[fieldname]);
523                 });
524
525                 
526                 if (res_computed_text.length) {
527                     the_title = res_computed_text;
528                 }
529                 else {
530                     var res_text= [];
531                     _.each(temp_ret, function(val,key) { res_text.push(val); });
532                     the_title = res_text.join(', ');
533                 }
534                 the_title = _.escape(the_title);
535                 
536                 
537                 the_title_avatar = '';
538                 
539                 if (! _.isUndefined(this.attendee_people)) {
540                     var MAX_ATTENDEES = 3;
541                     var attendee_showed = 0;
542                     var attendee_other = '';
543
544                     _.each(temp_ret[this.attendee_people],
545                         function (the_attendee_people) {
546                             attendees.push(the_attendee_people);
547                             attendee_showed += 1;
548                             if (attendee_showed<= MAX_ATTENDEES) {
549                                 if (self.avatar_model !== null) {
550                                        the_title_avatar += '<img title="' + self.all_attendees[the_attendee_people] + '" class="attendee_head"  \
551                                                             src="/web/binary/image?model=' + self.avatar_model + '&field=image_small&id=' + the_attendee_people + '"></img>';
552                                 }
553                                 else {
554                                     if (!self.colorIsAttendee || the_attendee_people != temp_ret[self.color_field]) {
555                                             tempColor = (self.all_filters[the_attendee_people] !== undefined) 
556                                                         ? self.all_filters[the_attendee_people].color
557                                                         : (self.all_filters[-1] ? self.all_filters[-1].color : 1);
558                                         the_title_avatar += '<i class="fa fa-user attendee_head color_'+tempColor+'" title="' + self.all_attendees[the_attendee_people] + '" ></i>';
559                                     }//else don't add myself
560                                 }
561                             }
562                             else {
563                                 attendee_other += self.all_attendees[the_attendee_people] +", ";
564                             }
565                         }
566                     );
567                     if (attendee_other.length>2) {
568                         the_title_avatar += '<span class="attendee_head" title="' + attendee_other.slice(0, -2) + '">+</span>';
569                     }
570                     the_title = the_title_avatar + the_title;
571                 }
572             }
573             
574             if (!date_stop && date_delay) {
575                 date_stop = date_start.clone().addHours(date_delay);
576             }
577             var r = {
578                 'start': date_start.toString('yyyy-MM-dd HH:mm:ss'),
579                 'end': date_stop.toString('yyyy-MM-dd HH:mm:ss'),
580                 'title': the_title,
581                 'allDay': (this.fields[this.date_start].type == 'date' || (this.all_day && evt[this.all_day]) || false),
582                 'id': evt.id,
583                 'attendees':attendees
584             };
585             if (!self.useContacts || self.all_filters[evt[this.color_field]] !== undefined) {
586                 if (this.color_field && evt[this.color_field]) {
587                     var color_key = evt[this.color_field];
588                     if (typeof color_key === "object") {
589                         color_key = color_key[0];
590                     }
591                     r.className = 'cal_opacity calendar_color_'+ this.get_color(color_key);
592                 }
593             }
594             else  { // if form all, get color -1
595                   r.className = 'cal_opacity calendar_color_'+ self.all_filters[-1].color;
596             }
597             return r;
598         },
599         
600         /**
601          * Transform fullcalendar event object to OpenERP Data object
602          */
603         get_event_data: function(event) {
604
605             // Normalize event_end without changing fullcalendars event.
606             var data = {
607                 name: event.title
608             };            
609             
610             var event_end = event.end;
611             //Bug when we move an all_day event from week or day view, we don't have a dateend or duration...            
612             if (event_end == null) {
613                 event_end = new Date(event.start).addHours(2);
614             }
615
616             if (event.allDay) {
617                 // Sometimes fullcalendar doesn't give any event.end.
618                 if (event_end == null || _.isUndefined(event_end)) {
619                     event_end = new Date(event.start);
620                 }
621                 if (this.all_day) {
622                     //event_end = (new Date(event_end.getTime())).addDays(1);
623                     date_start_day = new Date(Date.UTC(event.start.getFullYear(),event.start.getMonth(),event.start.getDate()));
624                     date_stop_day = new Date(Date.UTC(event_end.getFullYear(),event_end.getMonth(),event_end.getDate()));                    
625                 }
626                 else {
627                     date_start_day = new Date(event.start.getFullYear(),event.start.getMonth(),event.start.getDate(),7);
628                     date_stop_day = new Date(event_end.getFullYear(),event_end.getMonth(),event_end.getDate(),19);
629                 }
630                 data[this.date_start] = instance.web.parse_value(date_start_day, this.fields[this.date_start]);
631                 if (this.date_stop) {
632                     data[this.date_stop] = instance.web.parse_value(date_stop_day, this.fields[this.date_stop]);
633                 }
634                 diff_seconds = Math.round((date_stop_day.getTime() - date_start_day.getTime()) / 1000);
635                                 
636             }
637             else {
638                 data[this.date_start] = instance.web.parse_value(event.start, this.fields[this.date_start]);
639                 if (this.date_stop) {
640                     data[this.date_stop] = instance.web.parse_value(event_end, this.fields[this.date_stop]);
641                 }
642                 diff_seconds = Math.round((event_end.getTime() - event.start.getTime()) / 1000);
643             }
644
645             if (this.all_day) {
646                 data[this.all_day] = event.allDay;
647             }
648
649             if (this.date_delay) {
650                 
651                 data[this.date_delay] = diff_seconds / 3600;
652             }
653             return data;
654         },
655
656         do_search: function (domain, context, _group_by) {
657             var self = this;
658             this.shown.done(function () {
659                 self._do_search(domain, context, _group_by);
660             });
661         },
662         _do_search: function(domain, context, _group_by) {
663             var self = this;
664            if (! self.all_filters) {            
665                 self.all_filters = {}                
666            }
667
668             if (! _.isUndefined(this.event_source)) {
669                 this.$calendar.fullCalendar('removeEventSource', this.event_source);
670             }
671             this.event_source = {
672                 events: function(start, end, callback) {
673                     var current_event_source = self.event_source;
674                     self.dataset.read_slice(_.keys(self.fields), {
675                         offset: 0,
676                         domain: self.get_range_domain(domain, start, end),
677                         context: context,
678                     }).done(function(events) {
679                         if (self.dataset.index === null) {
680                             if (events.length) {
681                                 self.dataset.index = 0;
682                             }
683                         } else if (self.dataset.index >= events.length) {
684                             self.dataset.index = events.length ? 0 : null;
685                         }
686
687                         if (self.event_source !== current_event_source) {
688                             console.log("Consecutive ``do_search`` called. Cancelling.");
689                             return;
690                         }
691                         
692                         if (!self.useContacts) {  // If we use all peoples displayed in the current month as filter in sidebars
693                             var filter_value;
694                             var filter_item;
695                             
696                             self.now_filter_ids = [];
697
698                             _.each(events, function (e) {
699                                 filter_value = e[self.color_field][0];
700                                 if (!self.all_filters[e[self.color_field][0]]) {
701                                     filter_item = {
702                                         value: filter_value,
703                                         label: e[self.color_field][1],
704                                         color: self.get_color(filter_value),
705                                         avatar_model: (_.str.toBoolElse(self.avatar_filter, true) ? self.avatar_filter : false ),
706                                         is_checked: true
707                                     };
708                                     self.all_filters[e[self.color_field][0]] = filter_item;
709                                 }
710                                 if (! _.contains(self.now_filter_ids, filter_value)) {
711                                     self.now_filter_ids.push(filter_value);
712                                 }
713                             });
714
715                             if (self.sidebar) {
716                                 self.sidebar.filter.events_loaded();
717                                 self.sidebar.filter.set_filters();
718                                 
719                                 events = $.map(events, function (e) {
720                                     if (_.contains(self.now_filter_ids,e[self.color_field][0]) &&  self.all_filters[e[self.color_field][0]].is_checked) {
721                                         return e;
722                                     }
723                                     return null;
724                                 });
725                             }
726                             
727                         }
728                         else { //WE USE CONTACT
729                             if (self.attendee_people !== undefined) {
730                                 //if we don't filter on 'Everybody's Calendar
731                                 if (!self.all_filters[-1] || !self.all_filters[-1].is_checked) {
732                                     var checked_filter = $.map(self.all_filters, function(o) { if (o.is_checked) { return o.value; }});
733                                     // If we filter on contacts... we keep only events from coworkers
734                                     events = $.map(events, function (e) {
735                                         if (_.intersection(checked_filter,e[self.attendee_people]).length) {
736                                             return e;
737                                         }
738                                         return null;
739                                     });
740                                 }
741                             }
742
743                             
744                         }
745
746                         var all_attendees = $.map(events, function (e) { return e[self.attendee_people]; });
747                         all_attendees = _.chain(all_attendees).flatten().uniq().value();
748
749                         self.all_attendees = {};
750                         if (self.avatar_title !== null) {
751                             new instance.web.Model(self.avatar_title).query(["name"]).filter([["id", "in", all_attendees]]).all().then(function(result) {
752                                 _.each(result, function(item) {
753                                     self.all_attendees[item.id] = item.name;
754                                 });
755                             }).done(function() {
756                                 return self.perform_necessary_name_gets(events).then(callback);
757                             });
758                         }
759                         else {
760                             _.each(all_attendees,function(item){
761                                     self.all_attendees[item] = '';
762                             });
763                             return self.perform_necessary_name_gets(events).then(callback);
764                         }
765                     });
766                 },
767                 eventDataTransform: function (event) {
768                     return self.event_data_transform(event);
769                 },
770             };
771             this.$calendar.fullCalendar('addEventSource', this.event_source);
772         },
773         /**
774          * Build OpenERP Domain to filter object by this.date_start field
775          * between given start, end dates.
776          */
777         get_range_domain: function(domain, start, end) {
778             var format = instance.web.date_to_str;
779             
780             extend_domain = [[this.date_start, '>=', format(start.clone())],
781                      [this.date_start, '<=', format(end.clone())]];
782
783             if (this.date_stop) {
784                 //add at start 
785                 extend_domain.splice(0,0,'|','|','&');
786                 //add at end 
787                 extend_domain.push(
788                                 '&',
789                                 [this.date_start, '<=', format(start.clone())],
790                                 [this.date_stop, '>=', format(start.clone())],
791                                 '&',
792                                 [this.date_start, '<=', format(end.clone())],
793                                 [this.date_stop, '>=', format(start.clone())]
794                 );
795                 //final -> (A & B) | (C & D) | (E & F) ->  | | & A B & C D & E F
796             }
797             return new instance.web.CompoundDomain(domain, extend_domain);
798         },
799
800         /**
801          * Updates record identified by ``id`` with values in object ``data``
802          */
803         update_record: function(id, data) {
804             var self = this;
805             delete(data.name); // Cannot modify actual name yet
806             var index = this.dataset.get_id_index(id);
807             if (index !== null) {
808                 event_id = this.dataset.ids[index];
809                 this.dataset.write(event_id, data, {}).done(function() {
810                     if (is_virtual_id(event_id)) {
811                         // this is a virtual ID and so this will create a new event
812                         // with an unknown id for us.
813                         self.$calendar.fullCalendar('refetchEvents');
814                     } else {
815                         // classical event that we can refresh
816                         self.refresh_event(event_id);
817                     }
818                 });
819             }
820             return false;
821         },
822         open_event: function(id, title) {
823             var self = this;
824             if (! this.open_popup_action) {
825                 var index = this.dataset.get_id_index(id);
826                 this.dataset.index = index;
827                 if (this.write_right) {
828                     this.do_switch_view('form', null, { mode: "edit" });
829                 } else {
830                     this.do_switch_view('form', null, { mode: "view" });
831                 }
832             }
833             else {
834                 var pop = new instance.web.form.FormOpenPopup(this);
835                 pop.show_element(this.dataset.model, parseInt(id), this.dataset.get_context(), {
836                     title: _.str.sprintf(_t("View: %s"),title),
837                     view_id: +this.open_popup_action,
838                     res_id: id,
839                     target: 'new',
840                     readonly:true
841                 });
842
843                var form_controller = pop.view_form;
844                form_controller.on("load_record", self, function(){
845                     button_delete = _.str.sprintf("<button class='oe_button oe_bold delme'><span> %s </span></button>",_t("Delete"));
846                     button_edit = _.str.sprintf("<button class='oe_button oe_bold editme oe_highlight'><span> %s </span></button>",_t("Edit Event"));
847                     
848                     pop.$el.closest(".modal").find(".modal-footer").prepend(button_delete);
849                     pop.$el.closest(".modal").find(".modal-footer").prepend(button_edit);
850                     
851                     $('.delme').click(
852                         function() {
853                             $('.oe_form_button_cancel').trigger('click');
854                             self.remove_event(id);
855                         }
856                     );
857                     $('.editme').click(
858                         function() {
859                             $('.oe_form_button_cancel').trigger('click');
860                             self.dataset.index = self.dataset.get_id_index(id);
861                             self.do_switch_view('form', null, { mode: "edit" });
862                         }
863                     );
864                });
865             }
866             return false;
867         },
868
869         do_show: function() {            
870             if (this.$buttons) {
871                 this.$buttons.show();
872             }
873             this.do_push_state({});
874             this.shown.resolve();
875             return this._super();
876         },
877         do_hide: function () {
878             if (this.$buttons) {
879                 this.$buttons.hide();
880             }
881             return this._super();
882         },
883         is_action_enabled: function(action) {
884             if (action === 'create' && !this.options.creatable) {
885                 return false;
886             }
887             return this._super(action);
888         },
889
890         /**
891          * Handles a newly created record
892          *
893          * @param {id} id of the newly created record
894          */
895         quick_created: function (id) {
896
897             /** Note:
898              * it's of the most utter importance NOT to use inplace
899              * modification on this.dataset.ids as reference to this
900              * data is spread out everywhere in the various widget.
901              * Some of these reference includes values that should
902              * trigger action upon modification.
903              */
904             this.dataset.ids = this.dataset.ids.concat([id]);
905             this.dataset.trigger("dataset_changed", id);
906             this.refresh_event(id);
907         },
908         slow_created: function () {
909             // refresh all view, because maybe some recurrents item
910             var self = this;
911             if (self.sidebar) {
912                 // force filter refresh
913                 self.sidebar.filter.is_loaded = false;
914             }
915             self.$calendar.fullCalendar('refetchEvents');
916         },
917
918         remove_event: function(id) {
919             var self = this;
920             function do_it() {
921                 return $.when(self.dataset.unlink([id])).then(function() {
922                     self.$calendar.fullCalendar('removeEvents', id);
923                 });
924             }
925             if (this.options.confirm_on_delete) {
926                 if (confirm(_t("Are you sure you want to delete this record ?"))) {
927                     return do_it();
928                 }
929             } else
930                 return do_it();
931         },
932     });
933
934
935     /**
936      * Quick creation view.
937      *
938      * Triggers a single event "added" with a single parameter "name", which is the
939      * name entered by the user
940      *
941      * @class
942      * @type {*}
943      */
944     instance.web_calendar.QuickCreate = instance.web.Widget.extend({
945         template: 'CalendarView.quick_create',
946         
947         init: function(parent, dataset, buttons, options, data_template) {
948             this._super(parent);
949             this.dataset = dataset;
950             this._buttons = buttons || false;
951             this.options = options;
952
953             // Can hold data pre-set from where you clicked on agenda
954             this.data_template = data_template || {};
955         },
956         get_title: function () {
957             var parent = this.getParent();
958             if (_.isUndefined(parent)) {
959                 return _t("Create");
960             }
961             var title = (_.isUndefined(parent.field_widget)) ?
962                     (parent.string || parent.name) :
963                     parent.field_widget.string || parent.field_widget.name || '';
964             return _t("Create: ") + title;
965         },
966         start: function () {
967             var self = this;
968
969             if (this.options.disable_quick_create) {
970                 this.$el.hide();
971                 this.slow_create();
972                 return;
973             }
974
975             self.$input = this.$el.find('input');
976             self.$input.keyup(function enterHandler (event) {
977                 if(event.keyCode == 13){
978                     self.$input.off('keyup', enterHandler);
979                     if (!self.quick_add()){
980                         self.$input.on('keyup', enterHandler);
981                     }
982                 }
983             });
984             
985             var submit = this.$el.find(".oe_calendar_quick_create_add");
986             submit.click(function clickHandler() {
987                 submit.off('click', clickHandler);
988                 if (!self.quick_add()){
989                    submit.on('click', clickHandler);                }
990                 self.focus();
991             });
992             this.$el.find(".oe_calendar_quick_create_edit").click(function () {
993                 self.slow_add();
994                 self.focus();
995             });
996             this.$el.find(".oe_calendar_quick_create_close").click(function (ev) {
997                 ev.preventDefault();
998                 self.trigger('close');
999             });
1000             self.$input.keyup(function enterHandler (e) {
1001                 if (e.keyCode == 27 && self._buttons) {
1002                     self.trigger('close');
1003                 }
1004             });
1005             self.$el.dialog({ title: this.get_title()});
1006             self.on('added', self, function() {
1007                 self.trigger('close');
1008             });
1009             
1010             self.$el.on('dialogclose', self, function() {
1011                 self.trigger('close');
1012             });
1013
1014         },
1015         focus: function() {
1016             this.$el.find('input').focus();
1017         },
1018
1019         /**
1020          * Gathers data from the quick create dialog a launch quick_create(data) method
1021          */
1022         quick_add: function() {
1023             var val = this.$input.val();
1024             if (/^\s*$/.test(val)) {
1025                 return false;
1026             }
1027             return this.quick_create({'name': val}).always(function() { return true; });
1028         },
1029         
1030         slow_add: function() {
1031             var val = this.$input.val();
1032             this.slow_create({'name': val});
1033         },
1034
1035         /**
1036          * Handles saving data coming from quick create box
1037          */
1038         quick_create: function(data, options) {
1039             var self = this;
1040             return this.dataset.create($.extend({}, this.data_template, data), options)
1041                 .then(function(id) {
1042                     self.trigger('added', id);
1043                     self.$input.val("");
1044                 }).fail(function(r, event) {
1045                     event.preventDefault();
1046                     // This will occurs if there are some more fields required
1047                     self.slow_create(data);
1048                 });
1049         },
1050
1051         /**
1052          * Show full form popup
1053          */
1054          get_form_popup_infos: function() {
1055             var parent = this.getParent();
1056             var infos = {
1057                 view_id: false,
1058                 title: this.name,
1059             };
1060             if (!_.isUndefined(parent) && !(_.isUndefined(parent.ViewManager))) {
1061                 infos.view_id = parent.ViewManager.get_view_id('form');
1062             }
1063             return infos;
1064         },
1065         slow_create: function(data) {
1066             //if all day, we could reset time to display 00:00:00
1067             
1068             var self = this;
1069             var def = $.Deferred();
1070             var defaults = {};
1071             var created = false;
1072
1073             _.each($.extend({}, this.data_template, data), function(val, field_name) {
1074                 defaults['default_' + field_name] = val;
1075             });
1076                         
1077             var pop_infos = self.get_form_popup_infos();
1078             var pop = new instance.web.form.FormOpenPopup(this);
1079             var context = new instance.web.CompoundContext(this.dataset.context, defaults);
1080             pop.show_element(this.dataset.model, null, this.dataset.get_context(defaults), {
1081                 title: this.get_title(),
1082                 disable_multiple_selection: true,
1083                 view_id: pop_infos.view_id,
1084                 // Ensuring we use ``self.dataset`` and DO NOT create a new one.
1085                 create_function: function(data, options) {
1086                     return self.dataset.create(data, options).done(function(r) {
1087                     }).fail(function (r, event) {
1088                        if (!r.data.message) { //else manage by openerp
1089                             throw new Error(r);
1090                        }
1091                     });
1092                 },
1093                 read_function: function(id, fields, options) {
1094                     return self.dataset.read_ids.apply(self.dataset, arguments).done(function() {
1095                     }).fail(function (r, event) {
1096                         if (!r.data.message) { //else manage by openerp
1097                             throw new Error(r);
1098                         }
1099                     });
1100                 },
1101             });
1102             pop.on('closed', self, function() {
1103                 // ``self.trigger('close')`` would itself destroy all child element including
1104                 // the slow create popup, which would then re-trigger recursively the 'closed' signal.  
1105                 // Thus, here, we use a deferred and its state to cut the endless recurrence.
1106                 if (def.state() === "pending") {
1107                     def.resolve();
1108                 }
1109             });
1110             pop.on('create_completed', self, function(id) {
1111                 created = true;
1112                 self.trigger('slowadded');
1113             });
1114             def.then(function() {
1115                 if (created) {
1116                     var parent = self.getParent();
1117                     parent.$calendar.fullCalendar('refetchEvents');
1118                 }
1119                 self.trigger('close');
1120             });
1121             return def;
1122         },
1123     });
1124
1125
1126     /**
1127      * Form widgets
1128      */
1129
1130     function widget_calendar_lazy_init() {
1131         if (instance.web.form.Many2ManyCalendarView) {
1132             return;
1133         }
1134
1135         instance.web_calendar.FieldCalendarView = instance.web_calendar.CalendarView.extend({
1136
1137             init: function (parent) {
1138                 this._super.apply(this, arguments);
1139                 // Warning: this means only a field_widget should instanciate this Class
1140                 this.field_widget = parent;
1141             },
1142
1143             view_loading: function (fv) {
1144                 var self = this;
1145                 return $.when(this._super.apply(this, arguments)).then(function() {
1146                     self.on('event_rendered', this, function (event, element, view) {
1147
1148                     });
1149                 });
1150             },
1151
1152             // In forms, we could be hidden in a notebook. Thus we couldn't
1153             // render correctly fullcalendar so we try to detect when we are
1154             // not visible to wait for when we will be visible.
1155             init_calendar: function() {
1156                 if (this.$calendar.width() !== 0) { // visible
1157                     return this._super();
1158                 }
1159                 // find all parents tabs.
1160                 var def = $.Deferred();
1161                 var self = this;
1162                 this.$calendar.parents(".ui-tabs").on('tabsactivate', this, function() {
1163                     if (self.$calendar.width() !== 0) { // visible
1164                         self.$calendar.fullCalendar(self.get_fc_init_options());
1165                         def.resolve();
1166                     }
1167                 });
1168                 return def;
1169             },
1170         });
1171     }
1172
1173     instance.web_calendar.BufferedDataSet = instance.web.BufferedDataSet.extend({
1174
1175         /**
1176          * Adds verification on possible missing fields for the sole purpose of
1177          * O2M dataset being compatible with the ``slow_create`` detection of
1178          * missing fields... which is as simple to try to write and upon failure
1179          * go to ``slow_create``. Current BufferedDataSet would'nt fail because
1180          * they do not send data to the server at create time.
1181          */
1182         create: function (data, options) {
1183             var def = $.Deferred();
1184             var self = this;
1185             var create = this._super;
1186             if (_.isUndefined(this.required_fields)) {
1187                 this.required_fields = (new instance.web.Model(this.model))
1188                     .call('fields_get').then(function (fields_def) {
1189                         return _(fields_def).chain()
1190                          // equiv to .pairs()
1191                             .map(function (value, key) { return [key, value]; })
1192                          // equiv to .omit(self.field_widget.field.relation_field)
1193                             .filter(function (pair) { return pair[0] !== self.field_widget.field.relation_field; })
1194                             .filter(function (pair) { return pair[1].required; })
1195                             .map(function (pair) { return pair[0]; })
1196                             .value();
1197                     });
1198             }
1199             $.when(this.required_fields).then(function (required_fields) {
1200                 var missing_fields = _(required_fields).filter(function (v) {
1201                     return _.isUndefined(data[v]);
1202                 });
1203                 var default_get = (missing_fields.length !== 0) ?
1204                     self.default_get(missing_fields) : [];
1205                 $.when(default_get).then(function (defaults) {
1206
1207                     // Remove all fields that have a default from the missing fields.
1208                     missing_fields = _(missing_fields).filter(function (f) {
1209                         return _.isUndefined(defaults[f]);
1210                     });
1211                     if (missing_fields.length !== 0) {
1212                         def.reject(
1213                             _.str.sprintf(
1214                                 _t("Missing required fields %s"), missing_fields.join(", ")),
1215                             $.Event());
1216                         return;
1217                     }
1218                     create.apply(self, [data, options]).then(function (result) {
1219                         def.resolve(result);
1220                     });
1221                 });
1222             });
1223             return def;
1224         },
1225     });
1226
1227     instance.web_calendar.fields_dataset = new instance.web.Registry({
1228         'many2many': 'instance.web.DataSetStatic',
1229         'one2many': 'instance.web_calendar.BufferedDataSet',
1230     });
1231
1232
1233     function get_field_dataset_class(type) {
1234         var obj = instance.web_calendar.fields_dataset.get_any([type]);
1235         if (!obj) {
1236             throw new Error(_.str.sprintf(_t("Dataset for type '%s' is not defined."), type));
1237         }
1238
1239         // Override definition of legacy datasets to add field_widget context
1240         return obj.extend({
1241             init: function (parent) {
1242                 this._super.apply(this, arguments);
1243                 this.field_widget = parent;
1244             },
1245             get_context: function() {
1246                 this.context = this.field_widget.build_context();
1247                 return this.context;
1248             }
1249         });
1250     }
1251
1252     /**
1253      * Common part to manage any field using calendar view
1254      */
1255     instance.web_calendar.FieldCalendar = instance.web.form.AbstractField.extend({
1256
1257         disable_utility_classes: true,
1258         calendar_view_class: 'instance.web_calendar.FieldCalendarView',
1259
1260         init: function(field_manager, node) {
1261             this._super(field_manager, node);
1262             widget_calendar_lazy_init();
1263             this.is_loaded = $.Deferred();
1264             this.initial_is_loaded = this.is_loaded;
1265
1266             var self = this;
1267
1268             // This dataset will use current widget to '.build_context()'.
1269             var field_type = field_manager.fields_view.fields[node.attrs.name].type;
1270             this.dataset = new (get_field_dataset_class(field_type))(
1271                 this, this.field.relation);
1272
1273             this.dataset.on('unlink', this, function(_ids) {
1274                 this.dataset.trigger('dataset_changed');
1275             });
1276
1277             // quick_create widget instance will be attached when spawned
1278             this.quick_create = null;
1279
1280             this.no_rerender = true;
1281
1282         },
1283
1284         start: function() {
1285             this._super.apply(this, arguments);
1286
1287             var self = this;
1288
1289             self.load_view();
1290             self.on("change:effective_readonly", self, function() {
1291                 self.is_loaded = self.is_loaded.then(function() {
1292                     self.calendar_view.destroy();
1293                     return $.when(self.load_view()).done(function() {
1294                         self.render_value();
1295                     });
1296                 });
1297             });
1298         },
1299
1300         load_view: function() {
1301             var self = this;
1302             var calendar_view_class = get_class(this.calendar_view_class);
1303             this.calendar_view = new calendar_view_class(this, this.dataset, false, $.extend({
1304                 'create_text': _t("Add"),
1305                 'creatable': self.get("effective_readonly") ? false : true,
1306                 'quick_creatable': self.get("effective_readonly") ? false : true,
1307                 'read_only_mode': self.get("effective_readonly") ? true : false,
1308                 'confirm_on_delete': false,
1309             }, this.options));
1310             var embedded = (this.field.views || {}).calendar;
1311             if (embedded) {
1312                 this.calendar_view.set_embedded_view(embedded);
1313             }
1314             var loaded = $.Deferred();
1315             this.calendar_view.on("calendar_view_loaded", self, function() {
1316                 self.initial_is_loaded.resolve();
1317                 loaded.resolve();
1318             });
1319             this.calendar_view.on('switch_mode', this, this.open_popup);
1320             $.async_when().done(function () {
1321                 self.calendar_view.appendTo(self.$el);
1322             });
1323             return loaded;
1324         },
1325
1326         render_value: function() {
1327             var self = this;
1328             this.dataset.set_ids(this.get("value"));
1329             this.is_loaded = this.is_loaded.then(function() {
1330                 return self.calendar_view.do_search(self.build_domain(), self.dataset.get_context(), []);
1331             });
1332         },
1333
1334         open_popup: function(type, unused) {
1335             if (type !== "form") { return; }
1336             if (this.dataset.index == null) {
1337                 if (typeof this.open_popup_add === "function") {
1338                     this.open_popup_add();
1339                 }
1340             } else {
1341                 if (typeof this.open_popup_edit === "function") {
1342                     this.open_popup_edit();
1343                 }
1344             }
1345         },
1346
1347         open_popup_add: function() {
1348             throw new Error("Not Implemented");
1349         },
1350
1351         open_popup_edit: function() {
1352             var id = this.dataset.ids[this.dataset.index];
1353             var self = this;
1354             var pop = (new instance.web.form.FormOpenPopup(this));
1355             pop.show_element(this.field.relation, id, this.build_context(), {
1356                 title: _t("Open: ") + this.string,
1357                 write_function: function(id, data, _options) {
1358                     return self.dataset.write(id, data, {}).done(function() {
1359                         // Note that dataset will trigger itself the
1360                         // ``dataset_changed`` signal
1361                         self.calendar_view.refresh_event(id);
1362                     });
1363                 },
1364                 read_function: function(id, fields, options) {
1365                     return self.dataset.read_ids.apply(self.dataset, arguments).done(function() {
1366                     }).fail(function (r, event) {
1367                         throw new Error(r);
1368                     });
1369                 },
1370
1371                 alternative_form_view: this.field.views ? this.field.views.form : undefined,
1372                 parent_view: this.view,
1373                 child_name: this.name,
1374                 readonly: this.get("effective_readonly")
1375             });
1376         }
1377     });
1378
1379     instance.web_calendar.Sidebar = instance.web.Widget.extend({
1380         template: 'CalendarView.sidebar',
1381         
1382         start: function() {
1383             this._super();
1384             this.filter = new instance.web_calendar.SidebarFilter(this, this.getParent());
1385             this.filter.appendTo(this.$el.find('.oe_calendar_filter'));
1386         }
1387     });
1388     instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
1389         events: {
1390             'change input:checkbox': 'filter_click'
1391         },
1392         init: function(parent, view) {
1393             this._super(parent);
1394             this.view = view;
1395         },
1396         set_filters: function() {
1397             var self = this;
1398             _.forEach(self.view.all_filters, function(o) {
1399                 if (_.contains(self.view.now_filter_ids, o.value)) {
1400                     self.$('div.oe_calendar_responsible input[value=' + o.value + ']').prop('checked',o.is_checked);
1401                 }
1402             });
1403         },
1404         events_loaded: function(filters) {
1405             var self = this;
1406             if (filters == null) {
1407                 filters = [];
1408                 _.forEach(self.view.all_filters, function(o) {
1409                     if (_.contains(self.view.now_filter_ids, o.value)) {
1410                         filters.push(o);
1411                     }
1412                 });
1413             }            
1414             this.$el.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters }));
1415         },
1416         filter_click: function(e) {
1417             var self = this;
1418             if (self.view.all_filters[0] && e.target.value == self.view.all_filters[0].value) {
1419                 self.view.all_filters[0].is_checked = e.target.checked;
1420             } else {
1421                 self.view.all_filters[parseInt(e.target.value)].is_checked = e.target.checked;
1422             }
1423             self.view.$calendar.fullCalendar('refetchEvents');
1424         },
1425     });
1426
1427 };