diff --git a/addons/web_calendar/static/src/js/web_calendar.js b/addons/web_calendar/static/src/js/web_calendar.js
index f92e6e2262a..6377ee90ca6 100644
--- a/addons/web_calendar/static/src/js/web_calendar.js
+++ b/addons/web_calendar/static/src/js/web_calendar.js
@@ -1,1438 +1,945 @@
+odoo.define('web_calendar.CalendarView', ['web.core', 'web.data', 'web.form_common', 'web.Model', 'web.time', 'web.View', 'web_calendar.widgets'], function (require) {
+"use strict";
/*---------------------------------------------------------
* OpenERP web_calendar
*---------------------------------------------------------*/
-_.str.toBoolElse = function (str, elseValues, trueValues, falseValues) {
- var ret = _.str.toBool(str, trueValues, falseValues);
- if (_.isUndefined(ret)) {
- return elseValues;
- }
- return ret;
-};
+var core = require('web.core');
+var data = require('web.data');
+var form_common = require('web.form_common');
+var Model = require('web.Model');
+var time = require('web.time');
+var View = require('web.View');
+var widgets = require('web_calendar.widgets');
-openerp.web_calendar = function(instance) {
- var _t = instance.web._t,
- _lt = instance.web._lt,
- QWeb = instance.web.qweb;
+var CompoundDomain = data.CompoundDomain;
- function get_class(name) {
- return new instance.web.Registry({'tmp' : name}).get_object("tmp");
- }
+var _t = core._t;
+var _lt = core._lt;
+var QWeb = core.qweb;
- function get_fc_defaultOptions() {
- var converter = {
- A: 'TT',
- a: 'tt',
- };
- var shortTimeformat = moment._locale._longDateFormat.LT.replace(/([a-zA-Z])\1*/g, function (s) {
- return converter[s] || s;
+function get_fc_defaultOptions() {
+ var shortTimeformat = moment._locale._longDateFormat.LT;
+ var dateFormat = time.strftime_to_moment_format(_t.database.parameters.date_format);
+
+ // adapt format for fullcalendar v1.
+ // see http://fullcalendar.io/docs1/utilities/formatDate/
+ var conversions = [['YYYY', 'yyyy'], ['YY', 'y'], ['DDDD', 'dddd'], ['DD', 'dd']];
+ _.each(conversions, function(conv) {
+ dateFormat = dateFormat.replace(conv[0], conv[1]);
+ });
+
+ return {
+ weekNumberTitle: _t("W"),
+ allDayText: _t("All day"),
+ buttonText : {
+ today: _t("Today"),
+ month: _t("Month"),
+ week: _t("Week"),
+ day: _t("Day")
+ },
+ monthNames: moment.months(),
+ monthNamesShort: moment.monthsShort(),
+ dayNames: moment.weekdays(),
+ dayNamesShort: moment.weekdaysShort(),
+ firstDay: moment._locale._week.dow,
+ weekNumbers: true,
+ axisFormat : shortTimeformat.replace(/:mm/,'(:mm)'),
+ timeFormat : {
+ // for agendaWeek and agendaDay
+ agenda: shortTimeformat + '{ - ' + shortTimeformat + '}', // 5:00 - 6:30
+ // for all other views
+ '': shortTimeformat.replace(/:mm/,'(:mm)') // 7pm
+ },
+ titleFormat: {
+ month: 'MMMM yyyy',
+ week: dateFormat + "{ '—'"+ dateFormat,
+ day: dateFormat,
+ },
+ columnFormat: {
+ month: 'ddd',
+ week: 'ddd ' + dateFormat,
+ day: 'dddd ' + dateFormat,
+ },
+ weekMode : 'liquid',
+ aspectRatio: 1.8,
+ snapMinutes: 15,
+ };
+}
+
+function is_virtual_id(id) {
+ return typeof id === "string" && id.indexOf('-') >= 0;
+}
+
+function isNullOrUndef(value) {
+ return _.isUndefined(value) || _.isNull(value);
+}
+
+var CalendarView = View.extend({
+ template: "CalendarView",
+ display_name: _lt('Calendar'),
+ quick_create_instance: widgets.QuickCreate,
+
+ init: function (parent, dataset, view_id, options) {
+ this._super(parent);
+ this.ready = $.Deferred();
+ this.set_default_options(options);
+ this.dataset = dataset;
+ this.model = dataset.model;
+ this.fields_view = {};
+ this.view_id = view_id;
+ this.view_type = 'calendar';
+ this.color_map = {};
+ this.range_start = null;
+ this.range_stop = null;
+ this.selected_filters = [];
+
+ this.shown = $.Deferred();
+ },
+
+ set_default_options: function(options) {
+ this._super(options);
+ _.defaults(this.options, {
+ confirm_on_delete: true
});
- var dateFormat = instance.web.normalize_format(_t.database.parameters.date_format);
+ },
- // adapt format for fullcalendar v1.
- // see http://fullcalendar.io/docs1/utilities/formatDate/
- var conversions = [['YYYY', 'yyyy'], ['YY', 'y'], ['DDDD', 'dddd'], ['DD', 'dd']];
- _.each(conversions, function(conv) {
- dateFormat = dateFormat.replace(conv[0], conv[1]);
+ destroy: function() {
+ this.$calendar.fullCalendar('destroy');
+ if (this.$small_calendar) {
+ this.$small_calendar.datepicker('destroy');
+ }
+ this._super.apply(this, arguments);
+ },
+
+ view_loading: function (fv) {
+ /* xml view calendar options */
+ var attrs = fv.arch.attrs,
+ self = this;
+ this.fields_view = fv;
+ this.$calendar = this.$el.find(".oe_calendar_widget");
+
+ this.info_fields = [];
+
+ /* buttons */
+ this.$buttons = $(QWeb.render("CalendarView.buttons", {'widget': this}));
+ if (this.options.$buttons) {
+ this.$buttons.appendTo(this.options.$buttons);
+ } else {
+ this.$el.find('.oe_calendar_buttons').replaceWith(this.$buttons);
+ }
+
+ this.$buttons.on('click', 'button.oe_calendar_button_new', function () {
+ self.dataset.index = null;
+ self.do_switch_view('form');
});
- return {
- weekNumberTitle: _t("W"),
- allDayText: _t("All day"),
- buttonText : {
- today: _t("Today"),
- month: _t("Month"),
- week: _t("Week"),
- day: _t("Day")
- },
- monthNames: moment.months(),
- monthNamesShort: moment.monthsShort(),
- dayNames: moment.weekdays(),
- dayNamesShort: moment.weekdaysShort(),
- firstDay: moment._locale._week.dow,
- weekNumbers: true,
- axisFormat : shortTimeformat.replace(/:mm/,'(:mm)'),
- timeFormat : {
- // for agendaWeek and agendaDay
- agenda: shortTimeformat + '{ - ' + shortTimeformat + '}', // 5:00 - 6:30
- // for all other views
- '': shortTimeformat.replace(/:mm/,'(:mm)') // 7pm
- },
- titleFormat: {
- month: 'MMMM yyyy',
- week: dateFormat + "{ '—'"+ dateFormat,
- day: dateFormat,
- },
- columnFormat: {
- month: 'ddd',
- week: 'ddd ' + dateFormat,
- day: 'dddd ' + dateFormat,
- },
- weekMode : 'liquid',
- aspectRatio: 1.8,
- snapMinutes: 15,
- };
- }
+ if (!attrs.date_start) {
+ throw new Error(_t("Calendar view has not defined 'date_start' attribute."));
+ }
- function is_virtual_id(id) {
- return typeof id === "string" && id.indexOf('-') >= 0;
- }
+ this.$el.addClass(attrs['class']);
- function isNullOrUndef(value) {
- return _.isUndefined(value) || _.isNull(value);
- }
+ this.name = fv.name || attrs.string;
+ this.view_id = fv.view_id;
- instance.web.views.add('calendar', 'instance.web_calendar.CalendarView');
+ this.mode = attrs.mode; // one of month, week or day
+ this.date_start = attrs.date_start; // Field name of starting date field
+ this.date_delay = attrs.date_delay; // duration
+ this.date_stop = attrs.date_stop;
+ this.all_day = attrs.all_day;
+ this.how_display_event = '';
+ this.attendee_people = attrs.attendee;
- instance.web_calendar.CalendarView = instance.web.View.extend({
- template: "CalendarView",
- display_name: _lt('Calendar'),
- quick_create_instance: 'instance.web_calendar.QuickCreate',
+ //if quick_add = False, we don't allow quick_add
+ //if quick_add = not specified in view, we use the default quick_create_instance
+ //if quick_add = is NOT False and IS specified in view, we this one for quick_create_instance'
- init: function (parent, dataset, view_id, options) {
- this._super(parent);
- this.ready = $.Deferred();
- this.set_default_options(options);
- this.dataset = dataset;
- this.model = dataset.model;
- this.fields_view = {};
- this.view_id = view_id;
- this.view_type = 'calendar';
- this.color_map = {};
- this.range_start = null;
- this.range_stop = null;
- this.selected_filters = [];
+ this.quick_add_pop = (isNullOrUndef(attrs.quick_add) || _.str.toBoolElse(attrs.quick_add, true));
+ // The display format which will be used to display the event where fields are between "[" and "]"
+ if (!isNullOrUndef(attrs.display)) {
+ this.how_display_event = attrs.display; // String with [FIELD]
+ }
- this.shown = $.Deferred();
- },
+ // 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
+ if (isNullOrUndef(attrs.event_open_popup) || !_.str.toBoolElse(attrs.event_open_popup, true)) {
+ this.open_popup_action = false;
+ } else {
+ this.open_popup_action = attrs.event_open_popup;
+ }
+ // If this field is set to true, we will use the calendar_friends model as filter and not the color field.
+ this.useContacts = (!isNullOrUndef(attrs.use_contacts) && _.str.toBool(attrs.use_contacts)) && (!isNullOrUndef(self.options.$sidebar));
- set_default_options: function(options) {
- this._super(options);
- _.defaults(this.options, {
- confirm_on_delete: true
- });
- },
+ // 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
+ // The color is the color of the attendee, so don't need to show again that it will be present
+ this.colorIsAttendee = (!(isNullOrUndef(attrs.color_is_attendee) || !_.str.toBoolElse(attrs.color_is_attendee, true))) && (!isNullOrUndef(self.options.$sidebar));
- destroy: function() {
- this.$calendar.fullCalendar('destroy');
- if (this.$small_calendar) {
- this.$small_calendar.datepicker('destroy');
- }
- this._super.apply(this, arguments);
- },
+ // if we have not sidebar, (eg: Dashboard), we don't use the filter "coworkers"
+ if (isNullOrUndef(self.options.$sidebar)) {
+ this.useContacts = false;
+ this.colorIsAttendee = false;
+ this.attendee_people = undefined;
+ }
- view_loading: function (fv) {
- /* xml view calendar options */
- var attrs = fv.arch.attrs,
- self = this;
- this.fields_view = fv;
- this.$calendar = this.$el.find(".oe_calendar_widget");
-
- this.info_fields = [];
-
- /* buttons */
- this.$buttons = $(QWeb.render("CalendarView.buttons", {'widget': this}));
- if (this.options.$buttons) {
- this.$buttons.appendTo(this.options.$buttons);
- } else {
- this.$el.find('.oe_calendar_buttons').replaceWith(this.$buttons);
- }
-
- this.$buttons.on('click', 'button.oe_calendar_button_new', function () {
- self.dataset.index = null;
- self.do_switch_view('form');
- });
-
- if (!attrs.date_start) {
- throw new Error(_t("Calendar view has not defined 'date_start' attribute."));
- }
-
- this.$el.addClass(attrs['class']);
-
- this.name = fv.name || attrs.string;
- this.string = attrs.string || fv.name;
- this.view_id = fv.view_id;
-
- this.mode = attrs.mode; // one of month, week or day
- this.date_start = attrs.date_start; // Field name of starting date field
- this.date_delay = attrs.date_delay; // duration
- this.date_stop = attrs.date_stop;
- this.all_day = attrs.all_day;
- this.how_display_event = '';
- this.attendee_people = attrs.attendee;
-
- if (!isNullOrUndef(attrs.quick_create_instance)) {
- self.quick_create_instance = 'instance.' + attrs.quick_create_instance;
- }
-
- //if quick_add = False, we don't allow quick_add
- //if quick_add = not specified in view, we use the default quick_create_instance
- //if quick_add = is NOT False and IS specified in view, we this one for quick_create_instance'
-
- this.quick_add_pop = (isNullOrUndef(attrs.quick_add) || _.str.toBoolElse(attrs.quick_add, true));
- if (this.quick_add_pop && !isNullOrUndef(attrs.quick_add)) {
- self.quick_create_instance = 'instance.' + attrs.quick_add;
- }
- // The display format which will be used to display the event where fields are between "[" and "]"
- if (!isNullOrUndef(attrs.display)) {
- this.how_display_event = attrs.display; // String with [FIELD]
- }
-
- // 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
- if (isNullOrUndef(attrs.event_open_popup) || !_.str.toBoolElse(attrs.event_open_popup, true)) {
- this.open_popup_action = false;
- } else {
- this.open_popup_action = attrs.event_open_popup;
- }
- // If this field is set to true, we will use the calendar_friends model as filter and not the color field.
- this.useContacts = (!isNullOrUndef(attrs.use_contacts) && _.str.toBool(attrs.use_contacts)) && (!isNullOrUndef(self.options.$sidebar));
-
- // 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
- // The color is the color of the attendee, so don't need to show again that it will be present
- this.colorIsAttendee = (!(isNullOrUndef(attrs.color_is_attendee) || !_.str.toBoolElse(attrs.color_is_attendee, true))) && (!isNullOrUndef(self.options.$sidebar));
-
- // if we have not sidebar, (eg: Dashboard), we don't use the filter "coworkers"
- if (isNullOrUndef(self.options.$sidebar)) {
- this.useContacts = false;
- this.colorIsAttendee = false;
- this.attendee_people = undefined;
- }
-
-/*
- Will be more logic to do it in futur, but see below to stay Retro-compatible
-
- if (isNull(attrs.avatar_model)) {
- this.avatar_model = 'res.partner';
- }
- else {
- if (attrs.avatar_model == 'False') {
- this.avatar_model = null;
- }
- else {
- this.avatar_model = attrs.avatar_model;
- }
- }
-*/
- if (isNullOrUndef(attrs.avatar_model)) {
- this.avatar_model = null;
- } else {
- this.avatar_model = attrs.avatar_model;
- }
-
- if (isNullOrUndef(attrs.avatar_title)) {
- this.avatar_title = this.avatar_model;
- } else {
- this.avatar_title = attrs.avatar_title;
- }
-
- if (isNullOrUndef(attrs.avatar_filter)) {
- this.avatar_filter = this.avatar_model;
- } else {
- this.avatar_filter = attrs.avatar_filter;
- }
-
- this.color_field = attrs.color;
-
- if (this.color_field && this.selected_filters.length === 0) {
- var default_filter;
- if ((default_filter = this.dataset.context['calendar_default_' + this.color_field])) {
- this.selected_filters.push(default_filter + '');
- }
- }
-
- this.fields = fv.fields;
-
- for (var fld = 0; fld < fv.arch.children.length; fld++) {
- this.info_fields.push(fv.arch.children[fld].attrs.name);
- }
-
- self.shown.done(this._do_show_init.bind(this));
- var edit_check = new instance.web.Model(this.dataset.model)
- .call("check_access_rights", ["write", false])
- .then(function (write_right) {
- self.write_right = write_right;
- });
- var init = new instance.web.Model(this.dataset.model)
- .call("check_access_rights", ["create", false])
- .then(function (create_right) {
- self.create_right = create_right;
- self.ready.resolve();
- self.trigger('calendar_view_loaded', fv);
- });
- return $.when(edit_check, init);
- },
- _do_show_init: function () {
- var self = this;
- this.init_calendar().then(function() {
- $(window).trigger('resize');
- self.trigger('calendar_view_loaded', self.fields_view);
- });
- },
- get_fc_init_options: function () {
- //Documentation here : http://arshaw.com/fullcalendar/docs/
- var self = this;
- return $.extend({}, get_fc_defaultOptions(), {
+ /*
+ Will be more logic to do it in futur, but see below to stay Retro-compatible
- defaultView: (this.mode == "month")?"month":
- (this.mode == "week"?"agendaWeek":
- (this.mode == "day"?"agendaDay":"month")),
- header: {
- left: 'prev,next today',
- center: 'title',
- right: 'month,agendaWeek,agendaDay'
- },
- selectable: !this.options.read_only_mode && this.create_right,
- selectHelper: true,
- editable: !this.options.read_only_mode,
- droppable: true,
-
- // callbacks
-
- eventDrop: function (event, _day_delta, _minute_delta, _all_day, _revertFunc) {
- var data = self.get_event_data(event);
- self.proxy('update_record')(event._id, data); // we don't revert the event, but update it.
- },
- eventResize: function (event, _day_delta, _minute_delta, _revertFunc) {
- var data = self.get_event_data(event);
- self.proxy('update_record')(event._id, data);
- },
- eventRender: function (event, element, view) {
- element.find('.fc-event-title').html(event.title);
- },
- eventAfterRender: function (event, element, view) {
- if ((view.name !== 'month') && (((event.end-event.start)/60000)<=30)) {
- //if duration is too small, we see the html code of img
- var current_title = $(element.find('.fc-event-time')).text();
- var new_title = current_title.substr(0,current_title.indexOf("
0?current_title.indexOf("
= curView.start) {
- context.$calendar.fullCalendar('changeView','agendaDay');
- }
+ if (isNull(attrs.avatar_model)) {
+ this.avatar_model = 'res.partner';
}
- else if (curView.name != "agendaDay" || (curView.name == "agendaDay" && moment(curDate).diff(moment(curView.start))===0)) {
- context.$calendar.fullCalendar('changeView','agendaWeek');
- }
- context.$calendar.fullCalendar('gotoDate', obj.currentYear , obj.currentMonth, obj.currentDay);
- };
- },
-
- init_calendar: function() {
- var self = this;
-
- if (!this.sidebar && this.options.$sidebar) {
- translate = get_fc_defaultOptions();
- this.sidebar = new instance.web_calendar.Sidebar(this);
- this.sidebar.appendTo(this.$el.find('.oe_calendar_sidebar_container'));
-
- this.$small_calendar = self.$el.find(".oe_calendar_mini");
- this.$small_calendar.datepicker({
- onSelect: self.calendarMiniChanged(self),
- dayNamesMin : translate.dayNamesShort,
- monthNames: translate.monthNamesShort,
- firstDay: translate.firstDay,
- });
-
- this.extraSideBar();
- }
- self.$calendar.fullCalendar(self.get_fc_init_options());
-
- return $.when();
- },
- extraSideBar: function() {
- },
-
- open_quick_create: function(data_template) {
- if (!isNullOrUndef(this.quick)) {
- return this.quick.trigger('close');
- }
- var QuickCreate = get_class(this.quick_create_instance);
-
- this.options.disable_quick_create = this.options.disable_quick_create || !this.quick_add_pop;
-
- this.quick = new QuickCreate(this, this.dataset, true, this.options, data_template);
- this.quick.on('added', this, this.quick_created)
- .on('slowadded', this, this.slow_created)
- .on('close', this, function() {
- this.quick.destroy();
- delete this.quick;
- this.$calendar.fullCalendar('unselect');
- });
- this.quick.replace(this.$el.find('.oe_calendar_qc_placeholder'));
- this.quick.focus();
-
- },
-
- /**
- * Refresh one fullcalendar event identified by it's 'id' by reading OpenERP record state.
- * If event was not existent in fullcalendar, it'll be created.
- */
- refresh_event: function(id) {
- var self = this;
- if (is_virtual_id(id)) {
- // Should avoid "refreshing" a virtual ID because it can't
- // really be modified so it should never be refreshed. As upon
- // edition, a NEW event with a non-virtual id will be created.
- console.warn("Unwise use of refresh_event on a virtual ID.");
- }
- this.dataset.read_ids([id], _.keys(this.fields)).done(function (incomplete_records) {
- self.perform_necessary_name_gets(incomplete_records).then(function(records) {
- // Event boundaries were already changed by fullcalendar, but we need to reload them:
- var new_event = self.event_data_transform(records[0]);
- // fetch event_obj
- var event_objs = self.$calendar.fullCalendar('clientEvents', id);
- if (event_objs.length == 1) { // Already existing obj to update
- var event_obj = event_objs[0];
- // update event_obj
- _(new_event).each(function (value, key) {
- event_obj[key] = value;
- });
- self.$calendar.fullCalendar('updateEvent', event_obj);
- } else { // New event object to create
- self.$calendar.fullCalendar('renderEvent', new_event);
- // By forcing attribution of this event to this source, we
- // make sure that the event will be removed when the source
- // will be removed (which occurs at each do_search)
- self.$calendar.fullCalendar('clientEvents', id)[0].source = self.event_source;
+ else {
+ if (attrs.avatar_model == 'False') {
+ this.avatar_model = null;
}
- });
- });
- },
+ else {
+ this.avatar_model = attrs.avatar_model;
+ }
+ }
+ */
+ if (isNullOrUndef(attrs.avatar_model)) {
+ this.avatar_model = null;
+ } else {
+ this.avatar_model = attrs.avatar_model;
+ }
- get_color: function(key) {
- if (this.color_map[key]) {
- return this.color_map[key];
+ if (isNullOrUndef(attrs.avatar_title)) {
+ this.avatar_title = this.avatar_model;
+ } else {
+ this.avatar_title = attrs.avatar_title;
+ }
+
+ if (isNullOrUndef(attrs.avatar_filter)) {
+ this.avatar_filter = this.avatar_model;
+ } else {
+ this.avatar_filter = attrs.avatar_filter;
+ }
+
+ this.color_field = attrs.color;
+
+ if (this.color_field && this.selected_filters.length === 0) {
+ var default_filter;
+ if ((default_filter = this.dataset.context['calendar_default_' + this.color_field])) {
+ this.selected_filters.push(default_filter + '');
}
- var index = (((_.keys(this.color_map).length + 1) * 5) % 24) + 1;
- this.color_map[key] = index;
- return index;
- },
+ }
+
+ this.fields = fv.fields;
+
+ for (var fld = 0; fld < fv.arch.children.length; fld++) {
+ this.info_fields.push(fv.arch.children[fld].attrs.name);
+ }
+
+ self.shown.done(this._do_show_init.bind(this));
+ var edit_check = new Model(this.dataset.model)
+ .call("check_access_rights", ["write", false])
+ .then(function (write_right) {
+ self.write_right = write_right;
+ });
+ var init = new Model(this.dataset.model)
+ .call("check_access_rights", ["create", false])
+ .then(function (create_right) {
+ self.create_right = create_right;
+ self.ready.resolve();
+ self.trigger('calendar_view_loaded', fv);
+ });
+ return $.when(edit_check, init);
+ },
+ _do_show_init: function () {
+ var self = this;
+ this.init_calendar().then(function() {
+ $(window).trigger('resize');
+ self.trigger('calendar_view_loaded', self.fields_view);
+ });
+ },
+ get_fc_init_options: function () {
+ //Documentation here : http://arshaw.com/fullcalendar/docs/
+ var self = this;
+ return $.extend({}, get_fc_defaultOptions(), {
+
+ defaultView: (this.mode == "month")?"month":
+ (this.mode == "week"?"agendaWeek":
+ (this.mode == "day"?"agendaDay":"month")),
+ header: {
+ left: 'prev,next today',
+ center: 'title',
+ right: 'month,agendaWeek,agendaDay'
+ },
+ selectable: !this.options.read_only_mode && this.create_right,
+ selectHelper: true,
+ editable: !this.options.read_only_mode,
+ droppable: true,
+
+ // callbacks
+
+ eventDrop: function (event, _day_delta, _minute_delta, _all_day, _revertFunc) {
+ var data = self.get_event_data(event);
+ self.proxy('update_record')(event._id, data); // we don't revert the event, but update it.
+ },
+ eventResize: function (event, _day_delta, _minute_delta, _revertFunc) {
+ var data = self.get_event_data(event);
+ self.proxy('update_record')(event._id, data);
+ },
+ eventRender: function (event, element, view) {
+ element.find('.fc-event-title').html(event.title);
+ },
+ eventAfterRender: function (event, element, view) {
+ if ((view.name !== 'month') && (((event.end-event.start)/60000)<=30)) {
+ //if duration is too small, we see the html code of img
+ var current_title = $(element.find('.fc-event-time')).text();
+ var new_title = current_title.substr(0,current_title.indexOf("
0?current_title.indexOf("
= curView.start) {
+ context.$calendar.fullCalendar('changeView','agendaDay');
+ }
+ }
+ else if (curView.name != "agendaDay" || (curView.name == "agendaDay" && moment(curDate).diff(moment(curView.start))===0)) {
+ context.$calendar.fullCalendar('changeView','agendaWeek');
+ }
+ context.$calendar.fullCalendar('gotoDate', obj.currentYear , obj.currentMonth, obj.currentDay);
+ };
+ },
+
+ init_calendar: function() {
+ var self = this;
+
+ if (!this.sidebar && this.options.$sidebar) {
+ var translate = get_fc_defaultOptions();
+ this.sidebar = new widgets.Sidebar(this);
+ this.sidebar.appendTo(this.$el.find('.oe_calendar_sidebar_container'));
+
+ this.$small_calendar = self.$el.find(".oe_calendar_mini");
+ this.$small_calendar.datepicker({
+ onSelect: self.calendarMiniChanged(self),
+ dayNamesMin : translate.dayNamesShort,
+ monthNames: translate.monthNamesShort,
+ firstDay: translate.firstDay,
+ });
+
+ this.extraSideBar();
+ }
+ self.$calendar.fullCalendar(self.get_fc_init_options());
+ return $.when();
+ },
+ extraSideBar: function() {
+ },
- /**
- * In o2m case, records from dataset won't have names attached to their *2o values.
- * We should make sure this is the case.
- */
- perform_necessary_name_gets: function(evts) {
- var def = $.Deferred();
- var self = this;
- var to_get = {};
- _(this.info_fields).each(function (fieldname) {
- if (!_(["many2one", "one2one"]).contains(
- self.fields[fieldname].type))
+ get_quick_create_class: function () {
+ return widgets.QuickCreate;
+ },
+ open_quick_create: function(data_template) {
+ if (!isNullOrUndef(this.quick)) {
+ return this.quick.trigger('close');
+ }
+ var QuickCreate = this.get_quick_create_class();
+
+ this.options.disable_quick_create = this.options.disable_quick_create || !this.quick_add_pop;
+ this.quick = new QuickCreate(this, this.dataset, true, this.options, data_template);
+ this.quick.on('added', this, this.quick_created)
+ .on('slowadded', this, this.slow_created)
+ .on('close', this, function() {
+ this.quick.destroy();
+ delete this.quick;
+ this.$calendar.fullCalendar('unselect');
+ });
+ this.quick.replace(this.$el.find('.oe_calendar_qc_placeholder'));
+ this.quick.focus();
+
+ },
+
+ /**
+ * Refresh one fullcalendar event identified by it's 'id' by reading OpenERP record state.
+ * If event was not existent in fullcalendar, it'll be created.
+ */
+ refresh_event: function(id) {
+ var self = this;
+ if (is_virtual_id(id)) {
+ // Should avoid "refreshing" a virtual ID because it can't
+ // really be modified so it should never be refreshed. As upon
+ // edition, a NEW event with a non-virtual id will be created.
+ console.warn("Unwise use of refresh_event on a virtual ID.");
+ }
+ this.dataset.read_ids([id], _.keys(this.fields)).done(function (incomplete_records) {
+ self.perform_necessary_name_gets(incomplete_records).then(function(records) {
+ // Event boundaries were already changed by fullcalendar, but we need to reload them:
+ var new_event = self.event_data_transform(records[0]);
+ // fetch event_obj
+ var event_objs = self.$calendar.fullCalendar('clientEvents', id);
+ if (event_objs.length == 1) { // Already existing obj to update
+ var event_obj = event_objs[0];
+ // update event_obj
+ _(new_event).each(function (value, key) {
+ event_obj[key] = value;
+ });
+ self.$calendar.fullCalendar('updateEvent', event_obj);
+ } else { // New event object to create
+ self.$calendar.fullCalendar('renderEvent', new_event);
+ // By forcing attribution of this event to this source, we
+ // make sure that the event will be removed when the source
+ // will be removed (which occurs at each do_search)
+ self.$calendar.fullCalendar('clientEvents', id)[0].source = self.event_source;
+ }
+ });
+ });
+ },
+
+ get_color: function(key) {
+ if (this.color_map[key]) {
+ return this.color_map[key];
+ }
+ var index = (((_.keys(this.color_map).length + 1) * 5) % 24) + 1;
+ this.color_map[key] = index;
+ return index;
+ },
+
+
+ /**
+ * In o2m case, records from dataset won't have names attached to their *2o values.
+ * We should make sure this is the case.
+ */
+ perform_necessary_name_gets: function(evts) {
+ var def = $.Deferred();
+ var self = this;
+ var to_get = {};
+ _(this.info_fields).each(function (fieldname) {
+ if (!_(["many2one", "one2one"]).contains(
+ self.fields[fieldname].type))
+ return;
+ to_get[fieldname] = [];
+ _(evts).each(function (evt) {
+ var value = evt[fieldname];
+ if (value === false || (value instanceof Array)) {
return;
- to_get[fieldname] = [];
- _(evts).each(function (evt) {
- var value = evt[fieldname];
- if (value === false || (value instanceof Array)) {
- return;
- }
- to_get[fieldname].push(value);
- });
- if (to_get[fieldname].length === 0) {
- delete to_get[fieldname];
}
+ to_get[fieldname].push(value);
});
- var defs = _(to_get).map(function (ids, fieldname) {
- return (new instance.web.Model(self.fields[fieldname].relation))
- .call('name_get', ids).then(function (vals) {
- return [fieldname, vals];
- });
- });
-
- $.when.apply(this, defs).then(function() {
- var values = arguments;
- _(values).each(function(value) {
- var fieldname = value[0];
- var name_gets = value[1];
- _(name_gets).each(function(name_get) {
- _(evts).chain()
- .filter(function (e) {return e[fieldname] == name_get[0];})
- .each(function(evt) {
- evt[fieldname] = name_get;
- });
- });
+ if (to_get[fieldname].length === 0) {
+ delete to_get[fieldname];
+ }
+ });
+ var defs = _(to_get).map(function (ids, fieldname) {
+ return (new Model(self.fields[fieldname].relation))
+ .call('name_get', ids).then(function (vals) {
+ return [fieldname, vals];
+ });
+ });
+
+ $.when.apply(this, defs).then(function() {
+ var values = arguments;
+ _(values).each(function(value) {
+ var fieldname = value[0];
+ var name_gets = value[1];
+ _(name_gets).each(function(name_get) {
+ _(evts).chain()
+ .filter(function (e) {return e[fieldname] == name_get[0];})
+ .each(function(evt) {
+ evt[fieldname] = name_get;
+ });
});
- def.resolve(evts);
});
- return def;
- },
-
- /**
- * Transform OpenERP event object to fullcalendar event object
- */
- event_data_transform: function(evt) {
- var self = this;
+ def.resolve(evts);
+ });
+ return def;
+ },
+
+ /**
+ * Transform OpenERP event object to fullcalendar event object
+ */
+ event_data_transform: function(evt) {
+ var self = this;
+ var date_start;
+ var date_stop;
+ var date_delay = evt[this.date_delay] || 1.0,
+ all_day = this.all_day ? evt[this.all_day] : false,
+ res_computed_text = '',
+ the_title = '',
+ attendees = [];
- var date_delay = evt[this.date_delay] || 1.0,
- all_day = this.all_day ? evt[this.all_day] : false,
- res_computed_text = '',
- the_title = '',
- attendees = [];
+ if (!all_day) {
+ date_start = time.auto_str_to_date(evt[this.date_start]);
+ date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop]) : null;
+ }
+ else {
+ date_start = time.auto_str_to_date(evt[this.date_start].split(' ')[0],'start');
+ date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop].split(' ')[0],'start') : null;
+ }
- if (!all_day) {
- date_start = instance.web.auto_str_to_date(evt[this.date_start]);
- date_stop = this.date_stop ? instance.web.auto_str_to_date(evt[this.date_stop]) : null;
- }
- else {
- date_start = instance.web.auto_str_to_date(evt[this.date_start].split(' ')[0],'start');
- date_stop = this.date_stop ? instance.web.auto_str_to_date(evt[this.date_stop].split(' ')[0],'start') : null;
- }
-
- if (this.info_fields) {
- var temp_ret = {};
- res_computed_text = this.how_display_event;
-
- _.each(this.info_fields, function (fieldname) {
- var value = evt[fieldname];
- if (_.contains(["many2one", "one2one"], self.fields[fieldname].type)) {
- if (value === false) {
- temp_ret[fieldname] = null;
- }
- else if (value instanceof Array) {
- temp_ret[fieldname] = value[1]; // no name_get to make
- }
- else {
- throw new Error("Incomplete data received from dataset for record " + evt.id);
- }
+ if (this.info_fields) {
+ var temp_ret = {};
+ res_computed_text = this.how_display_event;
+
+ _.each(this.info_fields, function (fieldname) {
+ var value = evt[fieldname];
+ if (_.contains(["many2one", "one2one"], self.fields[fieldname].type)) {
+ if (value === false) {
+ temp_ret[fieldname] = null;
}
- else if (_.contains(["one2many","many2many"], self.fields[fieldname].type)) {
- if (value === false) {
- temp_ret[fieldname] = null;
- }
- else if (value instanceof Array) {
- temp_ret[fieldname] = value; // if x2many, keep all id !
- }
- else {
- throw new Error("Incomplete data received from dataset for record " + evt.id);
- }
+ else if (value instanceof Array) {
+ temp_ret[fieldname] = value[1]; // no name_get to make
}
else {
- temp_ret[fieldname] = value;
+ throw new Error("Incomplete data received from dataset for record " + evt.id);
+ }
+ }
+ else if (_.contains(["one2many","many2many"], self.fields[fieldname].type)) {
+ if (value === false) {
+ temp_ret[fieldname] = null;
+ }
+ else if (value instanceof Array) {
+ temp_ret[fieldname] = value; // if x2many, keep all id !
+ }
+ else {
+ throw new Error("Incomplete data received from dataset for record " + evt.id);
}
- res_computed_text = res_computed_text.replace("["+fieldname+"]",temp_ret[fieldname]);
- });
-
-
- if (res_computed_text.length) {
- the_title = res_computed_text;
}
else {
- var res_text= [];
- _.each(temp_ret, function(val,key) {
- if( typeof(val) == 'boolean' && val == false ) { }
- else { res_text.push(val) };
- });
- the_title = res_text.join(', ');
+ temp_ret[fieldname] = value;
}
- the_title = _.escape(the_title);
-
-
- the_title_avatar = '';
-
- if (! _.isUndefined(this.attendee_people)) {
- var MAX_ATTENDEES = 3;
- var attendee_showed = 0;
- var attendee_other = '';
+ res_computed_text = res_computed_text.replace("["+fieldname+"]",temp_ret[fieldname]);
+ });
- _.each(temp_ret[this.attendee_people],
- function (the_attendee_people) {
- attendees.push(the_attendee_people);
- attendee_showed += 1;
- if (attendee_showed<= MAX_ATTENDEES) {
- if (self.avatar_model !== null) {
- the_title_avatar += '
';
- }
- else {
- if (!self.colorIsAttendee || the_attendee_people != temp_ret[self.color_field]) {
- tempColor = (self.all_filters[the_attendee_people] !== undefined)
- ? self.all_filters[the_attendee_people].color
- : (self.all_filters[-1] ? self.all_filters[-1].color : 1);
- the_title_avatar += '';
- }//else don't add myself
- }
- }
- else {
- attendee_other += self.all_attendees[the_attendee_people] +", ";
- }
- }
- );
- if (attendee_other.length>2) {
- the_title_avatar += '+';
- }
- the_title = the_title_avatar + the_title;
- }
- }
- if (!date_stop && date_delay) {
- var m_start = moment(date_start).add(date_delay,'hours');
- date_stop = m_start.toDate();
- }
- var r = {
- 'start': moment(date_start).format('YYYY-MM-DD HH:mm:ss'),
- 'end': moment(date_stop).format('YYYY-MM-DD HH:mm:ss'),
- 'title': the_title,
- 'allDay': (this.fields[this.date_start].type == 'date' || (this.all_day && evt[this.all_day]) || false),
- 'id': evt.id,
- 'attendees':attendees
- };
- if (!self.useContacts || self.all_filters[evt[this.color_field]] !== undefined) {
- if (this.color_field && evt[this.color_field]) {
- var color_key = evt[this.color_field];
- if (typeof color_key === "object") {
- color_key = color_key[0];
- }
- r.className = 'cal_opacity calendar_color_'+ this.get_color(color_key);
- }
- }
- else { // if form all, get color -1
- r.className = 'cal_opacity calendar_color_'+ self.all_filters[-1].color;
- }
- return r;
- },
-
- /**
- * Transform fullcalendar event object to OpenERP Data object
- */
- get_event_data: function(event) {
-
- // Normalize event_end without changing fullcalendars event.
- var data = {
- name: event.title
- };
-
- var event_end = event.end;
- //Bug when we move an all_day event from week or day view, we don't have a dateend or duration...
- if (event_end == null) {
- var m_date = moment(event.start).add(2, 'hours');
- event_end = m_date.toDate();
- }
-
- if (event.allDay) {
- // Sometimes fullcalendar doesn't give any event.end.
- if (event_end == null || _.isUndefined(event_end)) {
- event_end = new Date(event.start);
- }
- if (this.all_day) {
- date_start_day = new Date(Date.UTC(event.start.getFullYear(),event.start.getMonth(),event.start.getDate()));
- date_stop_day = new Date(Date.UTC(event_end.getFullYear(),event_end.getMonth(),event_end.getDate()));
- }
- else {
- date_start_day = new Date(event.start.getFullYear(),event.start.getMonth(),event.start.getDate(),7);
- date_stop_day = new Date(event_end.getFullYear(),event_end.getMonth(),event_end.getDate(),19);
- }
- data[this.date_start] = instance.web.datetime_to_str(date_start_day);
- if (this.date_stop) {
- data[this.date_stop] = instance.web.datetime_to_str(date_stop_day);
- }
- diff_seconds = Math.round((date_stop_day.getTime() - date_start_day.getTime()) / 1000);
-
+ if (res_computed_text.length) {
+ the_title = res_computed_text;
}
else {
- data[this.date_start] = instance.web.datetime_to_str(event.start);
- if (this.date_stop) {
- data[this.date_stop] = instance.web.datetime_to_str(event_end);
- }
- diff_seconds = Math.round((event_end.getTime() - event.start.getTime()) / 1000);
+ var res_text= [];
+ _.each(temp_ret, function(val,key) {
+ if( typeof(val) === 'boolean' && val === false ) { }
+ else { res_text.push(val); }
+ });
+ the_title = res_text.join(', ');
}
+ the_title = _.escape(the_title);
+
+
+ var the_title_avatar = '';
+
+ if (! _.isUndefined(this.attendee_people)) {
+ var MAX_ATTENDEES = 3;
+ var attendee_showed = 0;
+ var attendee_other = '';
- if (this.all_day) {
- data[this.all_day] = event.allDay;
- }
-
- if (this.date_delay) {
-
- data[this.date_delay] = diff_seconds / 3600;
- }
- return data;
- },
-
- do_search: function (domain, context, _group_by) {
- var self = this;
- this.shown.done(function () {
- self._do_search(domain, context, _group_by);
- });
- },
- _do_search: function(domain, context, _group_by) {
- var self = this;
- if (! self.all_filters) {
- self.all_filters = {}
- }
-
- if (! _.isUndefined(this.event_source)) {
- this.$calendar.fullCalendar('removeEventSource', this.event_source);
- }
- this.event_source = {
- events: function(start, end, callback) {
- var current_event_source = self.event_source;
- self.dataset.read_slice(_.keys(self.fields), {
- offset: 0,
- domain: self.get_range_domain(domain, start, end),
- context: context,
- }).done(function(events) {
- if (self.dataset.index === null) {
- if (events.length) {
- self.dataset.index = 0;
+ _.each(temp_ret[this.attendee_people],
+ function (the_attendee_people) {
+ attendees.push(the_attendee_people);
+ attendee_showed += 1;
+ if (attendee_showed<= MAX_ATTENDEES) {
+ if (self.avatar_model !== null) {
+ the_title_avatar += '
';
+ }
+ else {
+ if (!self.colorIsAttendee || the_attendee_people != temp_ret[self.color_field]) {
+ var tempColor = (self.all_filters[the_attendee_people] !== undefined)
+ ? self.all_filters[the_attendee_people].color
+ : (self.all_filters[-1] ? self.all_filters[-1].color : 1);
+ the_title_avatar += '';
+ }//else don't add myself
}
- } else if (self.dataset.index >= events.length) {
- self.dataset.index = events.length ? 0 : null;
}
+ else {
+ attendee_other += self.all_attendees[the_attendee_people] +", ";
+ }
+ }
+ );
+ if (attendee_other.length>2) {
+ the_title_avatar += '+';
+ }
+ the_title = the_title_avatar + the_title;
+ }
+ }
+
+ if (!date_stop && date_delay) {
+ var m_start = moment(date_start).add(date_delay,'hours');
+ date_stop = m_start.toDate();
+ }
+ var r = {
+ 'start': moment(date_start).format('YYYY-MM-DD HH:mm:ss'),
+ 'end': moment(date_stop).format('YYYY-MM-DD HH:mm:ss'),
+ 'title': the_title,
+ 'allDay': (this.fields[this.date_start].type == 'date' || (this.all_day && evt[this.all_day]) || false),
+ 'id': evt.id,
+ 'attendees':attendees
+ };
+ if (!self.useContacts || self.all_filters[evt[this.color_field]] !== undefined) {
+ if (this.color_field && evt[this.color_field]) {
+ var color_key = evt[this.color_field];
+ if (typeof color_key === "object") {
+ color_key = color_key[0];
+ }
+ r.className = 'cal_opacity calendar_color_'+ this.get_color(color_key);
+ }
+ }
+ else { // if form all, get color -1
+ r.className = 'cal_opacity calendar_color_'+ self.all_filters[-1].color;
+ }
+ return r;
+ },
+
+ /**
+ * Transform fullcalendar event object to OpenERP Data object
+ */
+ get_event_data: function(event) {
+ var date_start_day;
+ var date_stop_day;
+ var diff_seconds;
- if (self.event_source !== current_event_source) {
- console.log("Consecutive ``do_search`` called. Cancelling.");
- return;
+ // Normalize event_end without changing fullcalendars event.
+ var data = {
+ name: event.title
+ };
+
+ var event_end = event.end;
+ //Bug when we move an all_day event from week or day view, we don't have a dateend or duration...
+ if (event_end === null) {
+ var m_date = moment(event.start).add(2, 'hours');
+ event_end = m_date.toDate();
+ }
+
+ if (event.allDay) {
+ // Sometimes fullcalendar doesn't give any event.end.
+ if (event_end === null || _.isUndefined(event_end)) {
+ event_end = new Date(event.start);
+ }
+ if (this.all_day) {
+ date_start_day = new Date(Date.UTC(event.start.getFullYear(),event.start.getMonth(),event.start.getDate()));
+ date_stop_day = new Date(Date.UTC(event_end.getFullYear(),event_end.getMonth(),event_end.getDate()));
+ }
+ else {
+ date_start_day = new Date(event.start.getFullYear(),event.start.getMonth(),event.start.getDate(),7);
+ date_stop_day = new Date(event_end.getFullYear(),event_end.getMonth(),event_end.getDate(),19);
+ }
+ data[this.date_start] = time.datetime_to_str(date_start_day);
+ if (this.date_stop) {
+ data[this.date_stop] = time.datetime_to_str(date_stop_day);
+ }
+ diff_seconds = Math.round((date_stop_day.getTime() - date_start_day.getTime()) / 1000);
+
+ }
+ else {
+ data[this.date_start] = time.datetime_to_str(event.start);
+ if (this.date_stop) {
+ data[this.date_stop] = time.datetime_to_str(event_end);
+ }
+ diff_seconds = Math.round((event_end.getTime() - event.start.getTime()) / 1000);
+ }
+
+ if (this.all_day) {
+ data[this.all_day] = event.allDay;
+ }
+
+ if (this.date_delay) {
+
+ data[this.date_delay] = diff_seconds / 3600;
+ }
+ return data;
+ },
+
+ do_search: function (domain, context, _group_by) {
+ var self = this;
+ this.shown.done(function () {
+ self._do_search(domain, context, _group_by);
+ });
+ },
+ _do_search: function(domain, context, _group_by) {
+ var self = this;
+ if (! self.all_filters) {
+ self.all_filters = {};
+ }
+
+ if (! _.isUndefined(this.event_source)) {
+ this.$calendar.fullCalendar('removeEventSource', this.event_source);
+ }
+ this.event_source = {
+ events: function(start, end, callback) {
+ var current_event_source = self.event_source;
+ self.dataset.read_slice(_.keys(self.fields), {
+ offset: 0,
+ domain: self.get_range_domain(domain, start, end),
+ context: context,
+ }).done(function(events) {
+ if (self.dataset.index === null) {
+ if (events.length) {
+ self.dataset.index = 0;
+ }
+ } else if (self.dataset.index >= events.length) {
+ self.dataset.index = events.length ? 0 : null;
+ }
+
+ if (self.event_source !== current_event_source) {
+ console.log("Consecutive ``do_search`` called. Cancelling.");
+ return;
+ }
+
+ if (!self.useContacts) { // If we use all peoples displayed in the current month as filter in sidebars
+ var filter_item;
+
+ self.now_filter_ids = [];
+
+ var color_field = self.fields[self.color_field];
+ _.each(events, function (e) {
+ var key,val = null;
+ if (self.color_field.type == "selection") {
+ key = e[self.color_field];
+ val = _.find( self.color_field.selection, function(name){ return name[0] === key;});
+ }
+ else {
+ key = e[self.color_field][0];
+ val = e[self.color_field];
+ }
+ if (!self.all_filters[key]) {
+ filter_item = {
+ value: key,
+ label: val[1],
+ color: self.get_color(key),
+ avatar_model: (_.str.toBoolElse(self.avatar_filter, true) ? self.avatar_filter : false ),
+ is_checked: true
+ };
+ self.all_filters[key] = filter_item;
+ }
+ if (! _.contains(self.now_filter_ids, key)) {
+ self.now_filter_ids.push(key);
+ }
+ });
+
+ if (self.sidebar) {
+ self.sidebar.filter.events_loaded();
+ self.sidebar.filter.set_filters();
+
+ events = $.map(events, function (e) {
+ var key = self.color_field.type == "selection" ? e[self.color_field] : e[self.color_field][0];
+ if (_.contains(self.now_filter_ids, key) && self.all_filters[key].is_checked) {
+ return e;
+ }
+ return null;
+ });
}
- if (!self.useContacts) { // If we use all peoples displayed in the current month as filter in sidebars
- var filter_item;
-
- self.now_filter_ids = [];
-
- var color_field = self.fields[self.color_field];
- _.each(events, function (e) {
- var key,val = null;
- if (self.color_field.type == "selection") {
- key = e[self.color_field];
- val = _.find( self.color_field.selection, function(name){ return name[0] === key;});
- }
- else {
- key = e[self.color_field][0];
- val = e[self.color_field];
- }
- if (!self.all_filters[key]) {
- filter_item = {
- value: key,
- label: val[1],
- color: self.get_color(key),
- avatar_model: (_.str.toBoolElse(self.avatar_filter, true) ? self.avatar_filter : false ),
- is_checked: true
- };
- self.all_filters[key] = filter_item;
- }
- if (! _.contains(self.now_filter_ids, key)) {
- self.now_filter_ids.push(key);
- }
- });
-
- if (self.sidebar) {
- self.sidebar.filter.events_loaded();
- self.sidebar.filter.set_filters();
-
+ }
+ else { //WE USE CONTACT
+ if (self.attendee_people !== undefined) {
+ //if we don't filter on 'Everybody's Calendar
+ if (!self.all_filters[-1] || !self.all_filters[-1].is_checked) {
+ var checked_filter = $.map(self.all_filters, function(o) { if (o.is_checked) { return o.value; }});
+ // If we filter on contacts... we keep only events from coworkers
events = $.map(events, function (e) {
- var key = self.color_field.type == "selection" ? e[self.color_field] : e[self.color_field][0];
- if (_.contains(self.now_filter_ids, key) && self.all_filters[key].is_checked) {
+ if (_.intersection(checked_filter,e[self.attendee_people]).length) {
return e;
}
return null;
});
}
-
}
- else { //WE USE CONTACT
- if (self.attendee_people !== undefined) {
- //if we don't filter on 'Everybody's Calendar
- if (!self.all_filters[-1] || !self.all_filters[-1].is_checked) {
- var checked_filter = $.map(self.all_filters, function(o) { if (o.is_checked) { return o.value; }});
- // If we filter on contacts... we keep only events from coworkers
- events = $.map(events, function (e) {
- if (_.intersection(checked_filter,e[self.attendee_people]).length) {
- return e;
- }
- return null;
- });
- }
- }
- }
- var all_attendees = $.map(events, function (e) { return e[self.attendee_people]; });
- all_attendees = _.chain(all_attendees).flatten().uniq().value();
+ }
+ var all_attendees = $.map(events, function (e) { return e[self.attendee_people]; });
+ all_attendees = _.chain(all_attendees).flatten().uniq().value();
- self.all_attendees = {};
- if (self.avatar_title !== null) {
- new instance.web.Model(self.avatar_title).query(["name"]).filter([["id", "in", all_attendees]]).all().then(function(result) {
- _.each(result, function(item) {
- self.all_attendees[item.id] = item.name;
- });
- }).done(function() {
- return self.perform_necessary_name_gets(events).then(callback);
- });
- }
- else {
- _.each(all_attendees,function(item){
- self.all_attendees[item] = '';
+ self.all_attendees = {};
+ if (self.avatar_title !== null) {
+ new Model(self.avatar_title).query(["name"]).filter([["id", "in", all_attendees]]).all().then(function(result) {
+ _.each(result, function(item) {
+ self.all_attendees[item.id] = item.name;
});
+ }).done(function() {
return self.perform_necessary_name_gets(events).then(callback);
- }
- });
- },
- eventDataTransform: function (event) {
- return self.event_data_transform(event);
- },
- };
- this.$calendar.fullCalendar('addEventSource', this.event_source);
- },
- /**
- * Build OpenERP Domain to filter object by this.date_start field
- * between given start, end dates.
- */
- get_range_domain: function(domain, start, end) {
- var format = instance.web.date_to_str;
-
- extend_domain = [[this.date_start, '>=', format(start)],
- [this.date_start, '<=', format(end)]];
-
- if (this.date_stop) {
- //add at start
- extend_domain.splice(0,0,'|','|','&');
- //add at end
- extend_domain.push(
- '&',
- [this.date_start, '<=', format(start)],
- [this.date_stop, '>=', format(start)],
- '&',
- [this.date_start, '<=', format(end)],
- [this.date_stop, '>=', format(start)]
- );
- //final -> (A & B) | (C & D) | (E & F) -> | | & A B & C D & E F
- }
- return new instance.web.CompoundDomain(domain, extend_domain);
- },
-
- /**
- * Updates record identified by ``id`` with values in object ``data``
- */
- update_record: function(id, data) {
- var self = this;
- delete(data.name); // Cannot modify actual name yet
- var index = this.dataset.get_id_index(id);
- if (index !== null) {
- event_id = this.dataset.ids[index];
- this.dataset.write(event_id, data, {}).done(function() {
- if (is_virtual_id(event_id)) {
- // this is a virtual ID and so this will create a new event
- // with an unknown id for us.
- self.$calendar.fullCalendar('refetchEvents');
- } else {
- // classical event that we can refresh
- self.refresh_event(event_id);
+ });
+ }
+ else {
+ _.each(all_attendees,function(item){
+ self.all_attendees[item] = '';
+ });
+ return self.perform_necessary_name_gets(events).then(callback);
}
});
- }
- return false;
- },
- open_event: function(id, title) {
- var self = this;
- if (! this.open_popup_action) {
- var index = this.dataset.get_id_index(id);
- this.dataset.index = index;
- if (this.write_right) {
- this.do_switch_view('form', null, { mode: "edit" });
+ },
+ eventDataTransform: function (event) {
+ return self.event_data_transform(event);
+ },
+ };
+ this.$calendar.fullCalendar('addEventSource', this.event_source);
+ },
+ /**
+ * Build OpenERP Domain to filter object by this.date_start field
+ * between given start, end dates.
+ */
+ get_range_domain: function(domain, start, end) {
+ var format = time.date_to_str;
+
+ var extend_domain = [[this.date_start, '>=', format(start)],
+ [this.date_start, '<=', format(end)]];
+
+ if (this.date_stop) {
+ //add at start
+ extend_domain.splice(0,0,'|','|','&');
+ //add at end
+ extend_domain.push(
+ '&',
+ [this.date_start, '<=', format(start)],
+ [this.date_stop, '>=', format(start)],
+ '&',
+ [this.date_start, '<=', format(end)],
+ [this.date_stop, '>=', format(start)]
+ );
+ //final -> (A & B) | (C & D) | (E & F) -> | | & A B & C D & E F
+ }
+ return new CompoundDomain(domain, extend_domain);
+ },
+
+ /**
+ * Updates record identified by ``id`` with values in object ``data``
+ */
+ update_record: function(id, data) {
+ var self = this;
+ var event_id;
+ delete(data.name); // Cannot modify actual name yet
+ var index = this.dataset.get_id_index(id);
+ if (index !== null) {
+ event_id = this.dataset.ids[index];
+ this.dataset.write(event_id, data, {}).done(function() {
+ if (is_virtual_id(event_id)) {
+ // this is a virtual ID and so this will create a new event
+ // with an unknown id for us.
+ self.$calendar.fullCalendar('refetchEvents');
} else {
- this.do_switch_view('form', null, { mode: "view" });
+ // classical event that we can refresh
+ self.refresh_event(event_id);
}
+ });
+ }
+ return false;
+ },
+ open_event: function(id, title) {
+ var self = this;
+ if (! this.open_popup_action) {
+ var index = this.dataset.get_id_index(id);
+ this.dataset.index = index;
+ if (this.write_right) {
+ this.do_switch_view('form', null, { mode: "edit" });
+ } else {
+ this.do_switch_view('form', null, { mode: "view" });
}
- else {
- var pop = new instance.web.form.FormOpenPopup(this);
- var id_cast = parseInt(id).toString() == id ? parseInt(id) : id;
- pop.show_element(this.dataset.model, id_cast, this.dataset.get_context(), {
- title: _.str.sprintf(_t("View: %s"),title),
- view_id: +this.open_popup_action,
- res_id: id_cast,
- target: 'new',
- readonly:true
- });
+ }
+ else {
+ var pop = new form_common.FormOpenPopup(this);
+ var id_cast = parseInt(id).toString() == id ? parseInt(id) : id;
+ pop.show_element(this.dataset.model, id_cast, this.dataset.get_context(), {
+ title: _.str.sprintf(_t("View: %s"),title),
+ view_id: +this.open_popup_action,
+ res_id: id_cast,
+ target: 'new',
+ readonly:true
+ });
- var form_controller = pop.view_form;
- form_controller.on("load_record", self, function(){
- button_delete = _.str.sprintf("",_t("Delete"));
- button_edit = _.str.sprintf("",_t("Edit Event"));
-
- pop.$el.closest(".modal").find(".modal-footer").prepend(button_delete);
- pop.$el.closest(".modal").find(".modal-footer").prepend(button_edit);
-
- $('.delme').click(
- function() {
- $('.oe_form_button_cancel').trigger('click');
- self.remove_event(id);
- }
- );
- $('.editme').click(
- function() {
- $('.oe_form_button_cancel').trigger('click');
- self.dataset.index = self.dataset.get_id_index(id);
- self.do_switch_view('form', null, { mode: "edit" });
- }
- );
- });
- }
+ var form_controller = pop.view_form;
+ form_controller.on("load_record", self, function(){
+ var button_delete = _.str.sprintf("",_t("Delete"));
+ var button_edit = _.str.sprintf("",_t("Edit Event"));
+
+ pop.$el.closest(".modal").find(".modal-footer").prepend(button_delete);
+ pop.$el.closest(".modal").find(".modal-footer").prepend(button_edit);
+
+ $('.delme').click(
+ function() {
+ $('.oe_form_button_cancel').trigger('click');
+ self.remove_event(id);
+ }
+ );
+ $('.editme').click(
+ function() {
+ $('.oe_form_button_cancel').trigger('click');
+ self.dataset.index = self.dataset.get_id_index(id);
+ self.do_switch_view('form', null, { mode: "edit" });
+ }
+ );
+ });
+ }
+ return false;
+ },
+
+ do_show: function() {
+ this.do_push_state({});
+ this.shown.resolve();
+ return this._super();
+ },
+ is_action_enabled: function(action) {
+ if (action === 'create' && !this.options.creatable) {
return false;
- },
+ }
+ return this._super(action);
+ },
- do_show: function() {
- this.do_push_state({});
- this.shown.resolve();
- return this._super();
- },
- is_action_enabled: function(action) {
- if (action === 'create' && !this.options.creatable) {
- return false;
- }
- return this._super(action);
- },
+ /**
+ * Handles a newly created record
+ *
+ * @param {id} id of the newly created record
+ */
+ quick_created: function (id) {
- /**
- * Handles a newly created record
- *
- * @param {id} id of the newly created record
+ /** Note:
+ * it's of the most utter importance NOT to use inplace
+ * modification on this.dataset.ids as reference to this
+ * data is spread out everywhere in the various widget.
+ * Some of these reference includes values that should
+ * trigger action upon modification.
*/
- quick_created: function (id) {
+ this.dataset.ids = this.dataset.ids.concat([id]);
+ this.dataset.trigger("dataset_changed", id);
+ this.refresh_event(id);
+ },
+ slow_created: function () {
+ // refresh all view, because maybe some recurrents item
+ var self = this;
+ if (self.sidebar) {
+ // force filter refresh
+ self.sidebar.filter.is_loaded = false;
+ }
+ self.$calendar.fullCalendar('refetchEvents');
+ },
- /** Note:
- * it's of the most utter importance NOT to use inplace
- * modification on this.dataset.ids as reference to this
- * data is spread out everywhere in the various widget.
- * Some of these reference includes values that should
- * trigger action upon modification.
- */
- this.dataset.ids = this.dataset.ids.concat([id]);
- this.dataset.trigger("dataset_changed", id);
- this.refresh_event(id);
- },
- slow_created: function () {
- // refresh all view, because maybe some recurrents item
- var self = this;
- if (self.sidebar) {
- // force filter refresh
- self.sidebar.filter.is_loaded = false;
- }
- self.$calendar.fullCalendar('refetchEvents');
- },
-
- remove_event: function(id) {
- var self = this;
- function do_it() {
- return $.when(self.dataset.unlink([id])).then(function() {
- self.$calendar.fullCalendar('removeEvents', id);
- });
- }
- if (this.options.confirm_on_delete) {
- if (confirm(_t("Are you sure you want to delete this record ?"))) {
- return do_it();
- }
- } else
+ remove_event: function(id) {
+ var self = this;
+ function do_it() {
+ return $.when(self.dataset.unlink([id])).then(function() {
+ self.$calendar.fullCalendar('removeEvents', id);
+ });
+ }
+ if (this.options.confirm_on_delete) {
+ if (confirm(_t("Are you sure you want to delete this record ?"))) {
return do_it();
- },
- });
-
-
- /**
- * Quick creation view.
- *
- * Triggers a single event "added" with a single parameter "name", which is the
- * name entered by the user
- *
- * @class
- * @type {*}
- */
- instance.web_calendar.QuickCreate = instance.web.Widget.extend({
- init: function(parent, dataset, buttons, options, data_template) {
- this._super(parent);
- this.dataset = dataset;
- this._buttons = buttons || false;
- this.options = options;
-
- // Can hold data pre-set from where you clicked on agenda
- this.data_template = data_template || {};
- },
- get_title: function () {
- var parent = this.getParent();
- if (_.isUndefined(parent)) {
- return _t("Create");
}
- var title = (_.isUndefined(parent.field_widget)) ?
- (parent.string || parent.name) :
- parent.field_widget.string || parent.field_widget.name || '';
- return _t("Create: ") + title;
- },
- start: function () {
- var self = this;
-
- if (this.options.disable_quick_create) {
- this.slow_create();
- return;
- }
- self.on('added', self, function() {
- self.trigger('close');
- });
-
- this.$dialog = new instance.web.Dialog(this, {
- title: this.get_title(),
- size: 'small',
- buttons: this._buttons ? [
- {text: _t("Create event"), oe_link_class: 'oe_highlight', click: function (e) {
- if (!self.quick_add()) {
- self.focus();
- }
- }},
- {text: _t("Edit event"), oe_link_class: 'oe_link', click: function (e) {
- self.slow_add();
- }}
- ] : []
- }, QWeb.render('CalendarView.quick_create', {widged: this}))
- .on('closed', self, function() {
- self.trigger('close');
- })
- .open();
- this.$input = this.$dialog.$('input').keyup(function enterHandler (e) {
- if(event.keyCode == 13){
- self.$input.off('keyup', enterHandler);
- if (!self.quick_add()){
- self.$input.on('keyup', enterHandler);
- }
- } else if (e.keyCode == 27 && self._buttons) {
- self.trigger('close');
- }
- });
- },
- focus: function() {
- this.$input.focus();
- },
-
- /**
- * Gathers data from the quick create dialog a launch quick_create(data) method
- */
- quick_add: function() {
- var val = this.$input.val();
- if (/^\s*$/.test(val)) {
- return false;
- }
- return this.quick_create({'name': val}).always(function() { return true; });
- },
-
- slow_add: function() {
- var val = this.$input.val();
- this.slow_create({'name': val});
- },
-
- /**
- * Handles saving data coming from quick create box
- */
- quick_create: function(data, options) {
- var self = this;
- return this.dataset.create($.extend({}, this.data_template, data), options)
- .then(function(id) {
- self.trigger('added', id);
- self.$input.val("");
- }).fail(function(r, event) {
- event.preventDefault();
- // This will occurs if there are some more fields required
- self.slow_create(data);
- });
- },
-
- /**
- * Show full form popup
- */
- get_form_popup_infos: function() {
- var parent = this.getParent();
- var infos = {
- view_id: false,
- title: this.name,
- };
- if (!_.isUndefined(parent) && !(_.isUndefined(parent.ViewManager))) {
- infos.view_id = parent.ViewManager.get_view_id('form');
- }
- return infos;
- },
- slow_create: function(data) {
- //if all day, we could reset time to display 00:00:00
-
- var self = this;
- var def = $.Deferred();
- var defaults = {};
- var created = false;
-
- _.each($.extend({}, this.data_template, data), function(val, field_name) {
- defaults['default_' + field_name] = val;
- });
-
- var pop_infos = self.get_form_popup_infos();
- var pop = new instance.web.form.FormOpenPopup(this);
- var context = new instance.web.CompoundContext(this.dataset.context, defaults);
- pop.show_element(this.dataset.model, null, this.dataset.get_context(defaults), {
- title: this.get_title(),
- disable_multiple_selection: true,
- view_id: pop_infos.view_id,
- // Ensuring we use ``self.dataset`` and DO NOT create a new one.
- create_function: function(data, options) {
- return self.dataset.create(data, options).done(function(r) {
- }).fail(function (r, event) {
- if (!r.data.message) { //else manage by openerp
- throw new Error(r);
- }
- });
- },
- read_function: function(id, fields, options) {
- return self.dataset.read_ids.apply(self.dataset, arguments).done(function() {
- }).fail(function (r, event) {
- if (!r.data.message) { //else manage by openerp
- throw new Error(r);
- }
- });
- },
- });
- pop.on('closed', self, function() {
- // ``self.trigger('close')`` would itself destroy all child element including
- // the slow create popup, which would then re-trigger recursively the 'closed' signal.
- // Thus, here, we use a deferred and its state to cut the endless recurrence.
- if (def.state() === "pending") {
- def.resolve();
- }
- });
- pop.on('create_completed', self, function(id) {
- created = true;
- self.trigger('slowadded');
- });
- def.then(function() {
- if (created) {
- var parent = self.getParent();
- parent.$calendar.fullCalendar('refetchEvents');
- }
- self.trigger('close');
- });
- return def;
- },
- });
+ } else
+ return do_it();
+ },
+});
- /**
- * Form widgets
- */
+core.view_registry.add('calendar', CalendarView);
- function widget_calendar_lazy_init() {
- if (instance.web.form.Many2ManyCalendarView) {
- return;
- }
-
- instance.web_calendar.FieldCalendarView = instance.web_calendar.CalendarView.extend({
-
- init: function (parent) {
- this._super.apply(this, arguments);
- // Warning: this means only a field_widget should instanciate this Class
- this.field_widget = parent;
- },
-
- view_loading: function (fv) {
- var self = this;
- return $.when(this._super.apply(this, arguments)).then(function() {
- self.on('event_rendered', this, function (event, element, view) {
-
- });
- });
- },
-
- // In forms, we could be hidden in a notebook. Thus we couldn't
- // render correctly fullcalendar so we try to detect when we are
- // not visible to wait for when we will be visible.
- init_calendar: function() {
- if (this.$calendar.width() !== 0) { // visible
- return this._super();
- }
- // find all parents tabs.
- var def = $.Deferred();
- var self = this;
- this.$calendar.parents(".ui-tabs").on('tabsactivate', this, function() {
- if (self.$calendar.width() !== 0) { // visible
- self.$calendar.fullCalendar(self.get_fc_init_options());
- def.resolve();
- }
- });
- return def;
- },
- });
- }
-
- instance.web_calendar.BufferedDataSet = instance.web.BufferedDataSet.extend({
-
- /**
- * Adds verification on possible missing fields for the sole purpose of
- * O2M dataset being compatible with the ``slow_create`` detection of
- * missing fields... which is as simple to try to write and upon failure
- * go to ``slow_create``. Current BufferedDataSet would'nt fail because
- * they do not send data to the server at create time.
- */
- create: function (data, options) {
- var def = $.Deferred();
- var self = this;
- var create = this._super;
- if (_.isUndefined(this.required_fields)) {
- this.required_fields = (new instance.web.Model(this.model))
- .call('fields_get').then(function (fields_def) {
- return _(fields_def).chain()
- // equiv to .pairs()
- .map(function (value, key) { return [key, value]; })
- // equiv to .omit(self.field_widget.field.relation_field)
- .filter(function (pair) { return pair[0] !== self.field_widget.field.relation_field; })
- .filter(function (pair) { return pair[1].required; })
- .map(function (pair) { return pair[0]; })
- .value();
- });
- }
- $.when(this.required_fields).then(function (required_fields) {
- var missing_fields = _(required_fields).filter(function (v) {
- return _.isUndefined(data[v]);
- });
- var default_get = (missing_fields.length !== 0) ?
- self.default_get(missing_fields) : [];
- $.when(default_get).then(function (defaults) {
-
- // Remove all fields that have a default from the missing fields.
- missing_fields = _(missing_fields).filter(function (f) {
- return _.isUndefined(defaults[f]);
- });
- if (missing_fields.length !== 0) {
- def.reject(
- _.str.sprintf(
- _t("Missing required fields %s"), missing_fields.join(", ")),
- $.Event());
- return;
- }
- create.apply(self, [data, options]).then(function (result) {
- def.resolve(result);
- });
- });
- });
- return def;
- },
- });
-
- instance.web_calendar.fields_dataset = new instance.web.Registry({
- 'many2many': 'instance.web.DataSetStatic',
- 'one2many': 'instance.web_calendar.BufferedDataSet',
- });
-
-
- function get_field_dataset_class(type) {
- var obj = instance.web_calendar.fields_dataset.get_any([type]);
- if (!obj) {
- throw new Error(_.str.sprintf(_t("Dataset for type '%s' is not defined."), type));
- }
-
- // Override definition of legacy datasets to add field_widget context
- return obj.extend({
- init: function (parent) {
- this._super.apply(this, arguments);
- this.field_widget = parent;
- },
- get_context: function() {
- this.context = this.field_widget.build_context();
- return this.context;
- }
- });
- }
-
- /**
- * Common part to manage any field using calendar view
- */
- instance.web_calendar.FieldCalendar = instance.web.form.AbstractField.extend({
-
- disable_utility_classes: true,
- calendar_view_class: 'instance.web_calendar.FieldCalendarView',
-
- init: function(field_manager, node) {
- this._super(field_manager, node);
- widget_calendar_lazy_init();
- this.is_loaded = $.Deferred();
- this.initial_is_loaded = this.is_loaded;
-
- var self = this;
-
- // This dataset will use current widget to '.build_context()'.
- var field_type = field_manager.fields_view.fields[node.attrs.name].type;
- this.dataset = new (get_field_dataset_class(field_type))(
- this, this.field.relation);
-
- this.dataset.on('unlink', this, function(_ids) {
- this.dataset.trigger('dataset_changed');
- });
-
- // quick_create widget instance will be attached when spawned
- this.quick_create = null;
-
- this.no_rerender = true;
-
- },
-
- start: function() {
- this._super.apply(this, arguments);
-
- var self = this;
-
- self.load_view();
- self.on("change:effective_readonly", self, function() {
- self.is_loaded = self.is_loaded.then(function() {
- self.calendar_view.destroy();
- return $.when(self.load_view()).done(function() {
- self.render_value();
- });
- });
- });
- },
-
- load_view: function() {
- var self = this;
- var calendar_view_class = get_class(this.calendar_view_class);
- this.calendar_view = new calendar_view_class(this, this.dataset, false, $.extend({
- 'create_text': _t("Add"),
- 'creatable': self.get("effective_readonly") ? false : true,
- 'quick_creatable': self.get("effective_readonly") ? false : true,
- 'read_only_mode': self.get("effective_readonly") ? true : false,
- 'confirm_on_delete': false,
- }, this.options));
- var embedded = (this.field.views || {}).calendar;
- if (embedded) {
- this.calendar_view.set_embedded_view(embedded);
- }
- var loaded = $.Deferred();
- this.calendar_view.on("calendar_view_loaded", self, function() {
- self.initial_is_loaded.resolve();
- loaded.resolve();
- });
- this.calendar_view.on('switch_mode', this, this.open_popup);
- $.async_when().done(function () {
- self.calendar_view.appendTo(self.$el);
- });
- return loaded;
- },
-
- render_value: function() {
- var self = this;
- this.dataset.set_ids(this.get("value"));
- this.is_loaded = this.is_loaded.then(function() {
- return self.calendar_view.do_search(self.build_domain(), self.dataset.get_context(), []);
- });
- },
-
- open_popup: function(type, unused) {
- if (type !== "form") { return; }
- if (this.dataset.index == null) {
- if (typeof this.open_popup_add === "function") {
- this.open_popup_add();
- }
- } else {
- if (typeof this.open_popup_edit === "function") {
- this.open_popup_edit();
- }
- }
- },
-
- open_popup_add: function() {
- throw new Error("Not Implemented");
- },
-
- open_popup_edit: function() {
- var id = this.dataset.ids[this.dataset.index];
- var self = this;
- var pop = (new instance.web.form.FormOpenPopup(this));
- pop.show_element(this.field.relation, id, this.build_context(), {
- title: _t("Open: ") + this.string,
- write_function: function(id, data, _options) {
- return self.dataset.write(id, data, {}).done(function() {
- // Note that dataset will trigger itself the
- // ``dataset_changed`` signal
- self.calendar_view.refresh_event(id);
- });
- },
- read_function: function(id, fields, options) {
- return self.dataset.read_ids.apply(self.dataset, arguments).done(function() {
- }).fail(function (r, event) {
- throw new Error(r);
- });
- },
-
- alternative_form_view: this.field.views ? this.field.views.form : undefined,
- parent_view: this.view,
- child_name: this.name,
- readonly: this.get("effective_readonly")
- });
- }
- });
-
- instance.web_calendar.Sidebar = instance.web.Widget.extend({
- template: 'CalendarView.sidebar',
-
- start: function() {
- this._super();
- this.filter = new instance.web_calendar.SidebarFilter(this, this.getParent());
- this.filter.appendTo(this.$el.find('.oe_calendar_filter'));
- }
- });
- instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
- events: {
- 'change input:checkbox': 'filter_click',
- 'click span.color_filter': 'select_previous',
-
- },
- init: function(parent, view) {
- this._super(parent);
- this.view = view;
- },
- set_filters: function() {
- var self = this;
- _.forEach(self.view.all_filters, function(o) {
- if (_.contains(self.view.now_filter_ids, o.value)) {
- self.$('div.oe_calendar_responsible input[value=' + o.value + ']').prop('checked',o.is_checked);
- }
- });
- },
- events_loaded: function(filters) {
- var self = this;
- if (filters == null) {
- filters = [];
- _.forEach(self.view.all_filters, function(o) {
- if (_.contains(self.view.now_filter_ids, o.value)) {
- filters.push(o);
- }
- });
- }
- this.$el.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters }));
- },
- filter_click: function(e) {
- var self = this;
- if (self.view.all_filters[0] && e.target.value == self.view.all_filters[0].value) {
- self.view.all_filters[0].is_checked = e.target.checked;
- } else {
- self.view.all_filters[e.target.value].is_checked = e.target.checked;
- }
- self.view.$calendar.fullCalendar('refetchEvents');
- },
- select_previous: function(e) {
- $(e.target).siblings('input').trigger('click');
- }
- });
-
-};
+return CalendarView;
+});
diff --git a/addons/web_calendar/static/src/js/widgets.js b/addons/web_calendar/static/src/js/widgets.js
new file mode 100644
index 00000000000..72afb373267
--- /dev/null
+++ b/addons/web_calendar/static/src/js/widgets.js
@@ -0,0 +1,254 @@
+odoo.define('web_calendar.widgets', ['web.core', 'web.Dialog', 'web.form_common', 'web.Widget'], function(require) {
+"use strict";
+
+var core = require('web.core');
+var data = require('web.data');
+var Dialog = require('web.Dialog');
+var form_common = require('web.form_common');
+var Widget = require('web.Widget');
+
+var _t = core._t;
+var QWeb = core.qweb;
+
+/**
+ * Quick creation view.
+ *
+ * Triggers a single event "added" with a single parameter "name", which is the
+ * name entered by the user
+ *
+ * @class
+ * @type {*}
+ */
+var QuickCreate = Widget.extend({
+ init: function(parent, dataset, buttons, options, data_template) {
+ this._super(parent);
+ this.dataset = dataset;
+ this._buttons = buttons || false;
+ this.options = options;
+
+ // Can hold data pre-set from where you clicked on agenda
+ this.data_template = data_template || {};
+ },
+ get_title: function () {
+ var parent = this.getParent();
+ if (_.isUndefined(parent)) {
+ return _t("Create");
+ }
+ var title = (_.isUndefined(parent.field_widget)) ?
+ (parent.string || parent.name) :
+ parent.field_widget.string || parent.field_widget.name || '';
+ return _t("Create: ") + title;
+ },
+ start: function () {
+ var self = this;
+
+ if (this.options.disable_quick_create) {
+ this.slow_create();
+ return;
+ }
+ self.on('added', self, function() {
+ self.trigger('close');
+ });
+
+ this.$dialog = new Dialog(this, {
+ title: this.get_title(),
+ size: 'small',
+ buttons: this._buttons ? [
+ {text: _t("Create event"), oe_link_class: 'oe_highlight', click: function (e) {
+ if (!self.quick_add()) {
+ self.focus();
+ }
+ }},
+ {text: _t("Edit event"), oe_link_class: 'oe_link', click: function (e) {
+ self.slow_add();
+ }}
+ ] : []
+ }, QWeb.render('CalendarView.quick_create', {widged: this}))
+ .on('closed', self, function() {
+ self.trigger('close');
+ })
+ .open();
+ this.$input = this.$dialog.$('input').keyup(function enterHandler (e) {
+ if(event.keyCode == 13){
+ self.$input.off('keyup', enterHandler);
+ if (!self.quick_add()){
+ self.$input.on('keyup', enterHandler);
+ }
+ } else if (e.keyCode == 27 && self._buttons) {
+ self.trigger('close');
+ }
+ });
+ },
+ focus: function() {
+ this.$input.focus();
+ },
+
+ /**
+ * Gathers data from the quick create dialog a launch quick_create(data) method
+ */
+ quick_add: function() {
+ var val = this.$input.val();
+ if (/^\s*$/.test(val)) {
+ return false;
+ }
+ return this.quick_create({'name': val}).always(function() { return true; });
+ },
+
+ slow_add: function() {
+ var val = this.$input.val();
+ this.slow_create({'name': val});
+ },
+
+ /**
+ * Handles saving data coming from quick create box
+ */
+ quick_create: function(data, options) {
+ var self = this;
+ return this.dataset.create($.extend({}, this.data_template, data), options)
+ .then(function(id) {
+ self.trigger('added', id);
+ self.$input.val("");
+ }).fail(function(r, event) {
+ event.preventDefault();
+ // This will occurs if there are some more fields required
+ self.slow_create(data);
+ });
+ },
+
+ /**
+ * Show full form popup
+ */
+ get_form_popup_infos: function() {
+ var parent = this.getParent();
+ var infos = {
+ view_id: false,
+ title: this.name,
+ };
+ if (!_.isUndefined(parent) && !(_.isUndefined(parent.ViewManager))) {
+ infos.view_id = parent.ViewManager.get_view_id('form');
+ }
+ return infos;
+ },
+ slow_create: function(_data) {
+ //if all day, we could reset time to display 00:00:00
+
+ var self = this;
+ var def = $.Deferred();
+ var defaults = {};
+ var created = false;
+
+ _.each($.extend({}, this.data_template, _data), function(val, field_name) {
+ defaults['default_' + field_name] = val;
+ });
+
+ var pop_infos = self.get_form_popup_infos();
+ var pop = new form_common.FormOpenPopup(this);
+ var context = new data.CompoundContext(this.dataset.context, defaults);
+ pop.show_element(this.dataset.model, null, this.dataset.get_context(defaults), {
+ title: this.get_title(),
+ disable_multiple_selection: true,
+ view_id: pop_infos.view_id,
+ // Ensuring we use ``self.dataset`` and DO NOT create a new one.
+ create_function: function(data, options) {
+ return self.dataset.create(data, options).done(function(r) {
+ }).fail(function (r, event) {
+ if (!r.data.message) { //else manage by openerp
+ throw new Error(r);
+ }
+ });
+ },
+ read_function: function(id, fields, options) {
+ return self.dataset.read_ids.apply(self.dataset, arguments).done(function() {
+ }).fail(function (r, event) {
+ if (!r.data.message) { //else manage by openerp
+ throw new Error(r);
+ }
+ });
+ },
+ });
+ pop.on('closed', self, function() {
+ // ``self.trigger('close')`` would itself destroy all child element including
+ // the slow create popup, which would then re-trigger recursively the 'closed' signal.
+ // Thus, here, we use a deferred and its state to cut the endless recurrence.
+ if (def.state() === "pending") {
+ def.resolve();
+ }
+ });
+ pop.on('create_completed', self, function(id) {
+ created = true;
+ self.trigger('slowadded');
+ });
+ def.then(function() {
+ if (created) {
+ var parent = self.getParent();
+ parent.$calendar.fullCalendar('refetchEvents');
+ }
+ self.trigger('close');
+ });
+ return def;
+ },
+});
+
+/**
+ * Common part to manage any field using calendar view
+ */
+var Sidebar = Widget.extend({
+ template: 'CalendarView.sidebar',
+
+ start: function() {
+ this._super();
+ this.filter = new SidebarFilter(this, this.getParent());
+ this.filter.appendTo(this.$el.find('.oe_calendar_filter'));
+ }
+});
+var SidebarFilter = Widget.extend({
+ events: {
+ 'change input:checkbox': 'filter_click',
+ 'click span.color_filter': 'select_previous',
+
+ },
+ init: function(parent, view) {
+ this._super(parent);
+ this.view = view;
+ },
+ set_filters: function() {
+ var self = this;
+ _.forEach(self.view.all_filters, function(o) {
+ if (_.contains(self.view.now_filter_ids, o.value)) {
+ self.$('div.oe_calendar_responsible input[value=' + o.value + ']').prop('checked',o.is_checked);
+ }
+ });
+ },
+ events_loaded: function(filters) {
+ var self = this;
+ if (!filters) {
+ filters = [];
+ _.forEach(self.view.all_filters, function(o) {
+ if (_.contains(self.view.now_filter_ids, o.value)) {
+ filters.push(o);
+ }
+ });
+ }
+ this.$el.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters }));
+ },
+ filter_click: function(e) {
+ var self = this;
+ if (self.view.all_filters[0] && e.target.value == self.view.all_filters[0].value) {
+ self.view.all_filters[0].is_checked = e.target.checked;
+ } else {
+ self.view.all_filters[e.target.value].is_checked = e.target.checked;
+ }
+ self.view.$calendar.fullCalendar('refetchEvents');
+ },
+ select_previous: function(e) {
+ $(e.target).siblings('input').trigger('click');
+ }
+});
+
+return {
+ QuickCreate: QuickCreate,
+ Sidebar: Sidebar,
+ SidebarFilter: SidebarFilter
+};
+
+});
diff --git a/addons/web_calendar/views/web_calendar.xml b/addons/web_calendar/views/web_calendar.xml
index 98785d8e3a0..7f7f17baccb 100644
--- a/addons/web_calendar/views/web_calendar.xml
+++ b/addons/web_calendar/views/web_calendar.xml
@@ -7,6 +7,7 @@
+