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); + }); }); });