1 openerp.base.form = function (openerp) {
3 openerp.base.views.add('form', 'openerp.base.FormView');
4 openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{
6 * Indicates that this view is not searchable, and thus that no search
7 * view should be displayed (if there is one active).
13 * @param {openerp.base.Session} session the current openerp session
14 * @param {String} element_id this view's root element id
15 * @param {openerp.base.DataSet} dataset the dataset this view will work with
16 * @param {String} view_id the identifier of the OpenERP view object
18 * @property {openerp.base.Registry} registry=openerp.base.form.widgets widgets registry for this form view instance
20 init: function(view_manager, session, element_id, dataset, view_id) {
21 this._super(session, element_id);
22 this.view_manager = view_manager || new openerp.base.NullViewManager();
23 this.dataset = dataset;
24 this.model = dataset.model;
25 this.view_id = view_id;
26 this.fields_view = {};
28 this.widgets_counter = 0;
32 this.show_invalid = true;
34 this.flags = this.view_manager.flags || {};
35 this.default_focus_field = null;
36 this.default_focus_button = null;
37 this.registry = openerp.base.form.widgets;
38 this.has_been_loaded = $.Deferred();
41 //this.log('Starting FormView '+this.model+this.view_id)
42 if (this.embedded_view) {
43 return $.Deferred().then(this.on_loaded).resolve({fields_view: this.embedded_view});
45 var context = new openerp.base.CompoundContext(this.dataset.context);
46 if (this.view_manager.action && this.view_manager.action.context) {
47 context.add(this.view_manager.action.context);
49 return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id,
50 toolbar:!!this.flags.sidebar, context: context}, this.on_loaded);
53 on_loaded: function(data) {
55 this.fields_view = data.fields_view;
56 var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
58 this.$element.html(QWeb.render(this.template, { 'frame': frame, 'view': this }));
59 _.each(this.widgets, function(w) {
62 this.$element.find('div.oe_form_pager button[data-pager-action]').click(function() {
63 var action = $(this).data('pager-action');
64 self.on_pager_action(action);
67 this.$element.find('#' + this.element_id + '_header button.oe_form_button_save').click(this.do_save);
68 this.$element.find('#' + this.element_id + '_header button.oe_form_button_save_edit').click(this.do_save_edit);
69 this.$element.find('#' + this.element_id + '_header button.oe_form_button_cancel').click(this.do_cancel);
70 this.$element.find('#' + this.element_id + '_header button.oe_form_button_new').click(this.on_button_new);
72 this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
73 this.has_been_loaded.resolve();
75 do_show: function () {
78 if (this.dataset.index === null || (this.dataset.index === 0 && this.dataset.ids.length == 0)) {
79 // null index means we should start a new record
80 // 0 index with empty ids means we called the form with empty dataset (wizards, switched to form from empty list, ...)
83 this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
85 this.view_manager.sidebar.do_refresh(true);
87 do_hide: function () {
90 on_record_loaded: function(record) {
93 this.datarecord = record;
94 for (var f in this.fields) {
95 var field = this.fields[f];
96 field.touched = false;
97 field.set_value(this.datarecord[f] || false);
101 // New record: Second pass in order to trigger the onchanges
103 this.show_invalid = false;
104 for (var f in record) {
105 this.on_form_changed(this.fields[f]);
108 this.on_form_changed();
109 this.show_invalid = this.ready = true;
111 this.log("No record received");
113 this.do_update_pager(record.id == null);
114 this.do_update_sidebar();
115 if (this.default_focus_field) {
116 this.default_focus_field.focus();
119 on_form_changed: function(widget) {
120 if (widget && widget.node.attrs.on_change) {
121 this.do_onchange(widget);
123 for (var w in this.widgets) {
130 on_pager_action: function(action) {
133 this.dataset.index = 0;
136 this.dataset.previous();
142 this.dataset.index = this.dataset.ids.length - 1;
147 do_update_pager: function(hide_index) {
148 var $pager = this.$element.find('#' + this.element_id + '_header div.oe_form_pager');
149 var index = hide_index ? '-' : this.dataset.index + 1;
150 $pager.find('span.oe_pager_index').html(index);
151 $pager.find('span.oe_pager_count').html(this.dataset.count);
153 do_onchange: function(widget, processed) {
154 processed = processed || [];
155 if (widget.node.attrs.on_change) {
158 var onchange = _.trim(widget.node.attrs.on_change);
159 var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
161 var method = call[1], args = [];
162 var argument_replacement = {
167 _.each(call[2].split(','), function(a) {
168 var field = _.trim(a);
169 if (field in argument_replacement) {
170 args.push(argument_replacement[field]);
171 } else if (self.fields[field]) {
172 var value = self.fields[field].get_value();
173 args.push(value == null ? false : value);
176 self.log("warning : on_change can't find field " + field, onchange);
180 url: '/base/dataset/call',
183 return this.rpc(ajax, {
184 model: this.dataset.model,
186 args: [(this.datarecord.id == null ? [] : [this.datarecord.id])].concat(args)
187 }, function(response) {
188 self.on_processed_onchange(response, processed);
191 this.log("Wrong on_change format", on_change);
195 on_processed_onchange: function(response, processed) {
196 var result = response.result;
198 for (var f in result.value) {
199 var field = this.fields[f];
201 var value = result.value[f];
202 processed.push(field.name);
203 if (field.get_value() != value) {
204 field.set_value(value);
205 if (_.indexOf(processed, field.name) < 0) {
206 this.do_onchange(field, processed);
210 this.log("warning : on_processed_onchange can't find field " + field, result);
213 this.on_form_changed();
215 if (result.warning) {
216 $(QWeb.render("DialogWarning", result.warning)).dialog({
220 $(this).dialog("close");
230 on_button_new: function() {
232 var context = new openerp.base.CompoundContext(this.dataset.context);
233 if (this.view_manager.action && this.view_manager.action.context) {
234 context.add(this.view_manager.action.context);
236 $.when(this.has_been_loaded).then(function() {
237 self.dataset.default_get(_.keys(self.fields_view.fields), context, function(result) {
238 self.on_record_loaded(result.result);
243 * Triggers saving the form's record. Chooses between creating a new
244 * record or saving an existing one depending on whether the record
245 * already has an id property.
247 * @param {Function} success callback on save success
248 * @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)
250 do_save: function(success, prepend_on_create) {
257 first_invalid_field = null;
258 for (var f in this.fields) {
263 if (!first_invalid_field) {
264 first_invalid_field = f;
266 } else if (f.touched) {
267 values[f.name] = f.get_value();
271 first_invalid_field.focus();
275 this.log("About to save", values);
276 if (!this.datarecord.id) {
277 this.dataset.create(values, function(r) {
278 self.on_created(r, success, prepend_on_create);
281 this.dataset.write(this.datarecord.id, values, function(r) {
282 self.on_saved(r, success);
288 do_save_edit: function() {
290 //this.switch_readonly(); Use promises
292 switch_readonly: function() {
294 switch_editable: function() {
296 on_invalid: function() {
298 _.each(this.fields, function(f) {
300 msg += "<li>" + f.string + "</li>";
304 this.notification.warn("The following fields are invalid :", msg);
306 on_saved: function(r, success) {
308 this.notification.warn("Record not saved", "Problem while saving record.");
310 this.notification.notify("Record saved", "The record #" + this.datarecord.id + " has been saved.");
318 * Updates the form' dataset to contain the new record:
320 * * Adds the newly created record to the current dataset (at the end by
322 * * Selects that record (sets the dataset's index to point to the new
324 * * Updates the pager and sidebar displays
327 * @param {Function} success callback to execute after having updated the dataset
328 * @param {Boolean} [prepend_on_create=false] adds the newly created record at the beginning of the dataset instead of the end
330 on_created: function(r, success, prepend_on_create) {
332 this.notification.warn("Record not created", "Problem while creating record.");
334 this.datarecord.id = r.result;
335 if (!prepend_on_create) {
336 this.dataset.ids.push(this.datarecord.id);
337 this.dataset.index = this.dataset.ids.length - 1;
339 this.dataset.ids.unshift(this.datarecord.id);
340 this.dataset.index = 0;
342 this.dataset.count++;
343 this.do_update_pager();
344 this.do_update_sidebar();
345 this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id);
347 success(_.extend(r, {created: true}));
352 do_search: function (domains, contexts, groupbys) {
353 this.notification.notify("Searching form");
355 on_action: function (action) {
356 this.notification.notify('Executing action ' + action);
358 do_cancel: function () {
359 this.notification.notify("Cancelling form");
361 do_update_sidebar: function() {
362 if (this.flags.sidebar === false) {
365 if (!this.datarecord.id) {
366 this.on_attachments_loaded([]);
368 this.rpc('/base/dataset/search_read', {
369 model: 'ir.attachment',
370 fields: ['name', 'url', 'type'],
371 domain: [['res_model', '=', this.dataset.model], ['res_id', '=', this.datarecord.id], ['type', 'in', ['binary', 'url']]],
372 context: this.dataset.context
373 }, this.on_attachments_loaded);
376 on_attachments_loaded: function(attachments) {
377 this.$sidebar = this.view_manager.sidebar.$element.find('.sidebar-attachments');
378 this.attachments = attachments;
379 this.$sidebar.html(QWeb.render('FormView.sidebar.attachments', this));
380 this.$sidebar.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete);
381 this.$sidebar.find('.oe-binary-file').change(this.on_attachment_changed);
383 on_attachment_changed: function(e) {
384 window[this.element_id + '_iframe'] = this.do_update_sidebar;
385 var $e = $(e.target);
386 if ($e.val() != '') {
387 this.$sidebar.find('form.oe-binary-form').submit();
388 $e.parent().find('input[type=file]').attr('disabled', 'true');
389 $e.parent().find('button').attr('disabled', 'true').find('img, span').toggle();
392 on_attachment_delete: function(e) {
393 var self = this, $e = $(e.currentTarget);
394 var name = _.trim($e.parent().find('a.oe-sidebar-attachments-link').text());
395 if (confirm("Do you really want to delete the attachment " + name + " ?")) {
396 this.rpc('/base/dataset/unlink', {
397 model: 'ir.attachment',
398 ids: [parseInt($e.attr('data-id'))]
400 $e.parent().remove();
401 self.notification.notify("Delete an attachment", "The attachment '" + name + "' has been deleted");
406 if (this.datarecord.id) {
407 this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
409 this.on_button_new();
415 openerp.base.form = {};
417 openerp.base.form.compute_domain = function(expr, fields) {
419 for (var i = expr.length - 1; i >= 0; i--) {
421 if (ex.length == 1) {
422 var top = stack.pop();
425 stack.push(stack.pop() || top);
428 stack.push(stack.pop() && top);
434 throw new Error('Unknown domain operator ' + ex);
438 var field = fields[ex[0]].value;
442 switch (op.toLowerCase()) {
445 stack.push(field == val);
449 stack.push(field != val);
452 stack.push(field < val);
455 stack.push(field > val);
458 stack.push(field <= val);
461 stack.push(field >= val);
464 stack.push(_(val).contains(field));
467 stack.push(!_(val).contains(field));
470 this.log("Unsupported operator in attrs :", op);
476 openerp.base.form.Widget = openerp.base.Controller.extend({
478 init: function(view, node) {
481 this.attrs = JSON.parse(this.node.attrs.attrs || '{}');
482 this.type = this.type || node.tag;
483 this.element_name = this.element_name || this.type;
484 this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_");
486 this._super(this.view.session, this.element_id);
488 this.view.widgets[this.element_id] = this;
489 this.children = node.children;
490 this.colspan = parseInt(node.attrs.colspan || 1);
492 this.string = this.string || node.attrs.string;
493 this.help = this.help || node.attrs.help;
494 this.invisible = (node.attrs.invisible == '1');
497 this.$element = $('#' + this.element_id);
499 process_attrs: function() {
500 var compute_domain = openerp.base.form.compute_domain;
501 for (var a in this.attrs) {
502 this[a] = compute_domain(this.attrs[a], this.view.fields);
505 update_dom: function() {
506 this.$element.toggle(!this.invisible);
509 var template = this.template;
510 return QWeb.render(template, { "widget": this });
514 openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
515 template: 'WidgetFrame',
516 init: function(view, node) {
517 this._super(view, node);
518 this.columns = node.attrs.col || 4;
523 for (var i = 0; i < node.children.length; i++) {
524 var n = node.children[i];
525 if (n.tag == "newline") {
531 this.set_row_cells_with(this.table[this.table.length - 1]);
534 if (this.table.length) {
535 this.set_row_cells_with(this.table[this.table.length - 1]);
538 this.table.push(row);
543 set_row_cells_with: function(row) {
544 for (var i = 0; i < row.length; i++) {
546 if (w.is_field_label) {
549 row[i + 1].width = Math.round((100 / this.columns) * (w.colspan + 1) - 1) + '%';
551 } else if (w.width === undefined) {
552 w.width = Math.round((100 / this.columns) * w.colspan) + '%';
556 handle_node: function(node) {
557 var type = this.view.fields_view.fields[node.attrs.name] || {};
558 var widget_type = node.attrs.widget || type.type || node.tag;
559 var widget = new (this.view.registry.get_object(widget_type)) (this.view, node);
560 if (node.tag == 'field') {
561 if (!this.view.default_focus_field || node.attrs.default_focus == '1') {
562 this.view.default_focus_field = widget;
564 if (node.attrs.nolabel != '1') {
565 var label = new (this.view.registry.get_object('label')) (this.view, node);
566 label["for"] = widget;
567 this.add_widget(label);
570 this.add_widget(widget);
572 add_widget: function(widget) {
573 var current_row = this.table[this.table.length - 1];
574 if (current_row.length && (this.x + widget.colspan) > this.columns) {
575 current_row = this.add_row();
577 current_row.push(widget);
578 this.x += widget.colspan;
583 openerp.base.form.WidgetNotebook = openerp.base.form.Widget.extend({
584 init: function(view, node) {
585 this._super(view, node);
586 this.template = "WidgetNotebook";
588 for (var i = 0; i < node.children.length; i++) {
589 var n = node.children[i];
590 if (n.tag == "page") {
591 var page = new openerp.base.form.WidgetFrame(this.view, n);
592 this.pages.push(page);
597 this._super.apply(this, arguments);
598 this.$element.tabs();
602 openerp.base.form.WidgetSeparator = openerp.base.form.Widget.extend({
603 init: function(view, node) {
604 this._super(view, node);
605 this.template = "WidgetSeparator";
609 openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
610 init: function(view, node) {
611 this._super(view, node);
612 this.template = "WidgetButton";
613 if (node.attrs.default_focus == '1') {
614 // TODO fme: provide enter key binding to widgets
615 this.view.default_focus_button = this;
619 this._super.apply(this, arguments);
620 this.$element.click(this.on_click);
622 on_click: function(saved) {
624 if (!this.node.attrs.special && this.view.touched && saved !== true) {
625 this.view.do_save(function() {
629 if (this.node.attrs.confirm) {
630 var dialog = $('<div>' + this.node.attrs.confirm + '</div>').dialog({
636 $(this).dialog("close");
639 $(this).dialog("close");
648 on_confirmed: function() {
651 this.view.execute_action(
652 this.node.attrs, this.view.dataset, this.session.action_manager,
653 this.view.datarecord.id, function (result) {
654 self.log("Button returned", result);
660 openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
661 init: function(view, node) {
662 this.is_field_label = true;
663 this.element_name = 'label_' + node.attrs.name;
665 this._super(view, node);
667 this.template = "WidgetLabel";
670 render: function () {
671 if (this['for'] && this.type !== 'label') {
672 return QWeb.render(this.template, {widget: this['for']});
674 // Actual label widgets should not have a false and have type label
675 return QWeb.render(this.template, {widget: this});
679 openerp.base.form.Field = openerp.base.form.Widget.extend({
680 init: function(view, node) {
681 this.name = node.attrs.name;
682 this.value = undefined;
683 view.fields[this.name] = this;
684 this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type;
685 this.element_name = "field_" + this.name + "_" + this.type;
687 this._super(view, node);
689 if (node.attrs.nolabel != '1' && this.colspan > 1) {
692 this.field = view.fields_view.fields[node.attrs.name] || {};
693 this.string = node.attrs.string || this.field.string;
694 this.help = node.attrs.help || this.field.help;
695 this.invisible = (this.invisible || this.field.invisible == '1');
696 this.nolabel = (this.field.nolabel || node.attrs.nolabel) == '1';
697 this.readonly = (this.field.readonly || node.attrs.readonly) == '1';
698 this.required = (this.field.required || node.attrs.required) == '1';
699 this.invalid = false;
700 this.touched = false;
702 set_value: function(value) {
704 this.invalid = false;
707 set_value_from_ui: function() {
708 this.value = undefined;
710 get_value: function() {
713 update_dom: function() {
714 this._super.apply(this, arguments);
715 this.$element.toggleClass('disabled', this.readonly);
716 this.$element.toggleClass('required', this.required);
717 if (this.view.show_invalid) {
718 this.$element.toggleClass('invalid', this.invalid);
721 on_ui_change: function() {
722 this.touched = this.view.touched = true;
725 this.set_value_from_ui();
726 this.view.on_form_changed(this);
731 validate: function() {
732 this.invalid = false;
738 openerp.base.form.FieldChar = openerp.base.form.Field.extend({
739 init: function(view, node) {
740 this._super(view, node);
741 this.template = "FieldChar";
744 this._super.apply(this, arguments);
745 this.$element.find('input').change(this.on_ui_change);
747 set_value: function(value) {
748 this._super.apply(this, arguments);
749 var show_value = (value != null && value !== false) ? value : '';
750 this.$element.find('input').val(show_value);
752 update_dom: function() {
753 this._super.apply(this, arguments);
754 this.$element.find('input').attr('disabled', this.readonly);
756 set_value_from_ui: function() {
757 this.value = this.$element.find('input').val();
759 validate: function() {
760 this.invalid = false;
761 var value = this.$element.find('input').val();
763 this.invalid = this.required;
764 } else if (this.validation_regex) {
765 this.invalid = !this.validation_regex.test(value);
769 this.$element.find('input').focus();
773 openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({
774 init: function(view, node) {
775 this._super(view, node);
776 this.template = "FieldEmail";
777 this.validation_regex = /@/;
780 this._super.apply(this, arguments);
781 this.$element.find('button').click(this.on_button_clicked);
783 on_button_clicked: function() {
784 if (!this.value || this.invalid) {
785 this.notification.warn("E-mail error", "Can't send email to invalid e-mail address");
787 location.href = 'mailto:' + this.value;
790 set_value: function(value) {
791 this._super.apply(this, arguments);
792 var show_value = (value != null && value !== false) ? value : '';
793 this.$element.find('a').attr('href', 'mailto:' + show_value);
797 openerp.base.form.FieldUrl = openerp.base.form.FieldChar.extend({
798 init: function(view, node) {
799 this._super(view, node);
800 this.template = "FieldUrl";
803 this._super.apply(this, arguments);
804 this.$element.find('button').click(this.on_button_clicked);
806 on_button_clicked: function() {
808 this.notification.warn("Resource error", "This resource is empty");
810 window.open(this.value);
815 openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
816 init: function(view, node) {
817 this._super(view, node);
818 this.validation_regex = /^-?\d+(\.\d+)?$/;
820 set_value: function(value) {
821 this._super.apply(this, [value]);
822 if (value === false || value === undefined) {
823 // As in GTK client, floats default to 0
826 var show_value = value.toFixed(2);
827 this.$element.find('input').val(show_value);
829 set_value_from_ui: function() {
830 this.value = Number(this.$element.find('input').val().replace(/,/g, '.'));
834 openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
835 init: function(view, node) {
836 this._super(view, node);
837 this.template = "FieldDate";
838 this.jqueryui_object = 'datetimepicker';
841 this._super.apply(this, arguments);
842 this.$element.find('input').change(this.on_ui_change)[this.jqueryui_object]({
843 dateFormat: 'yy-mm-dd',
844 timeFormat: 'hh:mm:ss'
847 set_value: function(value) {
848 this._super.apply(this, arguments);
849 if (value == null || value == false) {
850 this.$element.find('input').val('');
852 this.$element.find('input').unbind('change');
853 // jQuery UI date picker wrongly call on_change event herebelow
854 this.$element.find('input')[this.jqueryui_object]('setDate', this.parse(value));
855 this.$element.find('input').change(this.on_ui_change);
858 set_value_from_ui: function() {
859 this.value = this.$element.find('input')[this.jqueryui_object]('getDate') || false;
861 this.value = this.format(this.value);
864 validate: function() {
865 this.invalid = this.required && this.$element.find('input')[this.jqueryui_object]('getDate') === '';
868 this.$element.find('input').focus();
870 parse: openerp.base.parse_datetime,
871 format: openerp.base.format_datetime
874 openerp.base.form.FieldDate = openerp.base.form.FieldDatetime.extend({
875 init: function(view, node) {
876 this._super(view, node);
877 this.jqueryui_object = 'datepicker';
879 parse: openerp.base.parse_date,
880 format: openerp.base.format_date
883 openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({
884 init: function(view, node) {
885 this._super(view, node);
886 this.validation_regex = /^\d+:\d+$/;
888 set_value: function(value) {
889 this._super.apply(this, [value]);
890 if (value === false || value === undefined) {
891 // As in GTK client, floats default to 0
894 var show_value = _.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60));
895 this.$element.find('input').val(show_value);
897 set_value_from_ui: function() {
898 var time = this.$element.find('input').val().split(':');
899 this.set_value(parseInt(time[0], 10) + parseInt(time[1], 10) / 60);
903 openerp.base.form.FieldText = openerp.base.form.Field.extend({
904 init: function(view, node) {
905 this._super(view, node);
906 this.template = "FieldText";
907 this.validation_regex = null;
910 this._super.apply(this, arguments);
911 this.$element.find('textarea').change(this.on_ui_change);
913 set_value: function(value) {
914 this._super.apply(this, arguments);
915 var show_value = (value != null && value !== false) ? value : '';
916 this.$element.find('textarea').val(show_value);
918 update_dom: function() {
919 this._super.apply(this, arguments);
920 this.$element.find('textarea').attr('disabled', this.readonly);
922 set_value_from_ui: function() {
923 this.value = this.$element.find('textarea').val();
925 validate: function() {
926 this.invalid = false;
927 var value = this.$element.find('textarea').val();
929 this.invalid = this.required;
930 } else if (this.validation_regex) {
931 this.invalid = !this.validation_regex.test(value);
935 this.$element.find('textarea').focus();
939 openerp.base.form.FieldBoolean = openerp.base.form.Field.extend({
940 init: function(view, node) {
941 this._super(view, node);
942 this.template = "FieldBoolean";
946 this._super.apply(this, arguments);
947 this.$element.find('input').click(function() {
948 if ($(this).is(':checked') != self.value) {
953 set_value: function(value) {
954 this._super.apply(this, arguments);
955 this.$element.find('input')[0].checked = value;
957 set_value_from_ui: function() {
958 this.value = this.$element.find('input').is(':checked');
960 update_dom: function() {
961 this._super.apply(this, arguments);
962 this.$element.find('input').attr('disabled', this.readonly);
964 validate: function() {
965 this.invalid = this.required && !this.$element.find('input').is(':checked');
968 this.$element.find('input').focus();
972 openerp.base.form.FieldProgressBar = openerp.base.form.Field.extend({
973 init: function(view, node) {
974 this._super(view, node);
975 this.template = "FieldProgressBar";
978 this._super.apply(this, arguments);
979 this.$element.find('div').progressbar({
981 disabled: this.readonly
984 set_value: function(value) {
985 this._super.apply(this, arguments);
986 var show_value = Number(value);
987 if (show_value === NaN) {
990 this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(show_value + '%');
994 openerp.base.form.FieldTextXml = openerp.base.form.Field.extend({
995 // to replace view editor
998 openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
999 init: function(view, node) {
1000 this._super(view, node);
1001 this.template = "FieldSelection";
1004 this._super.apply(this, arguments);
1005 this.$element.find('select').change(this.on_ui_change);
1007 set_value: function(value) {
1008 this._super.apply(this, arguments);
1009 if (value != null && value !== false) {
1010 this.$element.find('select').val(value);
1012 this.$element.find('select').val('false');
1015 set_value_from_ui: function() {
1016 this.value = this.$element.find('select').val();
1018 update_dom: function() {
1019 this._super.apply(this, arguments);
1020 this.$element.find('select').attr('disabled', this.readonly);
1022 validate: function() {
1023 this.invalid = this.required && this.$element.find('select').val() === "";
1026 this.$element.find('select').focus();
1030 // jquery autocomplete tweak to allow html
1032 var proto = $.ui.autocomplete.prototype,
1033 initSource = proto._initSource;
1035 function filter( array, term ) {
1036 var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
1037 return $.grep( array, function(value) {
1038 return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
1043 _initSource: function() {
1044 if ( this.options.html && $.isArray(this.options.source) ) {
1045 this.source = function( request, response ) {
1046 response( filter( this.options.source, request.term ) );
1049 initSource.call( this );
1053 _renderItem: function( ul, item) {
1054 return $( "<li></li>" )
1055 .data( "item.autocomplete", item )
1056 .append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) )
1063 * Builds a new context usable for operations related to fields by merging
1064 * the fields'context with the action's context.
1066 var build_relation_context = function(relation_field) {
1067 var action = relation_field.view.view_manager.action || {};
1068 var a_context = action.context || {};
1069 var f_context = relation_field.field.context || {};
1070 var ctx = new openerp.base.CompoundContext(a_context).add(f_context);
1074 openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
1075 init: function(view, node) {
1076 this._super(view, node);
1077 this.template = "FieldMany2One";
1080 this.cm_id = _.uniqueId('m2o_cm_');
1081 this.last_search = [];
1086 this.$input = this.$element.find("input");
1087 this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
1088 this.$menu_btn = this.$element.find(".oe-m2o-cm-button");
1092 bindings[this.cm_id + "_search"] = function() {
1093 self._search_create_popup("search");
1095 bindings[this.cm_id + "_create"] = function() {
1096 self._search_create_popup("form");
1098 bindings[this.cm_id + "_open"] = function() {
1102 self.session.action_manager.do_action({
1103 "res_model": self.field.relation,
1104 "views":[[false,"form"]],
1105 "res_id": self.value[0],
1106 "type":"ir.actions.act_window",
1110 "context": build_relation_context(self)
1113 var cmenu = this.$menu_btn.contextMenu(this.cm_id, {'leftClickToo': true,
1114 bindings: bindings, itemStyle: {"color": ""},
1115 onContextMenu: function() {
1117 $("#" + self.cm_id + "_open").removeClass("oe-m2o-disabled-cm");
1119 $("#" + self.cm_id + "_open").addClass("oe-m2o-disabled-cm");
1125 // some behavior for input
1126 this.$input.keyup(function() {
1127 if (self.$input.val() === "") {
1128 self._change_int_value(null);
1129 } else if (self.value === null || (self.value && self.$input.val() !== self.value[1])) {
1130 self._change_int_value(undefined);
1133 this.$drop_down.click(function() {
1134 if (self.$input.autocomplete("widget").is(":visible")) {
1135 self.$input.autocomplete("close");
1138 self.$input.autocomplete("search", "");
1140 self.$input.autocomplete("search");
1142 self.$input.focus();
1145 var anyoneLoosesFocus = function() {
1146 if (!self.$input.is(":focus") &&
1147 !self.$input.autocomplete("widget").is(":visible") &&
1149 if(self.value === undefined && self.last_search.length > 0) {
1150 self._change_int_ext_value(self.last_search[0]);
1152 self._change_int_ext_value(null);
1156 this.$input.focusout(anyoneLoosesFocus);
1159 this.$input.autocomplete({
1160 source: function(req, resp) { self.get_search_result(req, resp); },
1161 select: function(event, ui) {
1164 self._change_int_value([item.id, item.name]);
1165 } else if (item.action) {
1166 self._change_int_value(undefined);
1171 focus: function(e, ui) {
1175 close: anyoneLoosesFocus,
1180 // autocomplete component content handling
1181 get_search_result: function(request, response) {
1182 var search_val = request.term;
1185 var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []);
1187 dataset.name_search([search_val, self.field.domain || [], 'ilike',
1188 build_relation_context(self), this.limit + 1], function(data) {
1189 self.last_search = data.result;
1190 // possible selections for the m2o
1191 var values = _.map(data.result, function(x) {
1192 return {label: $('<span />').text(x[1]).html(), name:x[1], id:x[0]};
1195 // search more... if more results that max
1196 if (values.length > self.limit) {
1197 values = values.slice(0, self.limit);
1198 values.push({label: "<em>Â Â Â Search More...</em>", action: function() {
1199 dataset.name_search([search_val, self.field.domain || [], 'ilike',
1200 build_relation_context(self), false], function(data) {
1201 self._change_int_value(null);
1202 self._search_create_popup("search", data.result);
1207 var raw_result = _(data.result).map(function(x) {return x[1];})
1208 if (search_val.length > 0 &&
1209 !_.include(raw_result, search_val) &&
1210 (!self.value || search_val !== self.value[1])) {
1211 values.push({label: '<em>Â Â Â Create "<strong>' +
1212 $('<span />').text(search_val).html() + '</strong>"</em>', action: function() {
1213 self._quick_create(search_val);
1217 values.push({label: "<em>Â Â Â Create and Edit...</em>", action: function() {
1218 self._change_int_value(null);
1219 self._search_create_popup("form");
1225 _quick_create: function(name) {
1227 var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []);
1228 dataset.call("name_create", [name, build_relation_context(self)], function(data) {
1229 self._change_int_ext_value(data.result);
1231 self._change_int_value(null);
1232 self._search_create_popup("form", undefined, {"default_name": name});
1235 // all search/create popup handling
1236 _search_create_popup: function(view, ids, context) {
1237 var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []);
1239 var pop = new openerp.base.form.SelectCreatePopup(null, self.view.session);
1240 pop.select_element(self.field.relation,{
1241 initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
1243 disable_multiple_selection: true
1244 }, self.view.domain || [],
1245 new openerp.base.CompoundContext(build_relation_context(self)).add(context || {}));
1246 pop.on_select_elements.add(function(element_ids) {
1247 dataset.call("name_get", [element_ids[0]], function(data) {
1248 self._change_int_ext_value(data.result[0]);
1253 _change_int_ext_value: function(value) {
1254 this._change_int_value(value);
1255 this.$input.val(this.value ? this.value[1] : "");
1257 _change_int_value: function(value) {
1259 if (this.original_value === undefined || (this.value !== undefined &&
1260 ((this.original_value ? this.original_value[0] : null) !== (this.value ? this.value[0] : null)))) {
1261 this.original_value = undefined;
1262 this.on_ui_change();
1265 set_value_from_ui: function() {},
1266 set_value: function(value) {
1268 this.original_value = value;
1269 this._change_int_ext_value(value);
1271 get_value: function() {
1272 if (this.value === undefined)
1273 throw "theorically unreachable state";
1274 return this.value ? this.value[0] : false;
1276 validate: function() {
1277 this.invalid = false;
1278 if (this.value === null) {
1279 this.invalid = this.required;
1285 # Values: (0, 0, { fields }) create
1286 # (1, ID, { fields }) update
1287 # (2, ID) remove (delete)
1288 # (3, ID) unlink one (target id or target of relation)
1290 # (5) unlink all (only valid for one2many)
1293 openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
1294 init: function(view, node) {
1295 this._super(view, node);
1296 this.template = "FieldOne2Many";
1297 this.is_started = $.Deferred();
1298 this.is_setted = $.Deferred();
1301 this._super.apply(this, arguments);
1305 this.dataset = new openerp.base.form.One2ManyDataset(this.session, this.field.relation);
1306 this.dataset.on_change.add_last(function() {
1307 self.on_ui_change();
1310 var modes = this.node.attrs.mode;
1311 modes = !!modes ? modes.split(",") : ["tree", "form"];
1313 _.each(modes, function(mode) {
1314 var view = {view_id: false, view_type: mode == "tree" ? "list" : mode};
1315 if (self.field.views && self.field.views[mode]) {
1316 view.embedded_view = self.field.views[mode];
1318 if(view.view_type === "list") {
1326 this.viewmanager = new openerp.base.ViewManager(this.view.session,
1327 this.element_id, this.dataset, views);
1328 var reg = new openerp.base.Registry();
1329 reg.add("form", openerp.base.views.map["form"]);
1330 reg.add("graph", openerp.base.views.map["graph"]);
1331 reg.add("list", "openerp.base.form.One2ManyListView");
1332 this.viewmanager.registry = reg;
1334 this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
1335 if (view_type == "list") {
1336 controller.o2m = self;
1337 } else if (view_type == "form") {
1340 self.is_started.resolve();
1342 this.viewmanager.start();
1344 $.when(this.is_started, this.is_setted).then(function() {
1345 if (modes[0] == "tree") {
1346 var view = self.viewmanager.views[self.viewmanager.active_view].controller;
1347 view.reload_content();
1349 // TODO niv: handle other types of views
1352 reload_current_view: function() {
1354 var view = self.viewmanager.views[self.viewmanager.active_view].controller;
1355 if(self.viewmanager.active_view === "list") {
1356 view.reload_content();
1357 } else if (self.viewmanager.active_view === "form") {
1358 // TODO niv: but fme did not implemented delete in form view anyway
1361 set_value_from_ui: function() {},
1362 set_value: function(value) {
1363 if(value != false) {
1364 this.dataset.reset_ids(value);
1365 this.is_setted.resolve();
1368 get_value: function() {
1369 var val = _.map(this.dataset.to_delete, function(v, k) {return [2, parseInt(k, 10)];});
1370 var val = val.concat(_.map(this.dataset.to_create, function(x) {return [0, 0, x];}));
1373 validate: function() {
1374 this.invalid = false;
1379 openerp.base.form.One2ManyListView = openerp.base.ListView.extend({
1380 do_add_record: function () {
1382 var pop = new openerp.base.form.SelectCreatePopup(null, self.o2m.view.session);
1383 pop.select_element(self.o2m.field.relation,{
1384 initial_view: "form",
1385 alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined
1387 pop.on_select_elements.add(function(element_ids) {
1388 var ids = self.o2m.dataset.ids;
1389 _.each(element_ids, function(x) {if (!_.include(ids, x)) ids.push(x);});
1390 self.o2m.dataset.set_ids(ids);
1391 self.o2m.reload_current_view();
1396 openerp.base.form.One2ManyDataset = openerp.base.DataSetStatic.extend({
1397 virtual_id_prefix: "one2many_v_id_",
1398 virtual_id_regex: /one2many_v_id_.*/,
1401 this._super.apply(this, arguments);
1404 create: function(data, callback, error_callback) {
1405 var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data};
1406 this.to_create.push(cached);
1407 this.set_ids(ids.concat([cached.id]));
1408 this.cache.push(cached);
1410 return $.Deferred().then(callback).resolve({result: cached.id}).promise();
1412 write: function (id, data, callback) {
1413 var record = _.select(this.to_create, function(x) {return x.id === id;});
1414 record = record || _.select(this.to_write, function(x) {return x.id === id;});
1416 $.extend(previous.value, data);
1418 record = {id: id, values: data};
1419 self.to_write.push(record);
1421 var cached = _.select(this.cache, function(x) {return x.id === id;});
1422 $.extend(cached.value, record.values);
1424 return $.Deferred().then(callback).resolve({result: true}).promise();
1426 unlink: function(ids) {
1428 var to_create_size = this.to_create.length;
1429 var remove = function(list, to_remove) {
1430 return _.reject(list, function(x) { return _.include(to_remove, x.id);});
1432 this.to_create = remove(this.to_create);
1433 this.to_write = remove(this.to_write);
1434 this.cache = remove(this.cache);
1435 this.set_ids(_.without.apply(_, [this.ids].concat(ids)));
1436 if (this.to_create == to_create_size) {
1437 _.each(ids, function(x) {self.to_delete.push({id:x})});
1441 reset_ids: function(ids) {
1443 this.to_delete = [];
1444 this.to_create = [];
1450 on_change: function() {},
1451 read_ids: function (ids, fields, callback) {
1454 _.each(ids, function(id) {
1455 var cached = _.detect(self.cache, function(x) {return x.id === id;});
1456 if (!cached || !_each.all(fields, function(x) {cached.values[x] !== undefined}))
1459 if (this.debug_mode) {
1460 // test to see if all the ids we try to load from db are real ids
1461 _.each(to_get, function(x) {
1462 if(typeof(x) == "string") {
1463 var test = self.virtual_id_regex.exec(x);
1464 if(test && test[0] === x) {
1465 throw "Trying to get value from virtual id";
1470 var completion = $.Deferred();
1471 $.when(completion).then(callback);
1472 var return_records = function() {
1473 var records = _.map(ids, function(id) {return _.detect(self.cache, function(c) {return c.id === id;}).values;});
1474 if (self.debug_mode) {
1475 if (_.include(records, undefined)) {
1476 throw "Record not correctly loaded";
1479 completion.resolve(records);
1482 var rpc_promise = this._super(to_get, fields, function(records) {
1483 _.each(records, function(record, index) {
1484 var id = to_get[index];
1485 var cached = _.detect(self.cache, function(x) {return x.id === id;});
1487 self.cache.push({id: id, values: record});
1489 // I assume cache value is prioritary
1490 self.cached = $.extend({}, record, cached);
1495 $.when(rpc_promise).fail(function() {completion.reject();});
1499 return completion.promise();
1503 openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
1504 init: function(view, node) {
1505 this._super(view, node);
1506 this.template = "FieldMany2Many";
1507 this.list_id = _.uniqueId("many2many");
1508 this.is_started = $.Deferred();
1509 this.is_setted = $.Deferred();
1512 this._super.apply(this, arguments);
1516 this.dataset = new openerp.base.DataSetStatic(
1517 this.session, this.field.relation);
1518 this.dataset.on_change.add_last(function(ids) {
1519 self.on_ui_change();
1522 this.list_view = new openerp.base.form.Many2ManyListView(
1523 null, this.view.session, this.list_id, this.dataset, false, {
1526 this.list_view.m2m_field = this;
1527 this.list_view.on_loaded.add_last(function() {
1528 self.is_started.resolve();
1530 this.list_view.start();
1531 $.when(this.is_started, this.is_setted).then(function() {
1532 self.list_view.reload_content();
1535 set_value: function(value) {
1536 if (value != false) {
1537 this.dataset.set_ids(value);
1538 this.is_setted.resolve();
1541 get_value: function() {
1542 return [[6,false,this.dataset.ids]];
1546 openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
1547 do_add_record: function () {
1548 var pop = new openerp.base.form.SelectCreatePopup(
1549 null, this.m2m_field.view.session);
1550 pop.select_element(this.model);
1552 pop.on_select_elements.add(function(element_ids) {
1553 _.each(element_ids, function(element_id) {
1554 if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
1555 self.dataset.set_ids([].concat(self.dataset.ids, [element_id]));
1556 self.reload_content();
1562 do_activate_record: function(index, id) {
1563 this.m2m_field.view.session.action_manager.do_action({
1564 "res_model": this.dataset.model,
1565 "views":[[false,"form"]],
1567 "type":"ir.actions.act_window",
1575 openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
1576 identifier_prefix: "selectcreatepopup",
1577 template: "SelectCreatePopup",
1581 * - initial_view: form or search (default search)
1582 * - disable_multiple_selection
1583 * - alternative_form_view
1585 select_element: function(model, options, domain, context) {
1587 this.domain = domain || [];
1588 this.context = context || {};
1589 this.options = options || {};
1590 this.initial_ids = this.options.initial_ids;
1591 jQuery(this.render()).dialog({title: '',
1598 this.dataset = new openerp.base.DataSetSearch(this.session, this.model,
1599 this.context, this.domain);
1600 if ((this.options.initial_view || "search") == "search") {
1601 this.setup_search_view();
1606 setup_search_view: function() {
1608 if (this.searchview) {
1609 this.searchview.stop();
1611 this.searchview = new openerp.base.SearchView(null, this.session,
1612 this.element_id + "_search", this.dataset, false, {
1613 "selectable": !this.options.disable_multiple_selection,
1616 this.searchview.on_search.add(function(domains, contexts, groupbys) {
1617 if (self.initial_ids) {
1618 self.view_list.do_search.call(self,[[["id", "in", self.initial_ids]]],
1619 contexts, groupbys);
1620 self.initial_ids = undefined;
1622 self.view_list.do_search.call(self, domains, contexts, groupbys);
1625 this.searchview.on_loaded.add_last(function () {
1626 var $buttons = self.searchview.$element.find(".oe_search-view-buttons");
1627 $buttons.append(QWeb.render("SelectCreatePopup.search.buttons"));
1628 var $cbutton = $buttons.find(".oe_selectcreatepopup-search-close");
1629 $cbutton.click(function() {
1632 var $sbutton = $buttons.find(".oe_selectcreatepopup-search-select");
1633 if(self.options.disable_multiple_selection) {
1636 $sbutton.click(function() {
1637 self.on_select_elements(self.selected_ids);
1639 self.view_list = new openerp.base.form.SelectCreateListView( null, self.session,
1640 self.element_id + "_view_list", self.dataset, false,
1641 {'deletable': false});
1642 self.view_list.popup = self;
1643 self.view_list.do_show();
1644 self.view_list.start().then(function() {
1645 self.searchview.do_search();
1648 this.searchview.start();
1650 on_select_elements: function(element_ids) {
1652 on_click_element: function(ids) {
1653 this.selected_ids = ids || [];
1654 if(this.selected_ids.length > 0) {
1655 this.$element.find(".oe_selectcreatepopup-search-select").removeAttr('disabled');
1657 this.$element.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled");
1660 new_object: function() {
1662 if (this.searchview) {
1663 this.searchview.hide();
1665 if (this.view_list) {
1666 this.view_list.$element.hide();
1668 this.dataset.index = null;
1669 this.view_form = new openerp.base.FormView(null, this.session,
1670 this.element_id + "_view_form", this.dataset, false);
1671 if (this.options.alternative_form_view) {
1672 this.view_form.set_embedded_view(this.options.alternative_form_view);
1674 this.view_form.start();
1675 this.view_form.on_loaded.add_last(function() {
1676 var $buttons = self.view_form.$element.find(".oe_form_buttons");
1677 $buttons.html(QWeb.render("SelectCreatePopup.form.buttons"));
1678 var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save");
1679 $nbutton.click(function() {
1680 self.view_form.do_save();
1682 var $cbutton = $buttons.find(".oe_selectcreatepopup-form-close");
1683 $cbutton.click(function() {
1687 this.view_form.on_created.add_last(function(r, success) {
1689 var id = arguments[0].result;
1690 self.on_select_elements([id]);
1693 this.view_form.do_show();
1697 openerp.base.form.SelectCreateListView = openerp.base.ListView.extend({
1698 do_add_record: function () {
1699 this.popup.new_object();
1701 select_record: function(index) {
1702 this.popup.on_select_elements([this.dataset.ids[index]]);
1704 do_select: function(ids, records) {
1705 this._super(ids, records);
1706 this.popup.on_click_element(ids);
1710 openerp.base.form.FieldReference = openerp.base.form.Field.extend({
1711 init: function(view, node) {
1712 this._super(view, node);
1713 this.template = "FieldReference";
1717 openerp.base.form.FieldBinary = openerp.base.form.Field.extend({
1718 init: function(view, node) {
1719 this._super(view, node);
1720 this.iframe = this.element_id + '_iframe';
1721 this.binary_value = false;
1724 this._super.apply(this, arguments);
1725 this.$element.find('input.oe-binary-file').change(this.on_file_change);
1726 this.$element.find('button.oe-binary-file-save').click(this.on_save_as);
1727 this.$element.find('.oe-binary-file-clear').click(this.on_clear);
1729 set_value_from_ui: function() {
1731 human_filesize : function(size) {
1732 var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
1734 while (size >= 1024) {
1738 return size.toFixed(2) + ' ' + units[i];
1740 on_file_change: function(e) {
1741 // TODO: on modern browsers, we could directly read the file locally on client ready to be used on image cropper
1742 // http://www.html5rocks.com/tutorials/file/dndfiles/
1743 // http://deepliquid.com/projects/Jcrop/demos.php?demo=handler
1744 window[this.iframe] = this.on_file_uploaded;
1745 if ($(e.target).val() != '') {
1746 this.$element.find('form.oe-binary-form input[name=session_id]').val(this.session.session_id);
1747 this.$element.find('form.oe-binary-form').submit();
1748 this.toggle_progress();
1751 toggle_progress: function() {
1752 this.$element.find('.oe-binary-progress, .oe-binary').toggle();
1754 on_file_uploaded: function(size, name, content_type, file_base64) {
1755 delete(window[this.iframe]);
1756 if (size === false) {
1757 this.notification.warn("File Upload", "There was a problem while uploading your file");
1758 // TODO: use openerp web exception handler
1759 console.log("Error while uploading file : ", name);
1761 this.on_file_uploaded_and_valid.apply(this, arguments);
1762 this.on_ui_change();
1764 this.toggle_progress();
1766 on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
1768 on_save_as: function() {
1769 if (!this.view.datarecord.id) {
1770 this.notification.warn("Can't save file", "The record has not yet been saved");
1772 var url = '/base/binary/saveas?session_id=' + this.session.session_id + '&model=' +
1773 this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name +
1774 '&fieldname=' + (this.node.attrs.filename || '') + '&t=' + (new Date().getTime())
1778 on_clear: function() {
1779 if (this.value !== false) {
1781 this.binary_value = false;
1782 this.on_ui_change();
1788 openerp.base.form.FieldBinaryFile = openerp.base.form.FieldBinary.extend({
1789 init: function(view, node) {
1790 this._super(view, node);
1791 this.template = "FieldBinaryFile";
1793 set_value: function(value) {
1794 this._super.apply(this, arguments);
1795 var show_value = (value != null && value !== false) ? value : '';
1796 this.$element.find('input').eq(0).val(show_value);
1798 on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
1799 this.value = file_base64;
1800 this.binary_value = true;
1801 var show_value = this.human_filesize(size);
1802 this.$element.find('input').eq(0).val(show_value);
1803 this.set_filename(name);
1805 set_filename: function(value) {
1806 var filename = this.node.attrs.filename;
1807 if (this.view.fields[filename]) {
1808 this.view.fields[filename].set_value(value);
1809 this.view.fields[filename].on_ui_change();
1812 on_clear: function() {
1813 this._super.apply(this, arguments);
1814 this.$element.find('input').eq(0).val('');
1815 this.set_filename('');
1819 openerp.base.form.FieldBinaryImage = openerp.base.form.FieldBinary.extend({
1820 init: function(view, node) {
1821 this._super(view, node);
1822 this.template = "FieldBinaryImage";
1825 this._super.apply(this, arguments);
1826 this.$image = this.$element.find('img.oe-binary-image');
1828 set_image_maxwidth: function() {
1829 this.$image.css('max-width', this.$element.width());
1831 on_file_change: function() {
1832 this.set_image_maxwidth();
1833 this._super.apply(this, arguments);
1835 on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
1836 this.value = file_base64;
1837 this.binary_value = true;
1838 this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64);
1840 on_clear: function() {
1841 this._super.apply(this, arguments);
1842 this.$image.attr('src', '/base/static/src/img/placeholder.png');
1844 set_value: function(value) {
1845 this._super.apply(this, arguments);
1846 this.set_image_maxwidth();
1847 var url = '/base/binary/image?session_id=' + this.session.session_id + '&model=' +
1848 this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime())
1849 this.$image.attr('src', url);
1854 * Registry of form widgets, called by :js:`openerp.base.FormView`
1856 openerp.base.form.widgets = new openerp.base.Registry({
1857 'frame' : 'openerp.base.form.WidgetFrame',
1858 'group' : 'openerp.base.form.WidgetFrame',
1859 'notebook' : 'openerp.base.form.WidgetNotebook',
1860 'separator' : 'openerp.base.form.WidgetSeparator',
1861 'label' : 'openerp.base.form.WidgetLabel',
1862 'button' : 'openerp.base.form.WidgetButton',
1863 'char' : 'openerp.base.form.FieldChar',
1864 'email' : 'openerp.base.form.FieldEmail',
1865 'url' : 'openerp.base.form.FieldUrl',
1866 'text' : 'openerp.base.form.FieldText',
1867 'text_wiki' : 'openerp.base.form.FieldText',
1868 'date' : 'openerp.base.form.FieldDate',
1869 'datetime' : 'openerp.base.form.FieldDatetime',
1870 'selection' : 'openerp.base.form.FieldSelection',
1871 'many2one' : 'openerp.base.form.FieldMany2One',
1872 'many2many' : 'openerp.base.form.FieldMany2Many',
1873 'one2many' : 'openerp.base.form.FieldOne2Many',
1874 'one2many_list' : 'openerp.base.form.FieldOne2Many',
1875 'reference' : 'openerp.base.form.FieldReference',
1876 'boolean' : 'openerp.base.form.FieldBoolean',
1877 'float' : 'openerp.base.form.FieldFloat',
1878 'integer': 'openerp.base.form.FieldFloat',
1879 'progressbar': 'openerp.base.form.FieldProgressBar',
1880 'float_time': 'openerp.base.form.FieldFloatTime',
1881 'image': 'openerp.base.form.FieldBinaryImage',
1882 'binary': 'openerp.base.form.FieldBinaryFile'
1887 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: