[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.
pull/2791/head
Houzéfa Abbasbhay 2024-04-04 12:48:32 +02:00
parent abe3289943
commit 907aa64876
No known key found for this signature in database
GPG Key ID: B30E9425D9198EC1
4 changed files with 84 additions and 39 deletions

View File

@ -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

View File

@ -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",
});
}
},

View File

@ -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,
});
},

View File

@ -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",
};