From 8de0b1776bfc9db460f0aed2a03cb490ce8ff973 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Houz=C3=A9fa=20Abbasbhay?=
Date: Thu, 4 Apr 2024 12:48:32 +0200
Subject: [PATCH] [IMP] web_timeline: Follow create/edit/delete attrs
In addition to security rights (was already implemented), now follow
`create="0"` / `edit="0"` / `delete="0"` attributes one can set onto the
`timeline` tag, same as in other Odoo views.
---
web_timeline/README.rst | 3 +-
web_timeline/__manifest__.py | 2 +-
web_timeline/readme/ROADMAP.rst | 1 -
web_timeline/static/description/index.html | 3 +-
.../static/src/js/timeline_controller.esm.js | 48 +++++++++-----
.../static/src/js/timeline_renderer.js | 63 +++++++++++++------
web_timeline/static/src/js/timeline_view.js | 11 +++-
7 files changed, 87 insertions(+), 44 deletions(-)
diff --git a/web_timeline/README.rst b/web_timeline/README.rst
index 05f77fe35..f281f42b5 100644
--- a/web_timeline/README.rst
+++ b/web_timeline/README.rst
@@ -7,7 +7,7 @@ Web timeline
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:bd504d36989179a1e11223c03dbf38ac45f0a792c6ad15a2786c9283bc64650b
+ !! source digest: sha256:de40cea8c12ae858a82a28ad5fae3c70c75def8b5b12311ed124973ec7da15c9
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
@@ -181,7 +181,6 @@ Known issues / Roadmap
* 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/__manifest__.py b/web_timeline/__manifest__.py
index 310d17726..d83fc9079 100644
--- a/web_timeline/__manifest__.py
+++ b/web_timeline/__manifest__.py
@@ -4,7 +4,7 @@
{
"name": "Web timeline",
"summary": "Interactive visualization chart to show events in time",
- "version": "16.0.1.1.4",
+ "version": "16.0.1.1.5",
"development_status": "Production/Stable",
"author": "ACSONE SA/NV, "
"Tecnativa, "
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/description/index.html b/web_timeline/static/description/index.html
index 00d9c2ebb..07e61e25d 100644
--- a/web_timeline/static/description/index.html
+++ b/web_timeline/static/description/index.html
@@ -367,7 +367,7 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:bd504d36989179a1e11223c03dbf38ac45f0a792c6ad15a2786c9283bc64650b
+!! source digest: sha256:de40cea8c12ae858a82a28ad5fae3c70c75def8b5b12311ed124973ec7da15c9
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Define a new view displaying events in an interactive visualization chart.
@@ -553,7 +553,6 @@ with the dragged start and end date.
- 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",
};