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