summaryrefslogtreecommitdiff
path: root/addons/base/static/src
diff options
context:
space:
mode:
authorAntony Lesuisse <al@openerp.com>2011-04-13 03:37:11 +0200
committerAntony Lesuisse <al@openerp.com>2011-04-13 03:37:11 +0200
commit3ac70cbecfda63682f4667994d64cd9526a8e1a3 (patch)
treed42d6267d79b9211a0c6c5027cc1c22506c95b7a /addons/base/static/src
parentbe257a50087140885759e5cd7c8f59f9423129eb (diff)
parent3b38e2c68873fdc4ab40c16afd0193ca7c49b6a9 (diff)
[MERGE] xmo listview, bug in the form pager
bzr revid: al@openerp.com-20110413013711-vp63eigz5aadm6wc
Diffstat (limited to 'addons/base/static/src')
-rw-r--r--addons/base/static/src/css/base.css71
-rw-r--r--addons/base/static/src/js/data.js2
-rw-r--r--addons/base/static/src/js/form.js120
-rw-r--r--addons/base/static/src/js/list.js167
-rw-r--r--addons/base/static/src/js/views.js57
-rw-r--r--addons/base/static/src/xml/base.xml92
6 files changed, 368 insertions, 141 deletions
diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css
index c6040f1b0fd..cf6d0beabd4 100644
--- a/addons/base/static/src/css/base.css
+++ b/addons/base/static/src/css/base.css
@@ -443,6 +443,7 @@ body.openerp {
padding-left: 10px;
}
+
.openerp .oe-searchview-render-line {
width:100%;
}
@@ -462,6 +463,75 @@ body.openerp {
background: url(../img/icons/gtk-remove.png) repeat-y;
padding-left: 18px;
}
+/* List */
+.openerp .oe-listview table {
+ clear: right;
+ width: 100%;
+ border-spacing: 0;
+ border: 1px solid silver;
+}
+
+.openerp .oe-listview tr.odd {
+ background-color: #f3f3f3;
+}
+.openerp .oe-listview tbody tr:hover {
+ background-color: #ecebf2;
+}
+.openerp .oe-listview tbody tr:hover {
+ background-color: #eae9f0;
+}
+
+.openerp .oe-listview td,
+.openerp .oe-listview th {
+ vertical-align: middle;
+}
+
+.openerp .oe-listview .oe-field-cell button {
+ padding: 0;
+ border: none;
+ background: none;
+}
+.openerp .oe-listview .oe-field-cell button:active {
+ opacity: 0.5;
+}
+
+.openerp .oe-listview th.oe-actions {
+ text-align: left;
+}
+.openerp .oe-listview th.oe-list-pager {
+ text-align: right;
+}
+
+/** list rounded corners
+
+ rounded corners are a pain on tables: need to round not only table, but
+ also on the first and last children of the first and last row
+ */
+.openerp .oe-listview table {
+ -webkit-border-radius: 7px;
+ -moz-border-radius: 7px;
+ border-radius: 7px;
+}
+.openerp .oe-listview table thead tr:first-child th:first-child {
+ -webkit-border-top-left-radius: 7px;
+ -moz-border-radius-topleft: 7px;
+ border-top-left-radius: 7px;
+}
+.openerp .oe-listview table thead tr:first-child th:last-child {
+ -webkit-border-top-right-radius: 7px;
+ -moz-border-radius-topright: 7px;
+ border-top-right-radius: 7px;
+}
+.openerp .oe-listview table tbody tr:last-child th:first-child {
+ -webkit-border-bottom-left-radius: 7px;
+ -moz-border-radius-bottomleft: 7px;
+ border-bottom-left-radius: 7px;
+}
+.openerp .oe-listview table tbody tr:last-child td:last-child {
+ -webkit-border-bottom-right-radius: 7px;
+ -moz-border-radius-bottomright: 7px;
+ border-bottom-right-radius: 7px;
+}
/* Notebook */
.openerp .oe_form_notebook {
@@ -489,7 +559,6 @@ body.openerp {
background: #f9f9f9;
}
-
/* Form */
.openerp table.oe_frame td {
color: #4c4c4c;
diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js
index 7b1a930c88f..5ed21be533c 100644
--- a/addons/base/static/src/js/data.js
+++ b/addons/base/static/src/js/data.js
@@ -108,6 +108,8 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
this.notification.notify("Unlink", ids);
},
call: function (method, ids, args, callback) {
+ this.notification.notify(
+ "Calling", this.model + '#' + method + '(' + ids + ')');
ids = ids || [];
args = args || [];
return this.rpc('/base/dataset/call', {
diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js
index b13ac7d8332..350d3e019bb 100644
--- a/addons/base/static/src/js/form.js
+++ b/addons/base/static/src/js/form.js
@@ -311,6 +311,64 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base
/** @namespace */
openerp.base.form = {};
+openerp.base.form.compute_domain = function(expr, fields) {
+ var stack = [];
+ for (var i = expr.length - 1; i >= 0; i--) {
+ var ex = expr[i];
+ if (ex.length == 1) {
+ var top = stack.pop();
+ switch (ex[0]) {
+ case '|':
+ stack.push(stack.pop() || top);
+ continue;
+ case '&':
+ stack.push(stack.pop() && top);
+ continue;
+ case '!':
+ stack.push(!top);
+ continue;
+ default:
+ throw new Error('Unknown domain operator ' + ex[0]);
+ }
+ }
+
+ var field = fields[ex[0]].value;
+ var op = ex[1];
+ var val = ex[2];
+
+ switch (op.toLowerCase()) {
+ case '=':
+ case '==':
+ stack.push(field == val);
+ break;
+ case '!=':
+ case '<>':
+ stack.push(field != val);
+ break;
+ case '<':
+ stack.push(field < val);
+ break;
+ case '>':
+ stack.push(field > val);
+ break;
+ case '<=':
+ stack.push(field <= val);
+ break;
+ case '>=':
+ stack.push(field >= val);
+ break;
+ case 'in':
+ stack.push(_.indexOf(val, field) > -1);
+ break;
+ case 'not in':
+ stack.push(_.indexOf(val, field) == -1);
+ break;
+ default:
+ this.log("Unsupported operator in attrs :", op);
+ }
+ }
+ return _.indexOf(stack, false) == -1;
+},
openerp.base.form.Widget = openerp.base.Controller.extend({
init: function(view, node) {
this.view = view;
@@ -335,68 +393,10 @@ openerp.base.form.Widget = openerp.base.Controller.extend({
this.$element = $('#' + this.element_id);
},
process_attrs: function() {
+ var compute_domain = openerp.base.form.compute_domain;
for (var a in this.attrs) {
- this[a] = this.eval_attrs(this.attrs[a]);
- }
- },
- eval_attrs: function(expr) {
- var stack = [];
- for (var i = 0; i < expr.length; i++) {
- var ex = expr[i];
- if (ex.length == 1) {
- stack.push(ex[0]);
- continue;
- }
-
- var field = this.view.fields[ex[0]].value;
- var op = ex[1];
- var val = ex[2];
-
- switch (op.toLowerCase()) {
- case '=':
- case '==':
- stack.push(field == val);
- break;
- case '!=':
- case '<>':
- stack.push(field != val);
- break;
- case '<':
- stack.push(field < val);
- break;
- case '>':
- stack.push(field > val);
- break;
- case '<=':
- stack.push(field <= val);
- break;
- case '>=':
- stack.push(field >= val);
- break;
- case 'in':
- stack.push(_.indexOf(val, field) > -1);
- break;
- case 'not in':
- stack.push(_.indexOf(val, field) == -1);
- break;
- default:
- this.log("Unsupported operator in attrs :", op);
- }
- }
-
- for (var j = stack.length-1; j >- 1; j--) {
- switch (stack[j]) {
- case '|':
- var result = stack[j + 1] || stack[j + 2];
- stack.splice(j, 3, result);
- break;
- case '&':
- var result = stack[j + 1] && stack[j + 2];
- stack.splice(j, 3, result);
- break;
- }
+ this[a] = compute_domain(this.attrs[a], this.view.fields);
}
- return _.indexOf(stack, false) == -1;
},
update_dom: function() {
this.$element.toggle(!this.invisible);
diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js
index 4b1eeaaf993..d823bc81489 100644
--- a/addons/base/static/src/js/list.js
+++ b/addons/base/static/src/js/list.js
@@ -8,7 +8,9 @@ openerp.base.ListView = openerp.base.Controller.extend(
// list rows can be deleted
'deletable': true,
// whether the column headers should be displayed
- 'header': true
+ 'header': true,
+ // display addition button, with that label
+ 'addable': "New"
},
/**
* @constructs
@@ -21,6 +23,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
* @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox)
* @param {Boolean} [options.header=true] should the list's header be displayed
* @param {Boolean} [options.deletable=true] are the list rows deletable
+ * @param {null|String} [options.addable="New"] should the new-record button be displayed, and what should its label be. Use ``null`` to hide the button.
*/
init: function(view_manager, session, element_id, dataset, view_id, options) {
this._super(session, element_id);
@@ -35,20 +38,37 @@ openerp.base.ListView = openerp.base.Controller.extend(
this.options = _.extend({}, this.defaults, options || {});
},
start: function() {
- //this.log('Starting ListView '+this.model+this.view_id)
+ this.$element.addClass('oe-listview');
return this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id,
toolbar:!!this.view_manager.sidebar}, this.on_loaded);
},
on_loaded: function(data) {
+ var self = this;
this.fields_view = data.fields_view;
//this.log(this.fields_view);
this.name = "" + this.fields_view.arch.attrs.string;
var fields = this.fields_view.fields;
+ var domain_computer = openerp.base.form.compute_domain;
this.columns = _(this.fields_view.arch.children).chain()
.map(function (field) {
var name = field.attrs.name;
- return _.extend({id: name, tag: field.tag}, field.attrs, fields[name]);
+ var column = _.extend({id: name, tag: field.tag},
+ field.attrs, fields[name]);
+ // attrs computer
+ if (column.attrs) {
+ var attrs = eval('(' + column.attrs + ')');
+ column.attrs_for = function (fields) {
+ var result = {};
+ for (var attr in attrs) {
+ result[attr] = domain_computer(attrs[attr], fields);
+ }
+ return result;
+ };
+ } else {
+ column.attrs_for = function () { return {}; };
+ }
+ return column;
}).value();
this.visible_columns = _.filter(this.columns, function (column) {
@@ -57,20 +77,46 @@ openerp.base.ListView = openerp.base.Controller.extend(
this.$element.html(QWeb.render("ListView", this));
// Head hook
- this.$element.find('#oe-list-delete').click(this.do_delete_selected);
+ this.$element.find('#oe-list-add').click(this.do_add_record);
+ this.$element.find('#oe-list-delete')
+ .hide()
+ .click(this.do_delete_selected);
+ var $table = this.$element.find('table');
// Cell events
- this.$element.find('table').delegate(
- 'th.oe-record-selector', 'click', function (e) {
- // A click in the selection cell should not activate the
- // linking feature
- e.stopImmediatePropagation();
+ $table.delegate(
+ 'th.oe-record-selector', 'click', function (e) {
+ // TODO: ~linear performances, would a simple counter work?
+ if ($table.find('th.oe-record-selector input:checked').length) {
+ $table.find('#oe-list-delete').show();
+ } else {
+ $table.find('#oe-list-delete').hide();
+ }
+ // A click in the selection cell should not activate the
+ // linking feature
+ e.stopImmediatePropagation();
});
- this.$element.find('table').delegate(
+ $table.delegate(
+ 'td.oe-field-cell button', 'click', function (e) {
+ var $cell = $(e.currentTarget).closest('td');
+ var col_index = $cell.prevAll('td').length;
+ var field = self.visible_columns[col_index];
+ var action = field.name;
+
+ var $row = $cell.parent('tr');
+ var row = self.rows[$row.prevAll().length];
+
+ var context = _.extend(
+ {}, self.dataset.context, field.context || {});
+ self.dataset.call(action, [row.data.id.value], [context],
+ self.do_reload);
+ e.stopImmediatePropagation();
+ });
+ $table.delegate(
'td.oe-record-delete button', 'click', this.do_delete);
// Global rows handlers
- this.$element.find('table').delegate(
+ $table.delegate(
'tr', 'click', this.on_select_row);
// sidebar stuff
@@ -85,11 +131,27 @@ openerp.base.ListView = openerp.base.Controller.extend(
* @returns {Promise} promise to the end of view rendering (list views are asynchronously filled for improved responsiveness)
*/
do_fill_table: function(records) {
+ var $table = this.$element.find('table');
this.rows = records;
- var $table = this.$element.find('table');
+ // Keep current selected record, if it's still in our new search
+ var current_record_id = this.dataset.ids[this.dataset.index];
+ this.dataset.ids = _(records).chain().map(function (record) {
+ return record.data.id.value;
+ }).value();
+ this.dataset.index = _.indexOf(this.dataset.ids, current_record_id);
+ if (this.dataset.index === -1) {
+ this.dataset.index = 0;
+ }
+
+ // TODO: offset, length, count
+ var results = this.rows.length;
+ $table.find('.oe-pager-last').text(results);
+ $table.find('.oe-pager-total').text(results);
+
+
// remove all data lines
- $table.find('tbody').remove();
+ var $old_body = $table.find('tbody');
// add new content
var columns = this.columns,
@@ -100,10 +162,10 @@ openerp.base.ListView = openerp.base.Controller.extend(
var PAGE_SIZE = 50,
bodies_count = Math.ceil(this.rows.length / PAGE_SIZE),
body = 0,
- $body = $('<tbody>').appendTo($table);
+ $body = $('<tbody class="ui-widget-content">').appendTo($table);
+ var rendered = $.Deferred();
var render_body = function () {
- var rendered = $.Deferred();
setTimeout(function () {
$body.append(
QWeb.render("ListView.rows", {
@@ -118,9 +180,29 @@ openerp.base.ListView = openerp.base.Controller.extend(
rendered.resolve();
}
}, 0);
- return rendered.promise();
};
- return render_body();
+ render_body();
+
+ return rendered.promise().then(function () {
+ $old_body.remove();
+ });
+ },
+ /**
+ * Asks the view manager to switch to a different view, using the provided
+ * record index (within the current dataset).
+ *
+ * If the index is null, ``switch_to_record`` asks for the creation of a
+ * new record.
+ *
+ * @param {Number|null} index the record index (in the current dataset) to switch to
+ * @param {String} [view="form"] the view to switch to
+ */
+ switch_to_record:function (index, view) {
+ view = view || 'form';
+ this.dataset.index = index;
+ _.delay(_.bind(function () {
+ this.view_manager.on_mode_switch(view);
+ }, this));
},
on_select_row: function (event) {
var $target = $(event.currentTarget);
@@ -130,26 +212,41 @@ openerp.base.ListView = openerp.base.Controller.extend(
// count number of preceding siblings to line clicked
var row = this.rows[$target.prevAll().length];
- var index = _.indexOf(this.dataset.ids, row.id);
+ var index = _.indexOf(this.dataset.ids, row.data.id.value);
if (index == undefined || index === -1) {
return;
}
- this.dataset.index = index;
- _.delay(_.bind(function () {
- this.view_manager.on_mode_switch('form');
- }, this));
-
- },
+ this.switch_to_record(index);
+ },
do_show: function () {
- // TODO: re-trigger search
this.$element.show();
+ if (this.hidden) {
+ this.do_reload();
+ this.hidden = false;
+ }
},
do_hide: function () {
this.$element.hide();
+ this.hidden = true;
+ },
+ /**
+ * Reloads the search view based on the current settings (dataset & al)
+ */
+ do_reload: function () {
+ // TODO: need to do 5 billion tons of pre-processing, bypass
+ // DataSet for now
+ //self.dataset.read_slice(self.dataset.fields, 0, self.limit,
+ // self.do_fill_table);
+ return this.rpc('/base/listview/fill', {
+ 'model': this.dataset.model,
+ 'id': this.view_id,
+ 'context': this.dataset.context,
+ 'domain': this.dataset.domain
+ }, this.do_fill_table);
},
do_search: function (domains, contexts, groupbys) {
var self = this;
- this.rpc('/base/session/eval_domain_and_context', {
+ return this.rpc('/base/session/eval_domain_and_context', {
domains: domains,
contexts: contexts,
group_by_seq: groupbys
@@ -157,12 +254,12 @@ openerp.base.ListView = openerp.base.Controller.extend(
// TODO: handle non-empty results.group_by with read_group
self.dataset.context = results.context;
self.dataset.domain = results.domain;
- self.dataset.read_slice(self.dataset.fields, 0, self.limit, self.do_fill_table);
+ return self.do_reload();
});
},
do_update: function () {
var self = this;
- self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table);
+ //self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table);
},
/**
* Handles the signal to delete a line from the DOM
@@ -173,7 +270,17 @@ openerp.base.ListView = openerp.base.Controller.extend(
// don't link to forms
e.stopImmediatePropagation();
this.dataset.unlink(
- [this.rows[$(e.currentTarget).closest('tr').prevAll().length].id]);
+ [this.rows[$(e.currentTarget).closest('tr').prevAll().length].data.id.value]);
+ },
+ /**
+ * Handles signal for the addition of a new record (can be a creation,
+ * can be the addition from a remote source, ...)
+ *
+ * The default implementation is to switch to a new record on the form view
+ */
+ do_add_record: function () {
+ this.notification.notify('Add', "New record");
+ this.switch_to_record(null);
},
/**
* Handles deletion of all selected lines
@@ -195,7 +302,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
var rows = this.rows;
return this.$element.find('th.oe-record-selector input:checked')
.closest('tr').map(function () {
- return rows[$(this).prevAll().length].id;
+ return rows[$(this).prevAll().length].data.id.value;
}).get();
}
});
diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js
index a0bb706714c..a1825824c2e 100644
--- a/addons/base/static/src/js/views.js
+++ b/addons/base/static/src/js/views.js
@@ -144,8 +144,10 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
this.searchview.stop();
}
this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", this.dataset, view_id, search_defaults);
- this.searchview.on_search.add(function() {
- self.views[self.active_view].controller.do_search.apply(self, arguments);
+ this.searchview.on_search.add(function(domains, contexts, groupbys) {
+ self.views[self.active_view].controller.do_search.call(
+ self, domains.concat(self.domains()),
+ contexts.concat(self.contexts()), groupbys);
});
return this.searchview.start();
},
@@ -159,6 +161,23 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
on_remove: function() {
},
on_edit: function() {
+ },
+ /**
+ * Domains added on searches by the view manager, to override in subsequent
+ * view manager in order to add new pieces of domains to searches
+ *
+ * @returns an empty list
+ */
+ domains: function () {
+ return [];
+ },
+ /**
+ * Contexts added on searches by the view manager.
+ *
+ * @returns an empty list
+ */
+ contexts: function () {
+ return [];
}
});
@@ -180,8 +199,6 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
this.sidebar.start();
}
- // init search view
- var view_id = this.action.search_view_id ? this.action.search_view_id[0] || false : false;
var search_defaults = {};
_.each(this.action.context, function (value, key) {
var match = /^search_default_(.*)$/.exec(key);
@@ -189,10 +206,12 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
search_defaults[match[1]] = value;
}
});
- var searchview_loaded = null;
- if (view_id) {
- searchview_loaded = this.setup_search_view(view_id,search_defaults);
- }
+
+ // init search view
+ var searchview_id = this.action.search_view_id && this.action.search_view_id[0];
+
+ var searchview_loaded = this.setup_search_view(
+ searchview_id || false, search_defaults);
// schedule auto_search
if (searchview_loaded != null && this.action['auto_search']) {
@@ -206,6 +225,28 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
this.sidebar.stop();
}
this._super();
+ },
+ /**
+ * adds action domain to the search domains
+ *
+ * @returns the action's domain
+ */
+ domains: function () {
+ if (!this.action.domain) {
+ return [];
+ }
+ return [this.action.domain];
+ },
+ /**
+ * adds action context to the search contexts
+ *
+ * @returns the action's context
+ */
+ contexts: function () {
+ if (!this.action.context) {
+ return [];
+ }
+ return [this.action.context];
}
});
diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml
index 9c73bbc66f3..129198f0ea4 100644
--- a/addons/base/static/src/xml/base.xml
+++ b/addons/base/static/src/xml/base.xml
@@ -154,38 +154,37 @@
</tr>
</table>
</t>
-<t t-name="ListView">
- <!--
- <h3><t t-esc="fields_view.arch['@string']"/></h3>
- -->
- <div class="oe_list_header">
- <div class="oe_list_buttons">
- <!--<button type="button" class="oe_list_button_new">New</button>-->
- </div>
- <div class="oe_list_pager">
- <button type="button" data-pager-action="first">First</button>
- <button type="button" data-pager-action="previous">&lt;&lt;</button>
+<table t-name="ListView">
+ <t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0)"/>
+ <t t-set="actions_span" t-value="Math.floor((options.deletable or options.addable) ? columns_count/2 : 0)"/>
+ <thead class="ui-widget-header">
+ <tr t-if="options.selectable">
+ <th t-if="actions_span" t-att-colspan="actions_span"
+ class="oe-actions">
+ <button type="button" id="oe-list-add"
+ t-if="options.addable">
+ <t t-esc="options.addable"/>
+ </button>
+ <button type="button" id="oe-list-delete"
+ t-if="options.deletable">
+ Delete
+ </button>
+ </th>
+ <th t-att-colspan="columns_count - actions_span"
+ class="oe-list-pager">
+ <button type="button" data-pager-action="first">First</button>
+ <button type="button" data-pager-action="previous"
+ >&lt;&lt;</button>
- <span class="oe_pager_index">0</span> / <span class="oe_pager_count">0</span>
+ <span class="oe-pager-first">1</span>
+ to <span class="oe-pager-last">1</span>
+ of <span class="oe-pager-total">1</span>
- <button type="button" data-pager-action="next">&gt;&gt;</button>
- <button type="button" data-pager-action="last">Last</button>
- </div>
- </div>
- <table>
- <t t-call="ListView.header"/>
- </table>
-</t>
-<t t-name="ListView.header">
- <thead t-if="options.header">
- <tr t-if="options.selectable and options.deletable">
- <th t-att-colspan="visible_columns.length + 2">
- <button type="button" id="oe-list-delete">
- Delete Selected
- </button>
+ <button type="button" data-pager-action="next">&gt;&gt;</button>
+ <button type="button" data-pager-action="last">Last</button>
</th>
</tr>
- <tr>
+ <tr t-if="options.header">
<th t-if="options.selectable"/>
<t t-foreach="columns" t-as="column">
<th t-if="column.invisible !== '1'">
@@ -197,27 +196,36 @@
<th t-if="options.deletable"/>
</tr>
</thead>
-</t>
+</table>
<t t-name="ListView.rows" t-foreach="rows" t-as="row">
- <t t-call="ListView.row"/>
+ <t t-call="ListView.row">
+ <t t-set="style" t-value="null"/>
+ <t-if test="row.color">
+ <t t-set="style" t-value="'color: ' + row.color"/>
+ </t-if>
+ </t>
</t>
-<tr t-name="ListView.row">
+<tr t-name="ListView.row" t-att-style="style"
+ t-att-class="row_parity">
<th t-if="options.selectable" class="oe-record-selector">
<input type="checkbox"/>
</th>
<t t-foreach="columns" t-as="column">
- <!-- TODO: handle attrs -->
- <td t-if="column.invisible !== '1'" t-att-title="column.help">
- <t t-set="is_button" t-value="column.tag === 'button'"/>
- <!-- TODO: get correct widget from form -->
- <t t-if="!is_button and row[column.id]">
- <t t-esc="row[column.id]"/>
+ <t t-set="attrs" t-value="column.attrs_for(row.data)"/>
+ <td t-if="column.invisible !== '1'" t-att-title="column.help"
+ class="oe-field-cell">
+ <t t-if="!attrs.invisible">
+ <t t-set="is_button" t-value="column.tag === 'button'"/>
+ <!-- TODO: get correct widget from form -->
+ <t t-if="!is_button and row['data'][column.id].value">
+ <t t-esc="row['data'][column.id].value"/>
+ </t>
+ <button type="button" t-att-title="column.help"
+ t-if="is_button">
+ <img t-att-src="'/base/static/src/img/icons/' + column.icon + '.png'"
+ t-att-alt="column.string"/>
+ </button>
</t>
- <button type="button" t-att-title="column.help"
- t-if="is_button">
- <img t-att-src="'/base/static/src/img/icons/' + column.icon + '.png'"
- t-att-alt="column.string"/>
- </button>
</td>
</t>
<td t-if="options.deletable" class='oe-record-delete'>