/* This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. */ (function() { 'use strict'; Sao.View = Sao.class_(Object, { init: function(screen, xml) { this.screen = screen; this.view_type = null; this.el = null; this.fields = {}; }, set_value: function() { }, get_fields: function() { return Object.keys(this.fields); }, get_buttons: function() { return []; } }); Sao.View.idpath2path = function(tree, idpath) { var path = []; var child_path; if (!idpath) { return []; } for (var i = 0, len = tree.rows.length; i < len; i++) { if (tree.rows[i].record.id == idpath[0]) { path.push(i); child_path = Sao.View.idpath2path(tree.rows[i], idpath.slice(1, idpath.length)); path = path.concat(child_path); break; } } return path; }; Sao.View.parse = function(screen, xml, children_field) { switch (xml.children().prop('tagName')) { case 'tree': return new Sao.View.Tree(screen, xml, children_field); case 'form': return new Sao.View.Form(screen, xml); } }; Sao.View.tree_column_get = function(type) { switch (type) { case 'char': return Sao.View.Tree.CharColumn; case 'many2one': return Sao.View.Tree.Many2OneColumn; case 'date': return Sao.View.Tree.DateColumn; case 'time': return Sao.View.Tree.TimeColumn; case 'timedelta': return Sao.View.Tree.TimeDeltaColumn; case 'one2many': return Sao.View.Tree.One2ManyColumn; case 'many2many': return Sao.View.Tree.Many2ManyColumn; case 'selection': return Sao.View.Tree.SelectionColumn; case 'reference': return Sao.View.Tree.ReferenceColumn; case 'float': case 'numeric': return Sao.View.Tree.FloatColumn; case 'integer': case 'biginteger': return Sao.View.Tree.IntegerColumn; case 'boolean': return Sao.View.Tree.BooleanColumn; case 'image': return Sao.View.Tree.ImageColumn; default: return Sao.View.Tree.CharColumn; } }; Sao.View.Tree = Sao.class_(Sao.View, { init: function(screen, xml, children_field) { Sao.View.Tree._super.init.call(this, screen, xml); this.view_type = 'tree'; this.selection_mode = (screen.attributes.selection_mode || Sao.common.SELECTION_SINGLE); this.el = jQuery('
', { 'class': 'treeview' }); this.expanded = {}; this.children_field = children_field; var top_node = xml.children()[0]; this.keyword_open = top_node.getAttribute('keyword_open'); this.editable = top_node.getAttribute('editable'); this.attributes = top_node.attributes; // Columns this.columns = []; this.editable_widgets = []; this.create_columns(screen.model, xml); // Table of records this.rows = []; this.table = jQuery('', { 'class': 'tree table table-hover' }); if (this.columns.filter(function(c) { return !c.attributes.tree_invisible; }).length > 1) { this.table.addClass('responsive'); this.table.addClass('responsive-header'); } this.el.append(this.table); var thead = jQuery(''); this.table.append(thead); var tr = jQuery(''); if (this.selection_mode != Sao.common.SELECTION_NONE) { var th = jQuery(''); this.table.append(this.tbody); // Footer for more var footer = jQuery('
', { 'class': 'treefooter' }); this.more = jQuery('
'); }, is_expanded: function() { return (this.path in this.tree.expanded); }, get_last_child: function() { if (!this.children_field || !this.is_expanded() || jQuery.isEmptyObject(this.rows)) { return this; } return this.rows[this.rows.length - 1].get_last_child(); }, get_id_path: function() { if (!this.parent_) { return [this.record.id]; } return this.parent_.get_id_path().concat([this.record.id]); }, build_widgets: function() { var table = jQuery('
'); this.selection = jQuery('', { 'type': 'checkbox', 'class': 'selection' }); this.selection.change(this.selection_changed.bind(this)); th.append(this.selection); tr.append(th); } thead.append(tr); this.columns.forEach(function(column) { th = jQuery('', { text: column.attributes.string }); if (column.attributes.tree_invisible) { th.hide(); } tr.append(th); }); this.tbody = jQuery('
'); table.css('width', '100%'); var row = jQuery(''); table.append(row); return [table, row]; }, construct: function(selected, expanded) { selected = selected || []; expanded = expanded || []; var el_node = this.el[0]; while (el_node.firstChild) { el_node.removeChild(el_node.firstChild); } var td; if (this.tree.selection_mode != Sao.common.SELECTION_NONE) { td = jQuery(''); editable_row.append(jQuery('
'); this.el.append(td); this.selection = jQuery('', { 'type': 'checkbox', 'class': 'selection' }); this.selection.change(this.selection_changed.bind(this)); td.append(this.selection); } var depth = this.path.split('.').length; for (var i = 0; i < this.tree.columns.length; i++) { var column = this.tree.columns[i]; td = jQuery('', { 'data-title': column.attributes.string }); td.one('click', {column: i, td: td}, this.select_row.bind(this)); var widgets = this.build_widgets(); var table = widgets[0]; var row = widgets[1]; td.append(table); if ((i === 0) && this.children_field) { var expanded_icon = 'glyphicon-plus'; if (this.is_expanded() || ~expanded.indexOf(this.record.id)) { expanded_icon = 'glyphicon-minus'; } this.expander = jQuery('', { 'class': 'glyphicon ' + expanded_icon }); this.expander.html(' '); this.expander.css('margin-left', (depth - 1) + 'em'); this.expander.css('float', 'left'); this.expander.click(this.toggle_row.bind(this)); row.append(jQuery('').append(this.expander ).css('width', 1)); } var j; if (column.prefixes) { for (j = 0; j < column.prefixes.length; j++) { var prefix = column.prefixes[j]; row.append(jQuery('').css('width', 1)); } } row.append(jQuery('')); if (column.suffixes) { for (j = 0; j < column.suffixes.length; j++) { var suffix = column.suffixes[j]; row.append(jQuery('').css('width', 1)); } } if (column.attributes.tree_invisible) { td.hide(); } this.el.append(td); } if (this.parent_) { var last_child = this.parent_.get_last_child(); last_child.el.after(this.el); } else { this.tree.tbody.append(this.el); } var row_id_path = this.get_id_path(); if (this.is_expanded() || Sao.common.contains(expanded, row_id_path)) { this.tree.expanded[this.path] = this; this.expand_children(selected, expanded); } }, redraw: function(selected, expanded) { selected = selected || []; expanded = expanded || []; var update_expander = function() { if (jQuery.isEmptyObject( this.record.field_get( this.children_field))) { this.expander.css('visibility', 'hidden'); } }; var child_offset = 0; if (this.tree.selection_mode != Sao.common.SELECTION_NONE) { child_offset += 1; } for (var i = 0; i < this.tree.columns.length; i++) { if ((i === 0) && this.children_field) { this.record.load(this.children_field).done( update_expander.bind(this)); } var column = this.tree.columns[i]; var inside_tr = jQuery(this.el.children()[i + child_offset]) .find('tr'); var current_td = jQuery(inside_tr.children()[0]); if (this.children_field) { current_td = current_td.next(); } var prefix, suffix; if (column.prefixes) { for (var j = 0; j < column.prefixes.length; j++) { prefix = column.prefixes[j]; current_td.html(prefix.render(this.record)); current_td = current_td.next(); } } jQuery(current_td).html(column.render(this.record)); if (column.suffixes) { current_td = current_td.next(); for (var k = 0; k < column.suffixes.length; k++) { suffix = column.suffixes[k]; current_td.html(suffix.render(this.record)); current_td = current_td.next(); } } } var row_id_path = this.get_id_path(); this.set_selection(Sao.common.contains(selected, row_id_path)); if (this.is_expanded() || Sao.common.contains(expanded, row_id_path)) { this.tree.expanded[this.path] = this; if (!this.record._values[this.children_field] || (this.record._values[this.children_field].length > 0 && this.rows.length === 0)) { this.expand_children(selected, expanded); } else { var child_row; for (i = 0; i < this.rows.length; i++) { child_row = this.rows[i]; child_row.redraw(selected, expanded); } } if (this.expander) { this.update_expander(true); } } else { if (this.expander) { this.update_expander(false); } } if (this.record.deleted() || this.record.removed()) { this.el.css('text-decoration', 'line-through'); } else { this.el.css('text-decoration', 'inherit'); } }, toggle_row: function() { if (this.is_expanded()) { this.update_expander(false); delete this.tree.expanded[this.path]; this.collapse_children(); } else { this.update_expander(true); this.tree.expanded[this.path] = this; this.expand_children(); } return false; }, update_expander: function(expanded) { if (expanded) { this.expander.removeClass('glyphicon-plus'); this.expander.addClass('glyphicon-minus'); } else { this.expander.removeClass('glyphicon-minus'); this.expander.addClass('glyphicon-plus'); } }, collapse_children: function() { this.rows.forEach(function(row, pos, rows) { row.collapse_children(); var node = row.el[0]; node.parentNode.removeChild(node); }); this.rows = []; }, expand_children: function(selected, expanded) { var add_children = function() { if (!jQuery.isEmptyObject(this.rows)) { return; } var add_row = function(record, pos, group) { var tree_row = new Sao.View.Tree.Row(this.tree, record, pos, this); tree_row.construct(selected, expanded); tree_row.redraw(selected, expanded); this.rows.push(tree_row); }; var children = this.record.field_get_client( this.children_field); children.forEach(add_row.bind(this)); }; this.record.load(this.children_field).done( add_children.bind(this)); }, select_row: function(event_) { if (this.tree.selection_mode == Sao.common.SELECTION_NONE) { this.tree.select_changed(this.record); this.tree.switch_(this.path); } else { if (!event_.ctrlKey) { this.tree.rows.forEach(function(row) { if (row != this) { row.set_selection(false); } }.bind(this)); this.selection_changed(); if (this.is_selected()) { this.tree.switch_(this.path); return; } } this.set_selection(!this.is_selected()); this.selection_changed(); } // The 'click'-event must be handled next time the row is clicked var td = event_.data.td; var column = event_.data.column; td.one('click', {column: column, td: td}, this.select_row.bind(this)); }, is_selected: function() { if (this.tree.selection_mode == Sao.common.SELECTION_NONE) { return false; } return this.selection.prop('checked'); }, set_selection: function(value) { if (this.tree.selection_mode == Sao.common.SELECTION_NONE) { return; } this.selection.prop('checked', value); if (!value) { this.tree.selection.prop('checked', false); } }, selection_changed: function() { var is_selected = this.is_selected(); this.set_selection(is_selected); if (is_selected) { this.tree.select_changed(this.record); } else { this.tree.select_changed( this.tree.selected_records()[0] || null); } this.tree.update_selection(); } }); Sao.View.Tree.RowEditable = Sao.class_(Sao.View.Tree.Row, { init: function(tree, record, pos, parent) { Sao.View.Tree.RowEditable._super.init.call(this, tree, record, pos, parent); this.edited_column = undefined; }, build_widgets: function() { var widgets = Sao.View.Tree.RowEditable._super.build_widgets(this); var table = widgets[0]; var editable_row = jQuery('
')); table.append(editable_row); return widgets; }, select_row: function(event_) { var previously_selected, previous_td; var inner_rows, readonly_row, editable_row, current_td; var field, widget; this.tree.rows.forEach(function(row) { if (row.is_selected()) { previously_selected = row; } if (row != this) { row.set_selection(false); } }.bind(this)); this.selection_changed(); var save_prm; if (previously_selected && previously_selected != this) { save_prm = previously_selected.record.save(); } else { save_prm = jQuery.when(); } save_prm.done(function () { if (previously_selected && previously_selected.edited_column !== undefined) { readonly_row = previously_selected.get_readonly_row(); editable_row = previously_selected.get_editable_row(); previous_td = previously_selected.get_active_td(); previous_td.one('click', { td: previous_td, column: previously_selected.edited_column }, previously_selected.select_row.bind(previously_selected)); readonly_row.show(); editable_row.hide(); previously_selected.edited_column = undefined; } if (this.is_selected()) { this.edited_column = event_.data.column; readonly_row = this.get_readonly_row(); editable_row = this.get_editable_row(); current_td = this.get_active_td(); widget = this.tree.editable_widgets[this.edited_column]; widget.view = this.tree; editable_row.find('td').append(widget.el); // We use keydown to be able to catch TAB events current_td.one('keydown', this.key_press.bind(this)); field = this.record.model.fields[widget.field_name]; widget.display(this.record, field); readonly_row.hide(); editable_row.show(); } else { this.set_selection(true); this.selection_changed(); var td = event_.data.td; var column = event_.data.column; td.one('click', {column: column, td: td}, this.select_row.bind(this)); } }.bind(this)); }, get_readonly_row: function() { return this._get_inner_element(1); }, get_editable_row: function() { return this._get_inner_element(2); }, get_active_td: function() { return this._get_inner_element(); }, _get_inner_element: function(child_index) { // We add two because of the selection box at the start and // because nth-child is 1-indexed var selector = 'td:nth-child(' + (this.edited_column + 2) + ')'; if (child_index !== undefined) { selector += ' table tr:nth-child(' + child_index + ')'; } return this.el.find(selector); }, key_press: function(event_) { var current_td, selector, next_column; if (event_.which == Sao.common.TAB_KEYCODE) { var sign = 1; if (event_.shiftKey) { sign = -1; } event_.preventDefault(); next_column = ((this.edited_column + sign * 1) % this.tree.columns.length); selector = 'td:nth-child(' + (next_column + 2) + ')'; window.setTimeout(function() { this.el.find(selector).trigger('click'); }.bind(this), 0); } else if (event_.which == Sao.common.UP_KEYCODE || event_.which == Sao.common.DOWN_KEYCODE) { var next_row; if (event_.which == Sao.common.UP_KEYCODE) { next_row = this.el.prev('tr'); } else { next_row = this.el.next('tr'); } selector = 'td:nth-child(' + (this.edited_column + 2) + ')'; window.setTimeout(function() { next_row.find(selector).trigger('click'); next_row.find(selector).trigger('click'); }.bind(this), 0); } this.get_active_td(this).one('keydown', this.key_press.bind(this)); } }); Sao.View.Tree.Affix = Sao.class_(Object, { init: function(attributes, protocol) { this.attributes = attributes; this.protocol = protocol || null; this.icon = attributes.icon; if (this.protocol && !this.icon) { this.icon = 'tryton-web-browser'; } }, get_cell: function() { var cell; if (this.protocol) { cell = jQuery(''); cell.append(jQuery('')); } else if (this.icon) { cell = jQuery(''); } else { cell = jQuery(''); } cell.addClass('column-affix'); return cell; }, render: function(record) { var cell = this.get_cell(); record.load(this.attributes.name).done(function() { var value, icon_prm; var field = record.model.fields[this.attributes.name]; //TODO set_state if (this.icon) { if (this.icon in record.model.fields) { var icon_field = record.model.fields[this.icon]; value = icon_field.get_client(record); } else { value = this.icon; } icon_prm = Sao.common.ICONFACTORY.register_icon(value); icon_prm.done(function(url) { var img_tag; if (cell.children('img').length) { img_tag = cell.children('img'); } else { img_tag = cell; } img_tag.attr('src', url); }.bind(this)); } else { value = this.attributes.string || ''; if (!value) { value = field.get_client(record) || ''; } cell.text(value); } }.bind(this)); return cell; } }); Sao.View.Tree.CharColumn = Sao.class_(Object, { class_: 'column-char', init: function(model, attributes) { this.type = 'field'; this.model = model; this.field = model.fields[attributes.name]; this.attributes = attributes; this.prefixes = []; this.suffixes = []; }, get_cell: function() { var cell = jQuery('
'); cell.addClass(this.class_); return cell; }, update_text: function(cell, record) { cell.text(this.field.get_client(record)); }, render: function(record) { var cell = this.get_cell(); record.load(this.attributes.name).done(function() { this.update_text(cell, record); this.field.set_state(record); var state_attrs = this.field.get_state_attrs(record); if (state_attrs.invisible) { cell.hide(); } else { cell.show(); } // TODO editable: readonly and required }.bind(this)); return cell; } }); Sao.View.Tree.IntegerColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-integer', init: function(model, attributes) { Sao.View.Tree.IntegerColumn._super.init.call(this, model, attributes); this.factor = Number(attributes.factor || 1); }, get_cell: function() { return Sao.View.Tree.IntegerColumn._super.get_cell.call(this); }, update_text: function(cell, record) { cell.text(this.field.get_client(record, this.factor)); } }); Sao.View.Tree.FloatColumn = Sao.class_(Sao.View.Tree.IntegerColumn, { class_: 'column-float' }); Sao.View.Tree.BooleanColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-boolean', get_cell: function() { return jQuery('', { 'type': 'checkbox', 'disabled': true, 'class': this.class_ }); }, update_text: function(cell, record) { cell.prop('checked', this.field.get(record)); } }); Sao.View.Tree.Many2OneColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-many2one' }); Sao.View.Tree.SelectionColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-selection', init: function(model, attributes) { Sao.View.Tree.SelectionColumn._super.init.call(this, model, attributes); Sao.common.selection_mixin.init.call(this); this.init_selection(); }, init_selection: function(key) { Sao.common.selection_mixin.init_selection.call(this, key); }, update_selection: function(record, callback) { Sao.common.selection_mixin.update_selection.call(this, record, this.field, callback); }, update_text: function(cell, record) { this.update_selection(record, function() { var value = this.field.get(record); var prm, text, found = false; for (var i = 0, len = this.selection.length; i < len; i++) { if (this.selection[i][0] === value) { found = true; text = this.selection[i][1]; break; } } if (!found) { prm = Sao.common.selection_mixin.get_inactive_selection .call(this, value).then(function(inactive) { return inactive[1]; }); } else { prm = jQuery.when(text); } prm.done(function(text_value) { cell.text(text_value); }.bind(this)); }.bind(this)); } }); Sao.View.Tree.ReferenceColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-reference', init: function(model, attributes) { Sao.View.Tree.ReferenceColumn._super.init.call(this, model, attributes); Sao.common.selection_mixin.init.call(this); this.init_selection(); }, init_selection: function(key) { Sao.common.selection_mixin.init_selection.call(this, key); }, update_text: function(cell, record) { var value = this.field.get_client(record); var model, name; if (!value) { model = ''; name = ''; } else { model = value[0]; name = value[1]; } if (model) { cell.text(this.selection[model] || model + ',' + name); } else { cell.text(name); } } }); Sao.View.Tree.DateColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-date', update_text: function(cell, record) { var value = this.field.get_client(record); var date_format = this.field.date_format(record); cell.text(Sao.common.format_date(date_format, value)); } }); Sao.View.Tree.TimeColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-time', update_text: function(cell, record) { var value = this.field.get_client(record); cell.text(Sao.common.format_time( this.field.time_format(record), value)); } }); Sao.View.Tree.TimeDeltaColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-timedelta' }); Sao.View.Tree.One2ManyColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-one2many', update_text: function(cell, record) { cell.text('( ' + this.field.get_client(record).length + ' )'); } }); Sao.View.Tree.Many2ManyColumn = Sao.class_(Sao.View.Tree.One2ManyColumn, { class_: 'column-many2many' }); Sao.View.Tree.ImageColumn = Sao.class_(Sao.View.Tree.CharColumn, { class_: 'column-image', get_cell: function() { var cell = jQuery(''); cell.addClass(this.class_); cell.css('width', '100%'); return cell; }, render: function(record) { var cell = this.get_cell(); record.load(this.attributes.name).done(function() { var value = this.field.get_client(record); if (value) { if (value > Sao.common.BIG_IMAGE_SIZE) { value = jQuery.when(null); } else { value = this.field.get_data(record); } } else { value = jQuery.when(null); } value.done(function(data) { var img_url, blob; if (!data) { img_url = null; } else { blob = new Blob([data[0][this.field.name]]); img_url = window.URL.createObjectURL(blob); } cell.attr('src', img_url); }.bind(this)); }.bind(this)); return cell; } }); Sao.View.Tree.ButtonColumn = Sao.class_(Object, { init: function(screen, attributes) { this.screen = screen; this.type = 'button'; this.attributes = attributes; }, render: function(record) { var button = new Sao.common.Button(this.attributes); button.el.click(record, this.button_clicked.bind(this)); var fields = jQuery.map(this.screen.model.fields, function(field, name) { if ((field.description.loading || 'eager') == 'eager') { return name; } else { return undefined; } }); // Wait at least one eager field is loaded before evaluating states record.load(fields[0]).done(function() { button.set_state(record); }); return button.el; }, button_clicked: function(event) { var record = event.data; if (record != this.screen.current_record) { return; } // TODO check state this.screen.button(this.attributes); } }); Sao.View.Form = Sao.class_(Sao.View, { init: function(screen, xml) { Sao.View.Form._super.init.call(this, screen, xml); this.view_type = 'form'; this.el = jQuery('
', { 'class': 'form' }); this.widgets = {}; this.widget_id = 0; this.state_widgets = []; this.containers = []; var root = xml.children()[0]; var container = this.parse(screen.model, root); this.el.append(container.el); }, parse: function(model, node, container) { if (container === undefined) { container = new Sao.View.Form.Container( Number(node.getAttribute('col') || 4)); this.containers.push(container); } var _parse = function(index, child) { var attributes = {}; for (var i = 0, len = child.attributes.length; i < len; i++) { var attribute = child.attributes[i]; attributes[attribute.name] = attribute.value; } ['readonly', 'invisible'].forEach(function(name) { if (attributes[name]) { attributes[name] = attributes[name] == 1; } }); ['yexpand', 'yfill', 'xexpand', 'xfill', 'colspan'].forEach( function(name) { if (attributes[name]) { attributes[name] = Number(attributes[name]); } }); switch (child.tagName) { case 'image': // TODO break; case 'separator': this._parse_separator( model, child, container, attributes); break; case 'label': this._parse_label(model, child, container, attributes); break; case 'newline': container.add_row(); break; case 'button': this._parse_button(child, container, attributes); break; case 'notebook': this._parse_notebook( model, child, container, attributes); break; case 'page': this._parse_page(model, child, container, attributes); break; case 'field': this._parse_field(model, child, container, attributes); break; case 'group': this._parse_group(model, child, container, attributes); break; case 'hpaned': // TODO break; case 'vpaned': // TODO break; case 'child': // TODO break; } }; jQuery(node).children().each(_parse.bind(this)); return container; }, _parse_separator: function(model, node, container, attributes) { var name = attributes.name; var text = attributes.string; if (name in model.fields) { if (!attributes.states && (name in model.fields)) { attributes.states = model.fields[name].description.states; } if (!text) { text = model.fields[name].description.string; } } var separator = new Sao.View.Form.Separator(text, attributes); this.state_widgets.push(separator); container.add(attributes, separator); }, _parse_label: function(model, node, container, attributes) { var name = attributes.name; var text = attributes.string; if (attributes.xexpand === undefined) { attributes.xexpand = 0; } if (name in model.fields) { if (name == this.screen.exclude_field) { container.add(attributes); return; } if (!attributes.states && (name in model.fields)) { attributes.states = model.fields[name].description.states; } if (!text) { // TODO RTL and translation text = model.fields[name] .description.string + ': '; } if (attributes.xalign === undefined) { attributes.xalign = 1.0; } } else if (!text) { // TODO get content } var label; if (text) { label = new Sao.View.Form.Label(text, attributes); this.state_widgets.push(label); } container.add(attributes, label); // TODO help }, _parse_button: function(node, container, attributes) { var button = new Sao.common.Button(attributes); this.state_widgets.push(button); container.add(attributes, button); button.el.click(button, this.button_clicked.bind(this)); // TODO help }, _parse_notebook: function(model, node, container, attributes) { if (attributes.colspan === undefined) { attributes.colspan = 4; } var notebook = new Sao.View.Form.Notebook(attributes); this.state_widgets.push(notebook); container.add(attributes, notebook); this.parse(model, node, notebook); }, _parse_page: function(model, node, container, attributes) { var text = attributes.string; if (attributes.name in model.fields) { // TODO check exclude // sync attributes if (!text) { text = model.fields[attributes.name] .description.string; } } if (!text) { text = 'No String Attr.'; // TODO translate } var page = this.parse(model, node); page = new Sao.View.Form.Page(container.add(page.el, text), attributes); this.state_widgets.push(page); }, _parse_field: function(model, node, container, attributes) { var name = attributes.name; if (!(name in model.fields) || name == this.screen.exclude_field) { container.add(attributes); return; } if (!attributes.widget) { attributes.widget = model.fields[name] .description.type; } var attribute_names = ['relation', 'domain', 'selection', 'relation_field', 'string', 'views', 'add_remove', 'sort', 'context', 'size', 'filename', 'autocomplete', 'translate', 'create', 'delete']; for (var i in attribute_names) { var attr = attribute_names[i]; if ((attr in model.fields[name].description) && (node.getAttribute(attr) === null)) { attributes[attr] = model.fields[name] .description[attr]; } } var WidgetFactory = Sao.View.form_widget_get( attributes.widget); if (!WidgetFactory) { container.add(attributes); return; } var widget = new WidgetFactory(name, model, attributes); widget.position = this.widget_id += 1; widget.view = this; // TODO expand, fill, help, height, width container.add(attributes, widget); if (this.widgets[name] === undefined) { this.widgets[name] = []; } this.widgets[name].push(widget); this.fields[name] = true; }, _parse_group: function(model, node, container, attributes) { var group = new Sao.View.Form.Group(attributes); group.add(this.parse(model, node)); this.state_widgets.push(group); container.add(attributes, group); }, get_buttons: function() { var buttons = []; for (var j in this.state_widgets) { var widget = this.state_widgets[j]; if (widget instanceof Sao.common.Button) { buttons.push(widget); } } return buttons; }, display: function() { var record = this.screen.current_record; var field; var name; var promesses = {}; if (record) { // Force to set fields in record // Get first the lazy one to reduce number of requests var fields = []; for (name in record.model.fields) { field = record.model.fields[name]; fields.push([name, field.description.loading || 'eager']); } fields.sort(function(a, b) { return a[1].localeCompare(b[1]); }); fields.forEach(function(e) { var name = e[0]; promesses[name] = record.load(name); }); } var set_state = function(record, field, name) { var prm = jQuery.when(); if (name in promesses) { prm = promesses[name]; } prm.done(function() { field.set_state(record); }); }; var display = function(record, field, name) { return function(widget) { var prm = jQuery.when(); if (name in promesses) { prm = promesses[name]; } prm.done(function() { widget.display(record, field); }); }; }; for (name in this.widgets) { var widgets = this.widgets[name]; field = null; if (record) { field = record.model.fields[name]; } if (field) { set_state(record, field, name); } widgets.forEach(display(record, field, name)); } jQuery.when.apply(jQuery, jQuery.map(promesses, function(p) { return p; }) ).done(function() { var j; for (j in this.state_widgets) { var state_widget = this.state_widgets[j]; state_widget.set_state(record); } for (j in this.containers) { var container = this.containers[j]; container.resize(); } }.bind(this)); }, set_value: function() { var record = this.screen.current_record; if (record) { var set_value = function(widget) { widget.set_value(record, this); }; for (var name in this.widgets) { if (name in record.model.fields) { var widgets = this.widgets[name]; var field = record.model.fields[name]; widgets.forEach(set_value, field); } } } }, button_clicked: function(event) { var button = event.data; this.screen.button(button.attributes); }, selected_records: function() { if (this.screen.current_record) { return [this.screen.current_record]; } return []; } }); Sao.View.Form.Container = Sao.class_(Object, { init: function(col) { if (col === undefined) col = 4; this.col = col; this.el = jQuery('', { 'class': 'form-container responsive responsive-noheader' }); this.add_row(); }, add_row: function() { this.el.append(jQuery('')); }, rows: function() { return this.el.children().children('tr'); }, row: function() { return this.rows().last(); }, add: function(attributes, widget) { var colspan = attributes.colspan; if (colspan === undefined) colspan = 1; var xfill = attributes.xfill; if (xfill === undefined) xfill = 1; var xexpand = attributes.xexpand; if (xexpand === undefined) xexpand = 1; var len = 0; var row = this.row(); row.children().map(function(i, e) { len += Number(jQuery(e).attr('colspan') || 1); }); if (len + colspan > this.col) { this.add_row(); row = this.row(); } var el; if (widget) { el = widget.el; } var cell = jQuery('
', { 'colspan': colspan, 'class': widget ? widget.class_ || '' : '' }).append(el); if (attributes.xalign !== undefined) { // TODO replace by start/end when supported cell.css('text-align', attributes.xalign >= 0.5? 'right': 'left'); } if (xexpand) { cell.addClass('xexpand'); cell.css('width', '100%'); } if (xfill) { cell.addClass('xfill'); if (xexpand && el) { el.css('width', '100%'); } } row.append(cell); }, resize: function() { var rows = this.rows().toArray(); var widths = []; var col = this.col; var has_expand = false; var i, j; var get_xexpands = function(row) { row = jQuery(row); var xexpands = []; i = 0; row.children().map(function() { var cell = jQuery(this); var colspan = Math.min(Number(cell.attr('colspan')), col); if (cell.hasClass('xexpand') && (!jQuery.isEmptyObject(cell.children())) && (cell.children().css('display') != 'none')) { xexpands.push([cell, i]); } i += colspan; }); return xexpands; }; // Sort rows to compute first the most constraining row // which are the one with the more xexpand cells // and with the less colspan rows.sort(function(a, b) { a = get_xexpands(a); b = get_xexpands(b); if (a.length == b.length) { var reduce = function(previous, current) { var cell = current[0]; var colspan = Math.min( Number(cell.attr('colspan')), col); return previous + colspan; }; return a.reduce(reduce, 0) - b.reduce(reduce, 0); } else { return b.length - a.length; } }); rows.forEach(function(row) { row = jQuery(row); var xexpands = get_xexpands(row); var width = 100 / xexpands.length; xexpands.forEach(function(e) { var cell = e[0]; i = e[1]; var colspan = Math.min(Number(cell.attr('colspan')), col); var current_width = 0; for (j = 0; j < colspan; j++) { current_width += widths[i + j] || 0; } for (j = 0; j < colspan; j++) { if (!current_width) { widths[i + j] = width / colspan; } else if (current_width > width) { // Split proprotionally the difference over all cells // following their current width var diff = current_width - width; if (widths[i + j]) { widths[i + j] -= (diff / (current_width / widths[i + j])); } } } }); if (!jQuery.isEmptyObject(xexpands)) { has_expand = true; } }); rows.forEach(function(row) { row = jQuery(row); i = 0; row.children().map(function() { var cell = jQuery(this); var colspan = Math.min(Number(cell.attr('colspan')), col); if (cell.hasClass('xexpand') && (cell.children().css('display') != 'none')) { var width = 0; for (j = 0; j < colspan; j++) { width += widths[i + j] || 0; } cell.css('width', width + '%'); } if (cell.children().css('display') == 'none') { cell.hide(); } else { cell.show(); } i += colspan; }); }); if (has_expand) { this.el.css('width', '100%'); } else { this.el.css('width', ''); } } }); var StateWidget = Sao.class_(Object, { init: function(attributes) { this.attributes = attributes; }, set_state: function(record) { var state_changes; if (record) { state_changes = record.expr_eval(this.attributes.states || {}); } else { state_changes = {}; } var invisible = state_changes.invisible; if (invisible === undefined) { invisible = this.attributes.invisible; } if (invisible) { this.hide(); } else { this.show(); } }, show: function() { this.el.show(); }, hide: function() { this.el.hide(); } }); Sao.View.Form.Separator = Sao.class_(StateWidget, { init: function(text, attributes) { Sao.View.Form.Separator._super.init.call(this, attributes); this.el = jQuery('
', { 'class': 'form-separator' }); if (text) { this.el.append(jQuery('