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