[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)
|
fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
|
||||||
return {'fields_view': fields_view}
|
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):
|
def process_colors(self, view, row, context):
|
||||||
colors = view['arch']['attrs'].get('colors')
|
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/views.js"></script>
|
||||||
<script type="text/javascript" src="/base/static/src/js/form.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.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/tree.js"></script>
|
||||||
<script type="text/javascript" src="/base/static/src/js/search.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>
|
<script type="text/javascript" src="/base/static/src/js/m2o.js"></script>
|
||||||
|
|
|
@ -27,6 +27,10 @@ body.openerp {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.openerp .oe-number {
|
||||||
|
text-align: right !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* STATES */
|
/* STATES */
|
||||||
.openerp .on_logged {
|
.openerp .on_logged {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -602,6 +606,7 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.openerp .oe-listview .oe-field-cell button:active {
|
.openerp .oe-listview .oe-field-cell button:active {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|
|
@ -132,6 +132,7 @@ openerp.base = function(instance) {
|
||||||
openerp.base.tree(instance);
|
openerp.base.tree(instance);
|
||||||
openerp.base.m2o(instance);
|
openerp.base.m2o(instance);
|
||||||
openerp.base.form(instance);
|
openerp.base.form(instance);
|
||||||
|
openerp.base.list.editable(instance);
|
||||||
};
|
};
|
||||||
|
|
||||||
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|
// 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) {
|
add: function (key, object_path) {
|
||||||
this.map[key] = object_path;
|
this.map[key] = object_path;
|
||||||
return this;
|
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) {
|
read_ids: function (ids, fields, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.rpc('/base/dataset/get', {
|
return this.rpc('/base/dataset/get', {
|
||||||
model: this.model,
|
model: this.model,
|
||||||
ids: ids,
|
ids: ids,
|
||||||
fields: fields
|
fields: fields
|
||||||
|
@ -279,10 +279,10 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
|
||||||
*/
|
*/
|
||||||
read_index: function (fields, callback) {
|
read_index: function (fields, callback) {
|
||||||
if (_.isEmpty(this.ids)) {
|
if (_.isEmpty(this.ids)) {
|
||||||
callback([]);
|
return $.Deferred().reject().promise();
|
||||||
} else {
|
} else {
|
||||||
fields = fields || false;
|
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]);
|
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).
|
* view should be displayed (if there is one active).
|
||||||
*/
|
*/
|
||||||
searchable: false,
|
searchable: false,
|
||||||
|
template: "FormView",
|
||||||
/**
|
/**
|
||||||
* @constructs
|
* @constructs
|
||||||
* @param {openerp.base.Session} session the current openerp session
|
* @param {openerp.base.Session} session the current openerp session
|
||||||
* @param {String} element_id this view's root element id
|
* @param {String} element_id this view's root element id
|
||||||
* @param {openerp.base.DataSet} dataset the dataset this view will work with
|
* @param {openerp.base.DataSet} dataset the dataset this view will work with
|
||||||
* @param {String} view_id the identifier of the OpenERP view object
|
* @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) {
|
init: function(view_manager, session, element_id, dataset, view_id) {
|
||||||
this._super(session, element_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.ready = false;
|
||||||
this.show_invalid = true;
|
this.show_invalid = true;
|
||||||
this.touched = false;
|
this.touched = false;
|
||||||
this.flags = this.view_manager.flags || {};
|
this.flags = this.view_manager.action.flags || {};
|
||||||
|
this.registry = openerp.base.form.widgets;
|
||||||
},
|
},
|
||||||
start: function() {
|
start: function() {
|
||||||
//this.log('Starting FormView '+this.model+this.view_id)
|
//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;
|
var self = this;
|
||||||
this.fields_view = data.fields_view;
|
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) {
|
_.each(this.widgets, function(w) {
|
||||||
w.start();
|
w.start();
|
||||||
});
|
});
|
||||||
|
@ -220,7 +224,15 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
|
||||||
self.on_record_loaded(result.result);
|
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;
|
var self = this;
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -243,7 +255,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
|
||||||
this.log("About to save", values);
|
this.log("About to save", values);
|
||||||
if (!this.datarecord.id) {
|
if (!this.datarecord.id) {
|
||||||
this.dataset.create(values, function(r) {
|
this.dataset.create(values, function(r) {
|
||||||
self.on_created(r, success);
|
self.on_created(r, success, prepend_on_create);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.dataset.write(this.datarecord.id, values, function(r) {
|
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) {
|
if (!r.result) {
|
||||||
this.notification.warn("Record not created", "Problem while creating record.");
|
this.notification.warn("Record not created", "Problem while creating record.");
|
||||||
} else {
|
} else {
|
||||||
this.datarecord.id = arguments[0].result;
|
this.datarecord.id = r.result;
|
||||||
this.dataset.ids.push(this.datarecord.id);
|
if (!prepend_on_create) {
|
||||||
this.dataset.index = this.dataset.ids.length - 1;
|
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.dataset.count++;
|
||||||
this.do_update_pager();
|
this.do_update_pager();
|
||||||
this.do_update_sidebar();
|
this.do_update_sidebar();
|
||||||
this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id);
|
this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id);
|
||||||
if (success) {
|
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({
|
openerp.base.form.Widget = openerp.base.Controller.extend({
|
||||||
|
template: 'Widget',
|
||||||
init: function(view, node) {
|
init: function(view, node) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
|
@ -435,7 +466,6 @@ openerp.base.form.Widget = openerp.base.Controller.extend({
|
||||||
this.view.widgets[this.element_id] = this;
|
this.view.widgets[this.element_id] = this;
|
||||||
this.children = node.children;
|
this.children = node.children;
|
||||||
this.colspan = parseInt(node.attrs.colspan || 1);
|
this.colspan = parseInt(node.attrs.colspan || 1);
|
||||||
this.template = "Widget";
|
|
||||||
|
|
||||||
this.string = this.string || node.attrs.string;
|
this.string = this.string || node.attrs.string;
|
||||||
this.help = this.help || node.attrs.help;
|
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({
|
openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
|
||||||
|
template: 'WidgetFrame',
|
||||||
init: function(view, node) {
|
init: function(view, node) {
|
||||||
this._super(view, node);
|
this._super(view, node);
|
||||||
this.template = "WidgetFrame";
|
|
||||||
this.columns = node.attrs.col || 4;
|
this.columns = node.attrs.col || 4;
|
||||||
this.x = 0;
|
this.x = 0;
|
||||||
this.y = 0;
|
this.y = 0;
|
||||||
|
@ -504,9 +534,9 @@ openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
|
||||||
handle_node: function(node) {
|
handle_node: function(node) {
|
||||||
var type = this.view.fields_view.fields[node.attrs.name] || {};
|
var type = this.view.fields_view.fields[node.attrs.name] || {};
|
||||||
var widget_type = node.attrs.widget || type.type || node.tag;
|
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') {
|
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;
|
label["for"] = widget;
|
||||||
this.add_widget(label);
|
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`
|
* Registry of form widgets, called by :js:`openerp.base.FormView`
|
||||||
*/
|
*/
|
||||||
openerp.base.form.widgets = new openerp.base.Registry({
|
openerp.base.form.widgets = new openerp.base.Registry({
|
||||||
|
'frame' : 'openerp.base.form.WidgetFrame',
|
||||||
'group' : 'openerp.base.form.WidgetFrame',
|
'group' : 'openerp.base.form.WidgetFrame',
|
||||||
'notebook' : 'openerp.base.form.WidgetNotebook',
|
'notebook' : 'openerp.base.form.WidgetNotebook',
|
||||||
'separator' : 'openerp.base.form.WidgetSeparator',
|
'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) {
|
'action': function (e, action_name, id, callback) {
|
||||||
self.do_action(action_name, id, callback);
|
self.do_action(action_name, id, callback);
|
||||||
},
|
},
|
||||||
'row_link': function (e, index, id, dataset) {
|
'row_link': function (e, id, dataset) {
|
||||||
self.do_activate_record(index, 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));
|
this.$element.html(QWeb.render("ListView", this));
|
||||||
|
|
||||||
// Head hook
|
// 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')
|
this.$element.find('#oe-list-delete')
|
||||||
.hide()
|
.attr('disabled', true)
|
||||||
.click(this.do_delete_selected);
|
.click(this.do_delete_selected);
|
||||||
this.$element.find('thead').delegate('th[data-id]', 'click', function (e) {
|
this.$element.find('thead').delegate('th[data-id]', 'click', function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -199,18 +201,24 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
||||||
return column.invisible !== '1';
|
return column.invisible !== '1';
|
||||||
});
|
});
|
||||||
|
|
||||||
this.aggregate_columns = _(this.columns).chain()
|
this.aggregate_columns = _(this.visible_columns)
|
||||||
.filter(function (column) {
|
|
||||||
return column['sum'] || column['avg'];})
|
|
||||||
.map(function (column) {
|
.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 {
|
return {
|
||||||
field: column.id,
|
field: column.id,
|
||||||
type: column.type,
|
type: column.type,
|
||||||
'function': func,
|
'function': aggregation_func,
|
||||||
label: column[func]
|
label: column[aggregation_func]
|
||||||
};
|
};
|
||||||
}).value();
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Used to handle a click on a table row, if no other handler caught the
|
* 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', {
|
return this.rpc('/base/listview/load', {
|
||||||
model: this.model,
|
model: this.model,
|
||||||
view_id: this.view_id,
|
view_id: this.view_id,
|
||||||
|
context: this.dataset.context,
|
||||||
toolbar: !!this.flags.sidebar
|
toolbar: !!this.flags.sidebar
|
||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
|
@ -289,25 +298,32 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
||||||
* @returns {$.Deferred} fold request evaluation promise
|
* @returns {$.Deferred} fold request evaluation promise
|
||||||
*/
|
*/
|
||||||
do_search: function (domains, contexts, groupbys) {
|
do_search: function (domains, contexts, groupbys) {
|
||||||
var self = this;
|
|
||||||
return this.rpc('/base/session/eval_domain_and_context', {
|
return this.rpc('/base/session/eval_domain_and_context', {
|
||||||
domains: domains,
|
domains: domains,
|
||||||
contexts: contexts,
|
contexts: contexts,
|
||||||
group_by_seq: groupbys
|
group_by_seq: groupbys
|
||||||
}, function (results) {
|
}, $.proxy(this, 'do_actual_search'));
|
||||||
self.dataset.context = results.context;
|
},
|
||||||
self.dataset.domain = results.domain;
|
/**
|
||||||
self.groups.datagroup = new openerp.base.DataGroup(
|
* Handler for the result of eval_domain_and_context, actually perform the
|
||||||
self.session, self.model,
|
* searching
|
||||||
results.domain, results.context,
|
*
|
||||||
results.group_by);
|
* @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']) {
|
if (_.isEmpty(results.group_by) && !results.context['group_by_no_leaf']) {
|
||||||
results.group_by = null;
|
results.group_by = null;
|
||||||
}
|
}
|
||||||
self.reload_view(!!results.group_by).then(
|
|
||||||
$.proxy(self, 'reload_content'));
|
this.reload_view(!!results.group_by).then(
|
||||||
});
|
$.proxy(this, 'reload_content'));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Handles the signal to delete a line from the DOM
|
* 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) {
|
do_select: function (ids, records) {
|
||||||
this.$element.find('#oe-list-delete')
|
this.$element.find('#oe-list-delete')
|
||||||
.toggle(!!ids.length);
|
.attr('disabled', !ids.length);
|
||||||
|
|
||||||
if (!records.length) {
|
if (!records.length) {
|
||||||
this.compute_aggregates();
|
this.compute_aggregates();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.compute_aggregates(records);
|
this.compute_aggregates(_(records).map(function (record) {
|
||||||
|
return {count: 1, values: record};
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Handles action button signals on a 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 {Number} index index of the record in the dataset
|
||||||
* @param {Object} id identifier of the activated record
|
* @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) {
|
do_activate_record: function (index, id, dataset) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -420,75 +438,56 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
|
||||||
* @param {Array} [records]
|
* @param {Array} [records]
|
||||||
*/
|
*/
|
||||||
compute_aggregates: function (records) {
|
compute_aggregates: function (records) {
|
||||||
if (_.isEmpty(this.aggregate_columns)) {
|
var columns = _(this.aggregate_columns).filter(function (column) {
|
||||||
return;
|
return column['function']; });
|
||||||
}
|
|
||||||
|
if (_.isEmpty(columns)) { return; }
|
||||||
|
|
||||||
if (_.isEmpty(records)) {
|
if (_.isEmpty(records)) {
|
||||||
records = this.groups.get_records();
|
records = this.groups.get_records();
|
||||||
}
|
}
|
||||||
|
|
||||||
var aggregator = this.build_aggregator(this.aggregate_columns);
|
var count = 0, sums = {};
|
||||||
this.display_aggregates(
|
_(columns).each(function (column) { sums[column.field] = 0; });
|
||||||
_(records).reduce(aggregator, aggregator).value());
|
_(records).each(function (record) {
|
||||||
},
|
count += record.count || 1;
|
||||||
/**
|
_(columns).each(function (column) {
|
||||||
* Creates a stateful callable aggregator object, which can be reduced over
|
var field = column.field;
|
||||||
* a collection of records in order to build the aggregations described
|
switch (column['function']) {
|
||||||
* 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;
|
|
||||||
case 'sum':
|
case 'sum':
|
||||||
value = (_(collection).chain()
|
sums[field] += record.values[field];
|
||||||
.filter(function (item) {
|
break;
|
||||||
return !_.isUndefined(item); })
|
case 'avg':
|
||||||
.reduce(function (total, item) {
|
sums[field] += record.count * record.values[field];
|
||||||
return total + item; }, 0).value());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
result[key] = value;
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
var aggregates = {};
|
||||||
};
|
_(columns).each(function (column) {
|
||||||
return aggregator;
|
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) {
|
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) {
|
_(this.aggregate_columns).each(function (column) {
|
||||||
$(_.sprintf(
|
if (!column['function']) {
|
||||||
"<span>%s: %.2f</span>",
|
return;
|
||||||
column.label, aggregation[column.field]))
|
}
|
||||||
.appendTo($footer);
|
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)
|
// TODO: implement reorder (drag and drop rows)
|
||||||
|
@ -521,8 +520,9 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
|
||||||
* @constructs
|
* @constructs
|
||||||
* @param {Object} opts display options, identical to those of :js:class:`openerp.base.ListView`
|
* @param {Object} opts display options, identical to those of :js:class:`openerp.base.ListView`
|
||||||
*/
|
*/
|
||||||
init: function (opts) {
|
init: function (group, opts) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
this.group = group;
|
||||||
|
|
||||||
this.options = opts.options;
|
this.options = opts.options;
|
||||||
this.columns = opts.columns;
|
this.columns = opts.columns;
|
||||||
|
@ -546,19 +546,26 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var $target = $(e.currentTarget),
|
var $target = $(e.currentTarget),
|
||||||
field = $target.closest('td').data('field'),
|
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) {
|
.delegate('tr', 'click', function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
$(self).trigger(
|
self.dataset.index = self.row_position(e.currentTarget);
|
||||||
'row_link',
|
self.row_clicked(e);
|
||||||
[self.row_position(e.currentTarget),
|
|
||||||
self.row_id(e.currentTarget),
|
|
||||||
self.dataset]);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
row_clicked: function () {
|
||||||
|
$(this).trigger(
|
||||||
|
'row_link',
|
||||||
|
[this.rows[this.dataset.index].data.id.value,
|
||||||
|
this.dataset]);
|
||||||
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
if (this.$current) {
|
if (this.$current) {
|
||||||
this.$current.remove();
|
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 = this.$_element.clone(true);
|
||||||
this.$current.empty().append($(QWeb.render('ListView.rows', this)));
|
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
|
* 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.
|
* @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')
|
this.$current.find('th.oe-record-selector input:checked')
|
||||||
.closest('tr').each(function () {
|
.closest('tr').each(function () {
|
||||||
var record = {};
|
var record = {};
|
||||||
_(rows[$(this).prevAll().length].data).each(function (obj, key) {
|
_(rows[$(this).data('index')].data).each(function (obj, key) {
|
||||||
record[key] = obj.value;
|
record[key] = obj.value;
|
||||||
});
|
});
|
||||||
result.ids.push(record.id);
|
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
|
* @returns {Number} the position of the row in this.rows
|
||||||
*/
|
*/
|
||||||
row_position: function (row) {
|
row_position: function (row) {
|
||||||
return $(row).prevAll().length;
|
return $(row).data('index');
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Returns the identifier of the object displayed in the provided table
|
* 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) {
|
_(row.data).each(function (obj, key) {
|
||||||
record[key] = obj.value;
|
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
|
// drag and drop
|
||||||
// editable?
|
|
||||||
});
|
});
|
||||||
openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Groups# */{
|
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
|
* Grouped display for the ListView. Handles basic DOM events and interacts
|
||||||
* with the :js:class:`~openerp.base.DataGroup` bound to it.
|
* 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;
|
return red_letter_tboday;
|
||||||
},
|
},
|
||||||
open_group: function (e, group) {
|
open: function (point_insertion) {
|
||||||
var row = e.currentTarget;
|
this.render().insertAfter(point_insertion);
|
||||||
|
},
|
||||||
if (this.children[group.value]) {
|
close: function () {
|
||||||
this.children[group.value].apoptosis();
|
this.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');
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Prefixes ``$node`` with floated spaces in order to indent it relative
|
* 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 self = this;
|
||||||
var placeholder = this.make_fragment();
|
var placeholder = this.make_fragment();
|
||||||
_(datagroups).each(function (group) {
|
_(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>');
|
var $row = $('<tr>');
|
||||||
if (group.openable) {
|
if (group.openable) {
|
||||||
$row.click(function (e) {
|
$row.click(function (e) {
|
||||||
if (!$row.data('open')) {
|
if (!$row.data('open')) {
|
||||||
$row.data('open', true);
|
$row.data('open', true)
|
||||||
self.open_group(e, group);
|
.find('span.ui-icon')
|
||||||
|
.removeClass('ui-icon-triangle-1-e')
|
||||||
|
.addClass('ui-icon-triangle-1-s');
|
||||||
|
child.open(self.point_insertion(e.currentTarget));
|
||||||
} else {
|
} else {
|
||||||
$row.removeData('open')
|
$row.removeData('open')
|
||||||
.find('span.ui-icon')
|
.find('span.ui-icon')
|
||||||
.removeClass('ui-icon-triangle-1-s')
|
.removeClass('ui-icon-triangle-1-s')
|
||||||
.addClass('ui-icon-triangle-1-e');
|
.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
|
// can have selections spanning multiple links
|
||||||
var selection = self.get_selection();
|
var selection = self.get_selection();
|
||||||
$this.trigger(e, [selection.ids, selection.records]);
|
$this.trigger(e, [selection.ids, selection.records]);
|
||||||
}).bind('action', function (e, name, id, callback) {
|
}).bind(this.passtrough_events, function (e) {
|
||||||
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) {
|
|
||||||
// additional positional parameters are provided to trigger as an
|
// additional positional parameters are provided to trigger as an
|
||||||
// Array, following the event type or event object, but are
|
// Array, following the event type or event object, but are
|
||||||
// provided to the .bind event handler as *args.
|
// 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) {
|
render_dataset: function (dataset) {
|
||||||
var rows = [],
|
var rows = [],
|
||||||
list = new openerp.base.ListView.List({
|
list = new openerp.base.ListView.List(this, {
|
||||||
options: this.options,
|
options: this.options,
|
||||||
columns: this.columns,
|
columns: this.columns,
|
||||||
dataset: dataset,
|
dataset: dataset,
|
||||||
|
@ -819,17 +898,8 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
|
||||||
_.filter(_.pluck(this.columns, 'name'), _.identity),
|
_.filter(_.pluck(this.columns, 'name'), _.identity),
|
||||||
0, false,
|
0, false,
|
||||||
function (records) {
|
function (records) {
|
||||||
var form_records = _(records).map(function (record) {
|
var form_records = _(records).map(
|
||||||
// TODO: colors handling
|
$.proxy(list, 'transform_record'));
|
||||||
var form_data = {},
|
|
||||||
form_record = {data: form_data};
|
|
||||||
|
|
||||||
_(record).each(function (value, key) {
|
|
||||||
form_data[key] = {value: value};
|
|
||||||
});
|
|
||||||
|
|
||||||
return form_record;
|
|
||||||
});
|
|
||||||
|
|
||||||
rows.splice(0, rows.length);
|
rows.splice(0, rows.length);
|
||||||
rows.push.apply(rows, form_records);
|
rows.push.apply(rows, form_records);
|
||||||
|
@ -881,6 +951,12 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
get_records: function () {
|
get_records: function () {
|
||||||
|
if (_(this.children).isEmpty()) {
|
||||||
|
return {
|
||||||
|
count: this.datagroup.length,
|
||||||
|
values: this.datagroup.aggregates
|
||||||
|
}
|
||||||
|
}
|
||||||
return _(this.children).chain()
|
return _(this.children).chain()
|
||||||
.map(function (child) {
|
.map(function (child) {
|
||||||
return child.get_records();
|
return child.get_records();
|
||||||
|
|
|
@ -9,7 +9,7 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
||||||
init: function(session, element_id) {
|
init: function(session, element_id) {
|
||||||
this._super(session, element_id);
|
this._super(session, element_id);
|
||||||
this.viewmanager = null;
|
this.viewmanager = null;
|
||||||
this.dialog_stack = []
|
this.dialog_stack = [];
|
||||||
// Temporary linking view_manager to session.
|
// Temporary linking view_manager to session.
|
||||||
// Will use controller_parent to find it when implementation will be done.
|
// Will use controller_parent to find it when implementation will be done.
|
||||||
session.action_manager = this;
|
session.action_manager = this;
|
||||||
|
@ -33,8 +33,7 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
||||||
case 'ir.actions.act_window':
|
case 'ir.actions.act_window':
|
||||||
if (action.target == 'new') {
|
if (action.target == 'new') {
|
||||||
var element_id = _.uniqueId("act_window_dialog");
|
var element_id = _.uniqueId("act_window_dialog");
|
||||||
var dialog = $('<div id="' + element_id + '"></div>');
|
$('<div>', {id: element_id}).dialog({
|
||||||
dialog.dialog({
|
|
||||||
title: action.name,
|
title: action.name,
|
||||||
modal: true,
|
modal: true,
|
||||||
width: '50%',
|
width: '50%',
|
||||||
|
@ -347,7 +346,7 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
|
||||||
var action = self.sections[index[0]].elements[index[1]];
|
var action = self.sections[index[0]].elements[index[1]];
|
||||||
action.flags = {
|
action.flags = {
|
||||||
new_window : true
|
new_window : true
|
||||||
}
|
};
|
||||||
self.session.action_manager.do_action(action);
|
self.session.action_manager.do_action(action);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -300,11 +300,13 @@
|
||||||
<th t-if="options.deletable"/>
|
<th t-if="options.deletable"/>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tfoot class="ui-widget-header" t-if="aggregate_columns.length">
|
<tfoot class="ui-widget-header">
|
||||||
<tr>
|
<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>
|
||||||
|
<td t-if="options.deletable"/>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
@ -316,7 +318,8 @@
|
||||||
</t-if>
|
</t-if>
|
||||||
</t>
|
</t>
|
||||||
</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">
|
<t t-foreach="columns" t-as="column">
|
||||||
<td t-if="column.meta">
|
<td t-if="column.meta">
|
||||||
|
|
||||||
|
@ -326,8 +329,10 @@
|
||||||
<input type="checkbox"/>
|
<input type="checkbox"/>
|
||||||
</th>
|
</th>
|
||||||
<t t-foreach="columns" t-as="column">
|
<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"
|
<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-set="attrs" t-value="column.attrs_for(row.data)"/>
|
||||||
<t t-if="!attrs.invisible">
|
<t t-if="!attrs.invisible">
|
||||||
<t t-set="is_button" t-value="column.tag === 'button'"/>
|
<t t-set="is_button" t-value="column.tag === 'button'"/>
|
||||||
|
@ -348,6 +353,9 @@
|
||||||
<button type="button" name="delete">♻</button>
|
<button type="button" name="delete">♻</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<t t-name="ListView.row.form">
|
||||||
|
<t t-raw="frame.render()"/>
|
||||||
|
</t>
|
||||||
<t t-name="FormView">
|
<t t-name="FormView">
|
||||||
<h2 class="oe_view_title"><t t-esc="view.fields_view.arch.attrs.string"/></h2>
|
<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'">
|
<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-save">Save</button>
|
||||||
<button type="button" class="oe_many2xselectpopup-form-close">Close</button>
|
<button type="button" class="oe_many2xselectpopup-form-close">Close</button>
|
||||||
</t>
|
</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>
|
</templates>
|
||||||
|
|
Loading…
Reference in New Issue