[FIX] Create new record in form view when dataset index is 0 and has empty ids list.
[odoo/odoo.git] / addons / base / static / src / js / form.js
1
2 openerp.base.form = function (openerp) {
3
4 openerp.base.views.add('form', 'openerp.base.FormView');
5 openerp.base.FormView =  openerp.base.View.extend( /** @lends openerp.base.FormView# */{
6     /**
7      * Indicates that this view is not searchable, and thus that no search
8      * view should be displayed (if there is one active).
9      */
10     searchable: false,
11     template: "FormView",
12     /**
13      * @constructs
14      * @param {openerp.base.Session} session the current openerp session
15      * @param {String} element_id this view's root element id
16      * @param {openerp.base.DataSet} dataset the dataset this view will work with
17      * @param {String} view_id the identifier of the OpenERP view object
18      *
19      * @property {openerp.base.Registry} registry=openerp.base.form.widgets widgets registry for this form view instance
20      */
21     init: function(view_manager, session, element_id, dataset, view_id) {
22         this._super(session, element_id);
23         this.view_manager = view_manager || new openerp.base.NullViewManager();
24         this.dataset = dataset;
25         this.model = dataset.model;
26         this.view_id = view_id;
27         this.fields_view = {};
28         this.widgets = {};
29         this.widgets_counter = 0;
30         this.fields = {};
31         this.datarecord = {};
32         this.ready = false;
33         this.show_invalid = true;
34         this.touched = false;
35         this.flags = this.view_manager.flags || {};
36         this.default_focus_field = null;
37         this.default_focus_button = null;
38         this.registry = openerp.base.form.widgets;
39         this.has_been_loaded = $.Deferred();
40     },
41     start: function() {
42         //this.log('Starting FormView '+this.model+this.view_id)
43         if (this.embedded_view) {
44             return $.Deferred().then(this.on_loaded).resolve({fields_view: this.embedded_view});
45         } else {
46             return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id,
47                 toolbar:!!this.flags.sidebar}, this.on_loaded);
48         }
49     },
50     on_loaded: function(data) {
51         var self = this;
52         this.fields_view = data.fields_view;
53         var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
54
55         this.$element.html(QWeb.render(this.template, { 'frame': frame, 'view': this }));
56         _.each(this.widgets, function(w) {
57             w.start();
58         });
59         this.$element.find('div.oe_form_pager button[data-pager-action]').click(function() {
60             var action = $(this).data('pager-action');
61             self.on_pager_action(action);
62         });
63
64         this.$element.find('#' + this.element_id + '_header button.oe_form_button_save').click(this.do_save);
65         this.$element.find('#' + this.element_id + '_header button.oe_form_button_save_edit').click(this.do_save_edit);
66         this.$element.find('#' + this.element_id + '_header button.oe_form_button_cancel').click(this.do_cancel);
67         this.$element.find('#' + this.element_id + '_header button.oe_form_button_new').click(this.on_button_new);
68
69         this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
70         this.has_been_loaded.resolve();
71     },
72     do_show: function () {
73         var self = this;
74         self.$element.show();
75         if (this.dataset.index === null || (this.dataset.index === 0 && this.dataset.ids.length == 0)) {
76             // null index means we should start a new record
77             // 0 index with empty ids means we called the form with empty dataset (wizards, switched to form from empty list, ...)
78             this.on_button_new();
79         } else {
80             this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
81         }
82         this.view_manager.sidebar.do_refresh(true);
83     },
84     do_hide: function () {
85         this.$element.hide();
86     },
87     on_record_loaded: function(record) {
88         this.touched = false;
89         if (record) {
90             this.datarecord = record;
91             for (var f in this.fields) {
92                 var field = this.fields[f];
93                 field.touched = false;
94                 field.set_value(this.datarecord[f] || false);
95                 field.validate();
96             }
97             if (!record.id) {
98                 // New record: Second pass in order to trigger the onchanges
99                 this.touched = true;
100                 this.show_invalid = false;
101                 for (var f in record) {
102                     this.on_form_changed(this.fields[f]);
103                 }
104             }
105             this.on_form_changed();
106             this.show_invalid = this.ready = true;
107         } else {
108             this.log("No record received");
109         }
110         this.do_update_pager(record.id == null);
111         this.do_update_sidebar();
112         if (this.default_focus_field) {
113             this.default_focus_field.focus();
114         }
115     },
116     on_form_changed: function(widget) {
117         if (widget && widget.node.attrs.on_change) {
118             this.do_onchange(widget);
119         } else {
120             for (var w in this.widgets) {
121                 w = this.widgets[w];
122                 w.process_attrs();
123                 w.update_dom();
124             }
125         }
126     },
127     on_pager_action: function(action) {
128         switch (action) {
129             case 'first':
130                 this.dataset.index = 0;
131                 break;
132             case 'previous':
133                 this.dataset.previous();
134                 break;
135             case 'next':
136                 this.dataset.next();
137                 break;
138             case 'last':
139                 this.dataset.index = this.dataset.ids.length - 1;
140                 break;
141         }
142         this.reload();
143     },
144     do_update_pager: function(hide_index) {
145         var $pager = this.$element.find('#' + this.element_id + '_header div.oe_form_pager');
146         var index = hide_index ? '-' : this.dataset.index + 1;
147         $pager.find('span.oe_pager_index').html(index);
148         $pager.find('span.oe_pager_count').html(this.dataset.count);
149     },
150     do_onchange: function(widget, processed) {
151         processed = processed || [];
152         if (widget.node.attrs.on_change) {
153             var self = this;
154             this.ready = false;
155             var onchange = _.trim(widget.node.attrs.on_change);
156             var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
157             if (call) {
158                 var method = call[1], args = [];
159                 var argument_replacement = {
160                     'False' : false,
161                     'True' : true,
162                     'None' : null
163                 }
164                 _.each(call[2].split(','), function(a) {
165                     var field = _.trim(a);
166                     if (field in argument_replacement) {
167                         args.push(argument_replacement[field]);
168                     } else if (self.fields[field]) {
169                         var value = self.fields[field].get_value();
170                         args.push(value == null ? false : value);
171                     } else {
172                         args.push(false);
173                         self.log("warning : on_change can't find field " + field, onchange);
174                     }
175                 });
176                 var ajax = {
177                     url: '/base/dataset/call',
178                     async: false
179                 };
180                 return this.rpc(ajax, {
181                     model: this.dataset.model,
182                     method: method,
183                     args: [(this.datarecord.id == null ? [] : [this.datarecord.id])].concat(args)
184                 }, function(response) {
185                     self.on_processed_onchange(response, processed);
186                 });
187             } else {
188                 this.log("Wrong on_change format", on_change);
189             }
190         }
191     },
192     on_processed_onchange: function(response, processed) {
193         var result = response.result;
194         if (result.value) {
195             for (var f in result.value) {
196                 var field = this.fields[f];
197                 if (field) {
198                     var value = result.value[f];
199                     processed.push(field.name);
200                     if (field.get_value() != value) {
201                         field.set_value(value);
202                         if (_.indexOf(processed, field.name) < 0) {
203                             this.do_onchange(field, processed);
204                         }
205                     }
206                 } else {
207                     this.log("warning : on_processed_onchange can't find field " + field, result);
208                 }
209             }
210             this.on_form_changed();
211         }
212         if (result.warning) {
213             $(QWeb.render("DialogWarning", result.warning)).dialog({
214                 modal: true,
215                 buttons: {
216                     Ok: function() {
217                         $(this).dialog("close");
218                     }
219                 }
220             });
221         }
222         if (result.domain) {
223             // Will be removed ?
224         }
225         this.ready = true;
226     },
227     on_button_new: function() {
228         var self = this;
229         $.when(this.has_been_loaded).then(function() {
230             self.dataset.default_get(_.keys(self.fields_view.fields), function(result) {
231                 self.on_record_loaded(result.result);
232             });
233         });
234     },
235     /**
236      * Triggers saving the form's record. Chooses between creating a new
237      * record or saving an existing one depending on whether the record
238      * already has an id property.
239      *
240      * @param {Function} success callback on save success
241      * @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)
242      */
243     do_save: function(success, prepend_on_create) {
244         var self = this;
245         if (!this.ready) {
246             return false;
247         }
248         var invalid = false;
249         var values = {};
250         for (var f in this.fields) {
251             f = this.fields[f];
252             if (f.invalid) {
253                 invalid = true;
254                 f.update_dom();
255             } else if (f.touched) {
256                 values[f.name] = f.get_value();
257             }
258         }
259         if (invalid) {
260             this.on_invalid();
261             return false;
262         } else {
263             this.log("About to save", values);
264             if (!this.datarecord.id) {
265                 this.dataset.create(values, function(r) {
266                     self.on_created(r, success, prepend_on_create);
267                 });
268             } else {
269                 this.dataset.write(this.datarecord.id, values, function(r) {
270                     self.on_saved(r, success);
271                 });
272             }
273             return true;
274         }
275     },
276     do_save_edit: function() {
277         this.do_save();
278         //this.switch_readonly(); Use promises
279     },
280     switch_readonly: function() {
281     },
282     switch_editable: function() {
283     },
284     on_invalid: function() {
285         var msg = "<ul>";
286         _.each(this.fields, function(f) {
287             if (f.invalid) {
288                 msg += "<li>" + f.string + "</li>";
289             }
290         });
291         msg += "</ul>";
292         this.notification.warn("The following fields are invalid :", msg);
293     },
294     on_saved: function(r, success) {
295         if (!r.result) {
296             this.notification.warn("Record not saved", "Problem while saving record.");
297         } else {
298             this.notification.notify("Record saved", "The record #" + this.datarecord.id + " has been saved.");
299             if (success) {
300                 success(r);
301             }
302         }
303     },
304     /**
305      * Updates the form' dataset to contain the new record:
306      *
307      * * Adds the newly created record to the current dataset (at the end by
308      *   default)
309      * * Selects that record (sets the dataset's index to point to the new
310      *   record's id).
311      * * Updates the pager and sidebar displays
312      *
313      * @param {Object} r
314      * @param {Function} success callback to execute after having updated the dataset
315      * @param {Boolean} [prepend_on_create=false] adds the newly created record at the beginning of the dataset instead of the end
316      */
317     on_created: function(r, success, prepend_on_create) {
318         if (!r.result) {
319             this.notification.warn("Record not created", "Problem while creating record.");
320         } else {
321             this.datarecord.id = r.result;
322             if (!prepend_on_create) {
323                 this.dataset.ids.push(this.datarecord.id);
324                 this.dataset.index = this.dataset.ids.length - 1;
325             } else {
326                 this.dataset.ids.unshift(this.datarecord.id);
327                 this.dataset.index = 0;
328             }
329             this.dataset.count++;
330             this.do_update_pager();
331             this.do_update_sidebar();
332             this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id);
333             if (success) {
334                 success(_.extend(r, {created: true}));
335             }
336         }
337     },
338     do_search: function (domains, contexts, groupbys) {
339         this.notification.notify("Searching form");
340     },
341     on_action: function (action) {
342         this.notification.notify('Executing action ' + action);
343     },
344     do_cancel: function () {
345         this.notification.notify("Cancelling form");
346     },
347     do_update_sidebar: function() {
348         if (this.flags.sidebar === false) {
349             return;
350         }
351         if (!this.datarecord.id) {
352             this.on_attachments_loaded([]);
353         } else {
354             this.rpc('/base/dataset/search_read', {
355                 model: 'ir.attachment',
356                 fields: ['name', 'url', 'type'],
357                 domain: [['res_model', '=', this.dataset.model], ['res_id', '=', this.datarecord.id], ['type', 'in', ['binary', 'url']]],
358                 context: this.dataset.context
359             }, this.on_attachments_loaded);
360         }
361     },
362     on_attachments_loaded: function(attachments) {
363         this.$sidebar = this.view_manager.sidebar.$element.find('.sidebar-attachments');
364         this.attachments = attachments;
365         this.$sidebar.html(QWeb.render('FormView.sidebar.attachments', this));
366         this.$sidebar.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete);
367         this.$sidebar.find('.oe-binary-file').change(this.on_attachment_changed);
368     },
369     on_attachment_changed: function(e) {
370         window[this.element_id + '_iframe'] = this.do_update_sidebar;
371         var $e = $(e.target);
372         if ($e.val() != '') {
373             this.$sidebar.find('form.oe-binary-form').submit();
374             $e.parent().find('input[type=file]').attr('disabled', 'true');
375             $e.parent().find('button').attr('disabled', 'true').find('img, span').toggle();
376         }
377     },
378     on_attachment_delete: function(e) {
379         var self = this, $e = $(e.currentTarget);
380         var name = _.trim($e.parent().find('a.oe-sidebar-attachments-link').text());
381         if (confirm("Do you really want to delete the attachment " + name + " ?")) {
382             this.rpc('/base/dataset/unlink', {
383                 model: 'ir.attachment',
384                 ids: [parseInt($e.attr('data-id'))]
385             }, function(r) {
386                 $e.parent().remove();
387                 self.notification.notify("Delete an attachment", "The attachment '" + name + "' has been deleted");
388             });
389         }
390     },
391     reload: function() {
392         if (this.datarecord.id) {
393             this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
394         } else {
395             this.on_button_new();
396         }
397     }
398 });
399
400 /** @namespace */
401 openerp.base.form = {};
402
403 openerp.base.form.compute_domain = function(expr, fields) {
404     var stack = [];
405     for (var i = expr.length - 1; i >= 0; i--) {
406         var ex = expr[i];
407         if (ex.length == 1) {
408             var top = stack.pop();
409             switch (ex) {
410                 case '|':
411                     stack.push(stack.pop() || top);
412                     continue;
413                 case '&':
414                     stack.push(stack.pop() && top);
415                     continue;
416                 case '!':
417                     stack.push(!top);
418                     continue;
419                 default:
420                     throw new Error('Unknown domain operator ' + ex);
421             }
422         }
423
424         var field = fields[ex[0]].value;
425         var op = ex[1];
426         var val = ex[2];
427
428         switch (op.toLowerCase()) {
429             case '=':
430             case '==':
431                 stack.push(field == val);
432                 break;
433             case '!=':
434             case '<>':
435                 stack.push(field != val);
436                 break;
437             case '<':
438                 stack.push(field < val);
439                 break;
440             case '>':
441                 stack.push(field > val);
442                 break;
443             case '<=':
444                 stack.push(field <= val);
445                 break;
446             case '>=':
447                 stack.push(field >= val);
448                 break;
449             case 'in':
450                 stack.push(_(val).contains(field));
451                 break;
452             case 'not in':
453                 stack.push(!_(val).contains(field));
454                 break;
455             default:
456                 this.log("Unsupported operator in attrs :", op);
457         }
458     }
459     return _.all(stack);
460 };
461
462 openerp.base.form.Widget = openerp.base.Controller.extend({
463     template: 'Widget',
464     init: function(view, node) {
465         this.view = view;
466         this.node = node;
467         this.attrs = JSON.parse(this.node.attrs.attrs || '{}');
468         this.type = this.type || node.tag;
469         this.element_name = this.element_name || this.type;
470         this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_");
471
472         this._super(this.view.session, this.element_id);
473
474         this.view.widgets[this.element_id] = this;
475         this.children = node.children;
476         this.colspan = parseInt(node.attrs.colspan || 1);
477
478         this.string = this.string || node.attrs.string;
479         this.help = this.help || node.attrs.help;
480         this.invisible = (node.attrs.invisible == '1');
481     },
482     start: function() {
483         this.$element = $('#' + this.element_id);
484     },
485     process_attrs: function() {
486         var compute_domain = openerp.base.form.compute_domain;
487         for (var a in this.attrs) {
488             this[a] = compute_domain(this.attrs[a], this.view.fields);
489         }
490     },
491     update_dom: function() {
492         this.$element.toggle(!this.invisible);
493     },
494     render: function() {
495         var template = this.template;
496         return QWeb.render(template, { "widget": this });
497     }
498 });
499
500 openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
501     template: 'WidgetFrame',
502     init: function(view, node) {
503         this._super(view, node);
504         this.columns = node.attrs.col || 4;
505         this.x = 0;
506         this.y = 0;
507         this.table = [];
508         this.add_row();
509         for (var i = 0; i < node.children.length; i++) {
510             var n = node.children[i];
511             if (n.tag == "newline") {
512                 this.add_row();
513             } else {
514                 this.handle_node(n);
515             }
516         }
517         this.set_row_cells_with(this.table[this.table.length - 1]);
518     },
519     add_row: function(){
520         if (this.table.length) {
521             this.set_row_cells_with(this.table[this.table.length - 1]);
522         }
523         var row = [];
524         this.table.push(row);
525         this.x = 0;
526         this.y += 1;
527         return row;
528     },
529     set_row_cells_with: function(row) {
530         for (var i = 0; i < row.length; i++) {
531             var w = row[i];
532             if (w.is_field_label) {
533                 w.width = "1%";
534                 if (row[i + 1]) {
535                     row[i + 1].width = Math.round((100 / this.columns) * (w.colspan + 1) - 1) + '%';
536                 }
537             } else if (w.width === undefined) {
538                 w.width = Math.round((100 / this.columns) * w.colspan) + '%';
539             }
540         }
541     },
542     handle_node: function(node) {
543         var type = this.view.fields_view.fields[node.attrs.name] || {};
544         var widget_type = node.attrs.widget || type.type || node.tag;
545         var widget = new (this.view.registry.get_object(widget_type)) (this.view, node);
546         if (node.tag == 'field') {
547             if (node.attrs.default_focus == '1') {
548                 this.view.default_focus_field = widget;
549             }
550             if (node.attrs.nolabel != '1') {
551                 var label = new (this.view.registry.get_object('label')) (this.view, node);
552                 label["for"] = widget;
553                 this.add_widget(label);
554             }
555         }
556         this.add_widget(widget);
557     },
558     add_widget: function(widget) {
559         var current_row = this.table[this.table.length - 1];
560         if (current_row.length && (this.x + widget.colspan) > this.columns) {
561             current_row = this.add_row();
562         }
563         current_row.push(widget);
564         this.x += widget.colspan;
565         return widget;
566     }
567 });
568
569 openerp.base.form.WidgetNotebook = openerp.base.form.Widget.extend({
570     init: function(view, node) {
571         this._super(view, node);
572         this.template = "WidgetNotebook";
573         this.pages = [];
574         for (var i = 0; i < node.children.length; i++) {
575             var n = node.children[i];
576             if (n.tag == "page") {
577                 var page = new openerp.base.form.WidgetFrame(this.view, n);
578                 this.pages.push(page);
579             }
580         }
581     },
582     start: function() {
583         this._super.apply(this, arguments);
584         this.$element.tabs();
585     }
586 });
587
588 openerp.base.form.WidgetSeparator = openerp.base.form.Widget.extend({
589     init: function(view, node) {
590         this._super(view, node);
591         this.template = "WidgetSeparator";
592     }
593 });
594
595 openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
596     init: function(view, node) {
597         this._super(view, node);
598         this.template = "WidgetButton";
599         if (node.attrs.default_focus == '1') {
600             // TODO fme: provide enter key binding to widgets
601             this.view.default_focus_button = this;
602         }
603     },
604     start: function() {
605         this._super.apply(this, arguments);
606         this.$element.click(this.on_click);
607     },
608     on_click: function(saved) {
609         var self = this;
610         if (!this.node.attrs.special && this.view.touched && saved !== true) {
611             this.view.do_save(function() {
612                 self.on_click(true);
613             });
614         } else {
615             if (this.node.attrs.confirm) {
616                 var dialog = $('<div>' + this.node.attrs.confirm + '</div>').dialog({
617                     title: 'Confirm',
618                     modal: true,
619                     buttons: {
620                         Ok: function() {
621                             self.on_confirmed();
622                             $(this).dialog("close");
623                         },
624                         Cancel: function() {
625                             $(this).dialog("close");
626                         }
627                     }
628                 });
629             } else {
630                 this.on_confirmed();
631             }
632         }
633     },
634     on_confirmed: function() {
635         var self = this;
636
637         this.view.execute_action(
638             this.node.attrs, this.view.dataset, this.session.action_manager,
639             this.view.datarecord.id, function (result) {
640                 self.log("Button returned", result);
641                 self.view.reload();
642             });
643     }
644 });
645
646 openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
647     init: function(view, node) {
648         this.is_field_label = true;
649         this.element_name = 'label_' + node.attrs.name;
650
651         this._super(view, node);
652
653         this.template = "WidgetLabel";
654         this.colspan = 1;
655     },
656     render: function () {
657         if (this['for'] && this.type !== 'label') {
658             return QWeb.render(this.template, {widget: this['for']});
659         }
660         // Actual label widgets should not have a false and have type label
661         return QWeb.render(this.template, {widget: this});
662     }
663 });
664
665 openerp.base.form.Field = openerp.base.form.Widget.extend({
666     init: function(view, node) {
667         this.name = node.attrs.name;
668         this.value = undefined;
669         view.fields[this.name] = this;
670         this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type;
671         this.element_name = "field_" + this.name + "_" + this.type;
672
673         this._super(view, node);
674
675         if (node.attrs.nolabel != '1' && this.colspan > 1) {
676             this.colspan--;
677         }
678         this.field = view.fields_view.fields[node.attrs.name] || {};
679         this.string = node.attrs.string || this.field.string;
680         this.help = node.attrs.help || this.field.help;
681         this.invisible = (this.invisible || this.field.invisible == '1');
682         this.nolabel = (this.field.nolabel || node.attrs.nolabel) == '1';
683         this.readonly = (this.field.readonly || node.attrs.readonly) == '1';
684         this.required = (this.field.required || node.attrs.required) == '1';
685         this.invalid = false;
686         this.touched = false;
687     },
688     set_value: function(value) {
689         this.value = value;
690         this.invalid = false;
691         this.update_dom();
692     },
693     set_value_from_ui: function() {
694         this.value = undefined;
695     },
696     get_value: function() {
697         return this.value;
698     },
699     update_dom: function() {
700         this._super.apply(this, arguments);
701         this.$element.toggleClass('disabled', this.readonly);
702         this.$element.toggleClass('required', this.required);
703         if (this.view.show_invalid) {
704             this.$element.toggleClass('invalid', this.invalid);
705         }
706     },
707     on_ui_change: function() {
708         this.touched = this.view.touched = true;
709         this.validate();
710         if (!this.invalid) {
711             this.set_value_from_ui();
712             this.view.on_form_changed(this);
713         } else {
714             this.update_dom();
715         }
716     },
717     validate: function() {
718         this.invalid = false;
719     },
720     focus: function() {
721     }
722 });
723
724 openerp.base.form.FieldChar = openerp.base.form.Field.extend({
725     init: function(view, node) {
726         this._super(view, node);
727         this.template = "FieldChar";
728     },
729     start: function() {
730         this._super.apply(this, arguments);
731         this.$element.find('input').change(this.on_ui_change);
732     },
733     set_value: function(value) {
734         this._super.apply(this, arguments);
735         var show_value = (value != null && value !== false) ? value : '';
736         this.$element.find('input').val(show_value);
737     },
738     update_dom: function() {
739         this._super.apply(this, arguments);
740         this.$element.find('input').attr('disabled', this.readonly);
741     },
742     set_value_from_ui: function() {
743         this.value = this.$element.find('input').val();
744     },
745     validate: function() {
746         this.invalid = false;
747         var value = this.$element.find('input').val();
748         if (value === "") {
749             this.invalid = this.required;
750         } else if (this.validation_regex) {
751             this.invalid = !this.validation_regex.test(value);
752         }
753     },
754     focus: function() {
755         this.$element.find('input').focus();
756     }
757 });
758
759 openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({
760     init: function(view, node) {
761         this._super(view, node);
762         this.template = "FieldEmail";
763         this.validation_regex = /@/;
764     },
765     start: function() {
766         this._super.apply(this, arguments);
767         this.$element.find('button').click(this.on_button_clicked);
768     },
769     on_button_clicked: function() {
770         if (!this.value || this.invalid) {
771             this.notification.warn("E-mail error", "Can't send email to invalid e-mail address");
772         } else {
773             location.href = 'mailto:' + this.value;
774         }
775     },
776     set_value: function(value) {
777         this._super.apply(this, arguments);
778         var show_value = (value != null && value !== false) ? value : '';
779         this.$element.find('a').attr('href', 'mailto:' + show_value);
780     }
781 });
782
783 openerp.base.form.FieldUrl = openerp.base.form.FieldChar.extend({
784     init: function(view, node) {
785         this._super(view, node);
786         this.template = "FieldUrl";
787     },
788     start: function() {
789         this._super.apply(this, arguments);
790         this.$element.find('button').click(this.on_button_clicked);
791     },
792     on_button_clicked: function() {
793         if (!this.value) {
794             this.notification.warn("Resource error", "This resource is empty");
795         } else {
796             window.open(this.value);
797         }
798     }
799 });
800
801 openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
802     init: function(view, node) {
803         this._super(view, node);
804         this.validation_regex = /^-?\d+(\.\d+)?$/;
805     },
806     set_value: function(value) {
807         this._super.apply(this, [value]);
808         if (value === false || value === undefined) {
809             // As in GTK client, floats default to 0
810             value = 0;
811         }
812         var show_value = value.toFixed(2);
813         this.$element.find('input').val(show_value);
814     },
815     set_value_from_ui: function() {
816         this.value = Number(this.$element.find('input').val().replace(/,/g, '.'));
817     }
818 });
819
820 openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
821     init: function(view, node) {
822         this._super(view, node);
823         this.template = "FieldDate";
824         this.jqueryui_object = 'datetimepicker';
825     },
826     start: function() {
827         this._super.apply(this, arguments);
828         this.$element.find('input').change(this.on_ui_change)[this.jqueryui_object]({
829             dateFormat: 'yy-mm-dd',
830             timeFormat: 'hh:mm:ss'
831         });
832     },
833     set_value: function(value) {
834         this._super.apply(this, arguments);
835         if (value == null || value == false) {
836             this.$element.find('input').val('');
837         } else {
838             this.$element.find('input').unbind('change');
839             // jQuery UI date picker wrongly call on_change event herebelow
840             this.$element.find('input')[this.jqueryui_object]('setDate', this.parse(value));
841             this.$element.find('input').change(this.on_ui_change);
842         }
843     },
844     set_value_from_ui: function() {
845         this.value = this.$element.find('input')[this.jqueryui_object]('getDate') || false;
846         if (this.value) {
847             this.value = this.format(this.value);
848         }
849     },
850     validate: function() {
851         this.invalid = this.required && this.$element.find('input')[this.jqueryui_object]('getDate') === '';
852     },
853     focus: function() {
854         this.$element.find('input').focus();
855     },
856     parse: openerp.base.parse_datetime,
857     format: openerp.base.format_datetime
858 });
859
860 openerp.base.form.FieldDate = openerp.base.form.FieldDatetime.extend({
861     init: function(view, node) {
862         this._super(view, node);
863         this.jqueryui_object = 'datepicker';
864     },
865     parse: openerp.base.parse_date,
866     format: openerp.base.format_date
867 });
868
869 openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({
870     init: function(view, node) {
871         this._super(view, node);
872         this.validation_regex = /^\d+:\d+$/;
873     },
874     set_value: function(value) {
875         this._super.apply(this, [value]);
876         if (value === false || value === undefined) {
877             // As in GTK client, floats default to 0
878             value = 0;
879         }
880         var show_value = _.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60));
881         this.$element.find('input').val(show_value);
882     },
883     set_value_from_ui: function() {
884         var time = this.$element.find('input').val().split(':');
885         this.set_value(parseInt(time[0], 10) + parseInt(time[1], 10) / 60);
886     }
887 });
888
889 openerp.base.form.FieldText = openerp.base.form.Field.extend({
890     init: function(view, node) {
891         this._super(view, node);
892         this.template = "FieldText";
893         this.validation_regex = null;
894     },
895     start: function() {
896         this._super.apply(this, arguments);
897         this.$element.find('textarea').change(this.on_ui_change);
898     },
899     set_value: function(value) {
900         this._super.apply(this, arguments);
901         var show_value = (value != null && value !== false) ? value : '';
902         this.$element.find('textarea').val(show_value);
903     },
904     update_dom: function() {
905         this._super.apply(this, arguments);
906         this.$element.find('textarea').attr('disabled', this.readonly);
907     },
908     set_value_from_ui: function() {
909         this.value = this.$element.find('textarea').val();
910     },
911     validate: function() {
912         this.invalid = false;
913         var value = this.$element.find('textarea').val();
914         if (value === "") {
915             this.invalid = this.required;
916         } else if (this.validation_regex) {
917             this.invalid = !this.validation_regex.test(value);
918         }
919     },
920     focus: function() {
921         this.$element.find('textarea').focus();
922     }
923 });
924
925 openerp.base.form.FieldBoolean = openerp.base.form.Field.extend({
926     init: function(view, node) {
927         this._super(view, node);
928         this.template = "FieldBoolean";
929     },
930     start: function() {
931         var self = this;
932         this._super.apply(this, arguments);
933         this.$element.find('input').click(function() {
934             if ($(this).is(':checked') != self.value) {
935                 self.on_ui_change();
936             }
937         });
938     },
939     set_value: function(value) {
940         this._super.apply(this, arguments);
941         this.$element.find('input')[0].checked = value;
942     },
943     set_value_from_ui: function() {
944         this.value = this.$element.find('input').is(':checked');
945     },
946     update_dom: function() {
947         this._super.apply(this, arguments);
948         this.$element.find('input').attr('disabled', this.readonly);
949     },
950     validate: function() {
951         this.invalid = this.required && !this.$element.find('input').is(':checked');
952     },
953     focus: function() {
954         this.$element.find('input').focus();
955     }
956 });
957
958 openerp.base.form.FieldProgressBar = openerp.base.form.Field.extend({
959     init: function(view, node) {
960         this._super(view, node);
961         this.template = "FieldProgressBar";
962     },
963     start: function() {
964         this._super.apply(this, arguments);
965         this.$element.find('div').progressbar({
966             value: this.value,
967             disabled: this.readonly
968         });
969     },
970     set_value: function(value) {
971         this._super.apply(this, arguments);
972         var show_value = Number(value);
973         if (show_value === NaN) {
974             show_value = 0;
975         }
976         this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(show_value + '%');
977     }
978 });
979
980 openerp.base.form.FieldTextXml = openerp.base.form.Field.extend({
981 // to replace view editor
982 });
983
984 openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
985     init: function(view, node) {
986         this._super(view, node);
987         this.template = "FieldSelection";
988     },
989     start: function() {
990         this._super.apply(this, arguments);
991         this.$element.find('select').change(this.on_ui_change);
992     },
993     set_value: function(value) {
994         this._super.apply(this, arguments);
995         if (value != null && value !== false) {
996             this.$element.find('select').val(value);
997         } else {
998             this.$element.find('select')[0].selectedIndex = 0;
999         }
1000     },
1001     set_value_from_ui: function() {
1002         this.value = this.$element.find('select').val();
1003     },
1004     update_dom: function() {
1005         this._super.apply(this, arguments);
1006         this.$element.find('select').attr('disabled', this.readonly);
1007     },
1008     validate: function() {
1009         this.invalid = this.required && this.$element.find('select').val() === "";
1010     },
1011     focus: function() {
1012         this.$element.find('select').focus();
1013     }
1014 });
1015
1016 // jquery autocomplete tweak to allow html
1017 (function() {
1018     var proto = $.ui.autocomplete.prototype,
1019         initSource = proto._initSource;
1020
1021     function filter( array, term ) {
1022         var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
1023         return $.grep( array, function(value) {
1024             return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
1025         });
1026     }
1027
1028     $.extend( proto, {
1029         _initSource: function() {
1030             if ( this.options.html && $.isArray(this.options.source) ) {
1031                 this.source = function( request, response ) {
1032                     response( filter( this.options.source, request.term ) );
1033                 };
1034             } else {
1035                 initSource.call( this );
1036             }
1037         },
1038
1039         _renderItem: function( ul, item) {
1040             return $( "<li></li>" )
1041                 .data( "item.autocomplete", item )
1042                 .append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) )
1043                 .appendTo( ul );
1044         }
1045     });
1046 })();
1047
1048 /**
1049  * Builds a new context usable for operations related to fields by merging
1050  * the fields'context with the action's context.
1051  */
1052 var build_relation_context = function(relation_field) {
1053     var action = relation_field.view.view_manager.action || {};
1054     var a_context = action.context || {};
1055     var f_context = relation_field.field.context || {};
1056     var ctx = new openerp.base.CompoundContext(a_context).add(f_context);
1057     return ctx;
1058 }
1059
1060 openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
1061     init: function(view, node) {
1062         this._super(view, node);
1063         this.template = "FieldMany2One";
1064         this.limit = 7;
1065         this.value = null;
1066         this.cm_id = _.uniqueId('m2o_cm_');
1067         this.last_search = [];
1068     },
1069     start: function() {
1070         this._super();
1071         var self = this;
1072         this.$input = this.$element.find("input");
1073         this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
1074         this.$menu_btn = this.$element.find(".oe-m2o-cm-button");
1075
1076         // context menu
1077         var bindings = {};
1078         bindings[this.cm_id + "_search"] = function() {
1079             self._search_create_popup("search");
1080         };
1081         bindings[this.cm_id + "_create"] = function() {
1082             self._search_create_popup("form");
1083         };
1084         bindings[this.cm_id + "_open"] = function() {
1085             if (!self.value) {
1086                 return;
1087             }
1088             self.session.action_manager.do_action({
1089                 "res_model": self.field.relation,
1090                 "views":[[false,"form"]],
1091                 "res_id": self.value[0],
1092                 "type":"ir.actions.act_window",
1093                 "view_type":"form",
1094                 "view_mode":"form",
1095                 "target":"new",
1096                 "context": build_relation_context(self)
1097             });
1098         };
1099         var cmenu = this.$menu_btn.contextMenu(this.cm_id, {'leftClickToo': true,
1100             bindings: bindings, itemStyle: {"color": ""},
1101             onContextMenu: function() {
1102                 if(self.value) {
1103                     $("#" + self.cm_id + "_open").removeClass("oe-m2o-disabled-cm");
1104                 } else {
1105                     $("#" + self.cm_id + "_open").addClass("oe-m2o-disabled-cm");
1106                 }
1107                 return true;
1108             }
1109         });
1110
1111         // some behavior for input
1112         this.$input.keyup(function() {
1113             if (self.$input.val() === "") {
1114                 self._change_int_value(null);
1115             } else if (self.value === null || (self.value && self.$input.val() !== self.value[1])) {
1116                 self._change_int_value(undefined);
1117             }
1118         });
1119         this.$drop_down.click(function() {
1120             if (self.$input.autocomplete("widget").is(":visible")) {
1121                 self.$input.autocomplete("close");
1122             } else {
1123                 if (self.value) {
1124                     self.$input.autocomplete("search", "");
1125                 } else {
1126                     self.$input.autocomplete("search");
1127                 }
1128                 self.$input.focus();
1129             }
1130         });
1131         var anyoneLoosesFocus = function() {
1132             if (!self.$input.is(":focus") &&
1133                     !self.$input.autocomplete("widget").is(":visible") &&
1134                     !self.value) {
1135                 if(self.value === undefined && self.last_search.length > 0) {
1136                     self._change_int_ext_value(self.last_search[0]);
1137                 } else {
1138                     self._change_int_ext_value(null);
1139                 }
1140             }
1141         }
1142         this.$input.focusout(anyoneLoosesFocus);
1143
1144         // autocomplete
1145         this.$input.autocomplete({
1146             source: function(req, resp) { self.get_search_result(req, resp); },
1147             select: function(event, ui) {
1148                 var item = ui.item;
1149                 if (item.id) {
1150                     self._change_int_value([item.id, item.name]);
1151                 } else if (item.action) {
1152                     self._change_int_value(undefined);
1153                     item.action();
1154                     return false;
1155                 }
1156             },
1157             focus: function(e, ui) {
1158                 e.preventDefault();
1159             },
1160             html: true,
1161             close: anyoneLoosesFocus,
1162             minLength: 0,
1163             delay: 0
1164         });
1165     },
1166     // autocomplete component content handling
1167     get_search_result: function(request, response) {
1168         var search_val = request.term;
1169         var self = this;
1170
1171         var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []);
1172
1173         dataset.name_search([search_val, self.field.domain || [], 'ilike',
1174                 build_relation_context(self), this.limit + 1], function(data) {
1175             self.last_search = data.result;
1176             // possible selections for the m2o
1177             var values = _.map(data.result, function(x) {
1178                 return {label: $('<span />').text(x[1]).html(), name:x[1], id:x[0]};
1179             });
1180
1181             // search more... if more results that max
1182             if (values.length > self.limit) {
1183                 values = values.slice(0, self.limit);
1184                 values.push({label: "<em>   Search More...</em>", action: function() {
1185                     dataset.name_search([search_val, self.field.domain || [], 'ilike',
1186                             build_relation_context(self), false], function(data) {
1187                         self._change_int_value(null);
1188                         self._search_create_popup("search", data.result);
1189                     });
1190                 }});
1191             }
1192             // quick create
1193             var raw_result = _(data.result).map(function(x) {return x[1];})
1194             if (search_val.length > 0 &&
1195                 !_.include(raw_result, search_val) &&
1196                 (!self.value || search_val !== self.value[1])) {
1197                 values.push({label: '<em>   Create "<strong>' +
1198                         $('<span />').text(search_val).html() + '</strong>"</em>', action: function() {
1199                     self._quick_create(search_val);
1200                 }});
1201             }
1202             // create...
1203             values.push({label: "<em>   Create and Edit...</em>", action: function() {
1204                 self._change_int_value(null);
1205                 self._search_create_popup("form");
1206             }});
1207
1208             response(values);
1209         });
1210     },
1211     _quick_create: function(name) {
1212         var self = this;
1213         var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []);
1214         dataset.call("name_create", [name, build_relation_context(self)], function(data) {
1215             self._change_int_ext_value(data.result);
1216         }, function(a, b) {
1217             self._change_int_value(null);
1218             self._search_create_popup("form", undefined, {"default_name": name});
1219         });
1220     },
1221     // all search/create popup handling
1222     _search_create_popup: function(view, ids, context) {
1223         var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []);
1224         var self = this;
1225         var pop = new openerp.base.form.SelectCreatePopup(null, self.view.session);
1226         pop.select_element(self.field.relation,{
1227                 initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
1228                 initial_view: view,
1229                 disable_multiple_selection: true
1230                 }, self.view.domain || [],
1231                 new openerp.base.CompoundContext(build_relation_context(self)).add(context || {}));
1232         pop.on_select_elements.add(function(element_ids) {
1233             dataset.call("name_get", [element_ids[0]], function(data) {
1234                 self._change_int_ext_value(data.result[0]);
1235                 pop.stop();
1236             });
1237         });
1238     },
1239     _change_int_ext_value: function(value) {
1240         this._change_int_value(value);
1241         this.$input.val(this.value ? this.value[1] : "");
1242     },
1243     _change_int_value: function(value) {
1244         this.value = value;
1245         if (this.original_value === undefined || (this.value !== undefined &&
1246             ((this.original_value ? this.original_value[0] : null) !== (this.value ? this.value[0] : null)))) {
1247             this.original_value = undefined;
1248             this.on_ui_change();
1249         }
1250     },
1251     set_value_from_ui: function() {},
1252     set_value: function(value) {
1253         this._super(value);
1254         this.original_value = value;
1255         this._change_int_ext_value(value);
1256     },
1257     get_value: function() {
1258         if (this.value === undefined)
1259             throw "theorically unreachable state";
1260         return this.value ? this.value[0] : false;
1261     },
1262     validate: function() {
1263         this.invalid = false;
1264         if (this.value === null) {
1265             this.invalid = this.required;
1266         }
1267     }
1268 });
1269
1270 openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
1271     init: function(view, node) {
1272         this._super(view, node);
1273         this.template = "FieldOne2Many";
1274         this.is_started = $.Deferred();
1275         this.is_setted = $.Deferred();
1276     },
1277     start: function() {
1278         this._super.apply(this, arguments);
1279
1280         var self = this;
1281
1282         this.dataset = new openerp.base.DataSetStatic(this.session, this.field.relation);
1283         this.dataset.on_unlink.add_last(function(ids) {
1284             self.dataset.set_ids(_.without.apply(_, [self.dataset.ids].concat(ids)));
1285             self.on_ui_change();
1286             self.reload_current_view();
1287         });
1288
1289         var modes = this.node.attrs.mode;
1290         modes = !!modes ? modes.split(",") : ["tree", "form"];
1291         var views = [];
1292         _.each(modes, function(mode) {
1293             var view = {view_id: false, view_type: mode == "tree" ? "list" : mode};
1294             if (self.field.views && self.field.views[mode]) {
1295                 view.embedded_view = self.field.views[mode];
1296             }
1297             if(view.view_type === "list") {
1298                 view.options = {
1299                 };
1300             }
1301             views.push(view);
1302         });
1303         this.views = views;
1304
1305         this.viewmanager = new openerp.base.ViewManager(this.view.session,
1306             this.element_id, this.dataset, views);
1307         var reg = new openerp.base.Registry();
1308         reg.add("form", openerp.base.views.map["form"]);
1309         reg.add("graph", openerp.base.views.map["graph"]);
1310         reg.add("list", "openerp.base.form.One2ManyListView");
1311         this.viewmanager.registry = reg;
1312
1313         this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
1314             if (view_type == "list") {
1315                 controller.o2m = self;
1316             } else if (view_type == "form") {
1317                 // TODO niv
1318             }
1319             self.is_started.resolve();
1320         });
1321         this.viewmanager.start();
1322
1323         $.when(this.is_started, this.is_setted).then(function() {
1324             if (modes[0] == "tree") {
1325                 var view = self.viewmanager.views[self.viewmanager.active_view].controller;
1326                 view.reload_content();
1327             }
1328             // TODO niv: handle other types of views
1329         });
1330     },
1331     reload_current_view: function() {
1332         var self = this;
1333         var view = self.viewmanager.views[self.viewmanager.active_view].controller;
1334         if(self.viewmanager.active_view === "list") {
1335             view.reload_content();
1336         } else if (self.viewmanager.active_view === "form") {
1337             // TODO niv: but fme did not implemented delete in form view anyway
1338         }
1339     },
1340     set_value: function(value) {
1341         if(value != false) {
1342             this.dataset.set_ids(value);
1343             this.is_setted.resolve();
1344         }
1345     },
1346     get_value: function(value) {
1347         //TODO niv
1348         return [];
1349     }
1350 });
1351
1352 openerp.base.form.One2ManyListView = openerp.base.ListView.extend({
1353     do_add_record: function () {
1354         var self = this;
1355         var pop = new openerp.base.form.SelectCreatePopup(null, self.o2m.view.session);
1356         pop.select_element(self.o2m.field.relation,{
1357             initial_view: "form",
1358             alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined
1359         });
1360         pop.on_select_elements.add(function(element_ids) {
1361             var ids = self.o2m.dataset.ids;
1362             _.each(element_ids, function(x) {if (!_.include(ids, x)) ids.push(x);});
1363             self.o2m.dataset.set_ids(ids);
1364             self.o2m.on_ui_change();
1365             self.o2m.reload_current_view();
1366         });
1367     }
1368 });
1369
1370 openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
1371     init: function(view, node) {
1372         this._super(view, node);
1373         this.template = "FieldMany2Many";
1374         this.list_id = _.uniqueId("many2many");
1375         this.is_started = $.Deferred();
1376         this.is_setted = $.Deferred();
1377     },
1378     start: function() {
1379         this._super.apply(this, arguments);
1380
1381         var self = this;
1382
1383         this.dataset = new openerp.base.DataSetStatic(
1384                 this.session, this.field.relation);
1385         this.dataset.on_unlink.add_last(function(ids) {
1386             self.list_view.reload_content();
1387
1388             self.on_ui_change();
1389         });
1390
1391         this.list_view = new openerp.base.form.Many2ManyListView(
1392                 null, this.view.session, this.list_id, this.dataset, false, {
1393                     'addable': 'Add'
1394             });
1395         this.list_view.m2m_field = this;
1396         this.list_view.on_loaded.add_last(function() {
1397             self.is_started.resolve();
1398         });
1399         this.list_view.start();
1400         $.when(this.is_started, this.is_setted).then(function() {
1401             self.list_view.reload_content();
1402         });
1403     },
1404     set_value: function(value) {
1405         if (value != false) {
1406             this.dataset.set_ids(value);
1407             this.is_setted.resolve();
1408         }
1409     },
1410     get_value: function() {
1411         return [[6,false,this.dataset.ids]];
1412     }
1413 });
1414
1415 openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
1416     do_add_record: function () {
1417         var pop = new openerp.base.form.SelectCreatePopup(
1418                 null, this.m2m_field.view.session);
1419         pop.select_element(this.model);
1420         var self = this;
1421         pop.on_select_elements.add(function(element_ids) {
1422             _.each(element_ids, function(element_id) {
1423                 if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
1424                     self.dataset.set_ids([].concat(self.dataset.ids, [element_id]));
1425                     self.reload_content();
1426                 }
1427             });
1428             pop.stop();
1429         });
1430     },
1431     do_activate_record: function(index, id) {
1432         this.m2m_field.view.session.action_manager.do_action({
1433             "res_model": this.dataset.model,
1434             "views":[[false,"form"]],
1435             "res_id": id,
1436             "type":"ir.actions.act_window",
1437             "view_type":"form",
1438             "view_mode":"form",
1439             "target":"new"
1440         });
1441     }
1442 });
1443
1444 openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
1445     identifier_prefix: "selectcreatepopup",
1446     template: "SelectCreatePopup",
1447     /**
1448      * options:
1449      * - initial_ids
1450      * - initial_view: form or search (default search)
1451      * - disable_multiple_selection
1452      * - alternative_form_view
1453      */
1454     select_element: function(model, options, domain, context) {
1455         this.model = model;
1456         this.domain = domain || [];
1457         this.context = context || {};
1458         this.options = options || {};
1459         this.initial_ids = this.options.initial_ids;
1460         jQuery(this.render()).dialog({title: '',
1461                     modal: true,
1462                     minWidth: 800});
1463         this.start();
1464     },
1465     start: function() {
1466         this._super();
1467         this.dataset = new openerp.base.DataSetSearch(this.session, this.model,
1468             this.context, this.domain);
1469         if ((this.options.initial_view || "search") == "search") {
1470             this.setup_search_view();
1471         } else { // "form"
1472             this.new_object();
1473         }
1474     },
1475     setup_search_view: function() {
1476         var self = this;
1477         if (this.searchview) {
1478             this.searchview.stop();
1479         }
1480         this.searchview = new openerp.base.SearchView(null, this.session,
1481                 this.element_id + "_search", this.dataset, false, {
1482                     "selectable": !this.options.disable_multiple_selection,
1483                     "deletable": false
1484                 });
1485         this.searchview.on_search.add(function(domains, contexts, groupbys) {
1486             if (self.initial_ids) {
1487                 self.view_list.do_search.call(self,[[["id", "in", self.initial_ids]]],
1488                     contexts, groupbys);
1489                 self.initial_ids = undefined;
1490             } else {
1491                 self.view_list.do_search.call(self, domains, contexts, groupbys);
1492             }
1493         });
1494         this.searchview.on_loaded.add_last(function () {
1495             var $buttons = self.searchview.$element.find(".oe_search-view-buttons");
1496             $buttons.append(QWeb.render("SelectCreatePopup.search.buttons"));
1497             var $cbutton = $buttons.find(".oe_selectcreatepopup-search-close");
1498             $cbutton.click(function() {
1499                 self.stop();
1500             });
1501             var $sbutton = $buttons.find(".oe_selectcreatepopup-search-select");
1502             if(self.options.disable_multiple_selection) {
1503                 $sbutton.hide();
1504             }
1505             $sbutton.click(function() {
1506                 self.on_select_elements(self.selected_ids);
1507             });
1508             self.view_list = new openerp.base.form.Many2XPopupListView( null, self.session,
1509                     self.element_id + "_view_list", self.dataset, false,
1510                     {'deletable': false});
1511             self.view_list.popup = self;
1512             self.view_list.do_show();
1513             self.view_list.start().then(function() {
1514                 self.searchview.do_search();
1515             });
1516         });
1517         this.searchview.start();
1518     },
1519     on_select_elements: function(element_ids) {
1520     },
1521     on_click_element: function(ids) {
1522         this.selected_ids = ids || [];
1523         if(this.selected_ids.length > 0) {
1524             this.$element.find(".oe_selectcreatepopup-search-select").removeAttr('disabled');
1525         } else {
1526             this.$element.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled");
1527         }
1528     },
1529     new_object: function() {
1530         var self = this;
1531         if (this.searchview) {
1532             this.searchview.hide();
1533         }
1534         if (this.view_list) {
1535             this.view_list.$element.hide();
1536         }
1537         this.dataset.index = null;
1538         this.view_form = new openerp.base.FormView(null, this.session,
1539                 this.element_id + "_view_form", this.dataset, false);
1540         if (this.options.alternative_form_view) {
1541             this.view_form.set_embedded_view(this.options.alternative_form_view);
1542         }
1543         this.view_form.start();
1544         this.view_form.on_loaded.add_last(function() {
1545             var $buttons = self.view_form.$element.find(".oe_form_buttons");
1546             $buttons.html(QWeb.render("SelectCreatePopup.form.buttons"));
1547             var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save");
1548             $nbutton.click(function() {
1549                 self.view_form.do_save();
1550             });
1551             var $cbutton = $buttons.find(".oe_selectcreatepopup-form-close");
1552             $cbutton.click(function() {
1553                 self.stop();
1554             });
1555         });
1556         this.view_form.on_created.add_last(function(r, success) {
1557             if (r.result) {
1558                 var id = arguments[0].result;
1559                 self.on_select_elements([id]);
1560             }
1561         });
1562         this.view_form.do_show();
1563     }
1564 });
1565
1566 openerp.base.form.Many2XPopupListView = openerp.base.ListView.extend({
1567     do_add_record: function () {
1568         this.popup.new_object();
1569     },
1570     select_record: function(index) {
1571         this.popup.on_select_elements([this.dataset.ids[index]]);
1572     },
1573     do_select: function(ids, records) {
1574         this._super(ids, records);
1575         this.popup.on_click_element(ids);
1576     }
1577 });
1578
1579 openerp.base.form.FieldReference = openerp.base.form.Field.extend({
1580     init: function(view, node) {
1581         this._super(view, node);
1582         this.template = "FieldReference";
1583     }
1584 });
1585
1586 openerp.base.form.FieldBinary = openerp.base.form.Field.extend({
1587     init: function(view, node) {
1588         this._super(view, node);
1589         this.iframe = this.element_id + '_iframe';
1590         this.binary_value = false;
1591     },
1592     start: function() {
1593         this._super.apply(this, arguments);
1594         this.$element.find('input.oe-binary-file').change(this.on_file_change);
1595         this.$element.find('button.oe-binary-file-save').click(this.on_save_as);
1596         this.$element.find('.oe-binary-file-clear').click(this.on_clear);
1597     },
1598     set_value_from_ui: function() {
1599     },
1600     human_filesize : function(size) {
1601         var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
1602         var i = 0;
1603         while (size >= 1024) {
1604             size /= 1024;
1605             ++i;
1606         }
1607         return size.toFixed(2) + ' ' + units[i];
1608     },
1609     on_file_change: function(e) {
1610         // TODO: on modern browsers, we could directly read the file locally on client ready to be used on image cropper
1611         // http://www.html5rocks.com/tutorials/file/dndfiles/
1612         // http://deepliquid.com/projects/Jcrop/demos.php?demo=handler
1613         window[this.iframe] = this.on_file_uploaded;
1614         if ($(e.target).val() != '') {
1615             this.$element.find('form.oe-binary-form input[name=session_id]').val(this.session.session_id);
1616             this.$element.find('form.oe-binary-form').submit();
1617             this.toggle_progress();
1618         }
1619     },
1620     toggle_progress: function() {
1621         this.$element.find('.oe-binary-progress, .oe-binary').toggle();
1622     },
1623     on_file_uploaded: function(size, name, content_type, file_base64) {
1624         delete(window[this.iframe]);
1625         if (size === false) {
1626             this.notification.warn("File Upload", "There was a problem while uploading your file");
1627             // TODO: use openerp web exception handler
1628             console.log("Error while uploading file : ", name);
1629         } else {
1630             this.on_file_uploaded_and_valid.apply(this, arguments);
1631             this.on_ui_change();
1632         }
1633         this.toggle_progress();
1634     },
1635     on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
1636     },
1637     on_save_as: function() {
1638         if (!this.view.datarecord.id) {
1639             this.notification.warn("Can't save file", "The record has not yet been saved");
1640         } else {
1641             var url = '/base/binary/saveas?session_id=' + this.session.session_id + '&model=' +
1642                 this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name +
1643                 '&fieldname=' + (this.node.attrs.filename || '') + '&t=' + (new Date().getTime())
1644             window.open(url);
1645         }
1646     },
1647     on_clear: function() {
1648         if (this.value !== false) {
1649             this.value = false;
1650             this.binary_value = false;
1651             this.on_ui_change();
1652         }
1653         return false;
1654     }
1655 });
1656
1657 openerp.base.form.FieldBinaryFile = openerp.base.form.FieldBinary.extend({
1658     init: function(view, node) {
1659         this._super(view, node);
1660         this.template = "FieldBinaryFile";
1661     },
1662     set_value: function(value) {
1663         this._super.apply(this, arguments);
1664         var show_value = (value != null && value !== false) ? value : '';
1665         this.$element.find('input').eq(0).val(show_value);
1666     },
1667     on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
1668         this.value = file_base64;
1669         this.binary_value = true;
1670         var show_value = this.human_filesize(size);
1671         this.$element.find('input').eq(0).val(show_value);
1672         this.set_filename(name);
1673     },
1674     set_filename: function(value) {
1675         var filename = this.node.attrs.filename;
1676         if (this.view.fields[filename]) {
1677             this.view.fields[filename].set_value(value);
1678             this.view.fields[filename].on_ui_change();
1679         }
1680     },
1681     on_clear: function() {
1682         this._super.apply(this, arguments);
1683         this.$element.find('input').eq(0).val('');
1684         this.set_filename('');
1685     }
1686 });
1687
1688 openerp.base.form.FieldBinaryImage = openerp.base.form.FieldBinary.extend({
1689     init: function(view, node) {
1690         this._super(view, node);
1691         this.template = "FieldBinaryImage";
1692     },
1693     start: function() {
1694         this._super.apply(this, arguments);
1695         this.$image = this.$element.find('img.oe-binary-image');
1696     },
1697     set_image_maxwidth: function() {
1698         this.$image.css('max-width', this.$element.width());
1699     },
1700     on_file_change: function() {
1701         this.set_image_maxwidth();
1702         this._super.apply(this, arguments);
1703     },
1704     on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
1705         this.value = file_base64;
1706         this.binary_value = true;
1707         this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64);
1708     },
1709     on_clear: function() {
1710         this._super.apply(this, arguments);
1711         this.$image.attr('src', '/base/static/src/img/placeholder.png');
1712     },
1713     set_value: function(value) {
1714         this._super.apply(this, arguments);
1715         this.set_image_maxwidth();
1716         var url = '/base/binary/image?session_id=' + this.session.session_id + '&model=' +
1717             this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime())
1718         this.$image.attr('src', url);
1719     }
1720 });
1721
1722 /**
1723  * Registry of form widgets, called by :js:`openerp.base.FormView`
1724  */
1725 openerp.base.form.widgets = new openerp.base.Registry({
1726     'frame' : 'openerp.base.form.WidgetFrame',
1727     'group' : 'openerp.base.form.WidgetFrame',
1728     'notebook' : 'openerp.base.form.WidgetNotebook',
1729     'separator' : 'openerp.base.form.WidgetSeparator',
1730     'label' : 'openerp.base.form.WidgetLabel',
1731     'button' : 'openerp.base.form.WidgetButton',
1732     'char' : 'openerp.base.form.FieldChar',
1733     'email' : 'openerp.base.form.FieldEmail',
1734     'url' : 'openerp.base.form.FieldUrl',
1735     'text' : 'openerp.base.form.FieldText',
1736     'text_wiki' : 'openerp.base.form.FieldText',
1737     'date' : 'openerp.base.form.FieldDate',
1738     'datetime' : 'openerp.base.form.FieldDatetime',
1739     'selection' : 'openerp.base.form.FieldSelection',
1740     'many2one' : 'openerp.base.form.FieldMany2One',
1741     'many2many' : 'openerp.base.form.FieldMany2Many',
1742     'one2many' : 'openerp.base.form.FieldOne2Many',
1743     'one2many_list' : 'openerp.base.form.FieldOne2Many',
1744     'reference' : 'openerp.base.form.FieldReference',
1745     'boolean' : 'openerp.base.form.FieldBoolean',
1746     'float' : 'openerp.base.form.FieldFloat',
1747     'integer': 'openerp.base.form.FieldFloat',
1748     'progressbar': 'openerp.base.form.FieldProgressBar',
1749     'float_time': 'openerp.base.form.FieldFloatTime',
1750     'image': 'openerp.base.form.FieldBinaryImage',
1751     'binary': 'openerp.base.form.FieldBinaryFile'
1752 });
1753
1754 };
1755
1756 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: