Gives new values for the fields contained in the view. The new values could not be setted
right after the call to this method. Setting new values can trigger on_changes.
- @param (dict) values A dictonnary with key = field name and value = new value.
- @return (Deferred) Is resolved after all the values are setted.
+ @param {Object} values A dictonary with key = field name and value = new value.
+ @return {$.Deferred} Is resolved after all the values are setted.
*/
set_values: function(values) {},
/**
Computes an OpenERP domain.
- @param (list) expression An OpenERP domain.
- @return (boolean) The computed value of the domain.
+ @param {Array} expression An OpenERP domain.
+ @return {boolean} The computed value of the domain.
*/
compute_domain: function(expression) {},
/**
the field are only supposed to use this context to evualuate their own, they should not
extend it.
- @return (CompoundContext) An OpenERP context.
+ @return {CompoundContext} An OpenERP context.
*/
build_eval_context: function() {},
};
* @param {instance.web.Session} session the current openerp session
* @param {instance.web.DataSet} dataset the dataset this view will work with
* @param {String} view_id the identifier of the OpenERP view object
- * @param {Object} options
- * - resize_textareas : [true|false|max_height]
*
* @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
*/
this.fields = {};
this.fields_order = [];
this.datarecord = {};
+ this._onchange_specs = {};
+ this.onchanges_mutex = new $.Mutex();
this.default_focus_field = null;
this.default_focus_button = null;
this.fields_registry = instance.web.form.widgets;
});
this.is_initialized = $.Deferred();
this.mutating_mutex = new $.Mutex();
- this.on_change_list = [];
this.save_list = [];
+ this.render_value_defs = [];
this.reload_mutex = new $.Mutex();
this.__clicked_inside = false;
this.__blur_timeout = null;
this.rendering_engine = new instance.web.form.FormRenderingEngine(this);
self.set({actual_mode: self.options.initial_mode});
this.has_been_loaded.done(function() {
+ self._build_onchange_specs();
self.on("change:actual_mode", self, self.check_actual_mode);
self.check_actual_mode();
self.on("change:actual_mode", self, self.init_pager);
this.dataset.ids.push(state.id);
}
this.dataset.select_id(state.id);
- this.do_show({ reload: warm });
+ this.do_show();
}
},
/**
opacity: '1',
filter: 'alpha(opacity = 100)'
});
+ instance.web.bus.trigger('form_view_shown', self);
});
},
do_hide: function () {
});
return $.when.apply(null, set_values).then(function() {
if (!record.id) {
- // New record: Second pass in order to trigger the onchanges
- // respecting the fields order defined in the view
- _.each(self.fields_order, function(field_name) {
- if (record[field_name] !== undefined) {
- var field = self.fields[field_name];
- field._dirty_flag = true;
- self.do_onchange(field);
- }
- });
+ // trigger onchanges
+ self.do_onchange(null);
}
self.on_form_changed();
self.rendering_engine.init_fields();
this.$pager.remove();
if (this.get("actual_mode") === "create")
return;
- this.$pager = $(QWeb.render("FormView.pager", {'widget':self})).hide();
+ this.$pager = $(QWeb.render("FormView.pager", {'widget':self}));
if (this.options.$pager) {
this.$pager.appendTo(this.options.$pager);
} else {
$(".oe_form_pager_state", this.$pager).html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length));
}
},
- parse_on_change: function (on_change, widget) {
- var self = this;
- var onchange = _.str.trim(on_change);
- var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
- if (!call) {
- throw new Error(_.str.sprintf( _t("Wrong on change format: %s"), onchange ));
- }
-
- var method = call[1];
- if (!_.str.trim(call[2])) {
- return {method: method, args: []};
- }
- var argument_replacement = {
- 'False': function () {return false;},
- 'True': function () {return true;},
- 'None': function () {return null;},
- 'context': function () {
- return new instance.web.CompoundContext(
- self.dataset.get_context(),
- widget.build_context() ? widget.build_context() : {});
- }
- };
- var parent_fields = null;
- var args = _.map(call[2].split(','), function (a, i) {
- var field = _.str.trim(a);
-
- // literal constant or context
- if (field in argument_replacement) {
- return argument_replacement[field]();
- }
- // literal number
- if (/^-?\d+(\.\d+)?$/.test(field)) {
- return Number(field);
- }
- // form field
- if (self.fields[field]) {
- var value_ = self.fields[field].get_value();
- return value_ === null || value_ === undefined ? false : value_;
- }
- // parent field
- var splitted = field.split('.');
- if (splitted.length > 1 && _.str.trim(splitted[0]) === "parent" && self.dataset.parent_view) {
- if (parent_fields === null) {
- parent_fields = self.dataset.parent_view.get_fields_values();
+ _build_onchange_specs: function() {
+ var self = this;
+ var find = function(field_name, root) {
+ var fields = [root];
+ while (fields.length) {
+ var node = fields.pop();
+ if (!node) {
+ continue;
}
- var p_val = parent_fields[_.str.trim(splitted[1])];
- if (p_val !== undefined) {
- return p_val === null || p_val === undefined ? false : p_val;
+ if (node.tag === 'field' && node.attrs.name === field_name) {
+ return node.attrs.on_change || "";
}
+ fields = _.union(fields, node.children);
}
- // string literal
- var first_char = field[0], last_char = field[field.length-1];
- if ((first_char === '"' && last_char === '"')
- || (first_char === "'" && last_char === "'")) {
- return field.slice(1, -1);
- }
+ return "";
+ };
- throw new Error("Could not get field with name '" + field +
- "' for onchange '" + onchange + "'");
+ self._onchange_specs = {};
+ _.each(this.fields, function(field, name) {
+ self._onchange_specs[name] = find(name, field.node);
+ _.each(field.field.views, function(view) {
+ _.each(view.fields, function(_, subname) {
+ self._onchange_specs[name + '.' + subname] = find(subname, view.arch);
+ });
+ });
});
-
- return {
- method: method,
- args: args
- };
},
- do_onchange: function(widget, processed) {
- var self = this;
- this.on_change_list = [{widget: widget, processed: processed}].concat(this.on_change_list);
- return this._process_operations();
+ _get_onchange_values: function() {
+ var field_values = this.get_fields_values();
+ if (field_values.id.toString().match(instance.web.BufferedDataSet.virtual_id_regex)) {
+ delete field_values.id;
+ }
+ if (this.dataset.parent_view) {
+ // this belongs to a parent view: add parent field if possible
+ var parent_view = this.dataset.parent_view;
+ var child_name = this.dataset.child_name;
+ var parent_name = parent_view.get_field_desc(child_name).relation_field;
+ if (parent_name) {
+ // consider all fields except the inverse of the parent field
+ var parent_values = parent_view.get_fields_values();
+ delete parent_values[child_name];
+ field_values[parent_name] = parent_values;
+ }
+ }
+ return field_values;
},
- _process_onchange: function(on_change_obj) {
+
+ do_onchange: function(widget) {
var self = this;
- var widget = on_change_obj.widget;
- var processed = on_change_obj.processed;
+ var onchange_specs = self._onchange_specs;
try {
- var def;
- processed = processed || [];
- processed.push(widget.name);
- var on_change = widget.node.attrs.on_change;
- if (on_change) {
- var change_spec = self.parse_on_change(on_change, widget);
- var ids = [];
+ var def = $.when({});
+ var change_spec = widget ? onchange_specs[widget.name] : null;
+ if (!widget || (!_.isEmpty(change_spec) && change_spec !== "0")) {
+ var ids = [],
+ trigger_field_name = widget ? widget.name : false,
+ values = self._get_onchange_values(),
+ context = new instance.web.CompoundContext(self.dataset.get_context());
+
+ if (widget && widget.build_context()) {
+ context.add(widget.build_context());
+ }
+ if (self.dataset.parent_view) {
+ var parent_name = self.dataset.parent_view.get_field_desc(self.dataset.child_name).relation_field;
+ context.add({field_parent: parent_name});
+ }
+
if (self.datarecord.id && !instance.web.BufferedDataSet.virtual_id_regex.test(self.datarecord.id)) {
// In case of a o2m virtual id, we should pass an empty ids list
ids.push(self.datarecord.id);
}
def = self.alive(new instance.web.Model(self.dataset.model).call(
- change_spec.method, [ids].concat(change_spec.args)));
- } else {
- def = $.when({});
+ "onchange", [ids, values, trigger_field_name, onchange_specs, context]));
}
- return def.then(function(response) {
- if (widget.field['change_default']) {
- var fieldname = widget.name;
- var value_;
- if (response.value && (fieldname in response.value)) {
- // Use value from onchange if onchange executed
- value_ = response.value[fieldname];
- } else {
- // otherwise get form value for field
- value_ = self.fields[fieldname].get_value();
- }
- var condition = fieldname + '=' + value_;
-
- if (value_) {
- return self.alive(new instance.web.Model('ir.values').call(
- 'get_defaults', [self.model, condition]
- )).then(function (results) {
- if (!results.length) {
+ this.onchanges_mutex.exec(function(){
+ return def.then(function(response) {
+ if (widget && widget.field['change_default']) {
+ var fieldname = widget.name;
+ var value_;
+ if (response.value && (fieldname in response.value)) {
+ // Use value from onchange if onchange executed
+ value_ = response.value[fieldname];
+ } else {
+ // otherwise get form value for field
+ value_ = self.fields[fieldname].get_value();
+ }
+ var condition = fieldname + '=' + value_;
+
+ if (value_) {
+ return self.alive(new instance.web.Model('ir.values').call(
+ 'get_defaults', [self.model, condition]
+ )).then(function (results) {
+ if (!results.length) {
+ return response;
+ }
+ if (!response.value) {
+ response.value = {};
+ }
+ for(var i=0; i<results.length; ++i) {
+ // [whatever, key, value]
+ var triplet = results[i];
+ response.value[triplet[1]] = triplet[2];
+ }
return response;
- }
- if (!response.value) {
- response.value = {};
- }
- for(var i=0; i<results.length; ++i) {
- // [whatever, key, value]
- var triplet = results[i];
- response.value[triplet[1]] = triplet[2];
- }
- return response;
- });
+ });
+ }
}
- }
- return response;
- }).then(function(response) {
- return self.on_processed_onchange(response, processed);
+ return response;
+ }).then(function(response) {
+ return self.on_processed_onchange(response);
+ });
});
+ return this.onchanges_mutex.def;
} catch(e) {
console.error(e);
instance.webclient.crashmanager.show_message(e);
return $.Deferred().reject();
}
},
- on_processed_onchange: function(result, processed) {
+ on_processed_onchange: function(result) {
try {
var fields = this.fields;
_(result.domain).each(function (domain, fieldname) {
if (!field) { return; }
field.node.attrs.domain = domain;
});
-
- if (result.value) {
- this._internal_set_values(result.value, processed);
+
+ if (!_.isEmpty(result.value)) {
+ this._internal_set_values(result.value);
}
+ // FIXME XXX a list of warnings?
if (!_.isEmpty(result.warning)) {
new instance.web.Dialog(this, {
size: 'medium',
var self = this;
return this.mutating_mutex.exec(function() {
function iterate() {
- var on_change_obj = self.on_change_list.shift();
- if (on_change_obj) {
- return self._process_onchange(on_change_obj).then(function() {
- return iterate();
- });
- }
- var defs = [];
+
+ var mutex = new $.Mutex();
_.each(self.fields, function(field) {
- defs.push(field.commit_value());
+ self.onchanges_mutex.def.then(function(){
+ mutex.exec(function(){
+ return field.commit_value();
+ });
+ });
});
+
var args = _.toArray(arguments);
- return $.when.apply($, defs).then(function() {
- if (self.on_change_list.length !== 0) {
- return iterate();
- }
+ return $.when.apply(null, [mutex.def, self.onchanges_mutex.def]).then(function() {
var save_obj = self.save_list.pop();
if (save_obj) {
return self._process_save(save_obj).then(function() {
return iterate();
});
},
- _internal_set_values: function(values, exclude) {
- exclude = exclude || [];
+ _internal_set_values: function(values) {
for (var f in values) {
if (!values.hasOwnProperty(f)) { continue; }
var field = this.fields[f];
field.set_value(value_);
field._inhibit_on_change_flag = false;
field._dirty_flag = true;
- if (!_.contains(exclude, field.name)) {
- this.do_onchange(field, exclude);
- }
}
}
}
* if the current record is not yet saved. It will then stay in create mode.
*/
to_edit_mode: function() {
+ this.onchanges_mutex = new $.Mutex();
this._actualize_mode("edit");
},
/**
} else if (mode === "create") {
mode = "edit";
}
+ this.render_value_defs = [];
this.set({actual_mode: mode});
},
check_actual_mode: function(source, options) {
self.trigger("save", result);
self.reload().then(function() {
self.to_view_mode();
- var parent = self.ViewManager.ActionManager.getParent();
- if(parent){
- parent.menu.do_reload_needaction();
+ var menu = instance.webclient.menu;
+ if (menu) {
+ menu.do_reload_needaction();
}
+ instance.web.bus.trigger('form_view_saved', self);
});
}).always(function(){
$(e.target).attr("disabled", false);
});
},
on_button_cancel: function(event) {
+ var self = this;
if (this.can_be_discarded()) {
if (this.get('actual_mode') === 'create') {
this.trigger('history_back');
} else {
this.to_view_mode();
- this.trigger('load_record', this.datarecord);
+ $.when.apply(null, this.render_value_defs).then(function(){
+ self.trigger('load_record', self.datarecord);
+ });
}
}
this.trigger('on_button_cancel');
var self = this;
var save_obj = {prepend_on_create: prepend_on_create, ret: null};
this.save_list.push(save_obj);
- return this._process_operations().then(function() {
+ return self._process_operations().then(function() {
if (save_obj.error)
return $.Deferred().reject();
return $.when.apply($, save_obj.ret);
- }).done(function() {
+ }).done(function(result) {
self.$el.removeClass('oe_form_dirty');
});
},
do_attach_tooltip: function(widget, trigger, options) {
widget = widget || this;
trigger = trigger || this.$el;
- var container = 'body';
- /*TODO: need to be refactor
- in the case we can find the view form in the parent,
- attach the element to it (to prevent tooltip to keep showing
- when switching view) or if we have a modal currently showing,
- attach tooltip to the modal to prevent the tooltip to show in the body in the
- case we close the modal too fast*/
- if ($(trigger).parents('.oe_view_manager_view_form').length > 0){
- container = $(trigger).parents('.oe_view_manager_view_form');
- }
- else {
- if (window.$('.modal.in').length>0){
- container = window.$('.modal.in:last()');
- }
- }
options = _.extend({
delay: { show: 500, hide: 0 },
- trigger: 'hover',
- container: container,
title: function() {
var template = widget.template + '.tooltip';
if (!QWeb.has_template(template)) {
instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.ReinitializeWidgetMixin, {
reinitialize: function() {
instance.web.form.ReinitializeWidgetMixin.reinitialize.call(this);
- this.render_value();
+ var res = this.render_value();
+ if (this.view && this.view.render_value_defs){
+ this.view.render_value_defs.push(res);
+ }
},
});
init: function (field_manager, node) {
this._super(field_manager, node);
},
+ start: function () {
+ var self = this;
+ this.states = [];
+ this._super.apply(this, arguments);
+ // hook on form view content changed: recompute the states, because it may be related to the current stage
+ this.getParent().on('view_content_has_changed', self, function () {
+ self.render_value();
+ });
+ },
prepare_dropdown_selection: function() {
var self = this;
var data = [];
- var selection = self.field.selection || [];
- _.map(selection, function(res) {
- var value = {
- 'name': res[0],
- 'tooltip': res[1],
- 'state_name': res[1],
- }
- if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
- else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
- else { value['state_class'] = 'oe_kanban_status oe_kanban_status_red'; }
- data.push(value);
+ var selection = this.field.selection || [];
+ var stage_id = _.isArray(this.view.datarecord.stage_id) ? this.view.datarecord.stage_id[0] : this.view.datarecord.stage_id;
+ var legend_field = this.options && this.options.states_legend_field || false;
+ var fields_to_read = _.map(
+ this.options && this.options.states_legend || {},
+ function (value, key, list) { return value; });
+ if (legend_field && fields_to_read && stage_id) {
+ var fetch_stage = new openerp.web.DataSet(
+ this,
+ self.view.fields[legend_field].field.relation).read_ids([stage_id],
+ fields_to_read);
+ }
+ else { var fetch_stage = $.Deferred().resolve(false); }
+ return $.when(fetch_stage).then(function (stage_data) {
+ _.map(selection, function(res) {
+ var value = {
+ 'name': res[0],
+ 'tooltip': res[1],
+ 'state_name': res[1],
+ }
+ if (stage_data && stage_data[0][self.options.states_legend[res[0]]]) {
+ value['state_name'] = stage_data[0][self.options.states_legend[res[0]]];
+ }
+ if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
+ else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
+ else { value['state_class'] = 'oe_kanban_status oe_kanban_status_red'; }
+ data.push(value);
+ });
+ return data;
});
- return data;
},
render_value: function() {
var self = this;
- this.record_id = self.view.datarecord.id;
- this.states = self.prepare_dropdown_selection();;
- this.$el.html(QWeb.render("KanbanSelection", {'widget': self}));
- this.$el.find('.oe_legend').click(self.do_action.bind(self));
- },
- do_action: function(e) {
+ this.record_id = this.view.datarecord.id;
+ var dd_fetched = this.prepare_dropdown_selection();;
+ return $.when(dd_fetched).then(function (states) {
+ self.states = states;
+ self.$el.html(QWeb.render("KanbanSelection", {'widget': self}));
+ self.$el.find('li').on('click', self.set_kanban_selection.bind(self));
+ })
+ },
+ /* setting the value: in view mode, perform an asynchronous call and reload
+ the form view; in edit mode, use set_value to save the new value that will
+ be written when saving the record. */
+ set_kanban_selection: function (ev) {
var self = this;
- var li = $(e.target).closest( "li" );
+ var li = $(ev.target).closest('li');
if (li.length) {
- var value = {};
- value[self.name] = String(li.data('value'));
- if (self.record_id) {
- return self.view.dataset._model.call('write', [[self.record_id], value, self.view.dataset.get_context()]).done(self.reload_record.bind(self));
- } else {
- return self.view.on_button_save().done(function(result) {
- if (result) {
- self.view.dataset._model.call('write', [[result], value, self.view.dataset.get_context()]).done(self.reload_record.bind(self));
- }
- });
+ var value = String(li.data('value'));
+ if (this.view.get('actual_mode') == 'view') {
+ var write_values = {}
+ write_values[self.name] = value;
+ return this.view.dataset._model.call(
+ 'write', [
+ [self.record_id],
+ write_values,
+ self.view.dataset.get_context()
+ ]).done(self.reload_record.bind(self));
+ }
+ else {
+ return this.set_value(value);
}
}
},
},
render_value: function() {
var self = this;
- this.record_id = self.view.datarecord.id;
- this.priorities = self.prepare_priority();
+ this.record_id = this.view.datarecord.id;
+ this.priorities = this.prepare_priority();
this.$el.html(QWeb.render("Priority", {'widget': this}));
- this.$el.find('.oe_legend').click(self.do_action.bind(self));
+ this.$el.find('li').on('click', this.set_priority.bind(this));
},
- do_action: function(e) {
+ /* setting the value: in view mode, perform an asynchronous call and reload
+ the form view; in edit mode, use set_value to save the new value that will
+ be written when saving the record. */
+ set_priority: function (ev) {
var self = this;
- var li = $(e.target).closest( "li" );
+ var li = $(ev.target).closest('li');
if (li.length) {
- var value = {};
- value[self.name] = String(li.data('value'));
- if (self.record_id) {
- return self.view.dataset._model.call('write', [[self.record_id], value, self.view.dataset.get_context()]).done(self.reload_record.bind(self));
- } else {
- return self.view.on_button_save().done(function(result) {
- if (result) {
- self.view.dataset._model.call('write', [[result], value, self.view.dataset.get_context()]).done(self.reload_record.bind(self));
- }
- });
+ var value = String(li.data('value'));
+ if (this.view.get('actual_mode') == 'view') {
+ var write_values = {}
+ write_values[self.name] = value;
+ return this.view.dataset._model.call(
+ 'write', [
+ [self.record_id],
+ write_values,
+ self.view.dataset.get_context()
+ ]).done(self.reload_record.bind(self));
+ }
+ else {
+ return this.set_value(value);
}
}
+
},
reload_record: function() {
this.view.reload();
this._super.apply(this, arguments);
this.on("change:effective_readonly", this, function () {
this.display_field();
- this.render_value();
});
this.display_field();
return this._super();
},
- render_value: function() {
- this.$('button.select_records').css('visibility', this.get('effective_readonly') ? 'hidden': '');
- },
set_value: function(value_) {
var self = this;
this.set('value', value_ || false);
var ds = new instance.web.DataSetStatic(self, model, self.build_context());
ds.call('search_count', [domain]).then(function (results) {
$('.oe_domain_count', self.$el).text(results + ' records selected');
- $('button span', self.$el).text(' Change selection');
+ if (self.get('effective_readonly')) {
+ $('button span', self.$el).text(' See selection');
+ }
+ else {
+ $('button span', self.$el).text(' Change selection');
+ }
});
} else {
$('.oe_domain_count', this.$el).text('0 record selected');
var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
this.pop = new instance.web.form.SelectCreatePopup(this);
this.pop.select_element(
- model, {title: 'Select records...'},
- [], this.build_context());
+ model, {
+ title: this.get('effective_readonly') ? 'Selected records' : 'Select records...',
+ readonly: this.get('effective_readonly'),
+ disable_multiple_selection: this.get('effective_readonly'),
+ no_create: this.get('effective_readonly'),
+ }, [], this.build_context());
this.pop.on("elements_selected", self, function(element_ids) {
if (this.pop.$('input.oe_list_record_selector').prop('checked')) {
var search_data = this.pop.searchview.build_search_data();
instance.web.DateTimeWidget = instance.web.Widget.extend({
template: "web.datepicker",
- jqueryui_object: 'datetimepicker',
type_of_date: "datetime",
events: {
- 'change .oe_datepicker_master': 'change_datetime',
+ 'dp.change .oe_datepicker_main': 'change_datetime',
+ 'dp.show .oe_datepicker_main': 'set_datetime_default',
'keypress .oe_datepicker_master': 'change_datetime',
},
init: function(parent) {
},
start: function() {
var self = this;
+ var l10n = _t.database.parameters;
+ var options = {
+ pickTime: true,
+ useSeconds: true,
+ startDate: moment({ y: 1900 }),
+ endDate: moment().add(200, "y"),
+ calendarWeeks: true,
+ icons : {
+ time: 'fa fa-clock-o',
+ date: 'fa fa-calendar',
+ up: 'fa fa-chevron-up',
+ down: 'fa fa-chevron-down'
+ },
+ language : moment.locale(),
+ format : instance.web.normalize_format(l10n.date_format +' '+ l10n.time_format),
+ };
this.$input = this.$el.find('input.oe_datepicker_master');
- this.$input_picker = this.$el.find('input.oe_datepicker_container');
-
- $.datepicker.setDefaults({
- clearText: _t('Clear'),
- clearStatus: _t('Erase the current date'),
- closeText: _t('Done'),
- closeStatus: _t('Close without change'),
- prevText: _t('<Prev'),
- prevStatus: _t('Show the previous month'),
- nextText: _t('Next>'),
- nextStatus: _t('Show the next month'),
- currentText: _t('Today'),
- currentStatus: _t('Show the current month'),
- monthNames: Date.CultureInfo.monthNames,
- monthNamesShort: Date.CultureInfo.abbreviatedMonthNames,
- monthStatus: _t('Show a different month'),
- yearStatus: _t('Show a different year'),
- weekHeader: _t('Wk'),
- weekStatus: _t('Week of the year'),
- dayNames: Date.CultureInfo.dayNames,
- dayNamesShort: Date.CultureInfo.abbreviatedDayNames,
- dayNamesMin: Date.CultureInfo.shortestDayNames,
- dayStatus: _t('Set DD as first week day'),
- dateStatus: _t('Select D, M d'),
- firstDay: Date.CultureInfo.firstDayOfWeek,
- initStatus: _t('Select a date'),
- isRTL: false
- });
- $.timepicker.setDefaults({
- timeOnlyTitle: _t('Choose Time'),
- timeText: _t('Time'),
- hourText: _t('Hour'),
- minuteText: _t('Minute'),
- secondText: _t('Second'),
- currentText: _t('Now'),
- closeText: _t('Done')
- });
-
- this.picker({
- onClose: this.on_picker_select,
- onSelect: this.on_picker_select,
- changeMonth: true,
- changeYear: true,
- showWeek: true,
- showButtonPanel: true,
- firstDay: Date.CultureInfo.firstDayOfWeek
- });
- // Some clicks in the datepicker dialog are not stopped by the
- // datepicker and "bubble through", unexpectedly triggering the bus's
- // click event. Prevent that.
- this.picker('widget').click(function (e) { e.stopPropagation(); });
-
- this.$el.find('img.oe_datepicker_trigger').click(function() {
- if (self.get("effective_readonly") || self.picker('widget').is(':visible')) {
- self.$input.focus();
- return;
- }
- self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date());
- self.$input_picker.show();
- self.picker('show');
- self.$input_picker.hide();
- });
+ if (this.type_of_date === 'date') {
+ options['pickTime'] = false;
+ options['useSeconds'] = false;
+ options['format'] = instance.web.normalize_format(l10n.date_format);
+ }
+ this.picker = this.$('.oe_datepicker_main').datetimepicker(options);
this.set_readonly(false);
this.set({'value': false});
},
- picker: function() {
- return $.fn[this.jqueryui_object].apply(this.$input_picker, arguments);
- },
- on_picker_select: function(text, instance_) {
- var date = this.picker('getDate');
- this.$input
- .val(date ? this.format_client(date) : '')
- .change()
- .focus();
- },
set_value: function(value_) {
this.set({'value': value_});
this.$input.val(value_ ? this.format_client(value_) : '');
},
set_value_from_ui_: function() {
var value_ = this.$input.val() || false;
- this.set({'value': this.parse_client(value_)});
+ this.set_value(this.parse_client(value_));
},
set_readonly: function(readonly) {
this.readonly = readonly;
this.$input.prop('readonly', this.readonly);
- this.$el.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
},
is_valid_: function() {
var value_ = this.$input.val();
format_client: function(v) {
return instance.web.format_value(v, {"widget": this.type_of_date});
},
+ set_datetime_default: function(){
+ //when opening datetimepicker the date and time by default should be the one from
+ //the input field if any or the current day otherwise
+ if (this.type_of_date === 'datetime') {
+ value = moment().second(0);
+ if (this.$input.val().length !== 0 && this.is_valid_()){
+ var value = this.$input.val();
+ }
+ this.$('.oe_datepicker_main').data('DateTimePicker').setValue(value);
+ }
+ },
change_datetime: function(e) {
if ((e.type !== "keypress" || e.which === 13) && this.is_valid_()) {
this.set_value_from_ui_();
});
instance.web.DateWidget = instance.web.DateTimeWidget.extend({
- jqueryui_object: 'datepicker',
type_of_date: "date"
});
if (! this.get("effective_readonly")) {
self._updating_editor = false;
this.$textarea = this.$el.find('textarea');
- var width = ((this.node.attrs || {}).editor_width || '100%');
+ var width = ((this.node.attrs || {}).editor_width || 'calc(100% - 4px)');
var height = ((this.node.attrs || {}).editor_height || 250);
this.$textarea.cleditor({
width: width, // width not including margins, borders or padding
this._super(field_manager, node);
this.selection = _.clone(this.field.selection) || [];
this.domain = false;
+ this.uniqueId = _.uniqueId("radio");
},
initialize_content: function () {
- this.uniqueId = _.uniqueId("radio");
this.on("change:effective_readonly", this, this.render_value);
this.field_manager.on("view_content_has_changed", this, this.get_selection);
this.get_selection();
render_value: function () {
var self = this;
this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
- this.$("input:checked").prop("checked", false);
if (this.get_value()) {
this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true);
this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
var self = this;
var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context());
- var blacklist = this.get_search_blacklist();
this.last_query = search_val;
+ var exclusion_domain = [], ids_blacklist = this.get_search_blacklist();
+ if (!_(ids_blacklist).isEmpty()) {
+ exclusion_domain.push(['id', 'not in', ids_blacklist]);
+ }
return this.orderer.add(dataset.name_search(
- search_val, new instance.web.CompoundDomain(self.build_domain(), [["id", "not in", blacklist]]),
+ search_val, new instance.web.CompoundDomain(self.build_domain(), exclusion_domain),
'ilike', this.limit + 1, self.build_context())).then(function(data) {
self.last_search = data;
// possible selections for the m2o
});
}
// create...
- if (!(self.options && self.options.no_create)){
+ if (!(self.options && (self.options.no_create || self.options.no_create_edit))){
values.push({
label: _t("Create and Edit..."),
action: function() {
this.$("p").text( text );
this.$buttons.html(QWeb.render("M2ODialog.buttons"));
this.$("input").val(this.getParent().last_query);
- this.$buttons.find(".oe_form_m2o_qc_button").click(function(){
- self.getParent()._quick_create(self.$("input").val());
- self.destroy();
+ this.$buttons.find(".oe_form_m2o_qc_button").click(function(e){
+ if (self.$("input").val() != ''){
+ self.getParent()._quick_create(self.$("input").val());
+ self.destroy();
+ } else{
+ e.preventDefault();
+ self.$("input").focus();
+ }
});
this.$buttons.find(".oe_form_m2o_sc_button").click(function(){
self.getParent()._search_create_popup("form", undefined, self.getParent()._create_context(self.$("input").val()));
}
self.floating = false;
}
- if (used && self.get("value") === false && ! self.no_ed && (self.options.no_create === false || self.options.no_create === undefined)) {
+ if (used && self.get("value") === false && ! self.no_ed && ! (self.options && (self.options.no_create || self.options.no_quick_create))) {
self.ed_def.reject();
self.uned_def.reject();
self.ed_def = $.Deferred();
focusout: anyoneLoosesFocus,
focus: function () { self.trigger('focused'); },
autocompleteopen: function () { ignore_blur = true; },
- autocompleteclose: function () { ignore_blur = false; },
+ autocompleteclose: function () { setTimeout(function() {ignore_blur = false;},0); },
blur: function () {
// autocomplete open
- if (ignore_blur) { return; }
+ if (ignore_blur) { $(this).focus(); return; }
if (_(self.getChildren()).any(function (child) {
return child instanceof instance.web.form.AbstractFormPopup;
})) { return; }
disable_utility_classes: true,
init: function(field_manager, node) {
this._super(field_manager, node);
- lazy_build_o2m_kanban_view();
this.is_loaded = $.Deferred();
this.initial_is_loaded = this.is_loaded;
this.form_last_update = $.Deferred();
reload_current_view: function() {
var self = this;
self.is_loaded = self.is_loaded.then(function() {
- var active_view = self.viewmanager.active_view;
- var view = self.viewmanager.views[active_view].controller;
- if(active_view === "list") {
- return view.reload_content();
- } else if (active_view === "form") {
+ var view = self.get_active_view();
+ if (view.type === "list") {
+ return view.controller.reload_content();
+ } else if (view.type === "form") {
if (self.dataset.index === null && self.dataset.ids.length >= 1) {
self.dataset.index = 0;
}
var act = function() {
- return view.do_show();
+ return view.controller.do_show();
};
self.form_last_update = self.form_last_update.then(act, act);
return self.form_last_update;
- } else if (view.do_search) {
- return view.do_search(self.build_domain(), self.dataset.get_context(), []);
+ } else if (view.controller.do_search) {
+ return view.controller.do_search(self.build_domain(), self.dataset.get_context(), []);
}
}, undefined);
return self.is_loaded;
},
+ get_active_view: function () {
+ /**
+ * Returns the current active view if any.
+ */
+ return (this.viewmanager && this.viewmanager.active_view);
+ },
set_value: function(value_) {
value_ = value_ || [];
var self = this;
+ var view = this.get_active_view();
this.dataset.reset_ids([]);
var ids;
if(value_.length >= 1 && value_[0] instanceof Array) {
return this.save_any_view();
},
save_any_view: function() {
- if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
- this.viewmanager.views[this.viewmanager.active_view] &&
- this.viewmanager.views[this.viewmanager.active_view].controller) {
- var view = this.viewmanager.views[this.viewmanager.active_view].controller;
- if (this.viewmanager.active_view === "form") {
- if (view.is_initialized.state() !== 'resolved') {
+ var view = this.get_active_view();
+ if (view) {
+ if (this.viewmanager.active_view.type === "form") {
+ if (view.controller.is_initialized.state() !== 'resolved') {
return $.when(false);
}
- return $.when(view.save());
- } else if (this.viewmanager.active_view === "list") {
- return $.when(view.ensure_saved());
+ return $.when(view.controller.save());
+ } else if (this.viewmanager.active_view.type === "list") {
+ return $.when(view.controller.ensure_saved());
}
}
return $.when(false);
},
is_syntax_valid: function() {
- if (! this.viewmanager || ! this.viewmanager.views[this.viewmanager.active_view])
+ var view = this.get_active_view();
+ if (!view){
return true;
- var view = this.viewmanager.views[this.viewmanager.active_view].controller;
- switch (this.viewmanager.active_view) {
+ }
+ switch (this.viewmanager.active_view.type) {
case 'form':
- return _(view.fields).chain()
+ return _(view.controller.fields).chain()
.invoke('is_valid')
.all(_.identity)
.value();
case 'list':
- return view.is_valid();
+ return view.controller.is_valid();
}
return true;
},
template: 'One2Many.viewmanager',
init: function(parent, dataset, views, flags) {
this._super(parent, dataset, views, _.extend({}, flags, {$sidebar: false}));
- this.registry = this.registry.extend({
+ this.registry = instance.web.views.extend({
list: 'instance.web.form.One2ManyListView',
form: 'instance.web.form.One2ManyFormView',
- kanban: 'instance.web.form.One2ManyKanbanView',
});
this.__ignore_blur = false;
},
this.o2m.trigger_on_change();
},
is_valid: function () {
- var editor = this.editor;
- var form = editor.form;
- // If no edition is pending, the listview can not be invalid (?)
- if (!editor.record) {
- return true;
- }
- // If the form has not been modified, the view can only be valid
- // NB: is_dirty will also be set on defaults/onchanges/whatever?
- // oe_form_dirty seems to only be set on actual user actions
- if (!form.$el.is('.oe_form_dirty')) {
+ var self = this;
+ if (!this.fields_view || !this.editable()){
return true;
}
- this.o2m._dirty_flag = true;
-
- // Otherwise validate internal form
- return _(form.fields).chain()
- .invoke(function () {
- this._check_css_flags();
- return this.is_valid();
- })
- .all(_.identity)
- .value();
+ var r;
+ return _.every(this.records.records, function(record){
+ r = record;
+ _.each(self.editor.form.fields, function(field){
+ field._inhibit_on_change_flag = true;
+ field.set_value(r.attributes[field.name]);
+ field._inhibit_on_change_flag = false;
+ });
+ return _.every(self.editor.form.fields, function(field){
+ field.process_modifiers();
+ field._check_css_flags();
+ return field.is_valid();
+ });
+ });
},
do_add_record: function () {
if (this.editable()) {
window.confirm = confirm;
}
},
- reload_record: function (record) {
- // Evict record.id from cache to ensure it will be reloaded correctly
- this.dataset.evict_record(record.get('id'));
+ reload_record: function (record, options) {
+ if (!options || !options['do_not_evict']) {
+ // Evict record.id from cache to ensure it will be reloaded correctly
+ this.dataset.evict_record(record.get('id'));
+ }
return this._super(record);
}
}
});
-var lazy_build_o2m_kanban_view = function() {
- if (! instance.web_kanban || instance.web.form.One2ManyKanbanView)
- return;
- instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({
- });
-};
-
instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
template: "FieldMany2ManyTags",
tag_template: "FieldMany2ManyTag",
}
});
},
+ // WARNING: duplicated in 4 other M2M widgets
set_value: function(value_) {
value_ = value_ || [];
if (value_.length >= 1 && value_[0] instanceof Array) {
- value_ = value_[0][2];
+ // value_ is a list of m2m commands. We only process
+ // LINK_TO and REPLACE_WITH in this context
+ var val = [];
+ _.each(value_, function (command) {
+ if (command[0] === commands.LINK_TO) {
+ val.push(command[1]); // (4, id[, _])
+ } else if (command[0] === commands.REPLACE_WITH) {
+ val = command[2]; // (6, _, ids)
+ }
+ });
+ value_ = val;
}
this._super(value_);
},
self.render_tag(data);
}
if (! values || values.length > 0) {
- this._display_orderer.add(self.get_render_data(values)).done(handle_names);
- }
- else{
+ return this._display_orderer.add(self.get_render_data(values)).done(handle_names);
+ } else {
handle_names([]);
}
},
this.list_view.destroy();
this.list_view = undefined;
},
+ // WARNING: duplicated in 4 other M2M widgets
set_value: function(value_) {
value_ = value_ || [];
if (value_.length >= 1 && value_[0] instanceof Array) {
- value_ = value_[0][2];
+ // value_ is a list of m2m commands. We only process
+ // LINK_TO and REPLACE_WITH in this context
+ var val = [];
+ _.each(value_, function (command) {
+ if (command[0] === commands.LINK_TO) {
+ val.push(command[1]); // (4, id[, _])
+ } else if (command[0] === commands.REPLACE_WITH) {
+ val = command[2]; // (6, _, ids)
+ }
+ });
+ value_ = val;
}
this._super(value_);
},
this.model,
{
title: _t("Add: ") + this.m2m_field.string,
+ alternative_form_view: this.m2m_field.field.views ? this.m2m_field.field.views["form"] : undefined,
no_create: this.m2m_field.options.no_create,
},
new instance.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]),
var pop = new instance.web.form.FormOpenPopup(this);
pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {
title: _t("Open: ") + this.m2m_field.string,
+ alternative_form_view: this.m2m_field.field.views ? this.m2m_field.field.views["form"] : undefined,
readonly: this.getParent().get("effective_readonly")
});
pop.on('write_completed', self, self.reload_content);
});
});
},
+ // WARNING: duplicated in 4 other M2M widgets
set_value: function(value_) {
value_ = value_ || [];
if (value_.length >= 1 && value_[0] instanceof Array) {
- value_ = value_[0][2];
+ // value_ is a list of m2m commands. We only process
+ // LINK_TO and REPLACE_WITH in this context
+ var val = [];
+ _.each(value_, function (command) {
+ if (command[0] === commands.LINK_TO) {
+ val.push(command[1]); // (4, id[, _])
+ } else if (command[0] === commands.REPLACE_WITH) {
+ val = command[2]; // (6, _, ids)
+ }
+ });
+ value_ = val;
}
this._super(value_);
},
this.domain = domain || [];
this.context = context || {};
this.options = options;
- _.defaults(this.options, {
- });
+ _.defaults(this.options, {});
},
init_dataset: function() {
var self = this;
if (this.options.alternative_form_view) {
this.view_form.set_embedded_view(this.options.alternative_form_view);
}
- this.view_form.appendTo(this.$el.find(".oe_popup_form"));
+ this.view_form.appendTo(this.$(".oe_popup_form").show());
this.view_form.on("form_view_loaded", self, function() {
var multi_select = self.row_id === null && ! self.options.disable_multiple_selection;
self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", {
this.display_popup();
},
start: function() {
- var self = this;
this.init_dataset();
if (this.options.initial_view == "search") {
- instance.web.pyeval.eval_domains_and_contexts({
+ var context = instance.web.pyeval.sync_eval_domains_and_contexts({
domains: [],
contexts: [this.context]
- }).done(function (results) {
- var search_defaults = {};
- _.each(results.context, function (value_, key) {
- var match = /^search_default_(.*)$/.exec(key);
- if (match) {
- search_defaults[match[1]] = value_;
- }
- });
- self.setup_search_view(search_defaults);
+ }).context;
+ var search_defaults = {};
+ _.each(context, function (value_, key) {
+ var match = /^search_default_(.*)$/.exec(key);
+ if (match) {
+ search_defaults[match[1]] = value_;
+ }
});
+ this.setup_search_view(search_defaults);
} else { // "form"
this.new_object();
}
if (this.searchview) {
this.searchview.destroy();
}
- if (this.searchview_drawer) {
- this.searchview_drawer.destroy();
- }
+ var $buttons = this.$('.o-search-options');
this.searchview = new instance.web.SearchView(this,
- this.dataset, false, search_defaults);
- this.searchview_drawer = new instance.web.SearchViewDrawer(this, this.searchview);
+ this.dataset, false, search_defaults, {$buttons: $buttons});
this.searchview.on('search_data', self, function(domains, contexts, groupbys) {
if (self.initial_ids) {
self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]),
self.do_search(domains.concat([self.domain]), contexts.concat(self.context), groupbys);
}
});
- this.searchview.on("search_view_loaded", self, function() {
+ this.searchview.appendTo(this.$(".o-popup-search")).done(function() {
+ self.searchview.toggle_visibility(true);
self.view_list = new instance.web.form.SelectCreateListView(self,
self.dataset, false,
_.extend({'deletable': false,
e.cancel = true;
});
self.view_list.popup = self;
- self.view_list.appendTo($(".oe_popup_list", self.$el)).then(function() {
+ self.view_list.appendTo(self.$(".oe_popup_list").show()).then(function() {
self.view_list.do_show();
}).then(function() {
self.searchview.do_search();
self.new_object();
});
});
- });
- this.searchview.appendTo(this.$(".oe_popup_search"));
+ });
},
do_search: function(domains, contexts, groupbys) {
var self = this;
},
new_object: function() {
if (this.searchview) {
- this.searchview.hide();
+ this.searchview.do_hide();
}
if (this.view_list) {
this.view_list.do_hide();
this._super.apply(this, arguments);
this.render_value();
this.set_filename('');
+ },
+ set_value: function(value_){
+ var changed = value_ !== this.get_value();
+ this._super.apply(this, arguments);
+ // By default, on binary images read, the server returns the binary size
+ // This is possible that two images have the exact same size
+ // Therefore we trigger the change in case the image value hasn't changed
+ // So the image is re-rendered correctly
+ if (!changed){
+ this.trigger("change:value", this, {
+ oldValue: value_,
+ newValue: value_
+ });
+ }
}
});
* Options on attribute ; "blockui" {Boolean} block the UI or not
* during the file is uploading
*/
-instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
+instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
template: "FieldBinaryFileUploader",
init: function(field_manager, node) {
this._super(field_manager, node);
this.fileupload_id = _.uniqueId('oe_fileupload_temp');
$(window).on(this.fileupload_id, _.bind(this.on_file_loaded, this));
},
- start: function() {
- this._super(this);
+ initialize_content: function() {
this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
},
+ // WARNING: duplicated in 4 other M2M widgets
set_value: function(value_) {
value_ = value_ || [];
if (value_.length >= 1 && value_[0] instanceof Array) {
- value_ = value_[0][2];
+ // value_ is a list of m2m commands. We only process
+ // LINK_TO and REPLACE_WITH in this context
+ var val = [];
+ _.each(value_, function (command) {
+ if (command[0] === commands.LINK_TO) {
+ val.push(command[1]); // (4, id[, _])
+ } else if (command[0] === commands.REPLACE_WITH) {
+ val = command[2]; // (6, _, ids)
+ }
+ });
+ value_ = val;
}
this._super(value_);
},
if (! _.isEqual(new_value, this.get("value")))
this.internal_set_value(new_value);
},
- set_value: function(value) {
- value = value || [];
- if (value.length >= 1 && value[0] instanceof Array) {
- value = value[0][2];
+ // WARNING: (mostly) duplicated in 4 other M2M widgets
+ set_value: function(value_) {
+ value_ = value_ || [];
+ if (value_.length >= 1 && value_[0] instanceof Array) {
+ // value_ is a list of m2m commands. We only process
+ // LINK_TO and REPLACE_WITH in this context
+ var val = [];
+ _.each(value_, function (command) {
+ if (command[0] === commands.LINK_TO) {
+ val.push(command[1]); // (4, id[, _])
+ } else if (command[0] === commands.REPLACE_WITH) {
+ val = command[2]; // (6, _, ids)
+ }
+ });
+ value_ = val;
}
var formatted = {};
- _.each(value, function(el) {
+ _.each(value_, function(el) {
formatted[JSON.stringify(el)] = true;
});
this._super(formatted);
value: this.get("value") || 0,
};
if (! this.node.attrs.nolabel) {
- options.text = this.string
+ if(this.options.label_field && this.view.datarecord[this.options.label_field]) {
+ options.text = this.view.datarecord[this.options.label_field];
+ }
+ else {
+ options.text = this.string;
+ }
}
this.$el.html(QWeb.render("StatInfo", options));
},