1 openerp.web.form = function (openerp) {
3 var _t = openerp.web._t;
4 var QWeb = openerp.web.qweb;
6 openerp.web.views.add('form', 'openerp.web.FormView');
7 openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */{
9 * Indicates that this view is not searchable, and thus that no search
10 * view should be displayed (if there is one active).
13 form_template: "FormView",
14 identifier_prefix: 'formview-',
16 * @constructs openerp.web.FormView
17 * @extends openerp.web.View
19 * @param {openerp.web.Session} session the current openerp session
20 * @param {openerp.web.DataSet} dataset the dataset this view will work with
21 * @param {String} view_id the identifier of the OpenERP view object
23 * @property {openerp.web.Registry} registry=openerp.web.form.widgets widgets registry for this form view instance
25 init: function(parent, dataset, view_id, options) {
27 this.set_default_options(options);
28 this.dataset = dataset;
29 this.model = dataset.model;
30 this.view_id = view_id || false;
31 this.fields_view = {};
33 this.widgets_counter = 0;
37 this.show_invalid = true;
38 this.dirty_for_user = false;
39 this.default_focus_field = null;
40 this.default_focus_button = null;
41 this.registry = openerp.web.form.widgets;
42 this.has_been_loaded = $.Deferred();
43 this.$form_header = null;
44 this.translatable_fields = [];
45 _.defaults(this.options, {"always_show_new_button": true,
46 "not_interactible_on_create": false});
50 return this.init_view();
52 init_view: function() {
53 if (this.embedded_view) {
54 var def = $.Deferred().then(this.on_loaded);
56 setTimeout(function() {def.resolve(self.embedded_view);}, 0);
59 var context = new openerp.web.CompoundContext(this.dataset.get_context());
60 return this.rpc("/web/view/load", {
62 "view_id": this.view_id,
64 toolbar: this.options.sidebar,
71 this.sidebar.attachments.stop();
74 _.each(this.widgets, function(w) {
79 reposition: function ($e) {
83 on_loaded: function(data) {
86 this.fields_view = data;
87 var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
89 this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'view': this });
91 this.$element.html(this.rendered);
92 _.each(this.widgets, function(w) {
95 this.$form_header = this.$element.find('.oe_form_header:first');
96 this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
97 var action = $(this).data('pager-action');
98 self.on_pager_action(action);
101 this.$form_header.find('button.oe_form_button_save').click(this.do_save);
102 this.$form_header.find('button.oe_form_button_save_edit').click(this.do_save_edit);
103 this.$form_header.find('button.oe_form_button_cancel').click(this.do_cancel);
104 this.$form_header.find('button.oe_form_button_new').click(this.on_button_new);
105 this.$form_header.find('button.oe_form_button_duplicate').click(this.on_button_duplicate);
106 this.$form_header.find('button.oe_form_button_toggle').click(function () {
107 self.translatable_fields = [];
110 self.$form_header.find('button').unbind('click');
111 self.registry = self.registry === openerp.web.form.widgets
112 ? openerp.web.form.readonly
113 : openerp.web.form.widgets;
114 self.on_loaded(self.fields_view);
118 if (this.options.sidebar && this.options.sidebar_id) {
119 this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
120 this.sidebar.start();
121 this.sidebar.do_unfold();
122 this.sidebar.attachments = new openerp.web.form.SidebarAttachments(this.sidebar, this.sidebar.add_section('attachments', "Attachments"), this);
123 this.sidebar.add_toolbar(this.fields_view.toolbar);
124 this.set_common_sidebar_sections(this.sidebar);
126 this.has_been_loaded.resolve();
128 do_show: function () {
130 if (this.dataset.index === null) {
131 // null index means we should start a new record
132 promise = this.on_button_new();
134 promise = this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
136 this.$element.show();
138 this.sidebar.$element.show();
142 do_hide: function () {
143 this.$element.hide();
145 this.sidebar.$element.hide();
148 on_record_loaded: function(record) {
150 throw("Form: No record received");
153 this.$form_header.find('.oe_form_on_create').show();
154 this.$form_header.find('.oe_form_on_update').hide();
155 if (!this.options["always_show_new_button"]) {
156 this.$form_header.find('button.oe_form_button_new').hide();
159 this.$form_header.find('.oe_form_on_create').hide();
160 this.$form_header.find('.oe_form_on_update').show();
161 this.$form_header.find('button.oe_form_button_new').show();
163 this.dirty_for_user = false;
164 this.datarecord = record;
165 for (var f in this.fields) {
166 var field = this.fields[f];
168 field.set_value(this.datarecord[f] || false);
172 // New record: Second pass in order to trigger the onchanges
173 this.show_invalid = false;
174 for (var f in record) {
175 var field = this.fields[f];
178 this.do_onchange(field);
182 this.on_form_changed();
183 this.show_invalid = this.ready = true;
184 this.do_update_pager(record.id == null);
186 this.sidebar.attachments.do_update();
187 this.sidebar.$element.find('.oe_sidebar_translate').toggleClass('oe_hide', !record.id);
189 if (this.default_focus_field && !this.embedded_view) {
190 this.default_focus_field.focus();
193 on_form_changed: function() {
194 for (var w in this.widgets) {
196 w.process_modifiers();
200 on_pager_action: function(action) {
201 if (this.can_be_discarded()) {
204 this.dataset.index = 0;
207 this.dataset.previous();
213 this.dataset.index = this.dataset.ids.length - 1;
219 do_update_pager: function(hide_index) {
220 var $pager = this.$form_header.find('div.oe_form_pager');
221 var index = hide_index ? '-' : this.dataset.index + 1;
222 $pager.find('span.oe_pager_index').html(index);
223 $pager.find('span.oe_pager_count').html(this.dataset.ids.length);
225 do_onchange: function(widget, processed) {
226 processed = processed || [];
227 if (widget.node.attrs.on_change) {
230 var onchange = _.trim(widget.node.attrs.on_change);
231 var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
233 var method = call[1], args = [];
234 var context_index = null;
235 var argument_replacement = {
236 'False' : function() {return false;},
237 'True' : function() {return true;},
238 'None' : function() {return null;},
239 'context': function(i) {
241 var ctx = widget.build_context ? widget.build_context() : {};
245 var parent_fields = null;
246 _.each(call[2].split(','), function(a, i) {
247 var field = _.trim(a);
248 if (field in argument_replacement) {
249 args.push(argument_replacement[field](i));
251 } else if (self.fields[field]) {
252 var value = self.fields[field].get_on_change_value();
253 args.push(value == null ? false : value);
256 var splitted = field.split('.');
257 if (splitted.length > 1 && _.trim(splitted[0]) === "parent" && self.dataset.parent_view) {
258 if (parent_fields === null) {
259 parent_fields = self.dataset.parent_view.get_fields_values();
261 var p_val = parent_fields[_.trim(splitted[1])];
262 if (p_val !== undefined) {
263 args.push(p_val == null ? false : p_val);
268 throw "Could not get field with name '" + field +
269 "' for onchange '" + onchange + "'";
272 url: '/web/dataset/call',
275 return this.rpc(ajax, {
276 model: this.dataset.model,
278 args: [(this.datarecord.id == null ? [] : [this.datarecord.id])].concat(args),
279 context_id: context_index === null ? null : context_index + 1
280 }, function(response) {
281 self.on_processed_onchange(response, processed);
284 console.log("Wrong on_change format", on_change);
288 on_processed_onchange: function(response, processed) {
289 var result = response;
291 for (var f in result.value) {
292 var field = this.fields[f];
293 // If field is not defined in the view, just ignore it
295 var value = result.value[f];
296 processed.push(field.name);
297 if (field.get_value() != value) {
298 field.set_value(value);
299 field.dirty = this.dirty_for_user = true;
300 if (_.indexOf(processed, field.name) < 0) {
301 this.do_onchange(field, processed);
306 this.on_form_changed();
308 if (!_.isEmpty(result.warning)) {
309 $(QWeb.render("DialogWarning", result.warning)).dialog({
313 $(this).dialog("close");
323 on_button_new: function() {
325 var def = $.Deferred();
326 $.when(this.has_been_loaded).then(function() {
327 if (self.can_be_discarded()) {
328 var keys = _.keys(self.fields_view.fields);
330 self.dataset.default_get(keys).then(self.on_record_loaded).then(function() {
334 self.on_record_loaded({});
339 return def.promise();
341 on_button_duplicate: function() {
343 var def = $.Deferred();
344 $.when(this.has_been_loaded).then(function() {
345 if (self.can_be_discarded()) {
346 self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
347 return self.on_created({ result : new_id });
353 return def.promise();
355 can_be_discarded: function() {
356 return !this.dirty_for_user || confirm(_t("Warning, the record has been modified, your changes will be discarded."));
359 * Triggers saving the form's record. Chooses between creating a new
360 * record or saving an existing one depending on whether the record
361 * already has an id property.
363 * @param {Function} success callback on save success
364 * @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new record, should that record be inserted at the start of the dataset (by default, records are added at the end)
366 do_save: function(success, prepend_on_create) {
369 return $.Deferred().reject();
371 var form_dirty = false,
372 form_invalid = false,
374 first_invalid_field = null;
375 for (var f in this.fields) {
380 if (!first_invalid_field) {
381 first_invalid_field = f;
383 } else if (f.is_dirty()) {
385 values[f.name] = f.get_value();
389 first_invalid_field.focus();
391 return $.Deferred().reject();
393 console.log("About to save", values);
394 if (!this.datarecord.id) {
395 return this.dataset.create(values).pipe(function(r) {
396 return self.on_created(r, undefined, prepend_on_create);
399 return this.dataset.write(this.datarecord.id, values, {}).pipe(function(r) {
400 return self.on_saved(r);
405 do_save_edit: function() {
407 //this.switch_readonly(); Use promises
409 switch_readonly: function() {
411 switch_editable: function() {
413 on_invalid: function() {
415 _.each(this.fields, function(f) {
417 msg += "<li>" + f.string + "</li>";
421 this.notification.warn("The following fields are invalid :", msg);
423 on_saved: function(r, success) {
425 // should not happen in the server, but may happen for internal purpose
426 return $.Deferred().reject();
428 return this.reload().then(success);
432 * Updates the form' dataset to contain the new record:
434 * * Adds the newly created record to the current dataset (at the end by
436 * * Selects that record (sets the dataset's index to point to the new
438 * * Updates the pager and sidebar displays
441 * @param {Function} success callback to execute after having updated the dataset
442 * @param {Boolean} [prepend_on_create=false] adds the newly created record at the beginning of the dataset instead of the end
444 on_created: function(r, success, prepend_on_create) {
446 // should not happen in the server, but may happen for internal purpose
447 return $.Deferred().reject();
449 this.datarecord.id = r.result;
450 if (!prepend_on_create) {
451 this.dataset.ids.push(this.datarecord.id);
452 this.dataset.index = this.dataset.ids.length - 1;
454 this.dataset.ids.unshift(this.datarecord.id);
455 this.dataset.index = 0;
457 this.do_update_pager();
459 this.sidebar.attachments.do_update();
461 console.debug("The record has been created with id #" + this.datarecord.id);
462 return this.reload().pipe(function() {
463 return _.extend(r, {created: true});
467 on_action: function (action) {
468 console.debug('Executing action', action);
470 do_cancel: function () {
471 console.debug("Cancelling form");
474 if (this.dataset.index == null || this.dataset.index < 0) {
475 return $.when(this.on_button_new());
477 return this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
480 get_fields_values: function() {
482 _.each(this.fields, function(value, key) {
483 var val = value.get_value();
488 get_selected_ids: function() {
489 var id = this.dataset.ids[this.dataset.index];
490 return id ? [id] : [];
492 recursive_save: function() {
494 return $.when(this.do_save()).pipe(function(res) {
495 if (self.dataset.parent_view)
496 return self.dataset.parent_view.recursive_save();
499 is_interactible_record: function() {
500 var id = this.datarecord.id;
502 if (this.options.not_interactible_on_create)
504 } else if (typeof(id) === "string") {
505 if(openerp.web.BufferedDataSet.virtual_id_regex.test(id))
511 openerp.web.FormDialog = openerp.web.Dialog.extend({
512 init: function(parent, options, view_id, dataset) {
513 this._super(parent, options);
514 this.dataset = dataset;
515 this.view_id = view_id;
520 this.form = new openerp.web.FormView(this, this.dataset, this.view_id, {
524 this.form.appendTo(this.$element);
525 this.form.on_created.add_last(this.on_form_dialog_saved);
526 this.form.on_saved.add_last(this.on_form_dialog_saved);
529 load_id: function(id) {
531 return this.dataset.read_ids([id], _.keys(this.form.fields_view.fields), function(records) {
532 self.form.on_record_loaded(records[0]);
535 on_form_dialog_saved: function(r) {
541 openerp.web.form = {};
543 openerp.web.form.SidebarAttachments = openerp.web.Widget.extend({
544 init: function(parent, element_id, form_view) {
545 this._super(parent, element_id);
546 this.view = form_view;
548 do_update: function() {
549 if (!this.view.datarecord.id) {
550 this.on_attachments_loaded([]);
552 (new openerp.web.DataSetSearch(
553 this, 'ir.attachment', this.view.dataset.get_context(),
555 ['res_model', '=', this.view.dataset.model],
556 ['res_id', '=', this.view.datarecord.id],
557 ['type', 'in', ['binary', 'url']]
558 ])).read_slice(['name', 'url', 'type'], {}, this.on_attachments_loaded);
561 on_attachments_loaded: function(attachments) {
562 this.attachments = attachments;
563 this.$element.html(QWeb.render('FormView.sidebar.attachments', this));
564 this.$element.find('.oe-binary-file').change(this.on_attachment_changed);
565 this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete);
567 on_attachment_changed: function(e) {
568 window[this.element_id + '_iframe'] = this.do_update;
569 var $e = $(e.target);
570 if ($e.val() != '') {
571 this.$element.find('form.oe-binary-form').submit();
572 $e.parent().find('input[type=file]').attr('disabled', 'true');
573 $e.parent().find('button').attr('disabled', 'true').find('img, span').toggle();
576 on_attachment_delete: function(e) {
577 var self = this, $e = $(e.currentTarget);
578 var name = _.trim($e.parent().find('a.oe-sidebar-attachments-link').text());
579 if (confirm("Do you really want to delete the attachment " + name + " ?")) {
580 this.rpc('/web/dataset/unlink', {
581 model: 'ir.attachment',
582 ids: [parseInt($e.attr('data-id'))]
584 $e.parent().remove();
585 self.notification.notify("Delete an attachment", "The attachment '" + name + "' has been deleted");
591 openerp.web.form.compute_domain = function(expr, fields) {
593 for (var i = expr.length - 1; i >= 0; i--) {
595 if (ex.length == 1) {
596 var top = stack.pop();
599 stack.push(stack.pop() || top);
602 stack.push(stack.pop() && top);
608 throw new Error('Unknown domain operator ' + ex);
612 var field = fields[ex[0]];
614 throw new Error("Domain references unknown field : " + ex[0]);
616 var field_value = field.get_value ? fields[ex[0]].get_value() : fields[ex[0]].value;
620 switch (op.toLowerCase()) {
623 stack.push(field_value == val);
627 stack.push(field_value != val);
630 stack.push(field_value < val);
633 stack.push(field_value > val);
636 stack.push(field_value <= val);
639 stack.push(field_value >= val);
642 stack.push(_(val).contains(field_value));
645 stack.push(!_(val).contains(field_value));
648 console.log("Unsupported operator in modifiers :", op);
651 return _.all(stack, _.identity);
654 openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.Widget# */{
656 identifier_prefix: 'formview-widget-',
658 * @constructs openerp.web.form.Widget
659 * @extends openerp.web.Widget
664 init: function(view, node) {
667 this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
668 this.type = this.type || node.tag;
669 this.element_name = this.element_name || this.type;
670 this.element_class = [
671 'formview', this.view.view_id, this.element_name,
672 this.view.widgets_counter++].join("_");
676 this.view.widgets[this.element_class] = this;
677 this.children = node.children;
678 this.colspan = parseInt(node.attrs.colspan || 1, 10);
679 this.decrease_max_width = 0;
681 this.string = this.string || node.attrs.string;
682 this.help = this.help || node.attrs.help;
683 this.invisible = this.modifiers['invisible'] === true;
684 this.classname = 'oe_form_' + this.type;
686 this.width = this.node.attrs.width;
689 this.$element = this.view.$element.find(
690 '.' + this.element_class.replace(/[^\r\n\f0-9A-Za-z_-]/g, "\\$&"));
692 process_modifiers: function() {
693 var compute_domain = openerp.web.form.compute_domain;
694 for (var a in this.modifiers) {
695 this[a] = compute_domain(this.modifiers[a], this.view.fields);
698 update_dom: function() {
699 this.$element.toggle(!this.invisible);
702 var template = this.template;
703 return QWeb.render(template, { "widget": this });
707 openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
708 template: 'WidgetFrame',
709 init: function(view, node) {
710 this._super(view, node);
711 this.columns = parseInt(node.attrs.col || 4, 10);
716 for (var i = 0; i < node.children.length; i++) {
717 var n = node.children[i];
718 if (n.tag == "newline") {
724 this.set_row_cells_with(this.table[this.table.length - 1]);
727 if (this.table.length) {
728 this.set_row_cells_with(this.table[this.table.length - 1]);
731 this.table.push(row);
736 set_row_cells_with: function(row) {
739 for (var i = 0; i < row.length; i++) {
740 bypass += row[i].width === undefined ? 0 : 1;
741 max_width -= row[i].decrease_max_width;
743 var size_unit = Math.round(max_width / (this.columns - bypass)),
745 for (var i = 0; i < row.length; i++) {
747 colspan_sum += w.colspan;
748 if (w.width === undefined) {
749 var width = (i === row.length - 1 && colspan_sum === this.columns) ? max_width : Math.round(size_unit * w.colspan);
751 w.width = width + '%';
755 handle_node: function(node) {
757 if (node.tag == 'field') {
758 type = this.view.fields_view.fields[node.attrs.name] || {};
760 var widget = new (this.view.registry.get_any(
761 [node.attrs.widget, type.type, node.tag])) (this.view, node);
762 if (node.tag == 'field') {
763 if (!this.view.default_focus_field || node.attrs.default_focus == '1') {
764 this.view.default_focus_field = widget;
766 if (node.attrs.nolabel != '1') {
767 var label = new (this.view.registry.get_object('label')) (this.view, node);
768 label["for"] = widget;
769 this.add_widget(label, widget.colspan + 1);
772 this.add_widget(widget);
774 add_widget: function(widget, colspan) {
775 colspan = colspan || widget.colspan;
776 var current_row = this.table[this.table.length - 1];
777 if (current_row.length && (this.x + colspan) > this.columns) {
778 current_row = this.add_row();
780 current_row.push(widget);
781 this.x += widget.colspan;
786 openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
787 template: 'WidgetNotebook',
788 init: function(view, node) {
789 this._super(view, node);
791 for (var i = 0; i < node.children.length; i++) {
792 var n = node.children[i];
793 if (n.tag == "page") {
794 var page = new (this.view.registry.get_object('notebookpage'))(
795 this.view, n, this, this.pages.length);
796 this.pages.push(page);
802 this._super.apply(this, arguments);
803 this.$element.find('> ul > li').each(function (index, tab_li) {
804 var page = self.pages[index],
805 id = _.uniqueId(self.element_name + '-');
806 page.element_id = id;
807 $(tab_li).find('a').attr('href', '#' + id);
809 this.$element.find('> div').each(function (index, page) {
810 page.id = self.pages[index].element_id;
812 this.$element.tabs();
813 this.view.on_button_new.add_last(this.do_select_first_visible_tab);
815 do_select_first_visible_tab: function() {
816 for (var i = 0; i < this.pages.length; i++) {
817 var page = this.pages[i];
818 if (page.invisible === false) {
819 this.$element.tabs('select', page.index);
826 openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
827 template: 'WidgetNotebookPage',
828 init: function(view, node, notebook, index) {
829 this.notebook = notebook;
831 this.element_name = 'page_' + index;
832 this._super(view, node);
835 this._super.apply(this, arguments);
836 this.$element_tab = this.notebook.$element.find(
837 '> ul > li:eq(' + this.index + ')');
839 update_dom: function() {
840 if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) {
841 this.notebook.do_select_first_visible_tab();
843 this.$element_tab.toggle(!this.invisible);
844 this.$element.toggle(!this.invisible);
848 openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
849 template: 'WidgetSeparator',
850 init: function(view, node) {
851 this._super(view, node);
852 this.orientation = node.attrs.orientation || 'horizontal';
853 if (this.orientation === 'vertical') {
856 this.classname += '_' + this.orientation;
860 openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
861 template: 'WidgetButton',
862 init: function(view, node) {
863 this._super(view, node);
864 this.force_disabled = false;
866 // We don't have button key bindings in the webclient
867 this.string = this.string.replace(/_/g, '');
869 if (node.attrs.default_focus == '1') {
870 // TODO fme: provide enter key binding to widgets
871 this.view.default_focus_button = this;
875 this._super.apply(this, arguments);
876 this.$element.find("button").click(this.on_click);
878 on_click: function() {
880 this.force_disabled = true;
881 this.check_disable();
882 this.execute_action().always(function() {
883 self.force_disabled = false;
884 self.check_disable();
887 execute_action: function() {
889 var exec_action = function() {
890 if (self.node.attrs.confirm) {
891 var def = $.Deferred();
892 var dialog = $('<div>' + self.node.attrs.confirm + '</div>').dialog({
897 self.on_confirmed().then(function() {
900 $(this).dialog("close");
904 $(this).dialog("close");
908 return def.promise();
910 return self.on_confirmed();
913 if (!this.node.attrs.special && (this.view.dirty_for_user || !this.view.datarecord.id)) {
914 return this.view.recursive_save().pipe(exec_action);
916 return exec_action();
919 on_confirmed: function() {
922 return this.view.do_execute_action(
923 this.node.attrs, this.view.dataset, this.view.datarecord.id, function () {
927 update_dom: function() {
929 this.check_disable();
931 check_disable: function() {
932 if (this.force_disabled || !this.view.is_interactible_record()) {
933 this.$element.find("button").attr("disabled", "disabled");
934 this.$element.find("button").css("color", "grey");
936 this.$element.find("button").removeAttr("disabled");
937 this.$element.find("button").css("color", "");
942 openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
943 template: 'WidgetLabel',
944 init: function(view, node) {
945 this.element_name = 'label_' + node.attrs.name;
947 this._super(view, node);
949 // TODO fme: support for attrs.align
950 if (this.node.tag == 'label' && (this.node.attrs.colspan || (this.string && this.string.length > 32))) {
951 this.template = "WidgetParagraph";
952 this.colspan = parseInt(this.node.attrs.colspan || 1, 10);
956 this.decrease_max_width = 1;
960 render: function () {
961 if (this['for'] && this.type !== 'label') {
962 return QWeb.render(this.template, {widget: this['for']});
964 // Actual label widgets should not have a false and have type label
965 return QWeb.render(this.template, {widget: this});
970 this.$element.find("label").dblclick(function() {
971 var widget = self['for'] || self;
972 console.log(widget.element_class , widget);
978 openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{
980 * @constructs openerp.web.form.Field
981 * @extends openerp.web.form.Widget
986 init: function(view, node) {
987 this.name = node.attrs.name;
988 this.value = undefined;
989 view.fields[this.name] = this;
990 this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type;
991 this.element_name = "field_" + this.name + "_" + this.type;
993 this._super(view, node);
995 if (node.attrs.nolabel != '1' && this.colspan > 1) {
998 this.field = view.fields_view.fields[node.attrs.name] || {};
999 this.string = node.attrs.string || this.field.string;
1000 this.help = node.attrs.help || this.field.help;
1001 this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1';
1002 this.readonly = this.modifiers['readonly'] === true;
1003 this.required = this.modifiers['required'] === true;
1004 this.invalid = false;
1007 this.classname = 'oe_form_field_' + this.type;
1010 this._super.apply(this, arguments);
1011 if (this.field.translate) {
1012 this.view.translatable_fields.push(this);
1013 this.$element.find('.oe_field_translate').click(this.on_translate);
1016 set_value: function(value) {
1018 this.invalid = false;
1020 this.on_value_changed();
1022 set_value_from_ui: function() {
1023 this.on_value_changed();
1025 on_value_changed: function() {
1027 on_translate: function() {
1028 this.view.open_translate_dialog(this);
1030 get_value: function() {
1033 is_valid: function() {
1034 return !this.invalid;
1036 is_dirty: function() {
1037 return this.dirty && !this.readonly;
1039 get_on_change_value: function() {
1040 return this.get_value();
1042 update_dom: function() {
1043 this._super.apply(this, arguments);
1044 if (this.field.translate) {
1045 this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id);
1047 if (!this.disable_utility_classes) {
1048 this.$element.toggleClass('disabled', this.readonly);
1049 this.$element.toggleClass('required', this.required);
1050 if (this.view.show_invalid) {
1051 this.$element.toggleClass('invalid', !this.is_valid());
1055 on_ui_change: function() {
1056 this.dirty = this.view.dirty_for_user = true;
1058 if (this.is_valid()) {
1059 this.set_value_from_ui();
1060 this.view.do_onchange(this);
1061 this.view.on_form_changed();
1066 validate: function() {
1067 this.invalid = false;
1071 _build_view_fields_values: function() {
1072 var a_dataset = this.view.dataset;
1073 var fields_values = this.view.get_fields_values();
1074 var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {};
1075 fields_values.parent = parent_values;
1076 return fields_values;
1078 _build_eval_context: function() {
1079 var a_dataset = this.view.dataset;
1080 return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values());
1083 * Builds a new context usable for operations related to fields by merging
1084 * the fields'context with the action's context.
1086 build_context: function() {
1087 var f_context = this.field.context || {};
1088 if (!!f_context.__ref) {
1089 var fields_values = this._build_eval_context();
1090 f_context = new openerp.web.CompoundDomain(f_context).set_eval_context(fields_values);
1092 // maybe the default_get should only be used when we do a default_get?
1093 var v_contexts = _.compact([this.node.attrs.default_get || null,
1094 this.node.attrs.context || null]);
1095 var v_context = new openerp.web.CompoundContext();
1096 _.each(v_contexts, function(x) {v_context.add(x);});
1097 if (_.detect(v_contexts, function(x) {return !!x.__ref;})) {
1098 var fields_values = this._build_eval_context();
1099 v_context.set_eval_context(fields_values);
1101 // if there is a context on the node, overrides the model's context
1102 var ctx = v_contexts.length > 0 ? v_context : f_context;
1105 build_domain: function() {
1106 var f_domain = this.field.domain || [];
1107 var n_domain = this.node.attrs.domain || null;
1108 // if there is a domain on the node, overrides the model's domain
1109 var final_domain = n_domain !== null ? n_domain : f_domain;
1110 if (!(final_domain instanceof Array)) {
1111 var fields_values = this._build_eval_context();
1112 final_domain = new openerp.web.CompoundDomain(final_domain).set_eval_context(fields_values);
1114 return final_domain;
1118 openerp.web.form.FieldChar = openerp.web.form.Field.extend({
1119 template: 'FieldChar',
1121 this._super.apply(this, arguments);
1122 this.$element.find('input').change(this.on_ui_change);
1124 set_value: function(value) {
1125 this._super.apply(this, arguments);
1126 var show_value = openerp.web.format_value(value, this, '');
1127 this.$element.find('input').val(show_value);
1130 update_dom: function() {
1131 this._super.apply(this, arguments);
1132 this.$element.find('input').attr('disabled', this.readonly);
1134 set_value_from_ui: function() {
1135 this.value = openerp.web.parse_value(this.$element.find('input').val(), this);
1138 validate: function() {
1139 this.invalid = false;
1141 var value = openerp.web.parse_value(this.$element.find('input').val(), this, '');
1142 this.invalid = this.required && value === '';
1144 this.invalid = true;
1148 this.$element.find('input').focus();
1152 openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
1153 template: 'FieldEmail',
1155 this._super.apply(this, arguments);
1156 this.$element.find('button').click(this.on_button_clicked);
1158 on_button_clicked: function() {
1159 if (!this.value || !this.is_valid()) {
1160 this.notification.warn("E-mail error", "Can't send email to invalid e-mail address");
1162 location.href = 'mailto:' + this.value;
1167 openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
1168 template: 'FieldUrl',
1170 this._super.apply(this, arguments);
1171 this.$element.find('button').click(this.on_button_clicked);
1173 on_button_clicked: function() {
1175 this.notification.warn("Resource error", "This resource is empty");
1177 window.open(this.value);
1182 openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
1183 set_value: function(value) {
1184 if (value === false || value === undefined) {
1185 // As in GTK client, floats default to 0
1189 this._super.apply(this, [value]);
1193 openerp.web.DateTimeWidget = openerp.web.Widget.extend({
1194 template: "web.datetimepicker",
1195 jqueryui_object: 'datetimepicker',
1196 type_of_date: "datetime",
1199 this.$element.find('input').change(this.on_change);
1201 onSelect: this.on_picker_select,
1205 showButtonPanel: false
1207 this.$element.find('img.oe_datepicker_trigger').click(function() {
1208 if (!self.readonly) {
1209 self.picker('setDate', self.value || new Date());
1210 self.$element.find('.oe_datepicker').toggle();
1213 this.$element.find('.ui-datepicker-inline').removeClass('ui-widget-content ui-corner-all');
1214 this.$element.find('button.oe_datepicker_close').click(function() {
1215 self.$element.find('.oe_datepicker').hide();
1217 this.set_readonly(false);
1220 picker: function() {
1221 return $.fn[this.jqueryui_object].apply(this.$element.find('.oe_datepicker_container'), arguments);
1223 on_picker_select: function(text, instance) {
1224 var date = this.picker('getDate');
1225 this.$element.find('input').val(date ? this.format_client(date) : '').change();
1227 set_value: function(value) {
1229 this.$element.find('input').val(value ? this.format_client(value) : '');
1231 get_value: function() {
1234 set_value_from_ui: function() {
1235 var value = this.$element.find('input').val() || false;
1236 this.value = this.parse_client(value);
1238 set_readonly: function(readonly) {
1239 this.readonly = readonly;
1240 this.$element.find('input').attr('disabled', this.readonly);
1241 this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
1243 is_valid: function(required) {
1244 var value = this.$element.find('input').val();
1249 this.parse_client(value);
1257 this.$element.find('input').focus();
1259 parse_client: function(v) {
1260 return openerp.web.parse_value(v, {"widget": this.type_of_date});
1262 format_client: function(v) {
1263 return openerp.web.format_value(v, {"widget": this.type_of_date});
1265 on_change: function() {
1266 if (this.is_valid()) {
1267 this.set_value_from_ui();
1272 openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
1273 jqueryui_object: 'datepicker',
1274 type_of_date: "date",
1275 on_picker_select: function(text, instance) {
1276 this._super(text, instance);
1277 this.$element.find('.oe_datepicker').hide();
1281 openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
1282 template: "EmptyComponent",
1283 build_widget: function() {
1284 return new openerp.web.DateTimeWidget(this);
1288 this._super.apply(this, arguments);
1289 this.datewidget = this.build_widget();
1290 this.datewidget.on_change.add(this.on_ui_change);
1291 this.datewidget.appendTo(this.$element);
1293 set_value: function(value) {
1295 this.datewidget.set_value(value);
1297 get_value: function() {
1298 return this.datewidget.get_value();
1300 update_dom: function() {
1301 this._super.apply(this, arguments);
1302 this.datewidget.set_readonly(this.readonly);
1304 validate: function() {
1305 this.invalid = !this.datewidget.is_valid(this.required);
1308 this.datewidget.focus();
1312 openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
1313 build_widget: function() {
1314 return new openerp.web.DateWidget(this);
1318 openerp.web.form.FieldText = openerp.web.form.Field.extend({
1319 template: 'FieldText',
1321 this._super.apply(this, arguments);
1322 this.$element.find('textarea').change(this.on_ui_change);
1324 set_value: function(value) {
1325 this._super.apply(this, arguments);
1326 var show_value = openerp.web.format_value(value, this, '');
1327 this.$element.find('textarea').val(show_value);
1329 update_dom: function() {
1330 this._super.apply(this, arguments);
1331 this.$element.find('textarea').attr('disabled', this.readonly);
1333 set_value_from_ui: function() {
1334 this.value = openerp.web.parse_value(this.$element.find('textarea').val(), this);
1337 validate: function() {
1338 this.invalid = false;
1340 var value = openerp.web.parse_value(this.$element.find('textarea').val(), this, '');
1341 this.invalid = this.required && value === '';
1343 this.invalid = true;
1347 this.$element.find('textarea').focus();
1351 openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
1352 template: 'FieldBoolean',
1355 this._super.apply(this, arguments);
1356 this.$element.find('input').click(function() {
1357 if ($(this).is(':checked') != self.value) {
1358 self.on_ui_change();
1362 set_value: function(value) {
1363 this._super.apply(this, arguments);
1364 this.$element.find('input')[0].checked = value;
1366 set_value_from_ui: function() {
1367 this.value = this.$element.find('input').is(':checked');
1370 update_dom: function() {
1371 this._super.apply(this, arguments);
1372 this.$element.find('input').attr('disabled', this.readonly);
1374 validate: function() {
1375 this.invalid = this.required && !this.$element.find('input').is(':checked');
1378 this.$element.find('input').focus();
1382 openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
1383 template: 'FieldProgressBar',
1385 this._super.apply(this, arguments);
1386 this.$element.find('div').progressbar({
1388 disabled: this.readonly
1391 set_value: function(value) {
1392 this._super.apply(this, arguments);
1393 var show_value = Number(value);
1394 if (isNaN(show_value)) {
1397 this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(show_value + '%');
1401 openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({
1402 // to replace view editor
1405 openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
1406 template: 'FieldSelection',
1407 init: function(view, node) {
1409 this._super(view, node);
1410 this.values = this.field.selection;
1411 _.each(this.values, function(v, i) {
1412 if (v[0] === false && v[1] === '') {
1413 self.values.splice(i, 1);
1416 this.values.unshift([false, '']);
1419 // Flag indicating whether we're in an event chain containing a change
1420 // event on the select, in order to know what to do on keyup[RETURN]:
1421 // * If the user presses [RETURN] as part of changing the value of a
1422 // selection, we should just let the value change and not let the
1423 // event broadcast further (e.g. to validating the current state of
1424 // the form in editable list view, which would lead to saving the
1425 // current row or switching to the next one)
1426 // * If the user presses [RETURN] with a select closed (side-effect:
1427 // also if the user opened the select and pressed [RETURN] without
1428 // changing the selected value), takes the action as validating the
1430 var ischanging = false;
1431 this._super.apply(this, arguments);
1432 this.$element.find('select')
1433 .change(this.on_ui_change)
1434 .change(function () { ischanging = true; })
1435 .click(function () { ischanging = false; })
1436 .keyup(function (e) {
1437 if (e.which !== 13 || !ischanging) { return; }
1438 e.stopPropagation();
1442 set_value: function(value) {
1443 value = value === null ? false : value;
1444 value = value instanceof Array ? value[0] : value;
1447 for (var i = 0, ii = this.values.length; i < ii; i++) {
1448 if (this.values[i][0] === value) index = i;
1450 this.$element.find('select')[0].selectedIndex = index;
1452 set_value_from_ui: function() {
1453 this.value = this.values[this.$element.find('select')[0].selectedIndex][0];
1456 update_dom: function() {
1457 this._super.apply(this, arguments);
1458 this.$element.find('select').attr('disabled', this.readonly);
1460 validate: function() {
1461 var value = this.values[this.$element.find('select')[0].selectedIndex];
1462 this.invalid = !(value && !(this.required && value[0] === false));
1465 this.$element.find('select').focus();
1469 // jquery autocomplete tweak to allow html
1471 var proto = $.ui.autocomplete.prototype,
1472 initSource = proto._initSource;
1474 function filter( array, term ) {
1475 var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
1476 return $.grep( array, function(value) {
1477 return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
1482 _initSource: function() {
1483 if ( this.options.html && $.isArray(this.options.source) ) {
1484 this.source = function( request, response ) {
1485 response( filter( this.options.source, request.term ) );
1488 initSource.call( this );
1492 _renderItem: function( ul, item) {
1493 return $( "<li></li>" )
1494 .data( "item.autocomplete", item )
1495 .append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) )
1501 openerp.web.form.dialog = function(content, options) {
1502 options = _.extend({
1509 options.autoOpen = true;
1510 var dialog = new openerp.web.Dialog(null, options);
1511 dialog.$dialog = $(content).dialog(dialog.dialog_options);
1512 return dialog.$dialog;
1515 openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
1516 template: 'FieldMany2One',
1517 init: function(view, node) {
1518 this._super(view, node);
1521 this.cm_id = _.uniqueId('m2o_cm_');
1522 this.last_search = [];
1523 this.tmp_value = undefined;
1528 this.$input = this.$element.find("input");
1529 this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
1530 this.$menu_btn = this.$element.find(".oe-m2o-cm-button");
1533 var init_context_menu_def = $.Deferred().then(function(e) {
1534 var rdataset = new openerp.web.DataSetStatic(self, "ir.values", self.build_context());
1535 rdataset.call("get", ['action', 'client_action_relate',
1536 [[self.field.relation, false]], false, rdataset.get_context()], false, 0)
1537 .then(function(result) {
1538 self.related_entries = result;
1540 var $cmenu = $("#" + self.cm_id);
1541 $cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self}));
1543 bindings[self.cm_id + "_search"] = function() {
1544 self._search_create_popup("search");
1546 bindings[self.cm_id + "_create"] = function() {
1547 self._search_create_popup("form");
1549 bindings[self.cm_id + "_open"] = function() {
1553 var pop = new openerp.web.form.FormOpenPopup(self.view);
1554 pop.show_element(self.field.relation, self.value[0],self.build_context(), {});
1555 pop.on_write_completed.add_last(function() {
1556 self.set_value(self.value[0]);
1559 _.each(_.range(self.related_entries.length), function(i) {
1560 bindings[self.cm_id + "_related_" + i] = function() {
1561 self.open_related(self.related_entries[i]);
1564 var cmenu = self.$menu_btn.contextMenu(self.cm_id, {'leftClickToo': true,
1565 bindings: bindings, itemStyle: {"color": ""},
1566 onContextMenu: function() {
1568 $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").removeClass("oe-m2o-disabled-cm");
1570 $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").addClass("oe-m2o-disabled-cm");
1573 }, menuStyle: {width: "200px"}
1575 setTimeout(function() {self.$menu_btn.trigger(e);}, 0);
1578 var ctx_callback = function(e) {init_context_menu_def.resolve(e); e.preventDefault()};
1579 this.$menu_btn.bind('contextmenu', ctx_callback);
1580 this.$menu_btn.click(ctx_callback);
1582 // some behavior for input
1583 this.$input.keyup(function() {
1584 if (self.$input.val() === "") {
1585 self._change_int_value(null);
1586 } else if (self.value === null || (self.value && self.$input.val() !== self.value[1])) {
1587 self._change_int_value(undefined);
1590 this.$drop_down.click(function() {
1591 if (self.$input.autocomplete("widget").is(":visible")) {
1592 self.$input.autocomplete("close");
1595 self.$input.autocomplete("search", "");
1597 self.$input.autocomplete("search");
1599 self.$input.focus();
1602 var anyoneLoosesFocus = function() {
1603 if (!self.$input.is(":focus") &&
1604 !self.$input.autocomplete("widget").is(":visible") &&
1606 if (self.value === undefined && self.last_search.length > 0) {
1607 self._change_int_ext_value(self.last_search[0]);
1609 self._change_int_ext_value(null);
1613 this.$input.focusout(anyoneLoosesFocus);
1615 var isSelecting = false;
1617 this.$input.autocomplete({
1618 source: function(req, resp) { self.get_search_result(req, resp); },
1619 select: function(event, ui) {
1623 self._change_int_value([item.id, item.name]);
1624 } else if (item.action) {
1625 self._change_int_value(undefined);
1630 focus: function(e, ui) {
1634 close: anyoneLoosesFocus,
1638 // used to correct a bug when selecting an element by pushing 'enter' in an editable list
1639 this.$input.keyup(function(e) {
1640 if (e.which === 13) {
1642 e.stopPropagation();
1644 isSelecting = false;
1647 // autocomplete component content handling
1648 get_search_result: function(request, response) {
1649 var search_val = request.term;
1652 var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.build_context());
1654 dataset.name_search(search_val, self.build_domain(), 'ilike',
1655 this.limit + 1, function(data) {
1656 self.last_search = data;
1657 // possible selections for the m2o
1658 var values = _.map(data, function(x) {
1659 return {label: $('<span />').text(x[1]).html(), name:x[1], id:x[0]};
1662 // search more... if more results that max
1663 if (values.length > self.limit) {
1664 values = values.slice(0, self.limit);
1665 values.push({label: _t("<em>Â Â Â Search More...</em>"), action: function() {
1666 dataset.name_search(search_val, self.build_domain(), 'ilike'
1667 , false, function(data) {
1668 self._change_int_value(null);
1669 self._search_create_popup("search", data);
1674 var raw_result = _(data.result).map(function(x) {return x[1];});
1675 if (search_val.length > 0 &&
1676 !_.include(raw_result, search_val) &&
1677 (!self.value || search_val !== self.value[1])) {
1678 values.push({label: _.sprintf(_t('<em>Â Â Â Create "<strong>%s</strong>"</em>'),
1679 $('<span />').text(search_val).html()), action: function() {
1680 self._quick_create(search_val);
1684 values.push({label: _t("<em>Â Â Â Create and Edit...</em>"), action: function() {
1685 self._change_int_value(null);
1686 self._search_create_popup("form", undefined, {"default_name": search_val});
1692 _quick_create: function(name) {
1694 var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.build_context());
1695 dataset.name_create(name, function(data) {
1696 self._change_int_ext_value(data);
1697 }).fail(function(error, event) {
1698 event.preventDefault();
1699 self._change_int_value(null);
1700 self._search_create_popup("form", undefined, {"default_name": name});
1703 // all search/create popup handling
1704 _search_create_popup: function(view, ids, context) {
1706 var pop = new openerp.web.form.SelectCreatePopup(this);
1707 pop.select_element(self.field.relation,{
1708 initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
1710 disable_multiple_selection: true
1711 }, self.build_domain(),
1712 new openerp.web.CompoundContext(self.build_context(), context || {}));
1713 pop.on_select_elements.add(function(element_ids) {
1714 var dataset = new openerp.web.DataSetStatic(self, self.field.relation, self.build_context());
1715 dataset.name_get([element_ids[0]], function(data) {
1716 self._change_int_ext_value(data[0]);
1720 _change_int_ext_value: function(value) {
1721 this._change_int_value(value);
1722 this.$input.val(this.value ? this.value[1] : "");
1724 _change_int_value: function(value) {
1726 var back_orig_value = this.original_value;
1727 if (this.value === null || this.value) {
1728 this.original_value = this.value;
1730 if (back_orig_value === undefined) { // first use after a set_value()
1733 if (this.value !== undefined && ((back_orig_value ? back_orig_value[0] : null)
1734 !== (this.value ? this.value[0] : null))) {
1735 this.on_ui_change();
1738 set_value: function(value) {
1739 value = value || null;
1740 this.invalid = false;
1742 this.tmp_value = value;
1744 self.on_value_changed();
1745 var real_set_value = function(rval) {
1746 self.tmp_value = undefined;
1748 self.original_value = undefined;
1749 self._change_int_ext_value(rval);
1751 if(typeof(value) === "number") {
1752 var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.build_context());
1753 dataset.name_get([value], function(data) {
1754 real_set_value(data[0]);
1755 }).fail(function() {self.tmp_value = undefined;});
1757 setTimeout(function() {real_set_value(value);}, 0);
1760 get_value: function() {
1761 if (this.tmp_value !== undefined) {
1762 if (this.tmp_value instanceof Array) {
1763 return this.tmp_value[0];
1765 return this.tmp_value ? this.tmp_value : false;
1767 if (this.value === undefined)
1768 return this.original_value ? this.original_value[0] : false;
1769 return this.value ? this.value[0] : false;
1771 validate: function() {
1772 this.invalid = false;
1773 var val = this.tmp_value !== undefined ? this.tmp_value : this.value;
1775 this.invalid = this.required;
1778 open_related: function(related) {
1782 var additional_context = {
1783 active_id: self.value[0],
1784 active_ids: [self.value[0]],
1785 active_model: self.field.relation
1787 self.rpc("/web/action/load", {
1788 action_id: related[2].id,
1789 context: additional_context
1790 }, function(result) {
1791 result.result.context = _.extend(result.result.context || {}, additional_context);
1792 self.do_action(result.result);
1795 focus: function () {
1796 this.$input.focus();
1801 # Values: (0, 0, { fields }) create
1802 # (1, ID, { fields }) update
1803 # (2, ID) remove (delete)
1804 # (3, ID) unlink one (target id or target of relation)
1806 # (5) unlink all (only valid for one2many)
1811 'create': function (values) {
1812 return [commands.CREATE, false, values];
1814 // (1, id, {values})
1816 'update': function (id, values) {
1817 return [commands.UPDATE, id, values];
1821 'delete': function (id) {
1822 return [commands.DELETE, id, false];
1824 // (3, id[, _]) removes relation, but not linked record itself
1826 'forget': function (id) {
1827 return [commands.FORGET, id, false];
1831 'link_to': function (id) {
1832 return [commands.LINK_TO, id, false];
1836 'delete_all': function () {
1837 return [5, false, false];
1839 // (6, _, ids) replaces all linked records with provided ids
1841 'replace_with': function (ids) {
1842 return [6, false, ids];
1845 openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
1846 template: 'FieldOne2Many',
1847 multi_selection: false,
1848 init: function(view, node) {
1849 this._super(view, node);
1850 this.is_started = $.Deferred();
1851 this.form_last_update = $.Deferred();
1852 this.disable_utility_classes = true;
1855 this._super.apply(this, arguments);
1859 this.dataset = new openerp.web.form.One2ManyDataSet(this, this.field.relation);
1860 this.dataset.o2m = this;
1861 this.dataset.parent_view = this.view;
1862 this.dataset.on_change.add_last(function() {
1863 self.on_ui_change();
1866 var modes = this.node.attrs.mode;
1867 modes = !!modes ? modes.split(",") : ["tree", "form"];
1869 _.each(modes, function(mode) {
1872 view_type: mode == "tree" ? "list" : mode,
1873 options: { sidebar : false }
1875 if (self.field.views && self.field.views[mode]) {
1876 view.embedded_view = self.field.views[mode];
1878 if(view.view_type === "list") {
1879 view.options.selectable = self.multi_selection;
1880 } else if (view.view_type === "form") {
1881 view.options.not_interactible_on_create = true;
1887 this.viewmanager = new openerp.web.ViewManager(this, this.dataset, views);
1888 this.viewmanager.registry = openerp.web.views.clone({
1889 list: 'openerp.web.form.One2ManyListView',
1890 form: 'openerp.web.form.One2ManyFormView'
1892 var once = $.Deferred().then(function() {
1893 self.form_last_update.resolve();
1895 this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
1896 if (view_type == "list") {
1897 controller.o2m = self;
1898 } else if (view_type == "form") {
1899 controller.on_record_loaded.add_last(function() {
1902 controller.on_pager_action.add_first(function() {
1903 self.save_form_view();
1905 controller.$element.find(".oe_form_button_save_edit").hide();
1906 } else if (view_type == "graph") {
1907 self.reload_current_view()
1909 self.is_started.resolve();
1911 this.viewmanager.on_mode_switch.add_first(function() {
1912 self.save_form_view();
1914 setTimeout(function () {
1915 self.viewmanager.appendTo(self.$element);
1918 reload_current_view: function() {
1920 var view = self.viewmanager.views[self.viewmanager.active_view].controller;
1921 if(self.viewmanager.active_view === "list") {
1922 view.reload_content();
1923 } else if (self.viewmanager.active_view === "form") {
1924 if (this.dataset.index === null && this.dataset.ids.length >= 1) {
1925 this.dataset.index = 0;
1927 this.form_last_update.then(function() {
1928 this.form_last_update = view.do_show();
1930 } else if (self.viewmanager.active_view === "graph") {
1931 view.do_search(this.build_domain(), this.dataset.get_context(), []);
1934 set_value: function(value) {
1935 value = value || [];
1937 this.dataset.reset_ids([]);
1938 if(value.length >= 1 && value[0] instanceof Array) {
1940 _.each(value, function(command) {
1941 var obj = {values: command[2]};
1942 switch (command[0]) {
1943 case commands.CREATE:
1944 obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
1946 self.dataset.to_create.push(obj);
1947 self.dataset.cache.push(_.clone(obj));
1950 case commands.UPDATE:
1951 obj['id'] = command[1];
1952 self.dataset.to_write.push(obj);
1953 self.dataset.cache.push(_.clone(obj));
1956 case commands.DELETE:
1957 self.dataset.to_delete.push({id: command[1]});
1959 case commands.LINK_TO:
1960 ids.push(command[1]);
1962 case commands.DELETE_ALL:
1963 self.dataset.delete_all = true;
1968 this.dataset.set_ids(ids);
1969 } else if (value.length >= 1 && typeof(value[0]) === "object") {
1971 this.dataset.delete_all = true;
1972 _.each(value, function(command) {
1973 var obj = {values: command};
1974 obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
1976 self.dataset.to_create.push(obj);
1977 self.dataset.cache.push(_.clone(obj));
1981 this.dataset.set_ids(ids);
1984 this.dataset.reset_ids(value);
1986 if (this.dataset.index === null && this.dataset.ids.length > 0) {
1987 this.dataset.index = 0;
1989 $.when(this.is_started).then(function() {
1990 self.reload_current_view();
1993 get_value: function() {
1997 var val = this.dataset.delete_all ? [commands.delete_all()] : [];
1998 val = val.concat(_.map(this.dataset.ids, function(id) {
1999 var alter_order = _.detect(self.dataset.to_create, function(x) {return x.id === id;});
2001 return commands.create(alter_order.values);
2003 alter_order = _.detect(self.dataset.to_write, function(x) {return x.id === id;});
2005 return commands.update(alter_order.id, alter_order.values);
2007 return commands.link_to(id);
2009 return val.concat(_.map(
2010 this.dataset.to_delete, function(x) {
2011 return commands['delete'](x.id);}));
2013 save_form_view: function() {
2014 if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
2015 this.viewmanager.views[this.viewmanager.active_view] &&
2016 this.viewmanager.views[this.viewmanager.active_view].controller) {
2017 var view = this.viewmanager.views[this.viewmanager.active_view].controller;
2018 if (this.viewmanager.active_view === "form") {
2019 var res = $.when(view.do_save());
2020 if (!res.isResolved() && !res.isRejected()) {
2021 throw "Asynchronous get_value() is not supported in form view.";
2028 is_valid: function() {
2030 return this._super();
2032 validate: function() {
2033 this.invalid = false;
2034 var view = this.viewmanager.views[this.viewmanager.active_view].controller;
2035 if (this.viewmanager.active_view === "form") {
2036 for (var f in view.fields) {
2038 if (!f.is_valid()) {
2039 this.invalid = true;
2045 is_dirty: function() {
2046 this.save_form_view();
2047 return this._super();
2049 update_dom: function() {
2050 this._super.apply(this, arguments);
2051 this.$element.toggleClass('disabled', this.readonly);
2055 openerp.web.form.One2ManyDataSet = openerp.web.BufferedDataSet.extend({
2056 get_context: function() {
2057 this.context = this.o2m.build_context();
2058 return this.context;
2062 openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
2065 openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
2066 do_add_record: function () {
2067 if (this.options.editable) {
2068 this._super.apply(this, arguments);
2071 var pop = new openerp.web.form.SelectCreatePopup(this);
2072 pop.on_default_get.add(self.dataset.on_default_get);
2073 pop.select_element(self.o2m.field.relation,{
2074 initial_view: "form",
2075 alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
2076 create_function: function(data, callback, error_callback) {
2077 return self.o2m.dataset.create(data).then(function(r) {
2078 self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result]));
2079 self.o2m.dataset.on_change();
2080 }).then(callback, error_callback);
2082 read_function: function() {
2083 return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
2085 parent_view: self.o2m.view,
2086 form_view_options: {'not_interactible_on_create':true}
2087 }, self.o2m.build_domain(), self.o2m.build_context());
2088 pop.on_select_elements.add_last(function() {
2089 self.o2m.reload_current_view();
2093 do_activate_record: function(index, id) {
2095 var pop = new openerp.web.form.FormOpenPopup(self.o2m.view);
2096 pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(),{
2098 alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
2099 parent_view: self.o2m.view,
2100 read_function: function() {
2101 return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
2103 form_view_options: {'not_interactible_on_create':true}
2105 pop.on_write.add(function(id, data) {
2106 self.o2m.dataset.write(id, data, {}, function(r) {
2107 self.o2m.reload_current_view();
2113 openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
2114 template: 'FieldMany2Many',
2115 multi_selection: false,
2116 init: function(view, node) {
2117 this._super(view, node);
2118 this.list_id = _.uniqueId("many2many");
2119 this.is_started = $.Deferred();
2122 this._super.apply(this, arguments);
2126 this.dataset = new openerp.web.form.Many2ManyDataSet(this, this.field.relation);
2127 this.dataset.m2m = this;
2128 this.dataset.on_unlink.add_last(function(ids) {
2129 self.on_ui_change();
2132 this.list_view = new openerp.web.form.Many2ManyListView(this, this.dataset, false, {
2134 'selectable': self.multi_selection
2136 this.list_view.m2m_field = this;
2137 this.list_view.on_loaded.add_last(function() {
2138 self.is_started.resolve();
2140 setTimeout(function () {
2141 self.list_view.appendTo($("#" + self.list_id));
2144 set_value: function(value) {
2145 value = value || [];
2146 if (value.length >= 1 && value[0] instanceof Array) {
2147 value = value[0][2];
2150 this.dataset.set_ids(value);
2152 $.when(this.is_started).then(function() {
2153 self.list_view.reload_content();
2156 get_value: function() {
2157 return [commands.replace_with(this.dataset.ids)];
2159 validate: function() {
2160 this.invalid = false;
2165 openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({
2166 get_context: function() {
2167 this.context = this.m2m.build_context();
2168 return this.context;
2174 * @extends openerp.web.ListView
2176 openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends openerp.web.form.Many2ManyListView# */{
2177 do_add_record: function () {
2178 var pop = new openerp.web.form.SelectCreatePopup(this);
2179 pop.select_element(this.model, {},
2180 new openerp.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]),
2181 this.m2m_field.build_context());
2183 pop.on_select_elements.add(function(element_ids) {
2184 _.each(element_ids, function(element_id) {
2185 if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
2186 self.dataset.set_ids([].concat(self.dataset.ids, [element_id]));
2187 self.m2m_field.on_ui_change();
2188 self.reload_content();
2193 do_activate_record: function(index, id) {
2195 var pop = new openerp.web.form.FormOpenPopup(this);
2196 pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {});
2197 pop.on_write_completed.add_last(function() {
2198 self.reload_content();
2205 * @extends openerp.web.OldWidget
2207 openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.SelectCreatePopup# */{
2208 identifier_prefix: "selectcreatepopup",
2209 template: "SelectCreatePopup",
2213 * - initial_view: form or search (default search)
2214 * - disable_multiple_selection
2215 * - alternative_form_view
2216 * - create_function (defaults to a naive saving behavior)
2218 * - form_view_options
2219 * - list_view_options
2222 select_element: function(model, options, domain, context) {
2225 this.domain = domain || [];
2226 this.context = context || {};
2227 this.options = _.defaults(options || {}, {"initial_view": "search", "create_function": function() {
2228 return self.create_row.apply(self, arguments);
2229 }, read_function: null});
2230 this.initial_ids = this.options.initial_ids;
2231 this.created_elements = [];
2232 openerp.web.form.dialog(this.render(), {close:function() {
2240 this.dataset = new openerp.web.ProxyDataSet(this, this.model,
2242 this.dataset.create_function = function() {
2243 return self.options.create_function.apply(null, arguments).then(function(r) {
2244 self.created_elements.push(r.result);
2247 this.dataset.write_function = function() {
2248 return self.write_row.apply(self, arguments);
2250 this.dataset.read_function = this.options.read_function;
2251 this.dataset.parent_view = this.options.parent_view;
2252 this.dataset.on_default_get.add(this.on_default_get);
2253 if (this.options.initial_view == "search") {
2254 this.setup_search_view();
2259 setup_search_view: function() {
2261 if (this.searchview) {
2262 this.searchview.stop();
2264 this.searchview = new openerp.web.SearchView(this,
2265 this.dataset, false, {
2266 "selectable": !this.options.disable_multiple_selection,
2269 this.searchview.on_search.add(function(domains, contexts, groupbys) {
2270 if (self.initial_ids) {
2271 self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]),
2272 contexts, groupbys);
2273 self.initial_ids = undefined;
2275 self.do_search(domains.concat([self.domain]), contexts, groupbys);
2278 this.searchview.on_loaded.add_last(function () {
2279 var $buttons = self.searchview.$element.find(".oe_search-view-buttons");
2280 $buttons.append(QWeb.render("SelectCreatePopup.search.buttons"));
2281 var $cbutton = $buttons.find(".oe_selectcreatepopup-search-close");
2282 $cbutton.click(function() {
2285 var $sbutton = $buttons.find(".oe_selectcreatepopup-search-select");
2286 if(self.options.disable_multiple_selection) {
2289 $sbutton.click(function() {
2290 self.on_select_elements(self.selected_ids);
2293 self.view_list = new openerp.web.form.SelectCreateListView(self,
2294 self.dataset, false,
2295 _.extend({'deletable': false}, self.options.list_view_options || {}));
2296 self.view_list.popup = self;
2297 self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() {
2298 self.view_list.do_show();
2299 }).pipe(function() {
2300 self.searchview.do_search();
2303 this.searchview.appendTo($("#" + this.element_id + "_search"));
2305 do_search: function(domains, contexts, groupbys) {
2307 this.rpc('/web/session/eval_domain_and_context', {
2308 domains: domains || [],
2309 contexts: contexts || [],
2310 group_by_seq: groupbys || []
2311 }, function (results) {
2312 self.view_list.do_search(results.domain, results.context, results.group_by);
2315 create_row: function() {
2317 var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
2318 wdataset.parent_view = this.options.parent_view;
2319 return wdataset.create.apply(wdataset, arguments);
2321 write_row: function() {
2323 var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
2324 wdataset.parent_view = this.options.parent_view;
2325 return wdataset.write.apply(wdataset, arguments);
2327 on_select_elements: function(element_ids) {
2329 on_click_element: function(ids) {
2330 this.selected_ids = ids || [];
2331 if(this.selected_ids.length > 0) {
2332 this.$element.find(".oe_selectcreatepopup-search-select").removeAttr('disabled');
2334 this.$element.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled");
2337 new_object: function() {
2339 if (this.searchview) {
2340 this.searchview.hide();
2342 if (this.view_list) {
2343 this.view_list.$element.hide();
2345 this.dataset.index = null;
2346 this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
2347 if (this.options.alternative_form_view) {
2348 this.view_form.set_embedded_view(this.options.alternative_form_view);
2350 this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form"));
2351 this.view_form.on_loaded.add_last(function() {
2352 var $buttons = self.view_form.$element.find(".oe_form_buttons");
2353 $buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self}));
2354 var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save-new");
2355 $nbutton.click(function() {
2356 $.when(self.view_form.do_save()).then(function() {
2357 self.view_form.on_button_new();
2360 var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save");
2361 $nbutton.click(function() {
2362 $.when(self.view_form.do_save()).then(function() {
2366 var $cbutton = $buttons.find(".oe_selectcreatepopup-form-close");
2367 $cbutton.click(function() {
2371 this.view_form.do_show();
2373 check_exit: function() {
2374 if (this.created_elements.length > 0) {
2375 this.on_select_elements(this.created_elements);
2379 on_default_get: function(res) {}
2382 openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({
2383 do_add_record: function () {
2384 this.popup.new_object();
2386 select_record: function(index) {
2387 this.popup.on_select_elements([this.dataset.ids[index]]);
2390 do_select: function(ids, records) {
2391 this._super(ids, records);
2392 this.popup.on_click_element(ids);
2398 * @extends openerp.web.OldWidget
2400 openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.FormOpenPopup# */{
2401 identifier_prefix: "formopenpopup",
2402 template: "FormOpenPopup",
2405 * - alternative_form_view
2406 * - auto_write (default true)
2409 * - form_view_options
2411 show_element: function(model, row_id, context, options) {
2413 this.row_id = row_id;
2414 this.context = context || {};
2415 this.options = _.defaults(options || {}, {"auto_write": true});
2416 jQuery(this.render()).dialog({title: '',
2424 this.dataset = new openerp.web.form.FormOpenDataset(this, this.model, this.context);
2425 this.dataset.fop = this;
2426 this.dataset.ids = [this.row_id];
2427 this.dataset.index = 0;
2428 this.dataset.parent_view = this.options.parent_view;
2429 this.setup_form_view();
2431 on_write: function(id, data) {
2433 if (!this.options.auto_write)
2436 var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
2437 wdataset.parent_view = this.options.parent_view;
2438 wdataset.write(id, data, {}, function(r) {
2439 self.on_write_completed();
2442 on_write_completed: function() {},
2443 setup_form_view: function() {
2445 this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
2446 if (this.options.alternative_form_view) {
2447 this.view_form.set_embedded_view(this.options.alternative_form_view);
2449 this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form"));
2450 this.view_form.on_loaded.add_last(function() {
2451 var $buttons = self.view_form.$element.find(".oe_form_buttons");
2452 $buttons.html(QWeb.render("FormOpenPopup.form.buttons"));
2453 var $nbutton = $buttons.find(".oe_formopenpopup-form-save");
2454 $nbutton.click(function() {
2455 self.view_form.do_save();
2457 var $cbutton = $buttons.find(".oe_formopenpopup-form-close");
2458 $cbutton.click(function() {
2461 self.view_form.do_show();
2463 this.dataset.on_write.add(this.on_write);
2467 openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({
2468 read_ids: function() {
2469 if (this.fop.options.read_function) {
2470 return this.fop.options.read_function.apply(null, arguments);
2472 return this._super.apply(this, arguments);
2477 openerp.web.form.FieldReference = openerp.web.form.Field.extend({
2478 template: 'FieldReference',
2479 init: function(view, node) {
2480 this._super(view, node);
2481 this.fields_view = {
2484 selection: view.fields_view.fields[this.name].selection
2491 this.get_fields_values = view.get_fields_values;
2492 this.do_onchange = this.on_form_changed = this.on_nop;
2495 this.selection = new openerp.web.form.FieldSelection(this, { attrs: {
2499 this.selection.on_value_changed.add_last(this.on_selection_changed);
2500 this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: {
2505 on_nop: function() {
2507 on_selection_changed: function() {
2508 this.m2o.field.relation = this.selection.get_value();
2509 this.m2o.set_value(null);
2513 this.selection.start();
2516 is_valid: function() {
2517 return this.required === false || typeof(this.get_value()) === 'string';
2519 is_dirty: function() {
2520 return this.selection.is_dirty() || this.m2o.is_dirty();
2522 set_value: function(value) {
2524 if (typeof(value) === 'string') {
2525 var vals = value.split(',');
2526 this.selection.set_value(vals[0]);
2527 this.m2o.set_value(parseInt(vals[1], 10));
2530 get_value: function() {
2531 var model = this.selection.get_value(),
2532 id = this.m2o.get_value();
2533 if (typeof(model) === 'string' && typeof(id) === 'number') {
2534 return model + ',' + id;
2541 openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
2542 init: function(view, node) {
2543 this._super(view, node);
2544 this.iframe = this.element_id + '_iframe';
2545 this.binary_value = false;
2548 this._super.apply(this, arguments);
2549 this.$element.find('input.oe-binary-file').change(this.on_file_change);
2550 this.$element.find('button.oe-binary-file-save').click(this.on_save_as);
2551 this.$element.find('.oe-binary-file-clear').click(this.on_clear);
2553 human_filesize : function(size) {
2554 var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
2556 while (size >= 1024) {
2560 return size.toFixed(2) + ' ' + units[i];
2562 on_file_change: function(e) {
2563 // TODO: on modern browsers, we could directly read the file locally on client ready to be used on image cropper
2564 // http://www.html5rocks.com/tutorials/file/dndfiles/
2565 // http://deepliquid.com/projects/Jcrop/demos.php?demo=handler
2566 window[this.iframe] = this.on_file_uploaded;
2567 if ($(e.target).val() != '') {
2568 this.$element.find('form.oe-binary-form input[name=session_id]').val(this.session.session_id);
2569 this.$element.find('form.oe-binary-form').submit();
2570 this.$element.find('.oe-binary-progress').show();
2571 this.$element.find('.oe-binary').hide();
2574 on_file_uploaded: function(size, name, content_type, file_base64) {
2575 delete(window[this.iframe]);
2576 if (size === false) {
2577 this.notification.warn("File Upload", "There was a problem while uploading your file");
2578 // TODO: use openerp web crashmanager
2579 console.warn("Error while uploading file : ", name);
2581 this.on_file_uploaded_and_valid.apply(this, arguments);
2582 this.on_ui_change();
2584 this.$element.find('.oe-binary-progress').hide();
2585 this.$element.find('.oe-binary').show();
2587 on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
2589 on_save_as: function() {
2590 if (!this.view.datarecord.id) {
2591 this.notification.warn("Can't save file", "The record has not yet been saved");
2593 var url = '/web/binary/saveas?session_id=' + this.session.session_id + '&model=' +
2594 this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name +
2595 '&fieldname=' + (this.node.attrs.filename || '') + '&t=' + (new Date().getTime());
2599 on_clear: function() {
2600 if (this.value !== false) {
2602 this.binary_value = false;
2603 this.on_ui_change();
2609 openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
2610 template: 'FieldBinaryFile',
2611 update_dom: function() {
2612 this._super.apply(this, arguments);
2613 this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly);
2615 set_value: function(value) {
2616 this._super.apply(this, arguments);
2617 var show_value = (value != null && value !== false) ? value : '';
2618 this.$element.find('input').eq(0).val(show_value);
2620 on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
2621 this.value = file_base64;
2622 this.binary_value = true;
2623 var show_value = this.human_filesize(size);
2624 this.$element.find('input').eq(0).val(show_value);
2625 this.set_filename(name);
2627 set_filename: function(value) {
2628 var filename = this.node.attrs.filename;
2629 if (this.view.fields[filename]) {
2630 this.view.fields[filename].set_value(value);
2631 this.view.fields[filename].on_ui_change();
2634 on_clear: function() {
2635 this._super.apply(this, arguments);
2636 this.$element.find('input').eq(0).val('');
2637 this.set_filename('');
2641 openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
2642 template: 'FieldBinaryImage',
2644 this._super.apply(this, arguments);
2645 this.$image = this.$element.find('img.oe-binary-image');
2647 update_dom: function() {
2648 this._super.apply(this, arguments);
2649 this.$element.find('.oe-binary').toggle(!this.readonly);
2651 set_value: function(value) {
2652 this._super.apply(this, arguments);
2653 this.set_image_maxwidth();
2654 var url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
2655 this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
2656 this.$image.attr('src', url);
2658 set_image_maxwidth: function() {
2659 this.$image.css('max-width', this.$element.width());
2661 on_file_change: function() {
2662 this.set_image_maxwidth();
2663 this._super.apply(this, arguments);
2665 on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
2666 this.value = file_base64;
2667 this.binary_value = true;
2668 this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64);
2670 on_clear: function() {
2671 this._super.apply(this, arguments);
2672 this.$image.attr('src', '/web/static/src/img/placeholder.png');
2676 openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
2677 template: "EmptyComponent",
2680 this.selected_value = null;
2684 set_value: function(value) {
2686 this.selected_value = value;
2690 render_list: function() {
2692 var shown = _.map(((this.node.attrs || {}).statusbar_visible || "").split(","),
2693 function(x) { return x.trim(); });
2694 shown = _.select(shown, function(x) { return x.length > 0; });
2696 if (shown.length == 0) {
2697 this.to_show = this.field.selection;
2699 this.to_show = _.select(this.field.selection, function(x) {
2700 return _.indexOf(shown, x[0]) !== -1 || x[0] === self.selected_value;
2704 var content = openerp.web.qweb.render("FieldStatus.content", {widget: this, _:_});
2705 this.$element.html(content);
2707 var colors = JSON.parse((this.node.attrs || {}).statusbar_colors || "{}");
2708 var color = colors[this.selected_value];
2710 var elem = this.$element.find("li.oe-arrow-list-selected span");
2711 elem.css("border-color", color);
2712 elem = this.$element.find("li.oe-arrow-list-selected .oe-arrow-list-before");
2713 elem.css("border-left-color", "rgba(0,0,0,0)");
2714 elem = this.$element.find("li.oe-arrow-list-selected .oe-arrow-list-after");
2715 elem.css("border-color", "rgba(0,0,0,0)");
2716 elem.css("border-left-color", color);
2721 openerp.web.form.WidgetNotebookReadonly = openerp.web.form.WidgetNotebook.extend({
2722 template: 'WidgetNotebook.readonly'
2724 openerp.web.form.FieldReadonly = openerp.web.form.Field.extend({
2727 openerp.web.form.FieldCharReadonly = openerp.web.form.FieldReadonly.extend({
2728 template: 'FieldChar.readonly',
2729 set_value: function (value) {
2730 this._super.apply(this, arguments);
2731 var show_value = openerp.web.format_value(value, this, '');
2732 this.$element.find('div').text(show_value);
2736 openerp.web.form.FieldURIReadonly = openerp.web.form.FieldCharReadonly.extend({
2737 template: 'FieldURI.readonly',
2739 set_value: function (value) {
2740 var displayed = this._super.apply(this, arguments);
2741 this.$element.find('a')
2742 .attr('href', this.scheme + ':' + displayed)
2746 openerp.web.form.FieldEmailReadonly = openerp.web.form.FieldURIReadonly.extend({
2749 openerp.web.form.FieldUrlReadonly = openerp.web.form.FieldURIReadonly.extend({
2750 set_value: function (value) {
2751 var s = /(\w+):(\.+)/.match(value);
2752 if (!(s[0] === 'http' || s[0] === 'https')) { return; }
2757 openerp.web.form.FieldBooleanReadonly = openerp.web.form.FieldCharReadonly.extend({
2758 set_value: function (value) {
2759 this._super(value ? '\u2714' : '\u2718');
2762 openerp.web.form.FieldSelectionReadonly = openerp.web.form.FieldReadonly.extend({
2763 template: 'FieldChar.readonly',
2764 init: function(view, node) {
2765 // lifted straight from r/w version
2767 this._super(view, node);
2768 this.values = this.field.selection;
2769 _.each(this.values, function(v, i) {
2770 if (v[0] === false && v[1] === '') {
2771 self.values.splice(i, 1);
2774 this.values.unshift([false, '']);
2776 set_value: function (value) {
2777 value = value === null ? false : value;
2778 value = value instanceof Array ? value[0] : value;
2779 var option = _(this.values)
2780 .detect(function (record) { return record[0] === value; });
2782 this.$element.find('div').text(option ? option[1] : this.values[0][1]);
2785 openerp.web.form.FieldMany2OneReadonly = openerp.web.form.FieldCharReadonly.extend({
2786 set_value: function (value) {
2787 value = value || null;
2788 this.invalid = false;
2790 this.tmp_value = value;
2792 self.on_value_changed();
2793 var real_set_value = function(rval) {
2794 self.$element.find('div').text(rval ? rval[1] : '');
2796 if(typeof(value) === "number") {
2797 var dataset = new openerp.web.DataSetStatic(
2798 this, this.field.relation, self.build_context());
2799 dataset.name_get([value], function(data) {
2800 real_set_value(data[0]);
2801 }).fail(function() {self.tmp_value = undefined;});
2803 setTimeout(function() {real_set_value(value);}, 0);
2809 * Registry of form widgets, called by :js:`openerp.web.FormView`
2811 openerp.web.form.widgets = new openerp.web.Registry({
2812 'frame' : 'openerp.web.form.WidgetFrame',
2813 'group' : 'openerp.web.form.WidgetFrame',
2814 'notebook' : 'openerp.web.form.WidgetNotebook',
2815 'notebookpage' : 'openerp.web.form.WidgetNotebookPage',
2816 'separator' : 'openerp.web.form.WidgetSeparator',
2817 'label' : 'openerp.web.form.WidgetLabel',
2818 'button' : 'openerp.web.form.WidgetButton',
2819 'char' : 'openerp.web.form.FieldChar',
2820 'email' : 'openerp.web.form.FieldEmail',
2821 'url' : 'openerp.web.form.FieldUrl',
2822 'text' : 'openerp.web.form.FieldText',
2823 'text_wiki' : 'openerp.web.form.FieldText',
2824 'date' : 'openerp.web.form.FieldDate',
2825 'datetime' : 'openerp.web.form.FieldDatetime',
2826 'selection' : 'openerp.web.form.FieldSelection',
2827 'many2one' : 'openerp.web.form.FieldMany2One',
2828 'many2many' : 'openerp.web.form.FieldMany2Many',
2829 'one2many' : 'openerp.web.form.FieldOne2Many',
2830 'one2many_list' : 'openerp.web.form.FieldOne2Many',
2831 'reference' : 'openerp.web.form.FieldReference',
2832 'boolean' : 'openerp.web.form.FieldBoolean',
2833 'float' : 'openerp.web.form.FieldFloat',
2834 'integer': 'openerp.web.form.FieldFloat',
2835 'float_time': 'openerp.web.form.FieldFloat',
2836 'progressbar': 'openerp.web.form.FieldProgressBar',
2837 'image': 'openerp.web.form.FieldBinaryImage',
2838 'binary': 'openerp.web.form.FieldBinaryFile',
2839 'statusbar': 'openerp.web.form.FieldStatus'
2841 openerp.web.form.readonly = openerp.web.form.widgets.clone({
2842 'notebook': 'openerp.web.form.WidgetNotebookReadonly',
2843 'char': 'openerp.web.form.FieldCharReadonly',
2844 'email': 'openerp.web.form.FieldEmailReadonly',
2845 'url': 'openerp.web.form.FieldUrlReadonly',
2846 'text': 'openerp.web.form.FieldCharReadonly',
2847 'text_wiki' : 'openerp.web.form.FieldCharReadonly',
2848 'date': 'openerp.web.form.FieldCharReadonly',
2849 'datetime': 'openerp.web.form.FieldCharReadonly',
2850 'selection' : 'openerp.web.form.FieldSelectionReadonly',
2851 'many2one': 'openerp.web.form.FieldMany2OneReadonly',
2852 'boolean': 'openerp.web.form.FieldBooleanReadonly',
2853 'float': 'openerp.web.form.FieldCharReadonly',
2854 'integer': 'openerp.web.form.FieldCharReadonly',
2855 'float_time': 'openerp.web.form.FieldCharReadonly'
2860 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: