diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index e354902c131..43afd983fb3 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -236,6 +236,9 @@ var CalendarController = AbstractController.extend({
// When clicking on a random day of a random other week, switch to week view
this.model.setScale('week');
}
+ if (event.data.scale) {
+ this.model.setScale(event.data.scale);
+ }
this.model.setDate(event.data.date);
this.reload();
},
diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js
index 806e9d4e03f..9bc9aa0cb85 100644
--- a/addons/web/static/src/js/views/calendar/calendar_renderer.js
+++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js
@@ -338,6 +338,7 @@ return AbstractRenderer.extend({
r_end: fc4Event.extendedProps.r_end && moment(fc4Event.extendedProps.r_end).utcOffset(0, true),
record: fc4Event.extendedProps.record,
attendees: fc4Event.extendedProps.attendees,
+ disableQuickCreate: fc4Event.extendedProps.disableQuickCreate,
});
}
}
@@ -375,6 +376,20 @@ return AbstractRenderer.extend({
return field_utils.format[field.type](record[fieldName], field, {forceString: true});
}
},
+ _preOpenCreate: function (data) {
+ if (this.$('.o_cw_popover').length) {
+ this._unselectEvent();
+ }
+ if (this.state.context.default_name) {
+ data.title = this.state.context.default_name;
+ }
+ this.trigger_up('openCreate', this._convertEventToFC3Event(data));
+ if (this.state.scale === 'year') {
+ this.calendar.view.unselect();
+ } else {
+ this.calendar.unselect();
+ }
+ },
/**
* Return the Object options for FullCalendar
*
@@ -405,7 +420,7 @@ return AbstractRenderer.extend({
eventClickInfo.jsEvent.stopPropagation();
var eventData = eventClickInfo.event;
self._unselectEvent();
- $(self.calendarElement).find(_.str.sprintf('[data-event-id=%s]', eventData.id)).addClass('o_cw_custom_highlight');
+ $(self.calendarElement).find(`[data-event-id=${eventData.id}]`).addClass('o_cw_custom_highlight');
self._renderEventPopover(eventData, $(eventClickInfo.el));
},
yearDateClick: function (info) {
@@ -427,20 +442,8 @@ return AbstractRenderer.extend({
}
},
select: function (selectionInfo) {
- // Clicking on the view, dispose any visible popover. Otherwise create a new event.
- if (self.$('.o_cw_popover').length) {
- self._unselectEvent();
- }
var data = {start: selectionInfo.start, end: selectionInfo.end, allDay: selectionInfo.allDay};
- if (self.state.context.default_name) {
- data.title = self.state.context.default_name;
- }
- self.trigger_up('openCreate', self._convertEventToFC3Event(data));
- if (self.state.scale === 'year') {
- self.calendar.view.unselect();
- } else {
- self.calendar.unselect();
- }
+ self._preOpenCreate(data);
},
eventRender: function (info) {
var event = info.event;
@@ -503,20 +506,20 @@ return AbstractRenderer.extend({
// The css ":hover" selector can't be used because these events
// are rendered using multiple elements.
eventMouseEnter: function (mouseEnterInfo) {
- $(self.calendarElement).find(_.str.sprintf('[data-event-id=%s]', mouseEnterInfo.event.id)).addClass('o_cw_custom_hover');
+ $(self.calendarElement).find(`[data-event-id=${mouseEnterInfo.event.id}]`).addClass('o_cw_custom_hover');
},
eventMouseLeave: function (mouseLeaveInfo) {
if (!mouseLeaveInfo.event.id) {
return;
}
- $(self.calendarElement).find(_.str.sprintf('[data-event-id=%s]', mouseLeaveInfo.event.id)).removeClass('o_cw_custom_hover');
+ $(self.calendarElement).find(`[data-event-id=${mouseLeaveInfo.event.id}]`).removeClass('o_cw_custom_hover');
},
eventDragStart: function (mouseDragInfo) {
- $(self.calendarElement).find(_.str.sprintf('[data-event-id=%s]', mouseDragInfo.event.id)).addClass('o_cw_custom_hover');
+ $(self.calendarElement).find(`[data-event-id=${mouseDragInfo.event.id}]`).addClass('o_cw_custom_hover');
self._unselectEvent();
},
eventResizeStart: function (mouseResizeInfo) {
- $(self.calendarElement).find(_.str.sprintf('[data-event-id=%s]', mouseResizeInfo.event.id)).addClass('o_cw_custom_hover');
+ $(self.calendarElement).find(`[data-event-id=${mouseResizeInfo.event.id}]`).addClass('o_cw_custom_hover');
self._unselectEvent();
},
eventLimitClick: function () {
@@ -709,7 +712,7 @@ return AbstractRenderer.extend({
if (options.avatar_field) {
_.each(options.filters, function (filter) {
if (!['all', false].includes(filter.value)) {
- var selector = _.str.sprintf('.o_calendar_filter_item[data-value=%s]', filter.value);
+ var selector = `.o_calendar_filter_item[data-value=${filter.value}]`;
sidebarFilter.$el.find(selector).popover({
animation: false,
trigger: 'hover',
@@ -719,7 +722,7 @@ return AbstractRenderer.extend({
delay: {show: 300, hide: 0},
content: function () {
return $('
', {
- src: _.str.sprintf('/web/image/%s/%s/%s', options.avatar_model, filter.value, options.avatar_field),
+ src: `/web/image/${options.avatar_model}/${filter.value}/${options.avatar_field}}`,
class: 'mx-auto',
});
},
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index 863a9edc993..1ce6dac94ee 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -197,6 +197,7 @@ odoo.define('web.test_utils', async function (require) {
triggerKeypressEvent: testUtilsDom.triggerKeypressEvent,
triggerMouseEvent: testUtilsDom.triggerMouseEvent,
triggerPositionalMouseEvent: testUtilsDom.triggerPositionalMouseEvent,
+ triggerPositionalTapEvents: testUtilsDom.triggerPositionalTapEvents,
dragAndDrop: testUtilsDom.dragAndDrop,
find: testUtilsDom.findItem,
getNode: testUtilsDom.getNode,
diff --git a/addons/web/static/tests/helpers/test_utils_dom.js b/addons/web/static/tests/helpers/test_utils_dom.js
index eedbcbe5155..f85fe9bec8b 100644
--- a/addons/web/static/tests/helpers/test_utils_dom.js
+++ b/addons/web/static/tests/helpers/test_utils_dom.js
@@ -33,6 +33,17 @@ odoo.define('web.test_utils_dom', function (require) {
clientY: args ? args.pageY : undefined,
view: window,
});
+ const touchEventMapping = args => Object.assign({}, args, {
+ cancelable: true,
+ bubbles: true,
+ composed: true,
+ view: window,
+ rotation: 0.0,
+ zoom: 1.0,
+ });
+ const touchEventCancelMapping = args => Object.assign({}, touchEventMapping(args), {
+ cancelable: false,
+ });
const noBubble = args => Object.assign({}, args, { bubbles: false });
const onlyBubble = args => Object.assign({}, args, { bubbles: true });
// TriggerEvent constructor/args processor mapping
@@ -70,6 +81,11 @@ odoo.define('web.test_utils_dom', function (require) {
dragover: { constructor: DragEvent, processParameters: onlyBubble },
drop: { constructor: DragEvent, processParameters: onlyBubble },
+ touchstart: { constructor: TouchEvent, processParameters: touchEventMapping },
+ touchend: { constructor: TouchEvent, processParameters: touchEventMapping },
+ touchmove: { constructor: TouchEvent, processParameters: touchEventMapping },
+ touchcancel: { constructor: TouchEvent, processParameters: touchEventCancelMapping },
+
input: { constructor: InputEvent, processParameters: onlyBubble },
compositionstart: { constructor: CompositionEvent, processParameters: onlyBubble },
@@ -533,6 +549,39 @@ odoo.define('web.test_utils_dom', function (require) {
return el;
}
+ /**
+ * Simulate a "TAP" (touch) event with a custom position x and y.
+ *
+ * @param {number} x
+ * @param {number} y
+ * @returns {HTMLElement}
+ */
+ async function triggerPositionalTapEvents(x, y) {
+ const element = document.elementFromPoint(x, y);
+ const touch = new Touch({
+ identifier: 0,
+ target: element,
+ clientX: x,
+ clientY: y,
+ pageX: x,
+ pageY: y,
+ });
+ await triggerEvent(element, 'touchstart', {
+ touches: [touch],
+ targetTouches: [touch],
+ changedTouches: [touch],
+ });
+ await triggerEvent(element, 'touchmove', {
+ touches: [touch],
+ targetTouches: [touch],
+ changedTouches: [touch],
+ });
+ await triggerEvent(element, 'touchend', {
+ changedTouches: [touch],
+ });
+ return element;
+ }
+
return {
click,
clickFirst,
@@ -547,5 +596,6 @@ odoo.define('web.test_utils_dom', function (require) {
triggerKeypressEvent,
triggerMouseEvent,
triggerPositionalMouseEvent,
+ triggerPositionalTapEvents,
};
});
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 308ac98fde8..46d6019328f 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -3938,6 +3938,56 @@ QUnit.module('Views', {
calendar.destroy();
});
+
+ QUnit.test('calendar: disableQuickCreate in data event', async function (assert) {
+ assert.expect(3);
+
+ testUtils.mock.patch(CalendarRenderer, {
+ _preOpenCreate: function (data) {
+ data = Object.assign({}, data, {
+ extendedProps: {disableQuickCreate: true},
+ });
+ return this._super.call(this, data);
+ },
+ });
+
+ let calendar = await testUtils.createCalendarView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch: ``,
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ intercepts: {
+ do_action(ev) {
+ assert.step('do_action');
+ assert.deepEqual(ev.data.action.context, {
+ default_start: "2016-12-12 06:30:00",
+ default_stop: "2016-12-12 11:00:00",
+ }, "should send the correct data to create events");
+ },
+ },
+ }, {positionalClicks: true});
+
+ // Create event
+ var $initCell = calendar.$('.fc-time-grid .fc-minor[data-time="06:30:00"] .fc-widget-content:last-child');
+ var $endCell = calendar.$('.fc-time-grid .fc-minor[data-time="10:30:00"] .fc-widget-content:last-child');
+
+ var left = $initCell.offset().left;
+ var top = $initCell.offset().top;
+ testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown");
+ top = $endCell.offset().top;
+ testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove");
+ testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup");
+ await testUtils.nextTick();
+
+ assert.verifySteps(['do_action']);
+
+ calendar.destroy();
+
+ testUtils.mock.unpatch(CalendarRenderer);
+ });
});
});