[MERGE] xmo editable lists
bzr revid: al@openerp.com-20110609012045-off7e8p7qem11vza
This commit is contained in:
commit
45eb411f04
|
@ -611,20 +611,6 @@ class ListView(View):
|
|||
fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
|
||||
return {'fields_view': fields_view}
|
||||
|
||||
def fields_view_get(self, request, model, view_id, view_type="tree",
|
||||
transform=True, toolbar=False, submenu=False):
|
||||
""" Sets @editable on the view's arch if it isn't already set and
|
||||
``set_editable`` is present in the request context
|
||||
"""
|
||||
view = super(ListView, self).fields_view_get(
|
||||
request, model, view_id, view_type, transform, toolbar, submenu)
|
||||
|
||||
view_attributes = view['arch']['attrs']
|
||||
if request.context.get('set_editable')\
|
||||
and 'editable' not in view_attributes:
|
||||
view_attributes['editable'] = 'bottom'
|
||||
return view
|
||||
|
||||
def process_colors(self, view, row, context):
|
||||
colors = view['arch']['attrs'].get('colors')
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<script type="text/javascript" src="/base/static/src/js/views.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/form.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/list.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/list-editable.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/tree.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/search.js"></script>
|
||||
<script type="text/javascript" src="/base/static/src/js/m2o.js"></script>
|
||||
|
|
|
@ -27,6 +27,10 @@ body.openerp {
|
|||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.openerp .oe-number {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
/* STATES */
|
||||
.openerp .on_logged {
|
||||
display: none;
|
||||
|
@ -602,6 +606,7 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
|
|||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe-listview .oe-field-cell button:active {
|
||||
opacity: 0.5;
|
||||
|
|
|
@ -132,6 +132,7 @@ openerp.base = function(instance) {
|
|||
openerp.base.tree(instance);
|
||||
openerp.base.m2o(instance);
|
||||
openerp.base.form(instance);
|
||||
openerp.base.list.editable(instance);
|
||||
};
|
||||
|
||||
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|
||||
|
|
|
@ -149,6 +149,16 @@ openerp.base.Registry = Class.extend( /** @lends openerp.base.Registry# */ {
|
|||
add: function (key, object_path) {
|
||||
this.map[key] = object_path;
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Creates and returns a copy of the current mapping, with the provided
|
||||
* mapping argument added in (replacing existing keys if needed)
|
||||
*
|
||||
* @param {Object} [mapping={}] a mapping of keys to object-paths
|
||||
*/
|
||||
clone: function (mapping) {
|
||||
return new openerp.base.Registry(
|
||||
_.extend({}, this.map, mapping || {}));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
|
|||
*/
|
||||
read_ids: function (ids, fields, callback) {
|
||||
var self = this;
|
||||
this.rpc('/base/dataset/get', {
|
||||
return this.rpc('/base/dataset/get', {
|
||||
model: this.model,
|
||||
ids: ids,
|
||||
fields: fields
|
||||
|
@ -279,10 +279,10 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
|
|||
*/
|
||||
read_index: function (fields, callback) {
|
||||
if (_.isEmpty(this.ids)) {
|
||||
callback([]);
|
||||
return $.Deferred().reject().promise();
|
||||
} else {
|
||||
fields = fields || false;
|
||||
this.read_ids([this.ids[this.index]], fields, function(records) {
|
||||
return this.read_ids([this.ids[this.index]], fields, function(records) {
|
||||
callback(records[0]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,12 +8,15 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
|
|||
* view should be displayed (if there is one active).
|
||||
*/
|
||||
searchable: false,
|
||||
template: "FormView",
|
||||
/**
|
||||
* @constructs
|
||||
* @param {openerp.base.Session} session the current openerp session
|
||||
* @param {String} element_id this view's root element id
|
||||
* @param {openerp.base.DataSet} dataset the dataset this view will work with
|
||||
* @param {String} view_id the identifier of the OpenERP view object
|
||||
*
|
||||
* @property {openerp.base.Registry} registry=openerp.base.form.widgets widgets registry for this form view instance
|
||||
*/
|
||||
init: function(view_manager, session, element_id, dataset, view_id) {
|
||||
this._super(session, element_id);
|
||||
|
@ -29,7 +32,8 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
|
|||
this.ready = false;
|
||||
this.show_invalid = true;
|
||||
this.touched = false;
|
||||
this.flags = this.view_manager.flags || {};
|
||||
this.flags = this.view_manager.action.flags || {};
|
||||
this.registry = openerp.base.form.widgets;
|
||||
},
|
||||
start: function() {
|
||||
//this.log('Starting FormView '+this.model+this.view_id)
|
||||
|
@ -44,9 +48,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
|
|||
var self = this;
|
||||
this.fields_view = data.fields_view;
|
||||
|
||||
var frame = new openerp.base.form.WidgetFrame(this, this.fields_view.arch);
|
||||
var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
|
||||
|
||||
this.$element.html(QWeb.render("FormView", { 'frame': frame, 'view': this }));
|
||||
this.$element.html(QWeb.render(this.template, { 'frame': frame, 'view': this }));
|
||||
_.each(this.widgets, function(w) {
|
||||
w.start();
|
||||
});
|
||||
|
@ -220,7 +224,15 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
|
|||
self.on_record_loaded(result.result);
|
||||
});
|
||||
},
|
||||
do_save: function(success) {
|
||||
/**
|
||||
* Triggers saving the form's record. Chooses between creating a new
|
||||
* record or saving an existing one depending on whether the record
|
||||
* already has an id property.
|
||||
*
|
||||
* @param {Function} success callback on save success
|
||||
* @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new record, should that record be inserted at the start of the dataset (by default, records are added at the end)
|
||||
*/
|
||||
do_save: function(success, prepend_on_create) {
|
||||
var self = this;
|
||||
if (!this.ready) {
|
||||
return false;
|
||||
|
@ -243,7 +255,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
|
|||
this.log("About to save", values);
|
||||
if (!this.datarecord.id) {
|
||||
this.dataset.create(values, function(r) {
|
||||
self.on_created(r, success);
|
||||
self.on_created(r, success, prepend_on_create);
|
||||
});
|
||||
} else {
|
||||
this.dataset.write(this.datarecord.id, values, function(r) {
|
||||
|
@ -281,19 +293,37 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
|
|||
}
|
||||
}
|
||||
},
|
||||
on_created: function(r, success) {
|
||||
/**
|
||||
* Updates the form' dataset to contain the new record:
|
||||
*
|
||||
* * Adds the newly created record to the current dataset (at the end by
|
||||
* default)
|
||||
* * Selects that record (sets the dataset's index to point to the new
|
||||
* record's id).
|
||||
* * Updates the pager and sidebar displays
|
||||
*
|
||||
* @param {Object} r
|
||||
* @param {Function} success callback to execute after having updated the dataset
|
||||
* @param {Boolean} [prepend_on_create=false] adds the newly created record at the beginning of the dataset instead of the end
|
||||
*/
|
||||
on_created: function(r, success, prepend_on_create) {
|
||||
if (!r.result) {
|
||||
this.notification.warn("Record not created", "Problem while creating record.");
|
||||
} else {
|
||||
this.datarecord.id = arguments[0].result;
|
||||
this.dataset.ids.push(this.datarecord.id);
|
||||
this.dataset.index = this.dataset.ids.length - 1;
|
||||
this.datarecord.id = r.result;
|
||||
if (!prepend_on_create) {
|
||||
this.dataset.ids.push(this.datarecord.id);
|
||||
this.dataset.index = this.dataset.ids.length - 1;
|
||||
} else {
|
||||
this.dataset.ids.unshift(this.datarecord.id);
|
||||
this.dataset.index = 0;
|
||||
}
|
||||
this.dataset.count++;
|
||||
this.do_update_pager();
|
||||
this.do_update_sidebar();
|
||||
this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id);
|
||||
if (success) {
|
||||
success(r);
|
||||
success(_.extend(r, {created: true}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -422,6 +452,7 @@ openerp.base.form.compute_domain = function(expr, fields) {
|
|||
};
|
||||
|
||||
openerp.base.form.Widget = openerp.base.Controller.extend({
|
||||
template: 'Widget',
|
||||
init: function(view, node) {
|
||||
this.view = view;
|
||||
this.node = node;
|
||||
|
@ -435,7 +466,6 @@ openerp.base.form.Widget = openerp.base.Controller.extend({
|
|||
this.view.widgets[this.element_id] = this;
|
||||
this.children = node.children;
|
||||
this.colspan = parseInt(node.attrs.colspan || 1);
|
||||
this.template = "Widget";
|
||||
|
||||
this.string = this.string || node.attrs.string;
|
||||
this.help = this.help || node.attrs.help;
|
||||
|
@ -460,9 +490,9 @@ openerp.base.form.Widget = openerp.base.Controller.extend({
|
|||
});
|
||||
|
||||
openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
|
||||
template: 'WidgetFrame',
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "WidgetFrame";
|
||||
this.columns = node.attrs.col || 4;
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
|
@ -504,9 +534,9 @@ openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
|
|||
handle_node: function(node) {
|
||||
var type = this.view.fields_view.fields[node.attrs.name] || {};
|
||||
var widget_type = node.attrs.widget || type.type || node.tag;
|
||||
var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, node);
|
||||
var widget = new (this.view.registry.get_object(widget_type)) (this.view, node);
|
||||
if (node.tag == 'field' && node.attrs.nolabel != '1') {
|
||||
var label = new (openerp.base.form.widgets.get_object('label')) (this.view, node);
|
||||
var label = new (this.view.registry.get_object('label')) (this.view, node);
|
||||
label["for"] = widget;
|
||||
this.add_widget(label);
|
||||
}
|
||||
|
@ -1369,6 +1399,7 @@ openerp.base.form.FieldBinaryImage = openerp.base.form.FieldBinary.extend({
|
|||
* Registry of form widgets, called by :js:`openerp.base.FormView`
|
||||
*/
|
||||
openerp.base.form.widgets = new openerp.base.Registry({
|
||||
'frame' : 'openerp.base.form.WidgetFrame',
|
||||
'group' : 'openerp.base.form.WidgetFrame',
|
||||
'notebook' : 'openerp.base.form.WidgetNotebook',
|
||||
'separator' : 'openerp.base.form.WidgetSeparator',
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
/**
|
||||
* @namespace handles editability case for lists, because it depends on form and forms already depends on lists it had to be split out
|
||||
*/
|
||||
openerp.base.list.editable = function (openerp) {
|
||||
var KEY_RETURN = 13,
|
||||
KEY_ESCAPE = 27;
|
||||
|
||||
// editability status of list rows
|
||||
openerp.base.ListView.prototype.defaults.editable = null;
|
||||
|
||||
var old_init = openerp.base.ListView.prototype.init,
|
||||
old_actual_search = openerp.base.ListView.prototype.do_actual_search,
|
||||
old_add_record = openerp.base.ListView.prototype.do_add_record,
|
||||
old_on_loaded = openerp.base.ListView.prototype.on_loaded;
|
||||
// TODO: not sure second @lends on existing item is correct, to check
|
||||
_.extend(openerp.base.ListView.prototype, /** @lends openerp.base.ListView# */{
|
||||
init: function () {
|
||||
var self = this;
|
||||
old_init.apply(this, arguments);
|
||||
$(this.groups).bind({
|
||||
'edit': function (e, id, dataset) {
|
||||
self.do_edit(dataset.index, id, dataset);
|
||||
},
|
||||
'saved': function () {
|
||||
if (self.groups.get_selection().length) {
|
||||
return;
|
||||
}
|
||||
self.compute_aggregates();
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Handles the activation of a record in editable mode (making a record
|
||||
* editable), called *after* the record has become editable.
|
||||
*
|
||||
* The default behavior is to setup the listview's dataset to match
|
||||
* whatever dataset was provided by the editing List
|
||||
*
|
||||
* @param {Number} index index of the record in the dataset
|
||||
* @param {Object} id identifier of the record being edited
|
||||
* @param {openerp.base.DataSet} dataset dataset in which the record is available
|
||||
*/
|
||||
do_edit: function (index, id, dataset) {
|
||||
_.extend(this.dataset, dataset);
|
||||
},
|
||||
/**
|
||||
* Sets editability status for the list, based on defaults, view
|
||||
* architecture and the provided flag, if any.
|
||||
*
|
||||
* @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom".
|
||||
*/
|
||||
set_editable: function (force) {
|
||||
// If ``force``, set editability to bottom
|
||||
// otherwise rely on view default
|
||||
// view' @editable is handled separately as we have not yet
|
||||
// fetched and processed the view at this point.
|
||||
this.options.editable = (
|
||||
(force && "bottom")
|
||||
|| this.defaults.editable);
|
||||
},
|
||||
/**
|
||||
* Replace do_actual_search to handle editability process
|
||||
*/
|
||||
do_actual_search: function (results) {
|
||||
this.set_editable(results.context['set_editable']);
|
||||
old_actual_search.call(this, results);
|
||||
},
|
||||
/**
|
||||
* Replace do_add_record to handle editability (and adding new record
|
||||
* as an editable row at the top or bottom of the list)
|
||||
*/
|
||||
do_add_record: function () {
|
||||
if (this.options.editable) {
|
||||
this.groups.new_record();
|
||||
} else {
|
||||
old_add_record.call(this);
|
||||
}
|
||||
},
|
||||
on_loaded: function (data, grouped) {
|
||||
// tree/@editable takes priority on everything else if present.
|
||||
this.options.editable = data.fields_view.arch.attrs.editable || this.options.editable;
|
||||
return old_on_loaded.call(this, data, grouped);
|
||||
}
|
||||
});
|
||||
|
||||
_.extend(openerp.base.ListView.Groups.prototype, /** @lends openerp.base.ListView.Groups# */{
|
||||
passtrough_events: openerp.base.ListView.Groups.prototype.passtrough_events + " edit saved",
|
||||
new_record: function () {
|
||||
// TODO: handle multiple children
|
||||
this.children[null].new_record();
|
||||
}
|
||||
});
|
||||
|
||||
var old_list_row_clicked = openerp.base.ListView.List.prototype.row_clicked;
|
||||
_.extend(openerp.base.ListView.List.prototype, /** @lends openerp.base.ListView.List */{
|
||||
row_clicked: function (event) {
|
||||
if (!this.options.editable) {
|
||||
return old_list_row_clicked.call(this, event);
|
||||
}
|
||||
this.edit_record();
|
||||
},
|
||||
/**
|
||||
* Checks if a record is being edited, and if so cancels it
|
||||
*/
|
||||
cancel_pending_edition: function () {
|
||||
if (!this.edition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.edition_index !== null) {
|
||||
this.reload_record(this.edition_index);
|
||||
}
|
||||
this.edition_form.stop();
|
||||
this.edition_form.$element.remove();
|
||||
delete this.edition_form;
|
||||
delete this.edition_index;
|
||||
delete this.edition;
|
||||
},
|
||||
render_row_as_form: function (row) {
|
||||
this.cancel_pending_edition();
|
||||
|
||||
var self = this;
|
||||
var $new_row = $('<tr>', {
|
||||
id: _.uniqueId('oe-editable-row-'),
|
||||
'class': $(row).attr('class'),
|
||||
click: function (e) {e.stopPropagation();}
|
||||
})
|
||||
.keyup(function (e) {
|
||||
switch (e.which) {
|
||||
case KEY_RETURN:
|
||||
self.save_row(true);
|
||||
break;
|
||||
case KEY_ESCAPE:
|
||||
self.cancel_edition();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
})
|
||||
.delegate('button.oe-edit-row-save', 'click', function () {
|
||||
self.save_row();
|
||||
})
|
||||
.delegate('button.oe-edit-row-cancel', 'click', function () {
|
||||
self.cancel_edition();
|
||||
});
|
||||
if (row) {
|
||||
$new_row.replaceAll(row);
|
||||
} else if (this.options.editable === 'top') {
|
||||
this.$current.prepend($new_row);
|
||||
} else if (this.options.editable) {
|
||||
this.$current.append($new_row);
|
||||
}
|
||||
this.edition = true;
|
||||
this.edition_index = this.dataset.index;
|
||||
this.edition_form = _.extend(new openerp.base.FormView(
|
||||
null, this.group.view.session, $new_row.attr('id'),
|
||||
this.dataset, false), {
|
||||
template: 'ListView.row.form',
|
||||
registry: openerp.base.list.form.widgets
|
||||
});
|
||||
$.when(this.edition_form.on_loaded({fields_view: this.get_fields_view()})).then(function () {
|
||||
// put in $.when just in case FormView.on_loaded becomes asynchronous
|
||||
$new_row.find('td')
|
||||
.addClass('oe-field-cell')
|
||||
.removeAttr('width')
|
||||
.end()
|
||||
.find('td:first').removeClass('oe-field-cell').end()
|
||||
.find('td:last').removeClass('oe-field-cell').end();
|
||||
// pad in case of groupby
|
||||
_(self.columns).each(function (column) {
|
||||
if (column.meta) {
|
||||
$new_row.prepend('<td>');
|
||||
}
|
||||
});
|
||||
|
||||
self.edition_form.do_show();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Saves the current row, and triggers the edition of its following
|
||||
* sibling if asked.
|
||||
*
|
||||
* @param {Boolean} [edit_next=false] should the next row become editable
|
||||
*/
|
||||
save_row: function (edit_next) {
|
||||
var self = this;
|
||||
this.edition_form.do_save(function (result) {
|
||||
self.reload_record(self.dataset.index, true).then(function () {
|
||||
self.edition_form.stop();
|
||||
delete self.edition_form;
|
||||
delete self.edition_index;
|
||||
delete self.edition;
|
||||
|
||||
$(self).trigger('saved', [self.dataset]);
|
||||
if (!edit_next) {
|
||||
return;
|
||||
}
|
||||
if (result.created) {
|
||||
self.new_record();
|
||||
return;
|
||||
}
|
||||
self.dataset.next();
|
||||
self.edit_record();
|
||||
});
|
||||
}, this.options.editable === 'top');
|
||||
},
|
||||
/**
|
||||
* Cancels the edition of the row for the current dataset index
|
||||
*/
|
||||
cancel_edition: function () {
|
||||
this.cancel_pending_edition();
|
||||
},
|
||||
/**
|
||||
* Edits record currently selected via dataset
|
||||
*/
|
||||
edit_record: function () {
|
||||
this.render_row_as_form(
|
||||
this.$current.children(
|
||||
_.sprintf('[data-index=%d]',
|
||||
this.dataset.index)));
|
||||
$(this).trigger(
|
||||
'edit',
|
||||
[this.rows[this.dataset.index].data.id.value, this.dataset]);
|
||||
},
|
||||
new_record: function () {
|
||||
this.dataset.index = null;
|
||||
this.render_row_as_form();
|
||||
}
|
||||
});
|
||||
openerp.base.list = {form: {}};
|
||||
openerp.base.list.form.WidgetFrame = openerp.base.form.WidgetFrame.extend({
|
||||
template: 'ListView.row.frame'
|
||||
});
|
||||
var form_widgets = openerp.base.form.widgets;
|
||||
openerp.base.list.form.widgets = form_widgets.clone({
|
||||
'frame': 'openerp.base.list.form.WidgetFrame'
|
||||
});
|
||||
// All form widgets inherit a problematic behavior from
|
||||
// openerp.base.form.WidgetFrame: the cell itself is removed when invisible
|
||||
// whether it's @invisible or @attrs[invisible]. In list view, only the
|
||||
// former should completely remove the cell. We need to override update_dom
|
||||
// on all widgets since we can't just hit on widget itself (I think)
|
||||
var list_form_widgets = openerp.base.list.form.widgets;
|
||||
_(list_form_widgets.map).each(function (widget_path, key) {
|
||||
if (key === 'frame') { return; }
|
||||
var new_path = 'openerp.base.list.form.' + key;
|
||||
|
||||
openerp.base.list.form[key] = (form_widgets.get_object(key)).extend({
|
||||
update_dom: function () {
|
||||
this.$element.children().css('visibility', '');
|
||||
if (this.invisible && this.node.attrs.invisible !== '1') {
|
||||
this.$element.children().css('visibility', 'hidden');
|
||||
} else {
|
||||
this._super();
|
||||
}
|
||||
}
|
||||
});
|
||||
list_form_widgets.add(key, new_path);
|
||||
});
|
||||
};
|
|
@ -83,8 +83,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
|||
'action': function (e, action_name, id, callback) {
|
||||
self.do_action(action_name, id, callback);
|
||||
},
|
||||
'row_link': function (e, index, id, dataset) {
|
||||
self.do_activate_record(index, id, dataset);
|
||||
'row_link': function (e, id, dataset) {
|
||||
self.do_activate_record(dataset.index, id, dataset);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -134,9 +134,11 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
|||
this.$element.html(QWeb.render("ListView", this));
|
||||
|
||||
// Head hook
|
||||
this.$element.find('#oe-list-add').click(this.do_add_record);
|
||||
this.$element.find('#oe-list-add')
|
||||
.click(this.do_add_record)
|
||||
.attr('disabled', grouped && this.options.editable);
|
||||
this.$element.find('#oe-list-delete')
|
||||
.hide()
|
||||
.attr('disabled', true)
|
||||
.click(this.do_delete_selected);
|
||||
this.$element.find('thead').delegate('th[data-id]', 'click', function (e) {
|
||||
e.stopPropagation();
|
||||
|
@ -199,18 +201,24 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
|||
return column.invisible !== '1';
|
||||
});
|
||||
|
||||
this.aggregate_columns = _(this.columns).chain()
|
||||
.filter(function (column) {
|
||||
return column['sum'] || column['avg'];})
|
||||
this.aggregate_columns = _(this.visible_columns)
|
||||
.map(function (column) {
|
||||
var func = column['sum'] ? 'sum' : 'avg';
|
||||
if (column.type !== 'integer' && column.type !== 'float') {
|
||||
return {};
|
||||
}
|
||||
var aggregation_func = column['group_operator'] || 'sum';
|
||||
|
||||
if (!column[aggregation_func]) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
field: column.id,
|
||||
type: column.type,
|
||||
'function': func,
|
||||
label: column[func]
|
||||
'function': aggregation_func,
|
||||
label: column[aggregation_func]
|
||||
};
|
||||
}).value();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Used to handle a click on a table row, if no other handler caught the
|
||||
|
@ -267,6 +275,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
|||
return this.rpc('/base/listview/load', {
|
||||
model: this.model,
|
||||
view_id: this.view_id,
|
||||
context: this.dataset.context,
|
||||
toolbar: !!this.flags.sidebar
|
||||
}, callback);
|
||||
}
|
||||
|
@ -289,25 +298,32 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
|||
* @returns {$.Deferred} fold request evaluation promise
|
||||
*/
|
||||
do_search: function (domains, contexts, groupbys) {
|
||||
var self = this;
|
||||
return this.rpc('/base/session/eval_domain_and_context', {
|
||||
domains: domains,
|
||||
contexts: contexts,
|
||||
group_by_seq: groupbys
|
||||
}, function (results) {
|
||||
self.dataset.context = results.context;
|
||||
self.dataset.domain = results.domain;
|
||||
self.groups.datagroup = new openerp.base.DataGroup(
|
||||
self.session, self.model,
|
||||
results.domain, results.context,
|
||||
results.group_by);
|
||||
}, $.proxy(this, 'do_actual_search'));
|
||||
},
|
||||
/**
|
||||
* Handler for the result of eval_domain_and_context, actually perform the
|
||||
* searching
|
||||
*
|
||||
* @param {Object} results results of evaluating domain and process for a search
|
||||
*/
|
||||
do_actual_search: function (results) {
|
||||
this.dataset.context = results.context;
|
||||
this.dataset.domain = results.domain;
|
||||
this.groups.datagroup = new openerp.base.DataGroup(
|
||||
this.session, this.model,
|
||||
results.domain, results.context,
|
||||
results.group_by);
|
||||
|
||||
if (_.isEmpty(results.group_by) && !results.context['group_by_no_leaf']) {
|
||||
results.group_by = null;
|
||||
}
|
||||
self.reload_view(!!results.group_by).then(
|
||||
$.proxy(self, 'reload_content'));
|
||||
});
|
||||
if (_.isEmpty(results.group_by) && !results.context['group_by_no_leaf']) {
|
||||
results.group_by = null;
|
||||
}
|
||||
|
||||
this.reload_view(!!results.group_by).then(
|
||||
$.proxy(this, 'reload_content'));
|
||||
},
|
||||
/**
|
||||
* Handles the signal to delete a line from the DOM
|
||||
|
@ -349,13 +365,15 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
|||
*/
|
||||
do_select: function (ids, records) {
|
||||
this.$element.find('#oe-list-delete')
|
||||
.toggle(!!ids.length);
|
||||
.attr('disabled', !ids.length);
|
||||
|
||||
if (!records.length) {
|
||||
this.compute_aggregates();
|
||||
return;
|
||||
}
|
||||
this.compute_aggregates(records);
|
||||
this.compute_aggregates(_(records).map(function (record) {
|
||||
return {count: 1, values: record};
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* Handles action button signals on a record
|
||||
|
@ -382,7 +400,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
|||
*
|
||||
* @param {Number} index index of the record in the dataset
|
||||
* @param {Object} id identifier of the activated record
|
||||
* @param {openobject.base.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups)
|
||||
* @param {openerp.base.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups)
|
||||
*/
|
||||
do_activate_record: function (index, id, dataset) {
|
||||
var self = this;
|
||||
|
@ -420,75 +438,56 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
|||
* @param {Array} [records]
|
||||
*/
|
||||
compute_aggregates: function (records) {
|
||||
if (_.isEmpty(this.aggregate_columns)) {
|
||||
return;
|
||||
}
|
||||
var columns = _(this.aggregate_columns).filter(function (column) {
|
||||
return column['function']; });
|
||||
|
||||
if (_.isEmpty(columns)) { return; }
|
||||
|
||||
if (_.isEmpty(records)) {
|
||||
records = this.groups.get_records();
|
||||
}
|
||||
|
||||
var aggregator = this.build_aggregator(this.aggregate_columns);
|
||||
this.display_aggregates(
|
||||
_(records).reduce(aggregator, aggregator).value());
|
||||
},
|
||||
/**
|
||||
* Creates a stateful callable aggregator object, which can be reduced over
|
||||
* a collection of records in order to build the aggregations described
|
||||
* by the parameter
|
||||
*
|
||||
* @param {Array} aggregation_descriptors
|
||||
*/
|
||||
build_aggregator: function (aggregation_descriptors) {
|
||||
var values = {};
|
||||
var descriptors = {};
|
||||
_(aggregation_descriptors).each(function (descriptor) {
|
||||
values[descriptor.field] = [];
|
||||
descriptors[descriptor.field] = descriptor;
|
||||
});
|
||||
|
||||
var aggregator = function (_i, record) {
|
||||
_(values).each(function (collection, key) {
|
||||
collection.push(record[key]);
|
||||
});
|
||||
|
||||
return aggregator;
|
||||
};
|
||||
aggregator.value = function () {
|
||||
var result = {};
|
||||
|
||||
_(values).each(function (collection, key) {
|
||||
var value;
|
||||
switch(descriptors[key]['function']) {
|
||||
case 'avg':
|
||||
value = (_(collection).chain()
|
||||
.filter(function (item) {
|
||||
return !_.isUndefined(item); })
|
||||
.reduce(function (total, item) {
|
||||
return total + item; }, 0).value()
|
||||
/ collection.length);
|
||||
break;
|
||||
var count = 0, sums = {};
|
||||
_(columns).each(function (column) { sums[column.field] = 0; });
|
||||
_(records).each(function (record) {
|
||||
count += record.count || 1;
|
||||
_(columns).each(function (column) {
|
||||
var field = column.field;
|
||||
switch (column['function']) {
|
||||
case 'sum':
|
||||
value = (_(collection).chain()
|
||||
.filter(function (item) {
|
||||
return !_.isUndefined(item); })
|
||||
.reduce(function (total, item) {
|
||||
return total + item; }, 0).value());
|
||||
sums[field] += record.values[field];
|
||||
break;
|
||||
case 'avg':
|
||||
sums[field] += record.count * record.values[field];
|
||||
break;
|
||||
}
|
||||
result[key] = value;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
return aggregator;
|
||||
var aggregates = {};
|
||||
_(columns).each(function (column) {
|
||||
var field = column.field;
|
||||
switch (column['function']) {
|
||||
case 'sum':
|
||||
aggregates[field] = sums[field];
|
||||
break;
|
||||
case 'avg':
|
||||
aggregates[field] = sums[field] / count;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.display_aggregates(aggregates);
|
||||
},
|
||||
display_aggregates: function (aggregation) {
|
||||
var $footer = this.$element.find('.oe-list-footer').empty();
|
||||
var $footer_cells = this.$element.find('.oe-list-footer');
|
||||
_(this.aggregate_columns).each(function (column) {
|
||||
$(_.sprintf(
|
||||
"<span>%s: %.2f</span>",
|
||||
column.label, aggregation[column.field]))
|
||||
.appendTo($footer);
|
||||
if (!column['function']) {
|
||||
return;
|
||||
}
|
||||
var pattern = (column.type == 'integer') ? '%d' : '%.2f';
|
||||
$footer_cells.filter(_.sprintf('[data-field=%s]', column.field))
|
||||
.text(_.sprintf(pattern, aggregation[column.field]));
|
||||
});
|
||||
}
|
||||
// TODO: implement reorder (drag and drop rows)
|
||||
|
@ -521,8 +520,9 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
|
|||
* @constructs
|
||||
* @param {Object} opts display options, identical to those of :js:class:`openerp.base.ListView`
|
||||
*/
|
||||
init: function (opts) {
|
||||
init: function (group, opts) {
|
||||
var self = this;
|
||||
this.group = group;
|
||||
|
||||
this.options = opts.options;
|
||||
this.columns = opts.columns;
|
||||
|
@ -546,19 +546,26 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
|
|||
e.stopPropagation();
|
||||
var $target = $(e.currentTarget),
|
||||
field = $target.closest('td').data('field'),
|
||||
record_id = self.row_id($target.closest('tr'));
|
||||
$row = $target.closest('tr'),
|
||||
record_id = self.row_id($row),
|
||||
index = self.row_position($row);
|
||||
|
||||
$(self).trigger('action', [field, record_id]);
|
||||
$(self).trigger('action', [field, record_id, function () {
|
||||
self.reload_record(index, true);
|
||||
}]);
|
||||
})
|
||||
.delegate('tr', 'click', function (e) {
|
||||
e.stopPropagation();
|
||||
$(self).trigger(
|
||||
'row_link',
|
||||
[self.row_position(e.currentTarget),
|
||||
self.row_id(e.currentTarget),
|
||||
self.dataset]);
|
||||
self.dataset.index = self.row_position(e.currentTarget);
|
||||
self.row_clicked(e);
|
||||
});
|
||||
},
|
||||
row_clicked: function () {
|
||||
$(this).trigger(
|
||||
'row_link',
|
||||
[this.rows[this.dataset.index].data.id.value,
|
||||
this.dataset]);
|
||||
},
|
||||
render: function () {
|
||||
if (this.$current) {
|
||||
this.$current.remove();
|
||||
|
@ -566,6 +573,18 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
|
|||
this.$current = this.$_element.clone(true);
|
||||
this.$current.empty().append($(QWeb.render('ListView.rows', this)));
|
||||
},
|
||||
get_fields_view: function () {
|
||||
// deep copy of view
|
||||
var view = $.extend(true, {}, this.group.view.fields_view);
|
||||
_(view.arch.children).each(function (widget) {
|
||||
widget.attrs.nolabel = true;
|
||||
if (widget.tag === 'button') {
|
||||
delete widget.attrs.string;
|
||||
}
|
||||
});
|
||||
view.arch.attrs.col = 2 * view.arch.children.length;
|
||||
return view;
|
||||
},
|
||||
/**
|
||||
* Gets the ids of all currently selected records, if any
|
||||
* @returns {Object} object with the keys ``ids`` and ``records``, holding respectively the ids of all selected records and the records themselves.
|
||||
|
@ -579,7 +598,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
|
|||
this.$current.find('th.oe-record-selector input:checked')
|
||||
.closest('tr').each(function () {
|
||||
var record = {};
|
||||
_(rows[$(this).prevAll().length].data).each(function (obj, key) {
|
||||
_(rows[$(this).data('index')].data).each(function (obj, key) {
|
||||
record[key] = obj.value;
|
||||
});
|
||||
result.ids.push(record.id);
|
||||
|
@ -594,7 +613,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
|
|||
* @returns {Number} the position of the row in this.rows
|
||||
*/
|
||||
row_position: function (row) {
|
||||
return $(row).prevAll().length;
|
||||
return $(row).data('index');
|
||||
},
|
||||
/**
|
||||
* Returns the identifier of the object displayed in the provided table
|
||||
|
@ -621,13 +640,86 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
|
|||
_(row.data).each(function (obj, key) {
|
||||
record[key] = obj.value;
|
||||
});
|
||||
return record;
|
||||
return {count: 1, values: record};
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Transforms a record from what is returned by a dataset read (a simple
|
||||
* mapping of ``$fieldname: $value``) to the format expected by list rows
|
||||
* and form views:
|
||||
*
|
||||
* data: {
|
||||
* $fieldname: {
|
||||
* value: $value
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* This format allows for the insertion of a bunch of metadata (names,
|
||||
* colors, etc...)
|
||||
*
|
||||
* @param {Object} record original record, in dataset format
|
||||
* @returns {Object} record displayable in a form or list view
|
||||
*/
|
||||
transform_record: function (record) {
|
||||
// TODO: colors handling
|
||||
var form_data = {},
|
||||
form_record = {data: form_data};
|
||||
|
||||
_(record).each(function (value, key) {
|
||||
form_data[key] = {value: value};
|
||||
});
|
||||
|
||||
return form_record;
|
||||
},
|
||||
/**
|
||||
* Reloads the record at index ``row_index`` in the list's rows.
|
||||
*
|
||||
* By default, simply re-renders the record. If the ``fetch`` parameter is
|
||||
* provided and ``true``, will first fetch the record anew.
|
||||
*
|
||||
* @param {Number} record_index index of the record to reload
|
||||
* @param {Boolean} fetch fetches the record from remote before reloading it
|
||||
*/
|
||||
reload_record: function (record_index, fetch) {
|
||||
var self = this;
|
||||
var read_p = null;
|
||||
if (fetch) {
|
||||
// save index to restore it later, if already set
|
||||
var old_index = this.dataset.index;
|
||||
this.dataset.index = record_index;
|
||||
read_p = this.dataset.read_index(
|
||||
_.filter(_.pluck(this.columns, 'name'), _.identity),
|
||||
function (record) {
|
||||
var form_record = self.transform_record(record);
|
||||
self.rows.splice(record_index, 1, form_record);
|
||||
self.dataset.index = old_index;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return $.when(read_p).then(function () {
|
||||
self.$current.children().eq(record_index)
|
||||
.replaceWith(self.render_record(record_index)); })
|
||||
},
|
||||
/**
|
||||
* Renders a list record to HTML
|
||||
*
|
||||
* @param {Number} record_index index of the record to render in ``this.rows``
|
||||
* @returns {String} QWeb rendering of the selected record
|
||||
*/
|
||||
render_record: function (record_index) {
|
||||
return QWeb.render('ListView.row', {
|
||||
columns: this.columns,
|
||||
options: this.options,
|
||||
row: this.rows[record_index],
|
||||
row_parity: (record_index % 2 === 0) ? 'even' : 'odd',
|
||||
row_index: record_index
|
||||
});
|
||||
}
|
||||
// drag and drop
|
||||
// editable?
|
||||
});
|
||||
openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Groups# */{
|
||||
passtrough_events: 'action deleted row_link',
|
||||
/**
|
||||
* Grouped display for the ListView. Handles basic DOM events and interacts
|
||||
* with the :js:class:`~openerp.base.DataGroup` bound to it.
|
||||
|
@ -679,24 +771,11 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
|
|||
}
|
||||
return red_letter_tboday;
|
||||
},
|
||||
open_group: function (e, group) {
|
||||
var row = e.currentTarget;
|
||||
|
||||
if (this.children[group.value]) {
|
||||
this.children[group.value].apoptosis();
|
||||
delete this.children[group.value];
|
||||
}
|
||||
var prospekt = this.children[group.value] = new openerp.base.ListView.Groups(this.view, {
|
||||
options: this.options,
|
||||
columns: this.columns
|
||||
});
|
||||
this.bind_child_events(prospekt);
|
||||
prospekt.datagroup = group;
|
||||
prospekt.render().insertAfter(
|
||||
this.point_insertion(row));
|
||||
$(row).find('span.ui-icon')
|
||||
.removeClass('ui-icon-triangle-1-e')
|
||||
.addClass('ui-icon-triangle-1-s');
|
||||
open: function (point_insertion) {
|
||||
this.render().insertAfter(point_insertion);
|
||||
},
|
||||
close: function () {
|
||||
this.apoptosis();
|
||||
},
|
||||
/**
|
||||
* Prefixes ``$node`` with floated spaces in order to indent it relative
|
||||
|
@ -716,18 +795,32 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
|
|||
var self = this;
|
||||
var placeholder = this.make_fragment();
|
||||
_(datagroups).each(function (group) {
|
||||
if (self.children[group.value]) {
|
||||
self.children[group.value].apoptosis();
|
||||
delete self.children[group.value];
|
||||
}
|
||||
var child = self.children[group.value] = new openerp.base.ListView.Groups(self.view, {
|
||||
options: self.options,
|
||||
columns: self.columns
|
||||
});
|
||||
self.bind_child_events(child);
|
||||
child.datagroup = group;
|
||||
|
||||
var $row = $('<tr>');
|
||||
if (group.openable) {
|
||||
$row.click(function (e) {
|
||||
if (!$row.data('open')) {
|
||||
$row.data('open', true);
|
||||
self.open_group(e, group);
|
||||
$row.data('open', true)
|
||||
.find('span.ui-icon')
|
||||
.removeClass('ui-icon-triangle-1-e')
|
||||
.addClass('ui-icon-triangle-1-s');
|
||||
child.open(self.point_insertion(e.currentTarget));
|
||||
} else {
|
||||
$row.removeData('open')
|
||||
.find('span.ui-icon')
|
||||
.removeClass('ui-icon-triangle-1-s')
|
||||
.addClass('ui-icon-triangle-1-e');
|
||||
_(self.children).each(function (child) {child.apoptosis();});
|
||||
child.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -779,21 +872,7 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
|
|||
// can have selections spanning multiple links
|
||||
var selection = self.get_selection();
|
||||
$this.trigger(e, [selection.ids, selection.records]);
|
||||
}).bind('action', function (e, name, id, callback) {
|
||||
if (!callback) {
|
||||
callback = function () {
|
||||
var $prev = child.$current.prev();
|
||||
if (!$prev.is('tbody')) {
|
||||
// ungrouped
|
||||
$(self.elements[0]).replaceWith(self.render());
|
||||
} else {
|
||||
// ghetto reload child (and its siblings)
|
||||
$prev.children().last().click();
|
||||
}
|
||||
};
|
||||
}
|
||||
$this.trigger(e, [name, id, callback]);
|
||||
}).bind('deleted row_link', function (e) {
|
||||
}).bind(this.passtrough_events, function (e) {
|
||||
// additional positional parameters are provided to trigger as an
|
||||
// Array, following the event type or event object, but are
|
||||
// provided to the .bind event handler as *args.
|
||||
|
@ -806,7 +885,7 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
|
|||
},
|
||||
render_dataset: function (dataset) {
|
||||
var rows = [],
|
||||
list = new openerp.base.ListView.List({
|
||||
list = new openerp.base.ListView.List(this, {
|
||||
options: this.options,
|
||||
columns: this.columns,
|
||||
dataset: dataset,
|
||||
|
@ -819,17 +898,8 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
|
|||
_.filter(_.pluck(this.columns, 'name'), _.identity),
|
||||
0, false,
|
||||
function (records) {
|
||||
var form_records = _(records).map(function (record) {
|
||||
// TODO: colors handling
|
||||
var form_data = {},
|
||||
form_record = {data: form_data};
|
||||
|
||||
_(record).each(function (value, key) {
|
||||
form_data[key] = {value: value};
|
||||
});
|
||||
|
||||
return form_record;
|
||||
});
|
||||
var form_records = _(records).map(
|
||||
$.proxy(list, 'transform_record'));
|
||||
|
||||
rows.splice(0, rows.length);
|
||||
rows.push.apply(rows, form_records);
|
||||
|
@ -881,6 +951,12 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
|
|||
return this;
|
||||
},
|
||||
get_records: function () {
|
||||
if (_(this.children).isEmpty()) {
|
||||
return {
|
||||
count: this.datagroup.length,
|
||||
values: this.datagroup.aggregates
|
||||
}
|
||||
}
|
||||
return _(this.children).chain()
|
||||
.map(function (child) {
|
||||
return child.get_records();
|
||||
|
|
|
@ -9,7 +9,7 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
|||
init: function(session, element_id) {
|
||||
this._super(session, element_id);
|
||||
this.viewmanager = null;
|
||||
this.dialog_stack = []
|
||||
this.dialog_stack = [];
|
||||
// Temporary linking view_manager to session.
|
||||
// Will use controller_parent to find it when implementation will be done.
|
||||
session.action_manager = this;
|
||||
|
@ -33,8 +33,7 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
|||
case 'ir.actions.act_window':
|
||||
if (action.target == 'new') {
|
||||
var element_id = _.uniqueId("act_window_dialog");
|
||||
var dialog = $('<div id="' + element_id + '"></div>');
|
||||
dialog.dialog({
|
||||
$('<div>', {id: element_id}).dialog({
|
||||
title: action.name,
|
||||
modal: true,
|
||||
width: '50%',
|
||||
|
@ -347,7 +346,7 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
|
|||
var action = self.sections[index[0]].elements[index[1]];
|
||||
action.flags = {
|
||||
new_window : true
|
||||
}
|
||||
};
|
||||
self.session.action_manager.do_action(action);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
|
|
@ -300,11 +300,13 @@
|
|||
<th t-if="options.deletable"/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot class="ui-widget-header" t-if="aggregate_columns.length">
|
||||
<tfoot class="ui-widget-header">
|
||||
<tr>
|
||||
<td t-att-colspan="columns_count" class='oe-list-footer'>
|
||||
|
||||
<td t-if="options.selectable"/>
|
||||
<td t-foreach="aggregate_columns" t-as="column" class="oe-list-footer oe-number"
|
||||
t-att-data-field="column.field" t-att-title="column.label">
|
||||
</td>
|
||||
<td t-if="options.deletable"/>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
@ -316,7 +318,8 @@
|
|||
</t-if>
|
||||
</t>
|
||||
</t>
|
||||
<tr t-name="ListView.row" t-att-style="style" t-att-class="row_parity">
|
||||
<tr t-name="ListView.row" t-att-style="style" t-att-class="row_parity"
|
||||
t-att-data-index="row_index">
|
||||
<t t-foreach="columns" t-as="column">
|
||||
<td t-if="column.meta">
|
||||
|
||||
|
@ -326,8 +329,10 @@
|
|||
<input type="checkbox"/>
|
||||
</th>
|
||||
<t t-foreach="columns" t-as="column">
|
||||
<t t-set="align" t-value="column.type === 'integer' or column.type == 'float'"/>
|
||||
<td t-if="!column.meta and column.invisible !== '1'" t-att-title="column.help"
|
||||
class="oe-field-cell" t-att-data-field="column.id">
|
||||
t-att-class="'oe-field-cell' + (align ? ' oe-number' : '')"
|
||||
t-att-data-field="column.id">
|
||||
<t t-set="attrs" t-value="column.attrs_for(row.data)"/>
|
||||
<t t-if="!attrs.invisible">
|
||||
<t t-set="is_button" t-value="column.tag === 'button'"/>
|
||||
|
@ -348,6 +353,9 @@
|
|||
<button type="button" name="delete">♻</button>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-name="ListView.row.form">
|
||||
<t t-raw="frame.render()"/>
|
||||
</t>
|
||||
<t t-name="FormView">
|
||||
<h2 class="oe_view_title"><t t-esc="view.fields_view.arch.attrs.string"/></h2>
|
||||
<div class="oe_form_header" t-att-id="view.element_id + '_header'">
|
||||
|
@ -882,4 +890,23 @@
|
|||
<button type="button" class="oe_many2xselectpopup-form-save">Save</button>
|
||||
<button type="button" class="oe_many2xselectpopup-form-close">Close</button>
|
||||
</t>
|
||||
|
||||
<t t-name="ListView.row.frame" t-extend="WidgetFrame">
|
||||
<t t-jquery="tr">
|
||||
$(document.createElement('t'))
|
||||
.append(this.contents())
|
||||
.attr({
|
||||
't-foreach': this.attr('t-foreach'),
|
||||
't-as': this.attr('t-as')
|
||||
})
|
||||
.replaceAll(this)
|
||||
.after($(document.createElement('td')).append(
|
||||
$(document.createElement('button')).attr({
|
||||
'class': 'oe-edit-row-save', 'type': 'button'}).text('Save')))
|
||||
.before($(document.createElement('td')).append(
|
||||
$(document.createElement('button')).attr({
|
||||
'class': 'oe-edit-row-cancel', 'type': 'button'}).text('Cancel')))
|
||||
.unwrap();
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
Loading…
Reference in New Issue