+ },
+ register_field: function(field, name) {
+ this.fields[name] = field;
+ this.fields_order.push(name);
+
+ field.on('focused', null, this.proxy('widgetFocused'))
+ .on('blurred', null, this.proxy('widgetBlurred'));
+ if (this.get_field(name).translate) {
+ this.translatable_fields.push(field);
+ }
+ field.on('changed_value', this, function() {
+ field._dirty_flag = true;
+ if (field.is_syntax_valid()) {
+ this.do_onchange(field);
+ this.on_form_changed(true);
+ this.do_notify_change();
+ }
+ });
+ },
+ get_field: function(field_name) {
+ return this.fields_view.fields[field_name];
+ },
+ is_create_mode: function() {
+ return !this.datarecord.id;
+ },
+});
+
+/**
+ * Interface to be implemented by rendering engines for the form view.
+ */
+instance.web.form.FormRenderingEngineInterface = instance.web.Class.extend({
+ set_fields_view: function(fields_view) {},
+ set_fields_registry: function(fields_registry) {},
+ render_to: function($element) {},
+});
+
+/**
+ * Default rendering engine for the form view.
+ *
+ * It is necessary to set the view using set_view() before usage.
+ */
+instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({
+ init: function(view) {
+ this.view = view;
+ },
+ set_fields_view: function(fvg) {
+ this.fvg = fvg;
+ this.version = parseFloat(this.fvg.arch.attrs.version);
+ if (isNaN(this.version)) {
+ this.version = 6.1;
+ }
+ },
+ set_tags_registry: function(tags_registry) {
+ this.tags_registry = tags_registry;
+ },
+ set_fields_registry: function(fields_registry) {
+ this.fields_registry = fields_registry;
+ },
+ // Backward compatibility tools, current default version: v6.1
+ process_version: function() {
+ if (this.version < 7.0) {
+ this.$form.find('form:first').wrapInner('<group col="4"/>');
+ this.$form.find('page').each(function() {
+ if (!$(this).parents('field').length) {
+ $(this).wrapInner('<group col="4"/>');
+ }
+ });
+ }
+ selector = 'form[version!="7.0"] page,form[version!="7.0"]';
+ },
+ render_to: function($target) {
+ var self = this;
+ this.$target = $target;
+
+ // TODO: I know this will save the world and all the kitten for a moment,
+ // but one day, we will have to get rid of xml2json
+ var xml = instance.web.json_node_to_xml(this.fvg.arch);
+ this.$form = $('<div class="oe_form">' + xml + '</div>');
+
+ this.process_version();
+
+ this.fields_to_init = [];
+ this.tags_to_init = [];
+ this.labels = {};
+ this.process(this.$form);
+
+ this.$form.appendTo(this.$target);
+
+ _.each(this.fields_to_init, function($elem) {
+ var name = $elem.attr("name");
+ if (!self.fvg.fields[name]) {
+ throw new Error("Field '" + name + "' specified in view could not be found.");
+ }
+ var obj = self.fields_registry.get_any([$elem.attr('widget'), self.fvg.fields[name].type]);
+ if (!obj) {
+ throw new Error("Widget type '"+ $elem.attr('widget') + "' is not implemented");
+ }
+ var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
+ var $label = self.labels[$elem.attr("name")];
+ if ($label) {
+ w.set_input_id($label.attr("for"));
+ }
+ self.alter_field(w);
+ self.view.register_field(w, $elem.attr("name"));
+ w.replace($elem);
+ });
+ _.each(this.tags_to_init, function($elem) {
+ var tag_name = $elem[0].tagName.toLowerCase();
+ var obj = self.tags_registry.get_object(tag_name);
+ var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
+ w.replace($elem);
+ });
+ // TODO: return a deferred
+ },
+ render_element: function(template /* dictionaries */) {
+ var dicts = [].slice.call(arguments).slice(1);
+ var dict = _.extend.apply(_, dicts);
+ dict['classnames'] = dict['class'] || ''; // class is a reserved word and might caused problem to Safari when used from QWeb
+ return $(QWeb.render(template, dict));
+ },
+ alter_field: function(field) {
+ },
+ toggle_layout_debugging: function() {
+ if (!this.$target.has('.oe_layout_debug_cell:first').length) {
+ this.$target.find('[title]').removeAttr('title');
+ this.$target.find('.oe_form_group_cell').each(function() {
+ var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan');
+ $(this).attr('title', text);
+ });
+ }
+ this.$target.toggleClass('oe_layout_debugging');
+ },
+ process: function($tag) {
+ var self = this;
+ var tagname = $tag[0].nodeName.toLowerCase();
+ if (this.tags_registry.contains(tagname)) {
+ this.tags_to_init.push($tag);
+ return $tag;
+ }
+ var fn = self['process_' + tagname];
+ if (fn) {
+ var args = [].slice.call(arguments);
+ args[0] = $tag;
+ return fn.apply(self, args);
+ } else {
+ // generic tag handling, just process children
+ $tag.children().each(function() {
+ self.process($(this));
+ });
+ self.handle_common_properties($tag, $tag);
+ $tag.removeAttr("modifiers");
+ return $tag;
+ }
+ },
+ process_sheet: function($sheet) {
+ var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes());
+ this.handle_common_properties($new_sheet, $sheet);
+ var $dst = $new_sheet.find('.oe_form_sheet');
+ $sheet.contents().appendTo($dst);
+ $sheet.before($new_sheet).remove();
+ this.process($new_sheet);
+ },
+ process_form: function($form) {
+ if ($form.find('> sheet').length === 0) {
+ $form.addClass('oe_form_nosheet');
+ }
+ var $new_form = this.render_element('FormRenderingForm', $form.getAttributes());
+ this.handle_common_properties($new_form, $form);
+ $form.contents().appendTo($new_form);
+ if ($form[0] === this.$form[0]) {
+ // If root element, replace it
+ this.$form = $new_form;
+ } else {
+ $form.before($new_form).remove();
+ }
+ this.process($new_form);
+ },
+ /*
+ * Used by direct <field> children of a <group> tag only
+ * This method will add the implicit <label...> for every field
+ * in the <group>
+ */
+ preprocess_field: function($field) {
+ var self = this;
+ var name = $field.attr('name'),
+ field_colspan = parseInt($field.attr('colspan'), 10),
+ field_modifiers = JSON.parse($field.attr('modifiers') || '{}');
+
+ if ($field.attr('nolabel') === '1')
+ return;
+ $field.attr('nolabel', '1');
+ var found = false;
+ this.$form.find('label[for="' + name + '"]').each(function(i ,el) {
+ $(el).parents().each(function(unused, tag) {
+ var name = tag.tagName.toLowerCase();
+ if (name === "field" || name in self.tags_registry.map)
+ found = true;
+ });
+ });
+ if (found)
+ return;
+
+ $label = $('<label/>').attr({
+ 'for' : name,
+ "modifiers": JSON.stringify({invisible: field_modifiers.invisible}),
+ "string": $field.attr('string'),
+ "help": $field.attr('help'),
+ "class": $field.attr('class'),
+ });
+ $label.insertBefore($field);
+ if (field_colspan > 1) {
+ $field.attr('colspan', field_colspan - 1);
+ }
+ return $label;
+ },
+ process_field: function($field) {
+ if ($field.parent().is('group')) {
+ // No implicit labels for normal fields, only for <group> direct children
+ var $label = this.preprocess_field($field);
+ if ($label) {
+ this.process($label);
+ }
+ }
+ this.fields_to_init.push($field);
+ return $field;
+ },
+ process_group: function($group) {
+ var self = this;
+ $group.children('field').each(function() {
+ self.preprocess_field($(this));
+ });
+ var $new_group = this.render_element('FormRenderingGroup', $group.getAttributes());
+ var $table;
+ if ($new_group.first().is('table.oe_form_group')) {
+ $table = $new_group;
+ } else if ($new_group.filter('table.oe_form_group').length) {
+ $table = $new_group.filter('table.oe_form_group').first();
+ } else {
+ $table = $new_group.find('table.oe_form_group').first();
+ }
+
+ var $tr, $td,
+ cols = parseInt($group.attr('col') || 2, 10),
+ row_cols = cols;
+
+ var children = [];
+ $group.children().each(function(a,b,c) {
+ var $child = $(this);
+ var colspan = parseInt($child.attr('colspan') || 1, 10);
+ var tagName = $child[0].tagName.toLowerCase();
+ var $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
+ var newline = tagName === 'newline';
+
+ // Note FME: those classes are used in layout debug mode
+ if ($tr && row_cols > 0 && (newline || row_cols < colspan)) {
+ $tr.addClass('oe_form_group_row_incomplete');
+ if (newline) {
+ $tr.addClass('oe_form_group_row_newline');
+ }
+ }
+ if (newline) {
+ $tr = null;
+ return;
+ }
+ if (!$tr || row_cols < colspan) {
+ $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table);
+ row_cols = cols;
+ }
+ row_cols -= colspan;
+
+ // invisibility transfer
+ var field_modifiers = JSON.parse($child.attr('modifiers') || '{}');
+ var invisible = field_modifiers.invisible;
+ self.handle_common_properties($td, $("<dummy>").attr("modifiers", JSON.stringify({invisible: invisible})));
+
+ $tr.append($td.append($child));
+ children.push($child[0]);
+ });
+ if (row_cols && $td) {
+ $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols);
+ }
+ $group.before($new_group).remove();
+
+ $table.find('> tbody > tr').each(function() {
+ var to_compute = [],
+ row_cols = cols,
+ total = 100;
+ $(this).children().each(function() {
+ var $td = $(this),
+ $child = $td.children(':first');
+ switch ($child[0].tagName.toLowerCase()) {
+ case 'separator':
+ if ($child.attr('orientation') === 'vertical') {
+ $td.addClass('oe_vertical_separator').attr('width', '1');
+ $td.empty();
+ row_cols-= $td.attr('colspan') || 1;
+ total--;
+ }
+ break;
+ case 'label':
+ if ($child.attr('for')) {
+ $td.attr('width', '1%').addClass('oe_form_group_cell_label');
+ row_cols-= $td.attr('colspan') || 1;
+ total--;
+ }
+ break;
+ default:
+ var width = _.str.trim($child.attr('width') || ''),
+ iwidth = parseInt(width, 10);
+ if (iwidth) {
+ if (width.substr(-1) === '%') {
+ total -= iwidth;
+ width = iwidth + '%';
+ } else {
+ // Absolute width
+ $td.css('min-width', width + 'px');
+ }
+ $td.attr('width', width);
+ $child.removeAttr('width');
+ row_cols-= $td.attr('colspan') || 1;
+ } else {
+ to_compute.push($td);
+ }
+
+ }
+ });
+ if (row_cols) {
+ var unit = Math.floor(total / row_cols);
+ if (!$(this).is('.oe_form_group_row_incomplete')) {
+ _.each(to_compute, function($td, i) {
+ var width = parseInt($td.attr('colspan'), 10) * unit;
+ $td.attr('width', width + '%');
+ total -= width;
+ });
+ }
+ }
+ });
+ _.each(children, function(el) {
+ self.process($(el));
+ });
+ this.handle_common_properties($new_group, $group);
+ return $new_group;
+ },
+ process_notebook: function($notebook) {
+ var self = this;
+ var pages = [];
+ $notebook.find('> page').each(function() {
+ var $page = $(this);
+ var page_attrs = $page.getAttributes();
+ page_attrs.id = _.uniqueId('notebook_page_');
+ var $new_page = self.render_element('FormRenderingNotebookPage', page_attrs);
+ $page.contents().appendTo($new_page);
+ $page.before($new_page).remove();
+ var ic = self.handle_common_properties($new_page, $page).invisibility_changer;
+ page_attrs.__page = $new_page;
+ page_attrs.__ic = ic;
+ pages.push(page_attrs);
+
+ $new_page.children().each(function() {
+ self.process($(this));
+ });
+ });
+ var $new_notebook = this.render_element('FormRenderingNotebook', { pages : pages });
+ $notebook.contents().appendTo($new_notebook);
+ $notebook.before($new_notebook).remove();
+ self.process($($new_notebook.children()[0]));
+ //tabs and invisibility handling
+ $new_notebook.tabs();
+ _.each(pages, function(page, i) {
+ if (! page.__ic)
+ return;
+ page.__ic.on("change:effective_invisible", null, function() {
+ var current = $new_notebook.tabs("option", "selected");
+ if (! pages[current].__ic || ! pages[current].__ic.get("effective_invisible"))
+ return;
+ var first_visible = _.find(_.range(pages.length), function(i2) {
+ return (! pages[i2].__ic) || (! pages[i2].__ic.get("effective_invisible"));
+ });
+ if (first_visible !== undefined) {
+ $new_notebook.tabs('select', first_visible);
+ }
+ });
+ });
+
+ this.handle_common_properties($new_notebook, $notebook);
+ return $new_notebook;
+ },
+ process_separator: function($separator) {
+ var $new_separator = this.render_element('FormRenderingSeparator', $separator.getAttributes());
+ $separator.before($new_separator).remove();
+ this.handle_common_properties($new_separator, $separator);
+ return $new_separator;
+ },
+ process_label: function($label) {
+ var name = $label.attr("for"),
+ field_orm = this.fvg.fields[name];
+ var dict = {
+ string: $label.attr('string') || (field_orm || {}).string || '',
+ help: $label.attr('help') || (field_orm || {}).help || '',
+ _for: name ? _.uniqueId('oe-field-input-') : undefined,
+ };
+ var align = parseFloat(dict.align);
+ if (isNaN(align) || align === 1) {
+ align = 'right';
+ } else if (align === 0) {
+ align = 'left';
+ } else {
+ align = 'center';
+ }
+ dict.align = align;
+ var $new_label = this.render_element('FormRenderingLabel', dict);
+ $label.before($new_label).remove();
+ this.handle_common_properties($new_label, $label);
+ if (name) {
+ this.labels[name] = $new_label;
+ }
+ return $new_label;
+ },
+ handle_common_properties: function($new_element, $node) {
+ var str_modifiers = $node.attr("modifiers") || "{}"
+ var modifiers = JSON.parse(str_modifiers);
+ var ic = null;
+ if (modifiers.invisible !== undefined)
+ ic = new instance.web.form.InvisibilityChanger(this.view, this.view, modifiers.invisible, $new_element);
+ $new_element.addClass($node.attr("class") || "");
+ $new_element.attr('style', $node.attr('style'));
+ return {invisibility_changer: ic,};
+ },