diff --git a/web_timeline/readme/ROADMAP.rst b/web_timeline/readme/ROADMAP.rst index bb674e558..6ddc95d41 100644 --- a/web_timeline/readme/ROADMAP.rst +++ b/web_timeline/readme/ROADMAP.rst @@ -1,6 +1,5 @@ * Implement a more efficient way of refreshing timeline after a record update; * Make ``attrs`` attribute work; -* Make action attributes work (create, edit, delete) like in form and tree views. * When grouping by m2m and more than one record is set, the timeline item appears only on one group. Allow showing in both groups. * When grouping by m2m and dragging for changing the time or the group, the changes on diff --git a/web_timeline/static/src/js/timeline_controller.esm.js b/web_timeline/static/src/js/timeline_controller.esm.js index 21318e0a3..fac8f11ed 100644 --- a/web_timeline/static/src/js/timeline_controller.esm.js +++ b/web_timeline/static/src/js/timeline_controller.esm.js @@ -12,6 +12,7 @@ import {Component} from "@odoo/owl"; export default AbstractController.extend({ custom_events: _.extend({}, AbstractController.prototype.custom_events, { onGroupClick: "_onGroupClick", + onItemDoubleClick: "_onItemDoubleClick", onUpdate: "_onUpdate", onRemove: "_onRemove", onMove: "_onMove", @@ -101,6 +102,17 @@ export default AbstractController.extend({ }); }, + /** + * Triggered on double-click on an item in read-only mode (otherwise, we use _onUpdate). + * + * @private + * @param {EventObject} event + * @returns {jQuery.Deferred} + */ + _onItemDoubleClick: function (event) { + return this.openItem(event.data.item, false); + }, + /** * Opens a form view of a clicked timeline * item (triggered by the TimelineRenderer). @@ -109,33 +121,35 @@ export default AbstractController.extend({ * @param {EventObject} event */ _onUpdate: function (event) { - this.renderer = event.data.renderer; - const rights = event.data.rights; const item = event.data.item; - const id = Number(item.evt.id) || item.evt.id; - const title = item.evt.__name; + const item_id = Number(item.evt.id) || item.evt.id; + return this.openItem(item_id, true); + }, + + /** Open specified item, either through modal, or by navigating to form view. */ + openItem: function (item_id, is_editable) { if (this.open_popup_action) { + const options = { + resModel: this.model.modelName, + resId: item_id, + context: this.getSession().user_context, + }; + if (is_editable) { + options.onRecordSaved = () => this.write_completed(); + } else { + options.preventEdit = true; + } this.Dialog = Component.env.services.dialog.add( FormViewDialog, - { - resId: id, - context: this.getSession().user_context, - title: title, - onRecordSaved: () => this.write_completed(), - resModel: this.model.modelName, - }, + options, {} ); } else { - let mode = "readonly"; - if (rights.write) { - mode = "edit"; - } this.trigger_up("switch_view", { view_type: "form", - res_id: id, - mode: mode, model: this.model.modelName, + res_id: item_id, + mode: is_editable ? "edit" : "readonly", }); } }, diff --git a/web_timeline/static/src/js/timeline_renderer.js b/web_timeline/static/src/js/timeline_renderer.js index 10f90cc0a..aba23f760 100644 --- a/web_timeline/static/src/js/timeline_renderer.js +++ b/web_timeline/static/src/js/timeline_renderer.js @@ -29,6 +29,9 @@ odoo.define("web_timeline.TimelineRenderer", function (require) { this.modelName = params.model; this.mode = params.mode; this.options = params.options; + this.can_create = params.can_create; + this.can_update = params.can_update; + this.can_delete = params.can_delete; this.min_height = params.min_height; this.date_start = params.date_start; this.date_stop = params.date_stop; @@ -192,22 +195,25 @@ odoo.define("web_timeline.TimelineRenderer", function (require) { */ init_timeline: function () { this._computeMode(); - this.options.editable = { - // Add new items by double tapping - add: this.modelClass.data.rights.create, + this.options.editable = {}; + if (this.can_update && this.modelClass.data.rights.write) { + this.options.onMove = this.on_move; + this.options.onUpdate = this.on_update; // Drag items horizontally - updateTime: this.modelClass.data.rights.write, + this.options.editable.updateTime = true; // Drag items from one group to another - updateGroup: this.modelClass.data.rights.write, + this.options.editable.updateGroup = true; + if (this.can_create && this.modelClass.data.rights.create) { + this.options.onAdd = this.on_add; + // Add new items by double tapping + this.options.editable.add = true; + } + } + if (this.can_delete && this.modelClass.data.rights.unlink) { + this.options.onRemove = this.on_remove; // Delete an item by tapping the delete button top right - remove: this.modelClass.data.rights.unlink, - }; - $.extend(this.options, { - onAdd: this.on_add, - onMove: this.on_move, - onUpdate: this.on_update, - onRemove: this.on_remove, - }); + this.options.editable.remove = true; + } this.options.xss = {disabled: true}; this.qweb = new QWeb(session.debug, {_s: session.origin}, false); if (this.arch.children.length) { @@ -218,7 +224,11 @@ odoo.define("web_timeline.TimelineRenderer", function (require) { } this.timeline = new vis.Timeline(this.$timeline.get(0), {}, this.options); - this.timeline.on("click", this.on_group_click); + this.timeline.on("click", this.on_timeline_click); + if (!this.options.onUpdate) { + // In read-only mode, catch double-clicks this way. + this.timeline.on("doubleClick", this.on_timeline_double_click); + } const group_bys = this.arch.attrs.default_group_by.split(","); this.last_group_bys = group_bys; this.last_domains = this.modelClass.data.domain; @@ -553,12 +563,12 @@ odoo.define("web_timeline.TimelineRenderer", function (require) { }, /** - * Handle a click on a group header. + * Handle a click within the timeline. * * @param {ClickEvent} e * @private */ - on_group_click: function (e) { + on_timeline_click: function (e) { if (e.what === "group-label" && e.group !== -1) { this._trigger( e, @@ -570,6 +580,24 @@ odoo.define("web_timeline.TimelineRenderer", function (require) { } }, + /** + * Handle a double-click within the timeline. + * + * @param {ClickEvent} e + * @private + */ + on_timeline_double_click: function (e) { + if (e.what === "item" && e.item !== -1) { + this._trigger( + e.item, + () => { + // No callback + }, + "onItemDoubleClick" + ); + } + }, + /** * Trigger onUpdate. * @@ -615,7 +643,7 @@ odoo.define("web_timeline.TimelineRenderer", function (require) { }, /** - * Trigger_up encapsulation adds by default the rights, and the renderer. + * Trigger_up encapsulation adds by default the renderer. * * @param {HTMLElement} item * @param {Function} callback @@ -626,7 +654,6 @@ odoo.define("web_timeline.TimelineRenderer", function (require) { this.trigger_up(trigger, { item: item, callback: callback, - rights: this.modelClass.data.rights, renderer: this, }); }, diff --git a/web_timeline/static/src/js/timeline_view.js b/web_timeline/static/src/js/timeline_view.js index f9e0f1e56..3971db4f7 100644 --- a/web_timeline/static/src/js/timeline_view.js +++ b/web_timeline/static/src/js/timeline_view.js @@ -22,6 +22,10 @@ odoo.define("web_timeline.TimelineView", function (require) { return _.isUndefined(value) || _.isNull(value); } + function toBoolDefaultTrue(value) { + return isNullOrUndef(value) ? true : utils.toBoolElse(value, true); + } + var TimelineView = AbstractView.extend({ display_name: _lt("Timeline"), icon: "fa fa-tasks", @@ -100,6 +104,9 @@ odoo.define("web_timeline.TimelineView", function (require) { this.rendererParams.model = this.modelName; this.rendererParams.view = this; this.rendererParams.options = this._preapre_vis_timeline_options(attrs); + this.rendererParams.can_create = toBoolDefaultTrue(attrs.create); + this.rendererParams.can_update = toBoolDefaultTrue(attrs.edit); + this.rendererParams.can_delete = toBoolDefaultTrue(attrs.delete); this.rendererParams.date_start = date_start; this.rendererParams.date_stop = date_stop; this.rendererParams.date_delay = date_delay; @@ -127,9 +134,7 @@ odoo.define("web_timeline.TimelineView", function (require) { selectable: true, multiselect: true, showCurrentTime: true, - stack: isNullOrUndef(attrs.stack) - ? true - : utils.toBoolElse(attrs.stack, true), + stack: toBoolDefaultTrue(attrs.stack), margin: attrs.margin ? JSON.parse(attrs.margin) : {item: 2}, zoomKey: attrs.zoomKey || "ctrlKey", };