From 0c59e5005236af3ad77ebe49ceb24f42436cd3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 30 Oct 2015 11:52:58 +0100 Subject: [PATCH] Split view.js --- Gruntfile.js | 4 + src/view.js | 5640 ------------------------------------------ src/view/calendar.js | 23 + src/view/form.js | 3602 +++++++++++++++++++++++++++ src/view/graph.js | 326 +++ src/view/tree.js | 1713 +++++++++++++ 6 files changed, 5668 insertions(+), 5640 deletions(-) create mode 100644 src/view/calendar.js create mode 100644 src/view/form.js create mode 100644 src/view/graph.js create mode 100644 src/view/tree.js diff --git a/Gruntfile.js b/Gruntfile.js index a2f9129f30..4fbc9e6501 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -13,6 +13,10 @@ module.exports = function(grunt) { 'src/tab.js', 'src/screen.js', 'src/view.js', + 'src/view/form.js', + 'src/view/tree.js', + 'src/view/graph.js', + 'src/view/calendar.js', 'src/action.js', 'src/common.js', 'src/window.js', diff --git a/src/view.js b/src/view.js index 1b283059d3..d8c5a7e88b 100644 --- a/src/view.js +++ b/src/view.js @@ -59,504 +59,6 @@ } }; - Sao.View.tree_column_get = function(type) { - switch (type) { - case 'char': - case 'text': - case 'binary': - return Sao.View.Tree.CharColumn; - case 'many2one': - return Sao.View.Tree.Many2OneColumn; - case 'one2one': - return Sao.View.Tree.One2OneColumn; - 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; - case 'url': - case 'email': - case 'callto': - case 'sip': - return Sao.View.Tree.URLColumn; - case 'progressbar': - return Sao.View.Tree.ProgressBar; - } - }; - - 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 responsive' - }); - this.expanded = {}; - this.children_field = children_field; - this.editable = Boolean(this.attributes.editable); - - // Columns - this.columns = []; - this.create_columns(screen.model, xml); - - // Table of records - this.rows = []; - this.table = jQuery('', { - 'class': 'tree table table-hover table-striped' - }); - 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('
', { - 'class': 'selection' - }); - 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(''); - var label = 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('
'); - 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 - }).append(jQuery('
', { // For responsive min-height - 'aria-hidden': true - })); - td.css('overflow', 'hidden'); - td.on('click keypress', {column: i, td: td}, - Sao.common.click_press(this.select_row.bind(this), - true)); - if (!this.tree.editable) { - td.dblclick(this.switch_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, - 'tabindex': 0 - }); - this.expander.html(' '); - this.expander.css('margin-left', (depth - 1) + 'em'); - this.expander.css('float', 'left'); - this.expander.on('click keypress', - Sao.common.click_press(this.toggle_row.bind(this))); - row.append(jQuery('
', { - 'class': 'expander' - }).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('', { - 'class': 'prefix' - }).css('width', 1)); - } - } - row.append(jQuery('', { - 'class': 'widget' - })); - if (column.suffixes) { - for (j = 0; j < column.suffixes.length; j++) { - var suffix = column.suffixes[j]; - row.append(jQuery('', { - 'class': 'suffix' - }).css('width', 1)); - } - } - - 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); - } - }, - _get_column_td: function(column_index, row) { - row = row || this.el; - var child_offset = 0; - if (this.tree.selection_mode != Sao.common.SELECTION_NONE) { - child_offset += 1; - } - return jQuery(row.children()[column_index + child_offset]); - }, - 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'); - } - }; - - 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 td = this._get_column_td(i); - var tr = td.find('tr'); - if (column.prefixes) { - for (var j = 0; j < column.prefixes.length; j++) { - var prefix = column.prefixes[j]; - jQuery(tr.children('.prefix')[j]) - .html(prefix.render(this.record)); - } - } - jQuery(tr.children('.widget')).html(column.render(this.record)); - if (column.suffixes) { - for (var k = 0; k < column.suffixes.length; k++) { - var suffix = column.suffixes[k]; - jQuery(tr.children('.suffix')[k]) - .html(suffix.render(this.record)); - } - } - if (column.attributes.tree_invisible || - column.header.css('display') == 'none') { - td.hide(); - } else { - td.show(); - } - } - 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 this.Class( - 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)); - }; - return this.record.load(this.children_field).done( - add_children.bind(this)); - }, - switch_row: function() { - if (window.getSelection) { - if (window.getSelection().empty) { // Chrome - window.getSelection().empty(); - } else if (window.getSelection().removeAllRanges) { // Firefox - window.getSelection().removeAllRanges(); - } - } else if (document.selection) { // IE? - document.selection.empty(); - } - if (this.tree.selection_mode != Sao.common.SELECTION_NONE) { - this.set_selection(true); - this.selection_changed(); - if (!this.is_selected()) { - return; - } - } - this.tree.switch_(this.path); - }, - select_row: function(event_) { - if (this.tree.selection_mode == Sao.common.SELECTION_NONE) { - this.tree.select_changed(this.record); - this.switch_row(); - } else { - if (!event_.ctrlKey && - this.tree.selection_mode == - Sao.common.SELECTION_SINGLE) { - this.tree.rows.forEach(function(row) { - row.set_selection(false); - }.bind(this)); - } - 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.on('click keypress', {column: column, td: td}, - Sao.common.click_press(this.select_row.bind(this), true)); - }, - 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(); - }, - record_to_path: function(record) { - // recursively get the path to the record - var i, path; - if (record == this.record) { - return []; - } else { - for (i = 0; i < this.rows.length; i++) { - path = this.rows[i].record_to_path(record); - if (path) { - path.unshift(i); - return path; - } - } - } - }, - expand_to_path: function(path) { - var row_idx, rest; - row_idx = path[0]; - rest = path.slice(1); - if (rest.length > 0) { - this.rows[row_idx].expand_children().done(function() { - this.rows[row_idx].expand_to_path(rest); - }.bind(this)); - } - }, - next_column: function(column, editable, sign) { - var i, readonly; - var column_index, state_attrs; - - sign = sign || 1; - if ((column === null) && (sign > 0)) { - column = -1; - } else if (column === null) { - column = 0; - } - column_index = 0; - for (i = 0; i < this.tree.columns.length; i++) { - column_index = ((column + (sign * (i + 1))) % - this.tree.columns.length); - // javascript modulo returns negative number for negative - // numbers - if (column_index < 0) { - column_index += this.tree.columns.length; - } - column = this.tree.columns[column_index]; - state_attrs = column.field.get_state_attrs(this.record); - if (editable) { - readonly = (column.attributes.readonly || - state_attrs.readonly); - } else { - readonly = false; - } - if (!(state_attrs.invisible || readonly)) { - break; - } - } - return column_index; - } - }); - - 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 = null; - }, - redraw: function(selected, expanded) { - var i, tr, td, widget; - var field; - - Sao.View.Tree.RowEditable._super.redraw.call(this, selected, - expanded); - // The autocompletion widget do not call display thus we have to - // call it when redrawing the row - for (i = 0; i < this.tree.columns.length; i++) { - td = this._get_column_td(i); - tr = td.find('tr'); - widget = jQuery(tr.children('.widget-editable')).data('widget'); - if (widget) { - field = this.record.model.fields[widget.field_name]; - widget.display(this.record, field); - } - } - }, - select_row: function(event_) { - var previously_selected, previous_td; - var inner_rows, readonly_row, editable_row, current_td; - var field, widget; - - function get_previously_selected(rows, selected) { - var i, r; - for (i = 0; i < rows.length; i++) { - r = rows[i]; - if (r.is_selected()) { - previously_selected = r; - } - if (r != selected) { - r.set_selection(false); - } - get_previously_selected(r.rows, selected); - } - } - get_previously_selected(this.tree.rows, this); - this.selection_changed(); - - var save_prm; - if (previously_selected && previously_selected != this && - !this.tree.screen.group.parent) { - save_prm = previously_selected.record.save(); - } else { - save_prm = jQuery.when(); - } - save_prm.done(function () { - if (previously_selected && - previously_selected.edited_column !== null) { - previous_td = previously_selected.get_active_td(); - previous_td.on('click keypress', { - td: previous_td, - column: previously_selected.edited_column - }, Sao.common.click_press( - previously_selected.select_row.bind(previously_selected), - true)); - var previous_column = this.tree.columns[ - previously_selected.edited_column]; - previously_selected.get_static_el() - .html(previous_column.render(previously_selected.record)) - .show(); - previously_selected.empty_editable_el(); - } - if (this.is_selected()) { - this.edited_column = event_.data.column; - current_td = this.get_active_td(); - var attributes = this.tree.columns[this.edited_column] - .attributes; - var EditableBuilder = Sao.View.editabletree_widget_get( - attributes.widget); - widget = new EditableBuilder(attributes.name, - this.tree.screen.model, attributes); - widget.view = this.tree; - // We have to define an overflow:visible in order for the - // completion widget to be shown - widget.el.on('focusin', function() { - jQuery(this).parents('.treeview td') - .css('overflow', 'visible'); - }); - widget.el.on('focusout', function() { - jQuery(this).parents('.treeview td') - .css('overflow', 'hidden'); - }); - var editable_el = this.get_editable_el(); - editable_el.append(widget.el); - editable_el.data('widget', widget); - // We use keydown to be able to catch TAB events - widget.el.on('keydown', this.key_press.bind(this)); - field = this.record.model.fields[widget.field_name]; - widget.display(this.record, field); - this.get_static_el().hide(); - this.get_editable_el().show(); - widget.focus(); - } else { - this.set_selection(true); - this.selection_changed(); - var td = event_.data.td; - var column = event_.data.column; - td.on('click keypress', {column: column, td: td}, - Sao.common.click_press(this.select_row.bind(this), - true)); - } - }.bind(this)); - }, - get_static_el: function() { - var td = this.get_active_td(); - return td.find('.widget'); - }, - get_editable_el: function() { - var td = this.get_active_td(); - var editable = td.find('.widget-editable'); - if (!editable.length) { - editable = jQuery('', { - 'class': 'widget-editable' - }).insertAfter(td.find('.widget')); - } - return editable; - }, - empty_editable_el: function() { - var editable; - editable = this.get_editable_el(); - editable.empty(); - editable.data('widget', null); - this.edited_column = null; - }, - get_active_td: function() { - return this._get_column_td(this.edited_column); - }, - key_press: function(event_) { - var current_td, selector, next_column, next_idx, i, next_row; - var states; - - if ((event_.which != Sao.common.TAB_KEYCODE) && - (event_.which != Sao.common.UP_KEYCODE) && - (event_.which != Sao.common.DOWN_KEYCODE) && - (event_.which != Sao.common.ESC_KEYCODE) && - (event_.which != Sao.common.RETURN_KEYCODE)) { - return; - } - var column = this.tree.columns[this.edited_column]; - if (column.field.validate(this.record)) { - if (event_.which == Sao.common.TAB_KEYCODE) { - var sign = 1; - if (event_.shiftKey) { - sign = -1; - } - event_.preventDefault(); - next_idx = this.next_column(this.edited_column, true, sign); - window.setTimeout(function() { - var td = this._get_column_td(next_idx); - td.triggerHandler('click', { - column: next_idx, - td: td - }); - }.bind(this), 0); - } else if (event_.which == Sao.common.UP_KEYCODE || - event_.which == Sao.common.DOWN_KEYCODE) { - if (event_.which == Sao.common.UP_KEYCODE) { - next_row = this.el.prev('tr'); - } else { - next_row = this.el.next('tr'); - } - next_column = this.edited_column; - this.record.validate(this.tree.get_fields()) - .then(function(validate) { - if (!validate) { - next_row = null; - var invalid_fields = - this.record.invalid_fields(); - for (i = 0; i < this.tree.columns.length; i++) { - var col = this.tree.columns[i]; - if (col.attributes.name in invalid_fields) { - next_column = i; - } - } - } else { - if (this.tree.screen.attributes.pre_validate) { - return this.record.pre_validate() - .fail(function() { - next_row = null; - }); - } else if (!this.tree.screen.model.parent) { - return this.record.save() - .fail(function() { - next_row = null; - }); - } - } - }.bind(this)).then(function() { - window.setTimeout(function() { - this._get_column_td(next_column, next_row) - .trigger('click').trigger('click'); - }.bind(this), 0); - }.bind(this)); - } else if (event_.which == Sao.common.ESC_KEYCODE) { - this.get_static_el().show(); - current_td = this.get_active_td(); - current_td.on('click keypress', - {column: this.edited_column, td: current_td}, - Sao.common.click_press(this.select_row.bind(this), - true)); - this.empty_editable_el(); - } else if (event_.which == Sao.common.RETURN_KEYCODE) { - var focus_cell = function(row) { - var td = this._get_column_td(this.edited_column, row); - td.triggerHandler('click'); - td.triggerHandler('click'); - }.bind(this); - if (this.tree.attributes.editable == 'bottom') { - next_row = this.el.next('tr'); - } else { - next_row = this.el.prev('tr'); - } - if (next_row.length) { - focus_cell(next_row); - } else { - // TODO access and size_limit - this.tree.screen.new_().done(function() { - var new_row; - var rows = this.tree.tbody.children('tr'); - if (this.tree.attributes.editable == 'bottom') { - new_row = rows.last(); - } else { - new_row = rows.first(); - } - focus_cell(new_row); - }.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('', { - 'target': '_new' - }); - cell.append(jQuery('')); - cell.click({'cell': cell}, this.clicked.bind(this)); - } else if (this.icon) { - cell = jQuery(''); - } else { - cell = jQuery(''); - cell.attr('tabindex', 0); - } - 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]; - var invisible = field.get_state_attrs(record).invisible; - if (invisible) { - cell.hide(); - } else { - cell.show(); - } - if (this.protocol) { - value = field.get(record); - if (!jQuery.isEmptyObject(value)) { - switch (this.protocol) { - case 'email': - value = 'mailto:' + value; - break; - case 'callto': - value = 'callto:' + value; - break; - case 'sip': - value = 'sip:' + value; - break; - } - } - cell.attr('src', value); - } - 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; - }, - clicked: function(event) { - event.preventDefault(); // prevent edition - window.open(event.data.cell.attr('src'), '_blank'); - } - }); - - 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 = []; - this.header = null; - }, - get_cell: function() { - var cell = jQuery('
', { - 'class': this.class_, - 'tabindex': 0 - }); - 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(); - } - }.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_, - 'tabindex': 0 - }); - }, - 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.One2OneColumn = Sao.class_(Sao.View.Tree.Many2OneColumn, { - class_: 'column-one2one' - }); - - 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_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_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); - } - }.bind(this)); - } - }); - - 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('', { - 'class': this.class_, - 'tabindex': 0 - }); - 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.URLColumn = Sao.class_(Sao.View.Tree.CharColumn, { - class_: 'column-url', - render: function(record) { - var cell = Sao.View.Tree.URLColumn._super.render.call(this, record); - this.field.set_state(record); - var state_attrs = this.field.get_state_attrs(record); - if (state_attrs.readonly) { - cell.hide(); - } else { - cell.show(); - } - } - }); - - Sao.View.Tree.ProgressBar = Sao.class_(Sao.View.Tree.CharColumn, { - class_: 'column-progressbar', - get_cell: function() { - var cell = jQuery('
', { - 'class': this.class_ + ' progress', - 'tabindex': 0 - }); - var progressbar = jQuery('
', { - 'class': 'progress-bar', - 'role': 'progressbar', - 'aria-valuemin': 0, - 'aria-valuemax': 100 - }).appendTo(cell); - progressbar.css('min-width: 2em'); - return cell; - }, - update_text: function(cell, record) { - var text = this.field.get_client(record, 100); - if (text) { - text = Sao.i18n.gettext('%1%', text); - } - var value = this.field.get(record) || 0; - var progressbar = cell.find('.progress-bar'); - progressbar.prop('aria-valuenow', value * 100); - progressbar.css('width', value + '%'); - progressbar.text(text); - } - }); - - 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; - } - var states = record.expr_eval(this.attributes.states || {}); - if (states.invisible || states.readonly) { - return; - } - this.screen.button(this.attributes); - } - }); - - Sao.View.Form = Sao.class_(Sao.View, { - editable: true, - 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 = []; - this.notebooks = []; - var root = xml.children()[0]; - var container = this.parse(screen.model, root); - this.el.append(container.el); - }, - _parse_node: function(model, child, container, attributes, labels) { - var widget; - switch (child.tagName) { - case 'image': - this._parse_image( - model, child, container, attributes); - break; - case 'separator': - this._parse_separator( - model, child, container, attributes); - break; - case 'label': - widget = this._parse_label( - model, child, container, attributes); - if (attributes.name && widget) { - labels[attributes.name] = widget; - } - 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': - widget = this._parse_field( - model, child, container, attributes); - if ((attributes.name in labels) && - widget && - widget.labelled) { - var label = labels[attributes.name]; - label.el.uniqueId(); - widget.labelled.uniqueId(); - widget.labelled.attr( - 'aria-labelledby', label.el.attr('id')); - label.el.attr('for', widget.labelled.attr('id')); - } - break; - case 'group': - this._parse_group(model, child, container, attributes); - break; - case 'hpaned': - this._parse_paned(model, child, container, attributes, - 'horizontal'); - break; - case 'vpaned': - this._parse_paned(model, child, container, attributes, - 'vertical'); - break; - case 'child': - this._parse_child(model, child, container, attributes); - break; - } - }, - parse: function(model, node, container) { - if (container === undefined) { - container = new Sao.View.Form.Container( - Number(node.getAttribute('col') || 4)); - this.containers.push(container); - } - var labels = {}; - 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]); - } - }); - this._parse_node(model, child, container, attributes, labels); - }; - jQuery(node).children().each(_parse.bind(this)); - return container; - }, - _parse_image: function(model, node, container, attributes) { - var image = new Sao.View.Form.Image_(attributes); - this.state_widgets.push(image); - container.add(attributes, image); - }, - _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; - } - } - var label; - if (text) { - label = new Sao.View.Form.Label(text, attributes); - this.state_widgets.push(label); - } - container.add(attributes, label); - return label; - }, - _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)); - }, - _parse_notebook: function(model, node, container, attributes) { - if (attributes.colspan === undefined) { - attributes.colspan = 4; - } - var notebook = new Sao.View.Form.Notebook(attributes); - this.notebooks.push(notebook); - 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) { - var field = model.fields[attributes.name]; - if (attributes.name == this.screen.exclude_field) { - return; - } - ['states', 'string'].forEach(function(attr) { - if ((attributes[attr] === undefined) && - (field.description[attr] !== undefined)) { - attributes[attr] = field.description[attr]; - } - }); - } - var page = this.parse(model, node); - page = new Sao.View.Form.Page( - container.add(page.el, attributes.string), 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', 'help', - 'relation_field', 'string', 'views', 'add_remove', 'sort', - 'context', 'size', 'filename', 'autocomplete', 'translate', - 'create', 'delete', 'selection_change_with', 'schema_model']; - 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, height, width - container.add(attributes, widget); - if (this.widgets[name] === undefined) { - this.widgets[name] = []; - } - this.widgets[name].push(widget); - this.fields[name] = true; - return widget; - }, - _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); - }, - _parse_paned: function(model, node, container, attributes, - orientation) { - if (attributes.yexpand === undefined) { - attributes.yexpand = true; - } - if (attributes.yfill === undefined) { - attributes.yfill = true; - } - var paned = new Sao.common.Paned(orientation); - // TODO position - container.add(attributes, paned); - this.parse(model, node, paned); - }, - _parse_child: function(model, node, paned, attributes) { - var container = this.parse(model, node); - var child; - if (!paned.get_child1().children().length) { - child = paned.get_child1(); - } else { - child = paned.get_child2(); - } - child.append(container.el); - }, - 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)); - } - return 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(); - } - Sao.View.resize(this.el); - }.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 []; - }, - set_cursor: function(new_, reset_view) { - var i, name, j; - var focus_el, notebook, child; - var widgets, error_el, pages, is_ancestor; - - var currently_focused = jQuery(document.activeElement); - var has_focus = currently_focused.closest(this.el) > 0; - if (reset_view || has_focus) { - if (reset_view) { - for (i = 0; i < this.notebooks.length; i++) { - notebook = this.notebooks[i]; - notebook.set_current_page(0); - } - } - if (this.attributes.cursor in this.widgets) { - focus_el = Sao.common.find_focusable_child( - this.widgets[this.attributes.cursor][0].el); - } else { - child = Sao.common.find_focusable_child(this.el); - if (child) { - child.focus(); - } - } - } - - var record = this.screen.current_record; - if (record) { - var invalid_widgets = []; - // We use the has-error class to find the invalid elements - // because Sao.common.find_focusable_child use the :visible - // selector which acts differently than GTK's get_visible - var error_els = this.el.find('.has-error'); - var invalid_fields = record.invalid_fields(); - for (name in invalid_fields) { - widgets = this.widgets[name]; - for (i = 0; i < error_els.length; i++) { - error_el = jQuery(error_els[i]); - for (j = 0; j < widgets.length; j++) { - if (error_el.closest(widgets[j].el).length > 0) { - invalid_widgets.push(error_el); - break; - } - } - } - } - if (invalid_widgets.length > 0) { - focus_el = Sao.common.find_first_focus_widget(this.el, - invalid_widgets); - } - } - - if (focus_el) { - for (i = 0; i < this.notebooks.length; i++) { - notebook = this.notebooks[i]; - pages = notebook.get_n_pages(); - for (j = 0; j < pages; j++) { - child = notebook.get_nth_page(j); - is_ancestor = ( - jQuery(focus_el).closest(child).length > 0); - if (is_ancestor) { - notebook.set_current_page(j); - break; - } - } - } - // Only input & textarea can grab the focus - jQuery(focus_el).find('input,select,textarea').focus(); - } - } - }); - - 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); - row.append(cell); - - if (!widget) { - return; - } - - // TODO yexpand - if (attributes.yfill) { - cell.css('vertical-align', 'top'); - } - - 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.css('width', '100%'); - } - } - - if (attributes.help) { - widget.el.data('toggle', 'tooltip'); - widget.el.attr('title', attributes.help); - widget.el.tooltip(); - } - }, - 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(':not(.tooltip)').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(':not(.tooltip)').css('display') != - 'none')) { - var width = 0; - for (j = 0; j < colspan; j++) { - width += widths[i + j] || 0; - } - cell.css('width', width + '%'); - } else { - cell.css('width', ''); - } - if (cell.children().css('display') == 'none') { - cell.css('visibility', 'collapse'); - } else { - cell.css('visibility', 'visible'); - } - 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('