3
0
Fork 0

[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.
16.0
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; * Implement a more efficient way of refreshing timeline after a record update;
* Make ``attrs`` attribute work; * 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 * When grouping by m2m and more than one record is set, the timeline item appears only
on one group. Allow showing in both groups. on one group. Allow showing in both groups.
* When grouping by m2m and dragging for changing the time or the group, the changes on * 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({ export default AbstractController.extend({
custom_events: _.extend({}, AbstractController.prototype.custom_events, { custom_events: _.extend({}, AbstractController.prototype.custom_events, {
onGroupClick: "_onGroupClick", onGroupClick: "_onGroupClick",
onItemDoubleClick: "_onItemDoubleClick",
onUpdate: "_onUpdate", onUpdate: "_onUpdate",
onRemove: "_onRemove", onRemove: "_onRemove",
onMove: "_onMove", 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 * Opens a form view of a clicked timeline
* item (triggered by the TimelineRenderer). * item (triggered by the TimelineRenderer).
@ -109,33 +121,35 @@ export default AbstractController.extend({
* @param {EventObject} event * @param {EventObject} event
*/ */
_onUpdate: function (event) { _onUpdate: function (event) {
this.renderer = event.data.renderer;
const rights = event.data.rights;
const item = event.data.item; const item = event.data.item;
const id = Number(item.evt.id) || item.evt.id; const item_id = Number(item.evt.id) || item.evt.id;
const title = item.evt.__name; 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) { 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( this.Dialog = Component.env.services.dialog.add(
FormViewDialog, FormViewDialog,
{ options,
resId: id,
context: this.getSession().user_context,
title: title,
onRecordSaved: () => this.write_completed(),
resModel: this.model.modelName,
},
{} {}
); );
} else { } else {
let mode = "readonly";
if (rights.write) {
mode = "edit";
}
this.trigger_up("switch_view", { this.trigger_up("switch_view", {
view_type: "form", view_type: "form",
res_id: id,
mode: mode,
model: this.model.modelName, 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.modelName = params.model;
this.mode = params.mode; this.mode = params.mode;
this.options = params.options; 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.min_height = params.min_height;
this.date_start = params.date_start; this.date_start = params.date_start;
this.date_stop = params.date_stop; this.date_stop = params.date_stop;
@ -192,22 +195,25 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
*/ */
init_timeline: function () { init_timeline: function () {
this._computeMode(); this._computeMode();
this.options.editable = { this.options.editable = {};
// Add new items by double tapping if (this.can_update && this.modelClass.data.rights.write) {
add: this.modelClass.data.rights.create, this.options.onMove = this.on_move;
this.options.onUpdate = this.on_update;
// Drag items horizontally // Drag items horizontally
updateTime: this.modelClass.data.rights.write, this.options.editable.updateTime = true;
// Drag items from one group to another // 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 // Delete an item by tapping the delete button top right
remove: this.modelClass.data.rights.unlink, this.options.editable.remove = true;
}; }
$.extend(this.options, {
onAdd: this.on_add,
onMove: this.on_move,
onUpdate: this.on_update,
onRemove: this.on_remove,
});
this.options.xss = {disabled: true}; this.options.xss = {disabled: true};
this.qweb = new QWeb(session.debug, {_s: session.origin}, false); this.qweb = new QWeb(session.debug, {_s: session.origin}, false);
if (this.arch.children.length) { 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 = 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(","); const group_bys = this.arch.attrs.default_group_by.split(",");
this.last_group_bys = group_bys; this.last_group_bys = group_bys;
this.last_domains = this.modelClass.data.domain; 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 * @param {ClickEvent} e
* @private * @private
*/ */
on_group_click: function (e) { on_timeline_click: function (e) {
if (e.what === "group-label" && e.group !== -1) { if (e.what === "group-label" && e.group !== -1) {
this._trigger( this._trigger(
e, 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. * 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 {HTMLElement} item
* @param {Function} callback * @param {Function} callback
@ -626,7 +654,6 @@ odoo.define("web_timeline.TimelineRenderer", function (require) {
this.trigger_up(trigger, { this.trigger_up(trigger, {
item: item, item: item,
callback: callback, callback: callback,
rights: this.modelClass.data.rights,
renderer: this, renderer: this,
}); });
}, },

View File

@ -22,6 +22,10 @@ odoo.define("web_timeline.TimelineView", function (require) {
return _.isUndefined(value) || _.isNull(value); return _.isUndefined(value) || _.isNull(value);
} }
function toBoolDefaultTrue(value) {
return isNullOrUndef(value) ? true : utils.toBoolElse(value, true);
}
var TimelineView = AbstractView.extend({ var TimelineView = AbstractView.extend({
display_name: _lt("Timeline"), display_name: _lt("Timeline"),
icon: "fa fa-tasks", icon: "fa fa-tasks",
@ -100,6 +104,9 @@ odoo.define("web_timeline.TimelineView", function (require) {
this.rendererParams.model = this.modelName; this.rendererParams.model = this.modelName;
this.rendererParams.view = this; this.rendererParams.view = this;
this.rendererParams.options = this._preapre_vis_timeline_options(attrs); 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_start = date_start;
this.rendererParams.date_stop = date_stop; this.rendererParams.date_stop = date_stop;
this.rendererParams.date_delay = date_delay; this.rendererParams.date_delay = date_delay;
@ -127,9 +134,7 @@ odoo.define("web_timeline.TimelineView", function (require) {
selectable: true, selectable: true,
multiselect: true, multiselect: true,
showCurrentTime: true, showCurrentTime: true,
stack: isNullOrUndef(attrs.stack) stack: toBoolDefaultTrue(attrs.stack),
? true
: utils.toBoolElse(attrs.stack, true),
margin: attrs.margin ? JSON.parse(attrs.margin) : {item: 2}, margin: attrs.margin ? JSON.parse(attrs.margin) : {item: 2},
zoomKey: attrs.zoomKey || "ctrlKey", zoomKey: attrs.zoomKey || "ctrlKey",
}; };