* view should be displayed (if there is one active).
*/
searchable: false,
- readonly : false,
template: "FormView",
display_name: _lt('Form'),
/**
this.on_change_mutex = new $.Mutex();
this.reload_mutex = new $.Mutex();
this.set({"force_readonly": false});
+ this.rendering_engine = new openerp.web.FormRenderingEngine(this);
},
start: function() {
this._super();
this.fields_order = [];
this.fields_view = data;
- var form = new openerp.web.FormRenderingEngine(this, data);
- form.appendTo(this.$element.find('.oe_form_content'));
+ this.rendering_engine.set_fields_view(data);
+ this.rendering_engine.render_to(this.$element.find('.oe_form_container'));
this.$form_header = this.$element.find('.oe_form_header:first');
this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
}
// form field
if (self.fields[field]) {
- var value = self.fields[field].get_on_change_value();
+ var value = self.fields[field].get_value();
return value == null ? false : value;
}
// parent field
value = response.value[fieldname];
} else {
// otherwise get form value for field
- value = self.fields[fieldname].get_on_change_value();
+ value = self.fields[fieldname].get_value();
}
var condition = fieldname + '=' + value;
if (!first_invalid_field) {
first_invalid_field = f;
}
- } else if (f.name !== 'id' && !f.readonly && (!self.datarecord.id || f.is_dirty())) {
+ } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f.is_dirty())) {
// Special case 'id' field, do not save this field
// on 'create' : save all non readonly fields
// on 'edit' : save non readonly modified fields
// or m2m
if (!value
|| field.invisible
- || field.readonly
+ || field.get("readonly")
|| field.field.type === 'one2many'
|| field.field.type === 'many2many') {
return false;
}
});
-openerp.web.FormRenderingEngine = openerp.web.Widget.extend({
- init: function(parent, fvg, registry) {
- var self = this;
- this._super.apply(this, arguments);
+/**
+ * Interface to be implemented by rendering engines for the form view.
+ */
+openerp.web.FormRenderingEngineInterface = {
+ set_fields_view: function(fields_view) {},
+ render_to: function($element) {},
+};
+
+/**
+ * Default rendering engine for the form view.
+ *
+ * It is necessary to set the view using set_view() before usage.
+ */
+openerp.web.FormRenderingEngine = openerp.web.Class.extend({
+ init: function(view) {
+ this.view = view;
+ this.legacy_mode = false;
+ },
+ set_fields_view: function(fvg) {
this.fvg = fvg;
- this.view = parent;
- this.fields_prefix = this.view.dataset ? this.view.dataset.model : '';
+ this.legacy_mode = (this.fvg.arch.tag === 'form');
+ },
+ set_registry: function(registry) {
+ this.registry = registry;
+ },
+ render_to: function($element) {
+ var self = this;
+ this.$element = $element;
// 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 = openerp.web.json_node_to_xml(fvg.arch),
- $form = this.$form = $(xml);
+ var xml = openerp.web.json_node_to_xml(this.fvg.arch);
+ this.$form = $(xml);
- // TODO: extract embeded views before preprocessing
- _.each(['field', 'group', 'notebook', 'separator', 'label'], function(tag) {
- var fn = self['process_' + tag];
- if (registry && registry.contains(tag)) {
- fn = registry.get_object(tag);
- }
- $form.find(tag).each(function() {
- fn.call(self, $(this), $form);
- });
- });
- },
- start: function() {
- var self = this;
- this._super.apply(this, arguments);
- this.$form.children().appendTo(this.$element);
+ this.process(this.$form);
+
+ this.$form.appendTo(this.$element);
// OpenERP views spec :
// - @width is obsolete ?
- // TODO: modifiers invisible. Add a special attribute, eg: data-invisible that should be used in order to create openerp.form.InvisibleWidgetG
this.$element.find('field, button').each(function() {
var $elem = $(this),
if (self.view.registry.contains(key)) {
var obj = self.view.registry.get_object(key);
var w = new (obj)(self.view, openerp.web.xml_to_json($elem[0]));
+ self.alter_field(w);
w.replace($elem);
}
});
+ $('<button>Debug layout</button>').appendTo(this.$element).click($.proxy(this.toggle_layout_debugging, this));
+ },
+ render_element: function(template, dict) {
+ dict = dict || {};
+ dict.legacy_mode = this.legacy_mode;
+ return $(QWeb.render(template, dict));
+ },
+ alter_field: function(field) {},
+ toggle_layout_debugging: function() {
+ if (!this.$element.has('.oe_layout_debug_cell:first').length) {
+ this.$element.find('.oe_form_group_cell').each(function() {
+ var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan'),
+ $span = $('<span class="oe_layout_debug_cell"/>').text(text);
+ $span.prependTo($(this));
+ });
+ }
+ this.$element.toggleClass('oe_layout_debugging');
+
+ },
+ process: function($tag) {
+ var self = this;
+ var tagname = $tag[0].nodeName.toLowerCase();
+ var fn = self['process_' + tagname];
+ if (this.registry && this.registry.contains(tagname)) {
+ fn = this.registry.get_object(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));
+ });
+ return $tag;
+ }
},
- process_field: function($field, $form) {
+ process_form: function($form) {
+ var $new_form = this.render_element('FormRenderingForm', $form.getAttributes());
+ $dst = this.legacy_mode ? $new_form.find('group:first') : $new_form;
+ $form.children().appendTo($dst);
+ 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);
+ },
+ preprocess_field: function($field) {
var name = $field.attr('name'),
field_orm = this.fvg.fields[name],
field_string = $field.attr('string') || field_orm.string || '',
field_help = $field.attr('help') || field_orm.help || '',
field_colspan = parseInt($field.attr('colspan'), 10);
-
- if (!field_orm) {
- throw new Error("Field '" + name + "' specified in view could not be found.");
- }
-
if ($field.attr('nolabel') !== '1') {
- var $label = $form.find('label[for="' + name + '"]');
+ $field.attr('nolabel', '1');
+ var $label = this.$form.find('label[for="' + name + '"]');
if (!$label.length) {
field_string = $label.attr('string') || $label.text() || field_string;
field_help = $label.attr('help') || field_help;
- $('<label/>').attr({
+ $label = $('<label/>').attr({
'for' : name,
'string' : field_string,
'help' : field_help
- }).insertBefore($field).text();
+ });
+ $label.insertBefore($field);
if (field_colspan > 1) {
$field.attr('colspan', field_colspan - 1);
}
}
+ return $label;
}
+ },
+ process_field: function($field) {
+ var $label = this.preprocess_field($field);
+ if ($label)
+ this.process($label);
+ var name = $field.attr('name'),
+ field_orm = this.fvg.fields[name],
+ field_string = $field.attr('string') || field_orm.string || '',
+ field_help = $field.attr('help') || field_orm.help || '',
+ field_colspan = parseInt($field.attr('colspan'), 10);
+
+ if (!field_orm) {
+ throw new Error("Field '" + name + "' specified in view could not be found.");
+ }
+
$field.attr({
'widget' : $field.attr('widget') || field_orm.type,
'string' : field_string,
'help' : field_help
});
+ return $field;
},
- process_group: function($group, $form) {
- var self = this,
- $new_group = $(QWeb.render('FormRenderingGroup', $group.getAttributes())),
+ process_group: function($group) {
+ var self = this;
+ $group.children('field').each(function() {
+ self.preprocess_field($(this));
+ });
+ var $new_group = $(QWeb.render('FormRenderingGroup', $group.getAttributes())),
$table;
if ($new_group.is('table')) {
$table = $new_group;
$table = $new_group.find('table:first');
}
$table.addClass('oe_form_group');
- var $tr,
+ var $tr, $td,
cols = parseInt($group.attr('col') || 4, 10),
row_cols = cols;
- $group.children().each(function() {
+ var children = [];
+ $group.children().each(function(a,b,c) {
var $child = $(this),
colspan = parseInt($child.attr('colspan') || 1, 10),
tagName = $child[0].tagName.toLowerCase();
row_cols = cols;
}
row_cols -= colspan;
- var $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
+ $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
$tr.append($td.append($child));
+ children.push($child[0]);
});
+ if (row_cols) {
+ $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols);
+ }
$group.before($new_group).remove();
// Now compute width of cells
$table.find('tbody > tr').each(function() {
var to_compute = [],
+ row_cols = cols,
total = 100;
$(this).children().each(function() {
var $td = $(this),
if ($child.attr('orientation') === 'vertical') {
$td.addClass('oe_vertical_separator').attr('width', '1');
$td.empty();
- cols--;
+ row_cols--;
}
break;
case 'label':
if ($child.attr('for')) {
$td.attr('width', '1%');
- cols--;
+ row_cols--;
total--;
}
break;
to_compute.push($td);
}
});
- var unit = Math.floor(total / cols);
+ var unit = Math.floor(total / row_cols);
_.each(to_compute, function($td, i) {
var width = parseInt($td.attr('colspan'), 10) * unit;
$td.attr('width', ((i == to_compute.length - 1) ? total : width) + '%');
total -= width;
});
});
+ _.each(children, function(el) {
+ self.process($(el));
+ });
+ return $new_group;
},
- process_notebook: function($notebook, $form) {
+ process_notebook: function($notebook) {
+ var self = this;
var pages = [];
$notebook.find('> page').each(function() {
var $page = $(this),
page_attrs = $page.getAttributes();
page_attrs.id = _.uniqueId('notebook_page_');
pages.push(page_attrs);
- var $new_page = $(QWeb.render('FormRenderingNotebookPage', page_attrs));
- $page.children().appendTo($new_page);
+ var $new_page = self.render_element('FormRenderingNotebookPage', page_attrs),
+ $dst = self.legacy_mode ? $new_page.find('group:first') : $new_page;
+ $page.children().appendTo($dst);
$page.before($new_page).remove();
});
var $new_notebook = $(QWeb.render('FormRenderingNotebook', { pages : pages }));
$notebook.children().appendTo($new_notebook);
$notebook.before($new_notebook).remove();
+ $new_notebook.children().each(function() {
+ self.process($(this));
+ });
$new_notebook.tabs();
+ return $new_notebook;
},
- process_separator: function($separator, $form) {
+ process_separator: function($separator) {
var $new_separator = $(QWeb.render('FormRenderingSeparator', $separator.getAttributes()));
$separator.before($new_separator).remove();
+ return $new_separator;
},
- process_label: function($label, $form) {
+ process_label: function($label) {
var dict = $label.getAttributes();
var align = parseFloat(dict.align);
if (isNaN(align) || align === 1) {
dict.align = align;
var $new_label = $(QWeb.render('FormRenderingLabel', dict));
$label.before($new_label).remove();
+ return $new_label;
}
});
},
process_modifiers: function() {
var compute_domain = openerp.web.form.compute_domain;
+ var to_set = {};
for (var a in this.modifiers) {
- this[a] = compute_domain(this.modifiers[a], this.view.fields);
+ var val = compute_domain(this.modifiers[a], this.view.fields);
+ this[a] = val;
+ to_set[a] = val;
}
+ this.set(to_set);
},
update_dom: function() {
this.$element.toggle(!this.invisible);
this.check_disable();
},
check_disable: function() {
- var disabled = (this.readonly || this.force_disabled || !this.view.is_interactible_record());
+ var disabled = (this.force_disabled || !this.view.is_interactible_record());
this.$element.prop('disabled', disabled);
this.$element.css('color', disabled ? 'grey' : '');
}
* able to provide the features necessary for the fields to work.
*
* Properties:
- * - force_readonly: boolean, When it is true, all the fields should always appear
- * in read only mode, no matter what the value of their "readonly" property can be.
+ * - ...
*/
openerp.web.form.FieldManagerInterface = {
*
* Properties:
* - readonly: boolean. If set to true the field should appear in readonly mode.
- *
+ * - force_readonly: boolean, When it is true, the field should always appear
+ * in read only mode, no matter what the value of the "readonly" property can be.
* Events:
* - ...
*
init: function(field_manager, node) {
this._super(field_manager, node);
this.name = this.node.attrs.name;
- this.value = undefined;
+ this.value = false;
this.view.fields[this.name] = this;
this.view.fields_order.push(this.name);
this.type = this.node.attrs.widget;
this.required = this.modifiers['required'] === true;
this.invalid = this.dirty = false;
- // because I'm lazy to refactor right now
- this.on("change:readonly", this, function() {this.readonly = this.get("readonly");});
-
// some events to make the property "effective_readonly" sync automatically with "readonly" and
// "force_readonly"
this.set({"readonly": this.modifiers['readonly'] === true});
var test_effective_readonly = function() {
- this.set({"effective_readonly": this.get("readonly") || this.view.get("force_readonly")});
+ this.set({"effective_readonly": this.get("readonly") || this.get("force_readonly")});
};
this.on("change:readonly", this, test_effective_readonly);
- this.view.on("change:force_readonly", this, test_effective_readonly);
+ this.on("change:force_readonly", this, test_effective_readonly);
_.bind(test_effective_readonly, this)();
+ // TODO: do something good about this
if (this.view) {
this.$label = this.view.$element.find('label[for="' + this.name + '"]');
if (this.$label.length) {
return !this.invalid;
},
is_dirty: function() {
- return this.dirty && !this.readonly;
- },
- get_on_change_value: function() {
- return this.get_value();
+ return this.dirty && !this.get("effective_readonly");
},
update_dom: function(show_invalid) {
this._super.apply(this, arguments);
this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id);
}
if (!this.disable_utility_classes) {
- this.$element.toggleClass('disabled', this.readonly);
+ this.$element.toggleClass('disabled', this.get("effective_readonly"));
this.$element.toggleClass('required', this.required);
if (show_invalid) {
this.$element.toggleClass('invalid', !this.is_valid());
}
}
- // one more shit code to avoid refactoring this.readonly right now
- this.set({"readonly": this.readonly});
},
on_ui_change: function() {
this.dirty = true;
this.render_value();
});
this.initialize_content();
+ this.render_value();
},
/**
* Called to destroy anything that could have been created previously, called before a
openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
init: function (view, node) {
this._super(view, node);
+ this.value = 0;
if (this.node.attrs.digits) {
- this.parse_digits(this.node.attrs.digits);
+ this.digits = py.eval(this.node.attrs.digits).toJSON();
} else {
this.digits = this.field.digits;
}
},
- parse_digits: function (digits_attr) {
- // could use a Python parser instead.
- var match = /^\s*[\(\[](\d+),\s*(\d+)/.exec(digits_attr);
- return [parseInt(match[1], 10), parseInt(match[2], 10)];
- },
set_value: function(value) {
if (value === false || value === undefined) {
// As in GTK client, floats default to 0
showButtonPanel: true
});
this.$element.find('img.oe_datepicker_trigger').click(function() {
- if (!self.readonly && !self.picker('widget').is(':visible')) {
+ if (!self.get("effective_readonly") && !self.picker('widget').is(':visible')) {
self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date());
self.$input_picker.show();
self.picker('show');
this._super.apply(this, arguments);
this.$element.progressbar({
value: this.value,
- disabled: this.readonly
+ disabled: this.get("effective_readonly")
});
},
set_value: function(value) {
this.new_object();
}
},
+ stop: function () {
+ this.$element.dialog('close');
+ this._super();
+ },
setup_search_view: function(search_defaults) {
var self = this;
if (this.searchview) {
template: 'FieldBinaryImage',
initialize_content: function() {
this._super();
- this.$image = this.$element.find('img.oe-binary-image');
+ this.$placeholder = $(".oe_form_field-binary-image-placeholder", this.$element);
if (!this.get("effective_readonly"))
this.$element.find('.oe-binary').show();
else
this.render_value();
},
render_value: function() {
- this.set_image_maxwidth();
-
var url;
if (this.value && this.value.substr(0, 10).indexOf(' ') == -1) {
url = 'data:image/png;base64,' + this.value;
- } else {
+ } else if (this.value) {
url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
+ } else {
+ url = "/web/static/src/img/placeholder.png";
}
- this.$image.attr('src', url);
- },
- set_image_maxwidth: function() {
- this.$image.css('max-width', this.$element.width());
+ var rendered = QWeb.render("FieldBinaryImage-img", {widget: this, url: url});;
+ this.$placeholder.html(rendered);
},
on_file_change: function() {
- this.set_image_maxwidth();
+ this.render_value();
this._super.apply(this, arguments);
},
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
this.value = file_base64;
this.binary_value = true;
- this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64);
+ this.render_value();
},
on_clear: function() {
this._super.apply(this, arguments);
- this.$image.attr('src', '/web/static/src/img/placeholder.png');
+ this.render_value();
}
});